included more features
This commit is contained in:
parent
52559be1c5
commit
aac79a689f
|
|
@ -0,0 +1,70 @@
|
|||
// ==DEBUG PANEL START==
|
||||
// Depends on: config.js, logger.js, queue.js
|
||||
(function () {
|
||||
const cfg = () => window.AI_REPO_CONFIG;
|
||||
const log = () => window.AI_REPO_LOGGER;
|
||||
|
||||
class DebugPanel {
|
||||
constructor() { this.root = null; }
|
||||
mount() {
|
||||
if (!cfg().get('debug.showPanel')) return;
|
||||
if (this.root) return;
|
||||
const root = document.createElement('div');
|
||||
root.style.cssText = `
|
||||
position:fixed; right:16px; bottom:16px; z-index:2147483647; width:460px; max-height:55vh;
|
||||
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) => {
|
||||
const btn = e.target.closest('button[data-act]'); if (!btn) return;
|
||||
const act = btn.getAttribute('data-act');
|
||||
if (act === 'copy') navigator.clipboard?.writeText(log().getRecentLogs(200));
|
||||
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;
|
||||
const t = document.createElement('div');
|
||||
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;';
|
||||
this.root.appendChild(t); setTimeout(() => t.remove(), 1000);
|
||||
}
|
||||
_tick() {
|
||||
if (!this.body) return;
|
||||
const rows = window.AI_REPO_LOGGER?.buffer?.slice(-80) || [];
|
||||
this.body.innerHTML = rows.map(e => `<div style="padding:2px 0;border-bottom:1px dashed #2a2a34;">${e.timestamp} ${e.level} ${e.message}</div>`).join('');
|
||||
requestAnimationFrame(() => this._tick());
|
||||
}
|
||||
}
|
||||
|
||||
const panel = new DebugPanel();
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => panel.mount());
|
||||
else panel.mount();
|
||||
|
||||
window.AI_REPO_DEBUG_PANEL = panel;
|
||||
})();
|
||||
// ==DEBUG PANEL END==
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
// ==DETECTOR START==
|
||||
// Depends on: config.js, logger.js, queue.js, command-parser.js, command-executor.js, storage.js
|
||||
(function () {
|
||||
const cfg = () => window.AI_REPO_CONFIG;
|
||||
const log = () => window.AI_REPO_LOGGER;
|
||||
|
||||
function extractAllBlocks(text) {
|
||||
const out = []; const re = /^\s*@bridge@[ \t]*\n([\s\S]*?)\n@end@[ \t]*(?:\n|$)/gm;
|
||||
let m; while ((m = re.exec(text)) !== null) out.push(m[0]);
|
||||
return out;
|
||||
}
|
||||
|
||||
function isAssistantMsg(el) {
|
||||
const sels = [
|
||||
'[data-message-author-role="assistant"]',
|
||||
'.chat-message:not([data-message-author-role="user"])',
|
||||
'.message-content'
|
||||
];
|
||||
return sels.some(s => el.matches?.(s) || el.querySelector?.(s));
|
||||
}
|
||||
|
||||
async function settleText(el, initial, windowMs, pollMs) {
|
||||
let deadline = Date.now() + windowMs;
|
||||
let last = initial;
|
||||
while (Date.now() < deadline) {
|
||||
await new Promise(r => setTimeout(r, pollMs));
|
||||
const fresh = el.textContent || '';
|
||||
const blocks = extractAllBlocks(fresh);
|
||||
const pick = blocks.join('\n'); // if multiple, concatenate for stability check (we’ll split later)
|
||||
if (pick === last && pick) continue; // stable—keep waiting out the window
|
||||
if (pick && pick !== last) { last = pick; deadline = Date.now() + windowMs; }
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
class Detector {
|
||||
constructor() {
|
||||
this.observer = null;
|
||||
this.processed = new WeakSet();
|
||||
this.clusterLookahead = 3;
|
||||
this.clusterWindowMs = 1000;
|
||||
}
|
||||
start() {
|
||||
this.observer = new MutationObserver((mutations) => {
|
||||
if (cfg().get('runtime.paused')) return;
|
||||
let should = false;
|
||||
for (const m of mutations) {
|
||||
if (m.type === 'childList') {
|
||||
for (const n of m.addedNodes) {
|
||||
if (n.nodeType === 1 && isAssistantMsg(n)) { this._handle(n); should = true; }
|
||||
}
|
||||
}
|
||||
if (m.type === 'characterData') {
|
||||
const el = m.target?.parentElement;
|
||||
if (el && isAssistantMsg(el)) should = true;
|
||||
}
|
||||
}
|
||||
if (should) {/* no-op: _handle already queued */}
|
||||
});
|
||||
this.observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true });
|
||||
if (cfg().get('ui.processExisting')) {
|
||||
document.querySelectorAll('[data-message-author-role], .chat-message, .message-content')
|
||||
.forEach(el => isAssistantMsg(el) && this._handle(el));
|
||||
}
|
||||
log().info('Detector started');
|
||||
}
|
||||
|
||||
async _handle(el) {
|
||||
if (this.processed.has(el)) return;
|
||||
this.processed.add(el);
|
||||
|
||||
// Debounce complete generation
|
||||
const debounce = cfg().get('execution.debounceDelay') || 0;
|
||||
if (debounce > 0) await new Promise(r => setTimeout(r, debounce));
|
||||
|
||||
// Settle
|
||||
const baseText = el.textContent || '';
|
||||
const stable = await settleText(el, baseText, cfg().get('execution.settleCheckMs') || 1200, cfg().get('execution.settlePollMs') || 250);
|
||||
const blocks = extractAllBlocks(stable);
|
||||
if (!blocks.length) { this.processed.delete(el); return; } // not a command after all
|
||||
|
||||
const maxPerMsg = cfg().get('queue.maxPerMessage') || 5;
|
||||
blocks.slice(0, maxPerMsg).forEach((cmdText, idx) => this._enqueueOne(el, cmdText, idx));
|
||||
|
||||
// Cluster rescan: look ahead a few assistant messages for chained blocks
|
||||
setTimeout(() => this._clusterRescan(el), this.clusterWindowMs);
|
||||
}
|
||||
|
||||
_enqueueOne(el, commandText, idx) {
|
||||
const history = window.AI_REPO_HISTORY;
|
||||
if (history.isProcessed(el, idx)) {
|
||||
this._addRunAgain(el, commandText, idx);
|
||||
return;
|
||||
}
|
||||
history.markProcessed(el, idx);
|
||||
|
||||
window.AI_REPO_QUEUE.push(async () => {
|
||||
try {
|
||||
const parsed = window.AI_REPO_PARSER.parse(commandText);
|
||||
const v = window.AI_REPO_PARSER.validate(parsed);
|
||||
if (!v.isValid) throw new Error(`Validation failed: ${v.errors.join(', ')}`);
|
||||
if (v.example) { log().info('Example command skipped'); return; }
|
||||
await window.AI_REPO_EXECUTOR.execute(parsed, el, `[${idx + 1}] ${parsed.action}`);
|
||||
} catch (e) {
|
||||
log().error('Command failed', { error: e.message });
|
||||
this._addRunAgain(el, commandText, idx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_addRunAgain(el, commandText, idx) {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = `Run Again #${idx + 1}`;
|
||||
btn.style.cssText = 'padding:4px 8px;margin:4px;border:1px solid #374151;border-radius:4px;background:#1f2937;color:#e5e7eb;cursor:pointer;';
|
||||
btn.addEventListener('click', () => this._enqueueOne(el, commandText, idx));
|
||||
el.appendChild(btn);
|
||||
}
|
||||
|
||||
_clusterRescan(anchor) {
|
||||
let scanned = 0; let cur = anchor.nextElementSibling;
|
||||
while (cur && scanned < this.clusterLookahead) {
|
||||
if (!isAssistantMsg(cur)) break;
|
||||
if (!this.processed.has(cur)) this._handle(cur);
|
||||
scanned++; cur = cur.nextElementSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.AI_REPO_DETECTOR = new Detector();
|
||||
})();
|
||||
// ==DETECTOR END==
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// ==FINGERPRINT (drop-in utility) ==
|
||||
(function(){
|
||||
function norm(s){ return (s||'').replace(/\r/g,'').replace(/\u200b/g,'').replace(/[ \t]+\n/g,'\n').trim(); }
|
||||
function hash(s){ let h=5381; for(let i=0;i<s.length;i++) h=((h<<5)+h)^s.charCodeAt(i); return (h>>>0).toString(36); }
|
||||
function commandLikeText(el){
|
||||
const blocks = el.querySelectorAll('pre code, pre, code');
|
||||
for (const b of blocks) {
|
||||
const t = norm(b.textContent || '');
|
||||
if (/@end@\s*$/m.test(t) && /(^|\n)\s*@bridge@\b/m.test(t) && /(^|\n)\s*action\s*:/m.test(t)) return t;
|
||||
}
|
||||
return norm((el.textContent || '').slice(0, 2000));
|
||||
}
|
||||
function prevContextHash(el) {
|
||||
const list = Array.from(document.querySelectorAll('[data-message-author-role], .chat-message, .message-content'));
|
||||
const idx = list.indexOf(el); if (idx <= 0) return '0';
|
||||
let rem = 2000, buf = '';
|
||||
for (let i=idx-1; i>=0 && rem>0; i--){
|
||||
const t = norm(list[i].textContent || ''); if (!t) continue;
|
||||
const take = t.slice(-rem); buf = take + buf; rem -= take.length;
|
||||
}
|
||||
return hash(buf.slice(-2000));
|
||||
}
|
||||
function intraPrefixHash(el){
|
||||
const t = el.textContent || '';
|
||||
const m = t.match(/@bridge@[\s\S]*?@end@/m);
|
||||
const endIdx = m ? t.indexOf(m[0]) : t.length;
|
||||
return hash(norm(t.slice(Math.max(0, endIdx - 2000), endIdx)));
|
||||
}
|
||||
window.AI_REPO_FINGERPRINT = function(el){
|
||||
const ch = hash(commandLikeText(el).slice(0, 2000));
|
||||
const ph = prevContextHash(el);
|
||||
const ih = intraPrefixHash(el);
|
||||
return `ch:${ch}|ph:${ph}|ih:${ih}`;
|
||||
};
|
||||
})();
|
||||
|
|
@ -154,6 +154,8 @@
|
|||
} else {
|
||||
window.AI_REPO_MAIN = new AIRepoCommander();
|
||||
window.AI_REPO_MAIN.initialize();
|
||||
// Kick off the advanced detector (restores settle/debounce, multi-block, cluster rescan)
|
||||
window.AI_REPO_DETECTOR?.start();
|
||||
}
|
||||
})();
|
||||
// ==MAIN END==
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
// ==PASTE SUBMIT START==
|
||||
// Depends on: config.js, logger.js
|
||||
/* global GM_setClipboard */
|
||||
(function () {
|
||||
const cfg = () => window.AI_REPO_CONFIG;
|
||||
const log = () => window.AI_REPO_LOGGER;
|
||||
|
||||
function findComposer() {
|
||||
const sels = [
|
||||
'#prompt-textarea',
|
||||
'.ProseMirror#prompt-textarea',
|
||||
'.ProseMirror[role="textbox"][contenteditable="true"]',
|
||||
'[data-testid="composer"] [contenteditable="true"][role="textbox"]',
|
||||
'main [contenteditable="true"][role="textbox"]',
|
||||
'textarea[data-testid="input-area"]',
|
||||
'[contenteditable="true"][aria-label*="Message"]',
|
||||
'textarea',
|
||||
'[contenteditable="true"]'
|
||||
];
|
||||
for (const s of sels) {
|
||||
const el = document.querySelector(s);
|
||||
if (!el) continue;
|
||||
const st = window.getComputedStyle(el);
|
||||
if (st.display === 'none' || st.visibility === 'hidden') continue;
|
||||
if (el.offsetParent === null && st.position !== 'fixed') continue;
|
||||
return el;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findSendButton(scopeEl) {
|
||||
const scope = scopeEl?.closest('form, [data-testid="composer"], main, body') || document;
|
||||
const sels = [
|
||||
'button[data-testid="send-button"]',
|
||||
'#composer-submit-button',
|
||||
'button[aria-label*="Send prompt"]',
|
||||
'button[aria-label*="Send message"]',
|
||||
'button[aria-label="Send"]',
|
||||
'button[aria-label*="Send"]',
|
||||
'form button'
|
||||
];
|
||||
for (const s of sels) {
|
||||
const b = scope.querySelector(s) || document.querySelector(s);
|
||||
if (!b) continue;
|
||||
const st = window.getComputedStyle(b);
|
||||
const disabled = b.disabled || b.getAttribute('aria-disabled') === 'true';
|
||||
const hidden = st.display === 'none' || st.visibility === 'hidden';
|
||||
const notRendered = b.offsetParent === null && st.position !== 'fixed';
|
||||
if (!disabled && !hidden && !notRendered) return b;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function pressEnter(el) {
|
||||
for (const t of ['keydown','keypress','keyup']) {
|
||||
const ok = el.dispatchEvent(new KeyboardEvent(t, { key:'Enter', code:'Enter', keyCode:13, which:13, bubbles:true, cancelable:true }));
|
||||
if (!ok) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function waitReady(timeoutMs = 12000) {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
const el = findComposer();
|
||||
if (el) {
|
||||
const current = (el.textContent || el.value || '').trim();
|
||||
const busy = el.closest('form, [data-testid="composer"], main, body')?.querySelector('[aria-busy="true"], [data-state="loading"], .typing-indicator');
|
||||
if (!busy && current.length === 0) return true;
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function pasteInto(el, text) {
|
||||
const payload = cfg().get('ui.appendTrailingNewline') ? (text.endsWith('\n') ? text : text + '\n') : text;
|
||||
try {
|
||||
// ClipboardEvent path
|
||||
const dt = new DataTransfer(); dt.setData('text/plain', payload);
|
||||
const evt = new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true });
|
||||
if (el.dispatchEvent(evt) && !evt.defaultPrevented) return true;
|
||||
} catch {}
|
||||
// ProseMirror path
|
||||
if (el.classList?.contains('ProseMirror')) {
|
||||
const node = document.createTextNode('\n' + payload.replace(/\n?$/,'\n') + '\n');
|
||||
el.innerHTML = ''; el.appendChild(node);
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
return true;
|
||||
}
|
||||
// Selection/contentEditable path
|
||||
try {
|
||||
if (el.isContentEditable || el.getAttribute('contenteditable') === 'true') {
|
||||
const sel = window.getSelection(); if (sel && sel.rangeCount === 0) {
|
||||
const r = document.createRange(); r.selectNodeContents(el); r.collapse(false); sel.removeAllRanges(); sel.addRange(r);
|
||||
}
|
||||
const range = window.getSelection()?.getRangeAt(0);
|
||||
if (range) {
|
||||
range.deleteContents(); const node = document.createTextNode(payload);
|
||||
range.insertNode(node); range.setStartAfter(node); range.setEndAfter(node);
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
// Textarea path
|
||||
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
||||
el.value = payload; el.dispatchEvent(new Event('input', { bubbles: true })); return true;
|
||||
}
|
||||
// Fallback to clipboard
|
||||
try {
|
||||
if (typeof GM_setClipboard === 'function') {
|
||||
GM_setClipboard(payload, { type:'text', mimetype:'text/plain' });
|
||||
alert('Content copied to clipboard. Press Ctrl/Cmd+V to paste.');
|
||||
return true;
|
||||
}
|
||||
} catch {}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function submitToComposer(text) {
|
||||
const auto = !!cfg().get('ui.autoSubmit');
|
||||
const ok = await waitReady(cfg().get('execution.settleCheckMs') || 1200);
|
||||
if (!ok) { log()?.warn('Composer not ready'); return false; }
|
||||
|
||||
const el = findComposer(); if (!el) { log()?.warn('Composer not found'); return false; }
|
||||
if (text && !pasteInto(el, text)) { log()?.warn('Paste failed'); return false; }
|
||||
|
||||
await new Promise(r => setTimeout(r, cfg().get('ui.postPasteDelayMs') || 600));
|
||||
if (!auto) return true;
|
||||
|
||||
const btn = findSendButton(el);
|
||||
if (btn) { btn.click(); return true; }
|
||||
return pressEnter(el);
|
||||
}
|
||||
|
||||
window.AI_REPO_PASTE = { submitToComposer };
|
||||
})();
|
||||
// ==PASTE SUBMIT END==
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// ==QUEUE START==
|
||||
// Depends on: config.js, logger.js
|
||||
(function () {
|
||||
class ExecutionQueue {
|
||||
constructor(opts = {}) {
|
||||
const cfg = window.AI_REPO_CONFIG;
|
||||
this.minDelayMs = opts.minDelayMs ?? cfg.get('queue.minDelayMs') ?? 1500;
|
||||
this.maxPerMinute = opts.maxPerMinute ?? cfg.get('queue.maxPerMinute') ?? 15;
|
||||
this.q = [];
|
||||
this.running = false;
|
||||
this.timestamps = [];
|
||||
this.onSizeChange = null;
|
||||
}
|
||||
push(task) {
|
||||
this.q.push(task);
|
||||
this.onSizeChange?.(this.q.length);
|
||||
if (!this.running) void this._drain();
|
||||
}
|
||||
clear() { this.q.length = 0; this.onSizeChange?.(0); }
|
||||
size() { return this.q.length; }
|
||||
_withinBudget() {
|
||||
const now = Date.now();
|
||||
this.timestamps = this.timestamps.filter(t => now - t < 60_000);
|
||||
return this.timestamps.length < this.maxPerMinute;
|
||||
}
|
||||
async _drain() {
|
||||
if (this.running) return;
|
||||
this.running = true;
|
||||
while (this.q.length) {
|
||||
while (!this._withinBudget()) await this._delay(400);
|
||||
const fn = this.q.shift();
|
||||
this.onSizeChange?.(this.q.length);
|
||||
try { await fn(); } catch (e) { window.AI_REPO_LOGGER?.warn('Queue task error', { error: String(e) }); }
|
||||
this.timestamps.push(Date.now());
|
||||
await this._delay(this.minDelayMs);
|
||||
}
|
||||
this.running = false;
|
||||
}
|
||||
_delay(ms){ return new Promise(r => setTimeout(r, ms)); }
|
||||
}
|
||||
window.AI_REPO_QUEUE = new ExecutionQueue();
|
||||
})();
|
||||
// ==QUEUE END==
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// ==RESPONSE BUFFER START==
|
||||
// Depends on: config.js, logger.js, queue.js, paste-submit.js
|
||||
(function () {
|
||||
function chunkByLines(s, limit) {
|
||||
const out = []; let start = 0;
|
||||
while (start < s.length) {
|
||||
const soft = s.lastIndexOf('\n', Math.min(start + limit, s.length));
|
||||
const end = soft > start ? soft + 1 : Math.min(start + limit, s.length);
|
||||
out.push(s.slice(start, end)); start = end;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function isSingleFence(s){ return /^```[^\n]*\n[\s\S]*\n```$/.test(s.trim()); }
|
||||
function splitRespectingFence(text, limit) {
|
||||
const t = text.trim(); if (!isSingleFence(t)) return chunkByLines(text, limit);
|
||||
const m = /^```([^\n]*)\n([\s\S]*)\n```$/.exec(t);
|
||||
const lang = (m?.[1] || 'text').trim(); const inner = m?.[2] ?? '';
|
||||
const chunks = chunkByLines(inner, limit - 16 - lang.length);
|
||||
return chunks.map(c => '```' + lang + '\n' + c.replace(/\n?$/, '\n') + '```');
|
||||
}
|
||||
|
||||
class ResponseBuffer {
|
||||
constructor() {
|
||||
this.pending = []; this.timer = null; this.flushing = false;
|
||||
}
|
||||
push({ label, content }) {
|
||||
if (!content) return;
|
||||
this.pending.push({ label, content });
|
||||
this._schedule();
|
||||
}
|
||||
_schedule() {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => this.flush(), 500);
|
||||
}
|
||||
_build() {
|
||||
const showHeadings = true; // readable by default
|
||||
const parts = [];
|
||||
for (const { label, content } of this.pending) {
|
||||
if (showHeadings && label) parts.push(`### ${label}\n`);
|
||||
parts.push(String(content).trimEnd(), '');
|
||||
}
|
||||
return parts.join('\n');
|
||||
}
|
||||
async flush() {
|
||||
if (this.flushing || !this.pending.length) return;
|
||||
this.flushing = true;
|
||||
const toPaste = this._build(); this.pending.length = 0;
|
||||
try {
|
||||
const limit = 250_000;
|
||||
if (toPaste.length > limit) {
|
||||
const chunks = splitRespectingFence(toPaste, limit);
|
||||
chunks.forEach((c, i) => {
|
||||
const header = `### Part ${i+1}/${chunks.length}\n`;
|
||||
const payload = header + c;
|
||||
window.AI_REPO_QUEUE.push(async () => {
|
||||
await window.AI_REPO_PASTE.submitToComposer(payload);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
window.AI_REPO_QUEUE.push(async () => {
|
||||
await window.AI_REPO_PASTE.submitToComposer(toPaste);
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
this.flushing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
window.AI_REPO_RESPONSES = new ResponseBuffer();
|
||||
})();
|
||||
// ==RESPONSE BUFFER END==
|
||||
|
|
@ -34,11 +34,10 @@
|
|||
}
|
||||
|
||||
_fingerprint(el, idx) {
|
||||
const text = (el.textContent || '').slice(0, 1000);
|
||||
const list = Array.from(document.querySelectorAll('[data-message-author-role], .chat-message, .message-content'));
|
||||
const pos = list.indexOf(el);
|
||||
return `conv:${this.conversationId}|pos:${pos}|idx:${idx}|hash:${this._hash(text)}`;
|
||||
const base = window.AI_REPO_FINGERPRINT ? window.AI_REPO_FINGERPRINT(el) : this._hash((el.textContent||'').slice(0,1000));
|
||||
return `${base}|idx:${idx}`;
|
||||
}
|
||||
|
||||
_hash(str) {
|
||||
let h = 5381;
|
||||
for (let i = 0; i < Math.min(str.length, 1000); i++) h = ((h << 5) + h) ^ str.charCodeAt(i);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
// ==UserScript==
|
||||
// @name AI Repo Commander (Modular)
|
||||
// @name AI Repo Commander (Full Features)
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 2.0.0
|
||||
// @description Modularized AI Repo Commander
|
||||
// @version 2.1.0
|
||||
// @description Full modular AI Repo Commander with all features
|
||||
// @author Robert Dickson
|
||||
// @match https://chat.openai.com/*
|
||||
// @match https://chatgpt.com/*
|
||||
// @match https://claude.ai/*
|
||||
// @match https://gemini.google.com/*
|
||||
// @grant GM_xmlhttpRequest
|
||||
// @grant GM_setClipboard
|
||||
// @connect n8n.brrd.tech
|
||||
// @connect *
|
||||
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/config.js
|
||||
|
|
@ -16,4 +18,9 @@
|
|||
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/command-parser.js
|
||||
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/command-executor.js
|
||||
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/main.js
|
||||
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/queue.js
|
||||
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/response-buffer.js
|
||||
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/paste-submit.js
|
||||
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/detector.js
|
||||
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/debug-panel.js
|
||||
// ==/UserScript==
|
||||
Loading…
Reference in New Issue