included more features
This commit is contained in:
parent
aac79a689f
commit
c3fb382127
|
|
@ -19,6 +19,7 @@
|
||||||
debug: {
|
debug: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
level: 2, // 0=off, 1=errors, 2=info, 3=verbose, 4=trace
|
level: 2, // 0=off, 1=errors, 2=info, 3=verbose, 4=trace
|
||||||
|
watchMs: 120000,
|
||||||
maxLines: 400,
|
maxLines: 400,
|
||||||
showPanel: true
|
showPanel: true
|
||||||
},
|
},
|
||||||
|
|
@ -27,13 +28,21 @@
|
||||||
debounceDelay: 6500,
|
debounceDelay: 6500,
|
||||||
settleCheckMs: 1300,
|
settleCheckMs: 1300,
|
||||||
settlePollMs: 250,
|
settlePollMs: 250,
|
||||||
requireTerminator: true
|
requireTerminator: true,
|
||||||
|
coldStartMs: 2000,
|
||||||
|
stuckAfterMs: 10 * 60 * 1000,
|
||||||
|
scanDebounceMs: 400,
|
||||||
|
fastWarnMs: 50,
|
||||||
|
slowWarnMs: 60000,
|
||||||
|
clusterRescanMs: 1000,
|
||||||
|
clusterMaxLookahead: 3
|
||||||
},
|
},
|
||||||
|
|
||||||
queue: {
|
queue: {
|
||||||
minDelayMs: 1500,
|
minDelayMs: 1500,
|
||||||
maxPerMinute: 15,
|
maxPerMinute: 15,
|
||||||
maxPerMessage: 5
|
maxPerMessage: 5,
|
||||||
|
waitForComposerMs: 12000
|
||||||
},
|
},
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
|
|
@ -41,7 +50,23 @@
|
||||||
appendTrailingNewline: true,
|
appendTrailingNewline: true,
|
||||||
postPasteDelayMs: 600,
|
postPasteDelayMs: 600,
|
||||||
showExecutedMarker: true,
|
showExecutedMarker: true,
|
||||||
processExisting: false // used by main.js
|
processExisting: false,
|
||||||
|
submitMode: 'button_first',
|
||||||
|
maxComposerWaitMs: 15 * 60 * 1000,
|
||||||
|
submitMaxRetries: 12
|
||||||
|
},
|
||||||
|
|
||||||
|
storage: {
|
||||||
|
dedupeTtlMs: 30 * 24 * 60 * 60 * 1000, // 30 days
|
||||||
|
cleanupAfterMs: 30000,
|
||||||
|
cleanupIntervalMs: 60000
|
||||||
|
},
|
||||||
|
|
||||||
|
response: {
|
||||||
|
bufferFlushDelayMs: 500,
|
||||||
|
sectionHeadings: true,
|
||||||
|
maxPasteChars: 250000,
|
||||||
|
splitLongResponses: true
|
||||||
},
|
},
|
||||||
|
|
||||||
// Runtime state (not persisted)
|
// Runtime state (not persisted)
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,496 @@
|
||||||
// ==DEBUG PANEL START==
|
// ==DEBUG PANEL START==
|
||||||
// Depends on: config.js, logger.js, queue.js
|
// Depends on: config.js, logger.js, queue.js, storage.js
|
||||||
|
/* global GM_notification */
|
||||||
(function () {
|
(function () {
|
||||||
const cfg = () => window.AI_REPO_CONFIG;
|
const cfg = () => window.AI_REPO_CONFIG;
|
||||||
const log = () => window.AI_REPO_LOGGER;
|
const log = () => window.AI_REPO_LOGGER;
|
||||||
|
const STORAGE_KEYS = window.AI_REPO_STORAGE_KEYS;
|
||||||
|
|
||||||
class DebugPanel {
|
class DebugPanel {
|
||||||
constructor() { this.root = null; }
|
constructor() {
|
||||||
mount() {
|
this.panel = null;
|
||||||
if (!cfg().get('debug.showPanel')) return;
|
this.bodyLogs = null;
|
||||||
if (this.root) return;
|
this.bodyTools = null;
|
||||||
const root = document.createElement('div');
|
this.collapsed = false;
|
||||||
root.style.cssText = `
|
this.drag = { active: false, dx: 0, dy: 0 };
|
||||||
position:fixed; right:16px; bottom:16px; z-index:2147483647; width:460px; max-height:55vh;
|
this.panelState = this._loadPanelState();
|
||||||
display:flex; flex-direction:column; background:rgba(20,20,24,.92); border:1px solid #3b3b46; border-radius:8px;
|
|
||||||
color:#e5e7eb; font:12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; box-shadow:0 16px 40px rgba(0,0,0,.55);
|
|
||||||
`;
|
|
||||||
root.innerHTML = `
|
|
||||||
<div style="display:flex;gap:8px;align-items:center;padding:8px;border-bottom:1px solid #2c2c33">
|
|
||||||
<strong style="flex:1">AI Repo Commander</strong>
|
|
||||||
<button data-act="copy" style="padding:4px 6px;">Copy logs</button>
|
|
||||||
<button data-act="pause" style="padding:4px 6px;">${cfg().get('runtime.paused')?'Resume':'Pause'}</button>
|
|
||||||
<button data-act="clearq" style="padding:4px 6px;background:#7c2d12;color:#fff;border:1px solid #991b1b">Clear Queue</button>
|
|
||||||
</div>
|
|
||||||
<div data-body style="overflow:auto;padding:8px;flex:1"></div>
|
|
||||||
`;
|
|
||||||
document.body.appendChild(root);
|
|
||||||
this.root = root;
|
|
||||||
this.body = root.querySelector('[data-body]');
|
|
||||||
this._wire();
|
|
||||||
this._tick();
|
|
||||||
}
|
}
|
||||||
_wire() {
|
|
||||||
this.root.addEventListener('click', (e) => {
|
_loadPanelState() {
|
||||||
const btn = e.target.closest('button[data-act]'); if (!btn) return;
|
try {
|
||||||
const act = btn.getAttribute('data-act');
|
return JSON.parse(localStorage.getItem(STORAGE_KEYS.panel) || '{}');
|
||||||
if (act === 'copy') navigator.clipboard?.writeText(log().getRecentLogs(200));
|
} catch { return {}; }
|
||||||
if (act === 'pause') {
|
|
||||||
const paused = !cfg().get('runtime.paused'); cfg().set('runtime.paused', paused);
|
|
||||||
btn.textContent = paused ? 'Resume' : 'Pause';
|
|
||||||
log().info(paused ? 'Paused' : 'Resumed');
|
|
||||||
}
|
|
||||||
if (act === 'clearq') window.AI_REPO_QUEUE.clear();
|
|
||||||
});
|
|
||||||
// queue badge
|
|
||||||
const q = window.AI_REPO_QUEUE;
|
|
||||||
if (q) q.onSizeChange = (n) => this._toast(`Queue: ${n}`);
|
|
||||||
}
|
}
|
||||||
_toast(msg) {
|
|
||||||
if (!this.root) return;
|
_savePanelState(partial) {
|
||||||
|
try {
|
||||||
|
const merged = { ...(this.panelState || {}), ...(partial || {}) };
|
||||||
|
this.panelState = merged;
|
||||||
|
localStorage.setItem(STORAGE_KEYS.panel, JSON.stringify(merged));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
flashBtn(btn, label = 'Done', ms = 900) {
|
||||||
|
if (!btn) return;
|
||||||
|
const old = btn.textContent;
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = `${label} ✓`;
|
||||||
|
btn.style.opacity = '0.7';
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = old;
|
||||||
|
btn.style.opacity = '';
|
||||||
|
}, ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast(msg, ms = 1200) {
|
||||||
|
if (!this.panel) return;
|
||||||
const t = document.createElement('div');
|
const t = document.createElement('div');
|
||||||
t.textContent = msg;
|
t.textContent = msg;
|
||||||
t.style.cssText = 'position:absolute; right:12px; bottom:12px; padding:6px 10px; background:#111827; color:#e5e7eb; border:1px solid #374151; border-radius:6px;';
|
t.style.cssText = `
|
||||||
this.root.appendChild(t); setTimeout(() => t.remove(), 1000);
|
position:absolute; right:12px; bottom:12px; padding:6px 10px;
|
||||||
|
background:#111827; color:#e5e7eb; border:1px solid #374151;
|
||||||
|
border-radius:6px; font:12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
opacity:.98; pointer-events:none; box-shadow:0 6px 20px rgba(0,0,0,.35)
|
||||||
|
`;
|
||||||
|
this.panel.appendChild(t);
|
||||||
|
setTimeout(() => t.remove(), ms);
|
||||||
}
|
}
|
||||||
_tick() {
|
|
||||||
if (!this.body) return;
|
copyLast(n=50) {
|
||||||
const rows = window.AI_REPO_LOGGER?.buffer?.slice(-80) || [];
|
const lines = log().getRecentLogs(n);
|
||||||
this.body.innerHTML = rows.map(e => `<div style="padding:2px 0;border-bottom:1px dashed #2a2a34;">${e.timestamp} ${e.level} ${e.message}</div>`).join('');
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
requestAnimationFrame(() => this._tick());
|
navigator.clipboard.writeText(lines).then(() => {
|
||||||
|
log().info(`Copied last ${n} lines to clipboard`);
|
||||||
|
this.toast(`Copied last ${n} logs`);
|
||||||
|
}).catch(e => this._fallbackCopy(lines, e));
|
||||||
|
} else {
|
||||||
|
this._fallbackCopy(lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_fallbackCopy(text, originalError = null) {
|
||||||
|
try {
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.style.cssText = 'position:fixed;inset:0;z-index:999999;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;';
|
||||||
|
const panel = document.createElement('div');
|
||||||
|
panel.style.cssText = 'background:#1f2937;color:#e5e7eb;padding:12px 12px 8px;border-radius:8px;width:min(760px,90vw);max-height:70vh;display:flex;flex-direction:column;gap:8px;box-shadow:0 10px 30px rgba(0,0,0,0.5)';
|
||||||
|
const title = document.createElement('div');
|
||||||
|
title.textContent = 'Copy to clipboard';
|
||||||
|
title.style.cssText = 'font:600 14px system-ui,sans-serif;';
|
||||||
|
const hint = document.createElement('div');
|
||||||
|
hint.textContent = 'Press Ctrl+C (Windows/Linux) or ⌘+C (macOS) to copy the selected text.';
|
||||||
|
hint.style.cssText = 'font:12px system-ui,sans-serif;opacity:0.85;';
|
||||||
|
const ta = document.createElement('textarea');
|
||||||
|
ta.value = text;
|
||||||
|
ta.readOnly = true;
|
||||||
|
ta.style.cssText = 'width:100%;height:40vh;font:12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;';
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.style.cssText = 'display:flex;gap:8px;justify-content:flex-end;';
|
||||||
|
const close = document.createElement('button');
|
||||||
|
close.textContent = 'Close';
|
||||||
|
close.style.cssText = 'padding:6px 10px;background:#374151;color:#e5e7eb;border:1px solid #4b5563;border-radius:6px;cursor:pointer;';
|
||||||
|
close.onclick = () => overlay.remove();
|
||||||
|
panel.append(title, hint, ta, row);
|
||||||
|
row.append(close);
|
||||||
|
overlay.append(panel);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
ta.focus();
|
||||||
|
ta.select();
|
||||||
|
log().warn('Clipboard API unavailable; showing manual copy UI', { error: originalError?.message });
|
||||||
|
} catch (e) {
|
||||||
|
log().warn('Clipboard copy failed', { error: originalError?.message || e.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mount() {
|
||||||
|
if (!document.body) { setTimeout(() => this.mount(), 100); return; }
|
||||||
|
if (!cfg().get('debug.showPanel')) return;
|
||||||
|
|
||||||
|
const root = document.createElement('div');
|
||||||
|
root.style.cssText = `
|
||||||
|
position: fixed; ${this.panelState.left!==undefined ? `left:${this.panelState.left}px; top:${this.panelState.top}px;` : 'right:16px; bottom:16px;'}
|
||||||
|
z-index: 2147483647;
|
||||||
|
width: 460px; max-height: 55vh; display: flex; flex-direction: column;
|
||||||
|
background: rgba(20,20,24,0.92); border:1px solid #3b3b46; border-radius: 8px;
|
||||||
|
color:#e5e7eb; font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
box-shadow: 0 16px 40px rgba(0,0,0,0.55); backdrop-filter: blur(4px);
|
||||||
|
`;
|
||||||
|
root.innerHTML = `
|
||||||
|
<div class="rc-header" style="display:flex; gap:8px; align-items:center; padding:8px; border-bottom:1px solid #2c2c33; cursor:move; user-select:none">
|
||||||
|
<strong style="flex:1">AI Repo Commander</strong>
|
||||||
|
<div class="rc-tabs" style="display:flex; gap:6px;">
|
||||||
|
<button class="rc-tab rc-tab-logs" style="padding:4px 6px;border:1px solid #374151;border-radius:4px;background:#1f2937;color:#e5e7eb;">Logs</button>
|
||||||
|
<button class="rc-tab rc-tab-tools" style="padding:4px 6px;border:1px solid #374151;border-radius:4px;background:#111827;color:#e5e7eb;">Tools & Settings</button>
|
||||||
|
</div>
|
||||||
|
<label style="display:flex;align-items:center;gap:4px;">Lvl
|
||||||
|
<select class="rc-level" style="background:#111827;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||||
|
<option value="0">off</option>
|
||||||
|
<option value="1">errors</option>
|
||||||
|
<option value="2" selected>info</option>
|
||||||
|
<option value="3">verbose</option>
|
||||||
|
<option value="4">trace</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button class="rc-copy" title="Copy last 50 lines" style="padding:4px 6px;">Copy</button>
|
||||||
|
<button class="rc-pause" title="Pause/resume scanning" style="padding:4px 6px;">Pause</button>
|
||||||
|
<button class="rc-collapse" title="Collapse/expand" style="padding:4px 6px;">▾</button>
|
||||||
|
<button class="rc-queue-clear" title="Clear command queue" style="padding:4px 6px;background:#7c2d12;color:#fff;border:1px solid #991b1b">Clear Queue (0)</button>
|
||||||
|
<button class="rc-stop" title="Stop API calls" style="padding:4px 6px;background:#7f1d1d;color:#fff;border:1px solid #991b1b">STOP API</button>
|
||||||
|
</div>
|
||||||
|
<div class="rc-body rc-body-logs" style="overflow:auto; padding:8px; display:block; flex:1"></div>
|
||||||
|
<div class="rc-body rc-body-tools" style="overflow:auto; padding:8px; display:none; flex:1">
|
||||||
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
|
||||||
|
<div style="grid-column:1 / -1;">
|
||||||
|
<h4 style="margin:0 0 6px 0;">Quick Actions</h4>
|
||||||
|
<button class="rc-clear-history" style="padding:6px 8px;border:1px solid #374151;border-radius:6px;background:#1f2937;color:#e5e7eb;">Clear History</button>
|
||||||
|
<button class="rc-copy-200" style="padding:6px 8px;border:1px solid #374151;border-radius:6px;background:#1f2937;color:#e5e7eb;margin-left:8px;">Copy last 200 logs</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 style="margin:8px 0 6px 0;">Toggles</h4>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
<input class="rc-toggle" data-key="api.enabled" type="checkbox"> ENABLE_API
|
||||||
|
</label>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
<input class="rc-toggle" data-key="ui.autoSubmit" type="checkbox"> AUTO_SUBMIT
|
||||||
|
</label>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
<input class="rc-toggle" data-key="ui.appendTrailingNewline" type="checkbox"> APPEND_NEWLINE
|
||||||
|
</label>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
<input class="rc-toggle" data-key="ui.processExisting" type="checkbox"> PROCESS_EXISTING
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 style="margin:8px 0 6px 0;">Delays</h4>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
DEBOUNCE_DELAY (ms) <input class="rc-num" data-key="execution.debounceDelay" type="number" min="0" step="100" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||||
|
</label>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
SETTLE_CHECK_MS <input class="rc-num" data-key="execution.settleCheckMs" type="number" min="0" step="100" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||||
|
</label>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
SETTLE_POLL_MS <input class="rc-num" data-key="execution.settlePollMs" type="number" min="50" step="50" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||||
|
</label>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
API_TIMEOUT_MS <input class="rc-num" data-key="api.timeout" type="number" min="10000" step="5000" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div style="grid-column:1 / -1;">
|
||||||
|
<h4 style="margin:8px 0 6px 0;">Queue Settings</h4>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
QUEUE_MIN_DELAY_MS <input class="rc-num" data-key="queue.minDelayMs" type="number" min="0" step="100" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||||
|
</label>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
QUEUE_MAX_PER_MINUTE <input class="rc-num" data-key="queue.maxPerMinute" type="number" min="1" step="1" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||||
|
</label>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
QUEUE_MAX_PER_MESSAGE <input class="rc-num" data-key="queue.maxPerMessage" type="number" min="1" step="1" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||||
|
</label>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
QUEUE_WAIT_FOR_COMPOSER_MS <input class="rc-num" data-key="queue.waitForComposerMs" type="number" min="1000" step="500" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div style="grid-column:1 / -1;">
|
||||||
|
<h4 style="margin:8px 0 6px 0;">Bridge Configuration</h4>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||||
|
Bridge Key:
|
||||||
|
<input class="rc-bridge-key" type="password"
|
||||||
|
style="flex:1;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:4px 8px;"
|
||||||
|
placeholder="Enter your bridge key here">
|
||||||
|
</label>
|
||||||
|
<div style="margin-top:6px;">
|
||||||
|
<button class="rc-save-bridge-key"
|
||||||
|
style="padding:6px 8px;border:1px solid #374151;border-radius:6px;background:#1f2937;color:#e5e7eb;">
|
||||||
|
Save Bridge Key
|
||||||
|
</button>
|
||||||
|
<button class="rc-clear-bridge-key"
|
||||||
|
style="padding:6px 8px;border:1px solid #374151;border-radius:6px;background:#1f2937;color:#e5e7eb;margin-left:8px;">
|
||||||
|
Clear Bridge Key
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="grid-column:1 / -1;">
|
||||||
|
<h4 style="margin:8px 0 6px 0;">Config JSON</h4>
|
||||||
|
<textarea class="rc-json" style="width:100%;height:140px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:6px;padding:6px;"></textarea>
|
||||||
|
<div style="margin-top:6px;">
|
||||||
|
<button class="rc-save-json" style="padding:6px 8px;border:1px solid #374151;border-radius:6px;background:#1f2937;color:#e5e7eb;">Save Config</button>
|
||||||
|
<button class="rc-reset-defaults" style="padding:6px 8px;border:1px solid #374151;border-radius:6px;background:#1f2937;color:#e5e7eb;margin-left:8px;">Reset to Defaults</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(root);
|
||||||
|
this.panel = root;
|
||||||
|
this.bodyLogs = root.querySelector('.rc-body-logs');
|
||||||
|
this.bodyTools = root.querySelector('.rc-body-tools');
|
||||||
|
|
||||||
|
this._wireControls();
|
||||||
|
this._startLogRefresh();
|
||||||
|
|
||||||
|
log().info('Debug panel mounted');
|
||||||
|
}
|
||||||
|
|
||||||
|
_wireControls() {
|
||||||
|
const root = this.panel;
|
||||||
|
|
||||||
|
// Log level selector
|
||||||
|
const sel = root.querySelector('.rc-level');
|
||||||
|
sel.value = String(cfg().get('debug.level'));
|
||||||
|
sel.addEventListener('change', () => log().setLevel(parseInt(sel.value, 10)));
|
||||||
|
|
||||||
|
// Copy buttons
|
||||||
|
root.querySelector('.rc-copy').addEventListener('click', (e) => {
|
||||||
|
this.copyLast(50);
|
||||||
|
this.flashBtn(e.currentTarget, 'Copied');
|
||||||
|
});
|
||||||
|
|
||||||
|
root.querySelector('.rc-copy-200').addEventListener('click', (e) => {
|
||||||
|
this.copyLast(200);
|
||||||
|
this.flashBtn(e.currentTarget, 'Copied');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pause/Resume
|
||||||
|
const pauseBtn = root.querySelector('.rc-pause');
|
||||||
|
pauseBtn.addEventListener('click', () => {
|
||||||
|
const paused = !cfg().get('runtime.paused');
|
||||||
|
cfg().set('runtime.paused', paused);
|
||||||
|
pauseBtn.textContent = paused ? 'Resume' : 'Pause';
|
||||||
|
pauseBtn.style.background = paused ? '#f59e0b' : '';
|
||||||
|
pauseBtn.style.color = paused ? '#111827' : '';
|
||||||
|
this.flashBtn(pauseBtn, paused ? 'Paused' : 'Resumed');
|
||||||
|
this.toast(paused ? 'Paused scanning' : 'Resumed scanning');
|
||||||
|
log().info(`Runtime ${paused ? 'paused' : 'resumed'}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Queue clear
|
||||||
|
const queueBtn = root.querySelector('.rc-queue-clear');
|
||||||
|
queueBtn.addEventListener('click', (e) => {
|
||||||
|
window.AI_REPO_QUEUE?.clear?.();
|
||||||
|
this.flashBtn(e.currentTarget, 'Cleared');
|
||||||
|
this.toast('Queue cleared');
|
||||||
|
log().warn('Command queue cleared');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emergency STOP
|
||||||
|
root.querySelector('.rc-stop').addEventListener('click', (e) => {
|
||||||
|
window.AI_REPO_STOP?.();
|
||||||
|
this.flashBtn(e.currentTarget, 'Stopped');
|
||||||
|
this.toast('Emergency STOP activated');
|
||||||
|
log().warn('Emergency STOP activated');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tabs
|
||||||
|
const tabLogs = root.querySelector('.rc-tab-logs');
|
||||||
|
const tabTools = root.querySelector('.rc-tab-tools');
|
||||||
|
const selectTab = (tools=false) => {
|
||||||
|
this.bodyLogs.style.display = tools ? 'none' : 'block';
|
||||||
|
this.bodyTools.style.display = tools ? 'block' : 'none';
|
||||||
|
tabLogs.style.background = tools ? '#111827' : '#1f2937';
|
||||||
|
tabTools.style.background = tools ? '#1f2937' : '#111827';
|
||||||
|
};
|
||||||
|
tabLogs.addEventListener('click', () => selectTab(false));
|
||||||
|
|
||||||
|
tabTools.addEventListener('click', () => {
|
||||||
|
selectTab(true);
|
||||||
|
this._loadToolsPanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Collapse
|
||||||
|
const collapseBtn = root.querySelector('.rc-collapse');
|
||||||
|
const setCollapsed = (c) => {
|
||||||
|
this.collapsed = c;
|
||||||
|
this.bodyLogs.style.display = c ? 'none' : 'block';
|
||||||
|
this.bodyTools.style.display = 'none';
|
||||||
|
collapseBtn.textContent = c ? '▸' : '▾';
|
||||||
|
this._savePanelState({ collapsed: c });
|
||||||
|
};
|
||||||
|
setCollapsed(!!this.panelState.collapsed);
|
||||||
|
collapseBtn.addEventListener('click', () => setCollapsed(!this.collapsed));
|
||||||
|
|
||||||
|
// Dragging
|
||||||
|
const header = root.querySelector('.rc-header');
|
||||||
|
header.addEventListener('mousedown', (e) => {
|
||||||
|
const tgt = e.target instanceof Element ? e.target : e.target?.parentElement;
|
||||||
|
if (tgt?.closest('button,select,input,textarea,label')) return;
|
||||||
|
this.drag.active = true;
|
||||||
|
const rect = root.getBoundingClientRect();
|
||||||
|
this.drag.dx = e.clientX - rect.left;
|
||||||
|
this.drag.dy = e.clientY - rect.top;
|
||||||
|
root.style.right = 'auto';
|
||||||
|
root.style.bottom = 'auto';
|
||||||
|
document.addEventListener('mousemove', onMove);
|
||||||
|
document.addEventListener('mouseup', onUp);
|
||||||
|
});
|
||||||
|
const onMove = (e) => {
|
||||||
|
if (!this.drag.active) return;
|
||||||
|
const x = Math.max(0, Math.min(window.innerWidth - this.panel.offsetWidth, e.clientX - this.drag.dx));
|
||||||
|
const y = Math.max(0, Math.min(window.innerHeight - 40, e.clientY - this.drag.dy));
|
||||||
|
this.panel.style.left = `${x}px`;
|
||||||
|
this.panel.style.top = `${y}px`;
|
||||||
|
};
|
||||||
|
const onUp = () => {
|
||||||
|
if (!this.drag.active) return;
|
||||||
|
this.drag.active = false;
|
||||||
|
this._savePanelState({
|
||||||
|
left: parseInt(this.panel.style.left||'0',10),
|
||||||
|
top: parseInt(this.panel.style.top||'0',10)
|
||||||
|
});
|
||||||
|
document.removeEventListener('mousemove', onMove);
|
||||||
|
document.removeEventListener('mouseup', onUp);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear History
|
||||||
|
root.querySelector('.rc-clear-history').addEventListener('click', (e) => {
|
||||||
|
try {
|
||||||
|
window.AI_REPO_HISTORY?.clear?.();
|
||||||
|
log().info('Conversation history cleared');
|
||||||
|
if (typeof GM_notification !== 'undefined') {
|
||||||
|
GM_notification({ title: 'AI Repo Commander', text: 'Execution marks cleared', timeout: 2500 });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log().warn('Error clearing history', { error: String(err) });
|
||||||
|
}
|
||||||
|
this.flashBtn(e.currentTarget, 'Cleared');
|
||||||
|
this.toast('Conversation marks cleared');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggles
|
||||||
|
root.querySelectorAll('.rc-toggle').forEach(inp => {
|
||||||
|
const key = inp.dataset.key;
|
||||||
|
inp.checked = !!cfg().get(key);
|
||||||
|
inp.addEventListener('change', () => {
|
||||||
|
cfg().set(key, !!inp.checked);
|
||||||
|
this.toast(`${key} = ${cfg().get(key) ? 'on' : 'off'}`);
|
||||||
|
log().info(`Config ${key} => ${cfg().get(key)}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Number inputs
|
||||||
|
root.querySelectorAll('.rc-num').forEach(inp => {
|
||||||
|
inp.value = String(cfg().get(inp.dataset.key) ?? '');
|
||||||
|
inp.addEventListener('change', () => {
|
||||||
|
const v = parseInt(inp.value, 10);
|
||||||
|
if (!Number.isNaN(v)) {
|
||||||
|
cfg().set(inp.dataset.key, v);
|
||||||
|
this.toast(`${inp.dataset.key} = ${v}`);
|
||||||
|
log().info(`Config ${inp.dataset.key} => ${v}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bridge Key
|
||||||
|
root.querySelector('.rc-save-bridge-key').addEventListener('click', (e) => {
|
||||||
|
const input = root.querySelector('.rc-bridge-key');
|
||||||
|
const val = (input.value || '').trim();
|
||||||
|
if (val && !/^•+$/.test(val)) {
|
||||||
|
cfg().set('api.bridgeKey', val);
|
||||||
|
input.value = '•'.repeat(8);
|
||||||
|
this.flashBtn(e.currentTarget, 'Saved');
|
||||||
|
this.toast('Bridge key saved');
|
||||||
|
log().info('Bridge key updated');
|
||||||
|
} else {
|
||||||
|
this.toast('Invalid key', 1500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
root.querySelector('.rc-clear-bridge-key').addEventListener('click', (e) => {
|
||||||
|
cfg().set('api.bridgeKey', '');
|
||||||
|
root.querySelector('.rc-bridge-key').value = '';
|
||||||
|
this.flashBtn(e.currentTarget, 'Cleared');
|
||||||
|
this.toast('Bridge key cleared');
|
||||||
|
log().info('Bridge key cleared');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Config JSON
|
||||||
|
root.querySelector('.rc-save-json').addEventListener('click', (e) => {
|
||||||
|
try {
|
||||||
|
const raw = root.querySelector('.rc-json').value;
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
|
||||||
|
// Don't override runtime/version from JSON
|
||||||
|
delete parsed.meta;
|
||||||
|
delete parsed.runtime;
|
||||||
|
|
||||||
|
// Merge into current config
|
||||||
|
Object.keys(parsed).forEach(section => {
|
||||||
|
if (typeof parsed[section] === 'object' && !Array.isArray(parsed[section])) {
|
||||||
|
Object.keys(parsed[section]).forEach(key => {
|
||||||
|
cfg().set(`${section}.${key}`, parsed[section][key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.flashBtn(e.currentTarget, 'Saved');
|
||||||
|
this.toast('Config saved');
|
||||||
|
log().info('Config JSON saved');
|
||||||
|
this._loadToolsPanel(); // Refresh
|
||||||
|
} catch (err) {
|
||||||
|
this.toast('Invalid JSON', 1500);
|
||||||
|
log().warn('Invalid JSON in config textarea', { error: String(err) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
root.querySelector('.rc-reset-defaults').addEventListener('click', (e) => {
|
||||||
|
if (!confirm('Reset all settings to defaults? This will reload the page.')) return;
|
||||||
|
localStorage.removeItem(STORAGE_KEYS.cfg);
|
||||||
|
this.flashBtn(e.currentTarget, 'Reset');
|
||||||
|
this.toast('Resetting...', 1500);
|
||||||
|
setTimeout(() => location.reload(), 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadToolsPanel() {
|
||||||
|
const root = this.panel;
|
||||||
|
|
||||||
|
// Load toggle states
|
||||||
|
root.querySelectorAll('.rc-toggle').forEach(inp => {
|
||||||
|
inp.checked = !!cfg().get(inp.dataset.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load number values
|
||||||
|
root.querySelectorAll('.rc-num').forEach(inp => {
|
||||||
|
inp.value = String(cfg().get(inp.dataset.key) ?? '');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load bridge key (masked)
|
||||||
|
const bridgeKeyInput = root.querySelector('.rc-bridge-key');
|
||||||
|
const bridgeKey = cfg().get('api.bridgeKey');
|
||||||
|
if (bridgeKeyInput) {
|
||||||
|
bridgeKeyInput.value = bridgeKey ? '•'.repeat(8) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load config JSON (sanitized)
|
||||||
|
const dump = cfg().config;
|
||||||
|
const sanitized = JSON.parse(JSON.stringify(dump));
|
||||||
|
if (sanitized.api && sanitized.api.bridgeKey) {
|
||||||
|
sanitized.api.bridgeKey = '•'.repeat(8);
|
||||||
|
}
|
||||||
|
root.querySelector('.rc-json').value = JSON.stringify(sanitized, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
_startLogRefresh() {
|
||||||
|
const renderLogs = () => {
|
||||||
|
if (!this.bodyLogs || this.collapsed) return;
|
||||||
|
const rows = log().buffer.slice(-80);
|
||||||
|
this.bodyLogs.innerHTML = rows.map(e =>
|
||||||
|
`<div style="padding:4px 0;border-bottom:1px dashed #2a2a34;white-space:pre-wrap;word-break:break-word;">${e.timestamp} ${e.level.padEnd(5)} ${e.message}${e.data ? ' ' + JSON.stringify(e.data) : ''}</div>`
|
||||||
|
).join('');
|
||||||
|
this.bodyLogs.scrollTop = this.bodyLogs.scrollHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
setInterval(renderLogs, 1000);
|
||||||
|
renderLogs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const panel = new DebugPanel();
|
const panel = new DebugPanel();
|
||||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => panel.mount());
|
if (document.readyState === 'loading') {
|
||||||
else panel.mount();
|
document.addEventListener('DOMContentLoaded', () => panel.mount());
|
||||||
|
} else {
|
||||||
|
panel.mount();
|
||||||
|
}
|
||||||
|
|
||||||
window.AI_REPO_DEBUG_PANEL = panel;
|
window.AI_REPO_DEBUG_PANEL = panel;
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,46 @@
|
||||||
constructor() {
|
constructor() {
|
||||||
this.config = window.AI_REPO_CONFIG;
|
this.config = window.AI_REPO_CONFIG;
|
||||||
this.buffer = [];
|
this.buffer = [];
|
||||||
|
this.loopCounts = new Map();
|
||||||
|
this.startedAt = Date.now();
|
||||||
|
|
||||||
|
// Cleanup loop counters periodically
|
||||||
|
setInterval(() => {
|
||||||
|
const watchMs = this.config.get('debug.watchMs') || 120000;
|
||||||
|
if (Date.now() - this.startedAt > watchMs * 2) {
|
||||||
|
this.loopCounts.clear();
|
||||||
|
this.startedAt = Date.now();
|
||||||
|
}
|
||||||
|
}, this.config.get('debug.watchMs') || 120000);
|
||||||
}
|
}
|
||||||
|
|
||||||
error(msg, data) { this._log(1, 'ERROR', msg, data); }
|
error(msg, data) { this._log(1, 'ERROR', msg, data); }
|
||||||
warn(msg, data) { this._log(2, 'WARN', msg, data); }
|
warn(msg, data) { this._log(2, 'WARN', msg, data); }
|
||||||
info(msg, data) { this._log(3, 'INFO', msg, data); }
|
info(msg, data) { this._log(3, 'INFO', msg, data); }
|
||||||
verbose(msg, data) { this._log(4, 'VERBOSE', msg, data); }
|
verbose(msg, data) { this._log(4, 'VERBOSE', msg, data); }
|
||||||
trace(msg, data) { this._log(5, 'TRACE', msg, data); }
|
trace(msg, data) { this._log(5, 'TRACE', msg, data); }
|
||||||
|
|
||||||
|
command(action, status, extra={}) {
|
||||||
|
const icon = {
|
||||||
|
detected:'👁️', parsing:'📝', validating:'✓', debouncing:'⏳',
|
||||||
|
executing:'⚙️', complete:'✅', error:'❌'
|
||||||
|
}[status] || '•';
|
||||||
|
this.info(`${icon} ${action} [${status}]`, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
logLoop(kind, msg) {
|
||||||
|
const k = `${kind}:${msg}`;
|
||||||
|
const cur = this.loopCounts.get(k) || 0;
|
||||||
|
const withinWatch = Date.now() - this.startedAt <= (this.config.get('debug.watchMs') || 120000);
|
||||||
|
if (!withinWatch && kind !== 'WARN') return;
|
||||||
|
if (cur >= 10) return;
|
||||||
|
this.loopCounts.set(k, cur + 1);
|
||||||
|
const suffix = (cur + 1) > 1 ? ` (${cur + 1}x)` : '';
|
||||||
|
if (kind === 'ERROR') this.error(`${msg}${suffix}`);
|
||||||
|
else if (kind === 'WARN') this.warn(`${msg}${suffix}`);
|
||||||
|
else this.info(`${msg}${suffix}`);
|
||||||
|
}
|
||||||
|
|
||||||
_log(levelNum, levelName, msg, data) {
|
_log(levelNum, levelName, msg, data) {
|
||||||
const enabled = !!this.config.get('debug.enabled');
|
const enabled = !!this.config.get('debug.enabled');
|
||||||
const level = this.config.get('debug.level') ?? 0;
|
const level = this.config.get('debug.level') ?? 0;
|
||||||
|
|
@ -50,6 +83,12 @@
|
||||||
`${e.timestamp} ${e.level.padEnd(7)} ${e.message}${e.data ? ' ' + JSON.stringify(e.data) : ''}`
|
`${e.timestamp} ${e.level.padEnd(7)} ${e.message}${e.data ? ' ' + JSON.stringify(e.data) : ''}`
|
||||||
).join('\n');
|
).join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLevel(n) {
|
||||||
|
const lv = Math.max(0, Math.min(4, n));
|
||||||
|
this.config.set('debug.level', lv);
|
||||||
|
this.info(`Log level => ${lv}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.AI_REPO_LOGGER = new Logger();
|
window.AI_REPO_LOGGER = new Logger();
|
||||||
|
|
|
||||||
12
src/main.js
12
src/main.js
|
|
@ -135,6 +135,18 @@
|
||||||
resume: () => { config.set('runtime.paused', false); logger.info('Resumed'); },
|
resume: () => { config.set('runtime.paused', false); logger.info('Resumed'); },
|
||||||
clearHistory: () => { history.clear(); logger.info('History cleared'); }
|
clearHistory: () => { history.clear(); logger.info('History cleared'); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Emergency STOP function
|
||||||
|
window.AI_REPO_STOP = () => {
|
||||||
|
config.set('api.enabled', false);
|
||||||
|
config.set('runtime.paused', true);
|
||||||
|
|
||||||
|
const queuedCount = window.AI_REPO_QUEUE?.size?.() || 0;
|
||||||
|
window.AI_REPO_QUEUE?.clear?.();
|
||||||
|
|
||||||
|
logger.error(`🚨 EMERGENCY STOP: cancelled ${queuedCount} queued command(s)`);
|
||||||
|
logger.error('API disabled and scanning paused');
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(ms) { return new Promise(r => setTimeout(r, ms)); }
|
delay(ms) { return new Promise(r => setTimeout(r, ms)); }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue