A userscript to manage a queue of prompts for ChatGPT.
// ==UserScript==
// @name ChatGPT Prompt Queue
// @description A userscript to manage a queue of prompts for ChatGPT.
// @author nihaltp
// @namespace https://github.com/nihaltp/uscripts
// @supportURL https://github.com/nihaltp/uscripts/issues/new?title=%5BBUG%5D%20ChatGPT%20Prompt%20Queue%20dist%2Fchatgpt.user.js&body=File%3A%20AI%20Queue%2Fdist%2Fchatgpt.user.js%0A%0ADescribe%20issue%20here...
// @homepageURL https://github.com/nihaltp/uscripts
// @homepage https://github.com/nihaltp/uscripts
// @license MIT
// @match https://chatgpt.com/*
// @match https://chat.openai.com/*
// @icon https://chatgpt.com/favicon.ico
// @version 3.0.21
// @grant none
// @run-at document-idle
// ==/UserScript==
(() => {
// AI Queue/core/state.js
var queueState = {
queue: [],
failedQueue: [],
running: false,
editingId: null,
draggedId: null,
awaitingChatScopeSync: false,
};
function resetQueueState({ includeFailedQueue = false } = {}) {
queueState.queue.length = 0;
if (includeFailedQueue) {
queueState.failedQueue.length = 0;
}
queueState.running = false;
queueState.editingId = null;
queueState.draggedId = null;
queueState.awaitingChatScopeSync = false;
}
// AI Queue/core/logging.js
window.aiQueueDebug = false;
function isDebugEnabled() {
return Boolean(globalThis.aiQueueDebug);
}
function log(...args) {
const force = typeof args[args.length - 1] === 'boolean' ? args.pop() : false;
if (!force && !isDebugEnabled()) return;
console.log('[AI QUEUE]', ...args);
}
function error(...args) {
console.error('[AI QUEUE]', ...args);
}
function formatError(err) {
if (err instanceof Error) {
return err.message;
}
if (typeof err === 'string') {
return err;
}
if (err && typeof err.message === 'string') {
return err.message;
}
try {
return JSON.stringify(err);
} catch {
return String(err);
}
}
function throwError(...args) {
error(...args);
throw new Error(args.join(' '));
}
// AI Queue/core/utils.js
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function isAttached(element) {
return !!element && document.contains(element);
}
function isVisible(element) {
if (!isAttached(element)) return false;
if (!(element instanceof HTMLElement)) return false;
const style = window.getComputedStyle(element);
if (!style) return false;
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
return false;
}
const rect = element.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) return false;
return (
rect.bottom > 0 &&
rect.right > 0 &&
rect.top < window.innerHeight &&
rect.left < window.innerWidth
);
}
function isActionButtonElement(element) {
return (
!!element &&
element instanceof HTMLElement &&
element.matches('button, [role="button"], input[type="button"], input[type="submit"]')
);
}
function isOwnMutation(target) {
return (
!!target &&
(target.closest?.('#pq-panel') || target.closest?.('.pq-toolbar') || target.id === 'pq-panel')
);
}
// AI Queue/styles/ui.css
var ui_default =
'@keyframes pq-pulse {\r\n 0% {\r\n transform: scale(1);\r\n opacity: 1;\r\n }\r\n\r\n 50% {\r\n transform: scale(1.06);\r\n opacity: 0.75;\r\n }\r\n\r\n 100% {\r\n transform: scale(1);\r\n opacity: 1;\r\n }\r\n}';
// AI Queue/core/ui.js
var repairTimer = null;
var lastRepairAt = 0;
var repairing = false;
var urlWatcher = null;
var lastKnownUrl = location.href;
var mutationObserver = null;
function ensureToolbarStyles() {
if (document.querySelector('#pq-styles')) return;
const style = document.createElement('style');
style.id = 'pq-styles';
style.textContent = ui_default;
document.head.appendChild(style);
}
function getPanel() {
return document.querySelector('#pq-panel');
}
function ensurePanelAttached(panel = getPanel()) {
const currentPanel = getPanel() || panel || null;
if (!currentPanel) return false;
const root = document.documentElement || document.body;
if (!root) return false;
if (!root.contains(currentPanel)) {
root.appendChild(currentPanel);
}
log('attached', root.contains(currentPanel));
log('parent node', currentPanel.parentNode);
return true;
}
function showPanel(createPanel) {
log('TOGGLE PANEL');
let panel = getPanel();
if (!panel) {
createPanel?.();
panel = getPanel();
}
if (!panel) {
error('panel missing');
return;
}
const isHidden = panel.hidden || getComputedStyle(panel).display === 'none';
if (isHidden) {
if (!ensurePanelAttached(panel)) {
error('failed to attach panel');
return;
}
panel.hidden = false;
panel.style.setProperty('display', 'block', 'important');
panel.style.setProperty('visibility', 'visible', 'important');
panel.style.setProperty('opacity', '1', 'important');
panel.style.setProperty('pointer-events', 'auto', 'important');
panel.style.setProperty('z-index', '2147483647', 'important');
log('final state', {
display: getComputedStyle(panel).display,
visibility: getComputedStyle(panel).visibility,
rect: panel.getBoundingClientRect(),
});
return;
}
panel.hidden = true;
panel.style.setProperty('display', 'none', 'important');
panel.style.setProperty('pointer-events', 'none', 'important');
log('panel hidden');
}
function hidePanel(panel = getPanel()) {
if (!panel) return;
panel.hidden = true;
panel.style.setProperty('display', 'none', 'important');
panel.style.setProperty('pointer-events', 'none', 'important');
log('panel hidden');
}
function updateToolbarButton(toolbarButton, queue, running) {
if (!toolbarButton || !isAttached(toolbarButton)) return;
const count = queue.length;
toolbarButton.textContent = count > 0 ? `Queue (${count})` : 'Queue';
toolbarButton.style.animation = running ? 'pq-pulse 1.2s infinite' : '';
toolbarButton.style.opacity = running ? '1' : count > 0 ? '1' : '0.8';
}
function repairUi(
reason = 'repair',
createPanel,
setupPanelEvents,
setupPanelDrag2,
ensureToolbarButton
) {
if (repairing) return;
repairing = true;
try {
log('repair', reason);
createPanel();
setupPanelEvents?.();
setupPanelDrag2?.();
ensureToolbarButton?.();
} finally {
repairing = false;
}
}
function requestRepair(
reason = 'repair',
createPanel,
setupPanelEvents,
setupPanelDrag2,
ensureToolbarButton
) {
const now = Date.now();
const delay = Math.max(0, 2e3 - (now - lastRepairAt));
if (repairTimer) {
clearTimeout(repairTimer);
}
repairTimer = setTimeout(() => {
lastRepairAt = Date.now();
repairUi(reason, createPanel, setupPanelEvents, setupPanelDrag2, ensureToolbarButton);
}, delay);
}
function startDomObserver(
createPanel,
setupPanelEvents,
setupPanelDrag2,
ensureToolbarButton,
isOwnMutationOverride
) {
if (mutationObserver) return;
const target = document.body || document.documentElement;
if (!target) return;
mutationObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
const hasExternalChange = [...mutation.addedNodes, ...mutation.removedNodes].some(
(node) => node && !isOwnMutation(node)
);
if (hasExternalChange) {
requestRepair(
'dom-mutation',
createPanel,
setupPanelEvents,
setupPanelDrag2,
ensureToolbarButton
);
break;
}
}
if (mutation.type === 'attributes') {
if (!isOwnMutationOverride?.(mutation.target)) {
requestRepair(
'attribute-mutation',
createPanel,
setupPanelEvents,
setupPanelDrag2,
ensureToolbarButton
);
break;
}
}
}
});
mutationObserver.observe(target, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['aria-busy', 'aria-disabled', 'disabled'],
});
}
function patchHistoryMethod(methodName) {
const original = history[methodName];
if (typeof original !== 'function' || original.__pqPatched) return;
const patched = function (...args) {
const result = original.apply(this, args);
requestRepair('history-' + methodName);
return result;
};
patched.__pqPatched = true;
history[methodName] = patched;
}
function startUrlWatcher(
createPanel,
setupPanelEvents,
setupPanelDrag2,
ensureToolbarButton,
onUrlChange
) {
const handleUrlChange = (reason, previousUrl, currentUrl) => {
onUrlChange?.(previousUrl, currentUrl, reason);
requestRepair(reason, createPanel, setupPanelEvents, setupPanelDrag2, ensureToolbarButton);
};
patchHistoryMethod('pushState');
patchHistoryMethod('replaceState');
window.addEventListener('popstate', () => {
const previousUrl = lastKnownUrl;
lastKnownUrl = location.href;
handleUrlChange('popstate', previousUrl, lastKnownUrl);
});
window.addEventListener('hashchange', () => {
const previousUrl = lastKnownUrl;
lastKnownUrl = location.href;
handleUrlChange('hashchange', previousUrl, lastKnownUrl);
});
if (urlWatcher) clearInterval(urlWatcher);
urlWatcher = setInterval(() => {
if (location.href !== lastKnownUrl) {
const previousUrl = lastKnownUrl;
lastKnownUrl = location.href;
handleUrlChange('url-change', previousUrl, lastKnownUrl);
}
}, 1e3);
}
// AI Queue/core/panel.js
function createBasePanel(titleText, includeFailedList = false) {
log('createBasePanel called');
let panel = document.querySelector('#pq-panel');
if (!panel) {
panel = document.createElement('div');
panel.id = 'pq-panel';
Object.assign(panel.style, {
position: 'fixed',
top: '80px',
left: '24px',
bottom: 'auto',
right: 'auto',
width: '320px',
minHeight: '200px',
maxHeight: '70vh',
overflowY: 'auto',
background: '#202123',
color: 'white',
border: '1px solid #444',
borderRadius: '16px',
padding: '12px',
zIndex: '2147483647',
boxShadow: '0 10px 40px rgba(0,0,0,0.6)',
display: 'none',
transform: 'none',
});
const title = document.createElement('div');
title.style.display = 'flex';
title.style.alignItems = 'center';
title.style.justifyContent = 'space-between';
title.style.gap = '12px';
title.style.fontSize = '18px';
title.style.fontWeight = 'bold';
title.style.marginBottom = '10px';
const titleLabel = document.createElement('span');
titleLabel.textContent = titleText;
const closeBtn = document.createElement('button');
closeBtn.id = 'pq-close';
closeBtn.type = 'button';
closeBtn.textContent = 'Close';
Object.assign(closeBtn.style, {
flexShrink: '0',
padding: '4px 10px',
borderRadius: '9999px',
border: '1px solid #555',
background: '#2a2a2a',
color: '#fff',
cursor: 'pointer',
});
closeBtn.addEventListener('click', () => hidePanel(panel));
title.appendChild(titleLabel);
title.appendChild(closeBtn);
const textarea = document.createElement('textarea');
textarea.id = 'pq-input';
textarea.placeholder = 'Enter prompt...';
Object.assign(textarea.style, {
width: '100%',
height: '80px',
resize: 'vertical',
color: '#fff',
background: '#222',
border: '1px solid #444',
borderRadius: '6px',
padding: '8px',
boxSizing: 'border-box',
});
const addBtn = document.createElement('button');
addBtn.id = 'pq-add';
addBtn.style.marginTop = '10px';
addBtn.style.width = '100%';
addBtn.textContent = 'Add To Queue';
const manageChatsBtn = document.createElement('button');
manageChatsBtn.id = 'pq-manage-chats';
manageChatsBtn.style.marginTop = '10px';
manageChatsBtn.style.width = '100%';
manageChatsBtn.textContent = 'Manage Chat Prompts';
const startBtn = document.createElement('button');
startBtn.id = 'pq-start';
startBtn.style.marginTop = '10px';
startBtn.style.width = '100%';
startBtn.textContent = 'Start Queue';
const status = document.createElement('div');
status.id = 'pq-status';
status.style.marginTop = '10px';
status.textContent = 'Idle';
const list = document.createElement('ol');
list.id = 'pq-list';
list.style.marginTop = '10px';
list.style.paddingLeft = '20px';
panel.appendChild(title);
panel.appendChild(textarea);
panel.appendChild(addBtn);
panel.appendChild(manageChatsBtn);
panel.appendChild(startBtn);
panel.appendChild(status);
panel.appendChild(list);
if (includeFailedList) {
const failedTitle = document.createElement('div');
failedTitle.id = 'pq-failed-title';
failedTitle.style.marginTop = '12px';
failedTitle.style.fontSize = '13px';
failedTitle.style.opacity = '0.8';
failedTitle.textContent = 'Failed Prompts';
const failedList = document.createElement('ol');
failedList.id = 'pq-failed-list';
failedList.style.marginTop = '6px';
failedList.style.paddingLeft = '20px';
panel.appendChild(failedTitle);
panel.appendChild(failedList);
}
const root = document.documentElement || document.body;
if (root) {
root.appendChild(panel);
}
} else {
ensurePanelAttached(panel);
}
log('createBasePanel panel', panel);
setTimeout(() => {
log('panel element', panel);
log('computed display', getComputedStyle(panel).display);
log('computed visibility', getComputedStyle(panel).visibility);
log('rect', panel.getBoundingClientRect());
log('parent', panel.parentElement);
}, 0);
return panel;
}
// AI Queue/core/queue.js
function deleteQueueItem(id, queue, renderQueue, saveQueue2) {
const index = queue.findIndex((item) => item.id === id);
if (index === -1) {
log('Item to delete not found in queue:', id);
return;
}
queue.splice(index, 1);
renderQueue();
saveQueue2?.();
}
function editQueueItem(id, queue, updateUI) {
const item = queue.find((item2) => item2.id === id);
if (!item) {
log('Item to edit not found in queue:', id);
return;
}
updateUI(id, item.prompt);
}
function moveQueueItem(fromId, toId, queue, renderQueue, saveQueue2) {
const fromIndex = queue.findIndex((item) => item.id === fromId);
const toIndex = queue.findIndex((item) => item.id === toId);
if (fromIndex === -1 || toIndex === -1) return;
const [movedItem] = queue.splice(fromIndex, 1);
queue.splice(toIndex, 0, movedItem);
renderQueue();
saveQueue2?.();
}
function setStatus(panel, text) {
if (!panel) return;
const status = panel.querySelector('#pq-status');
if (status) {
status.textContent = text;
}
}
// AI Queue/core/queue-ui.js
function createQueueItemElement(item, { renderQueue, saveQueue: saveQueue2 }) {
const li = document.createElement('li');
li.style.marginBottom = '10px';
li.draggable = false;
const row = document.createElement('div');
row.style.display = 'flex';
row.style.gap = '6px';
row.style.alignItems = 'flex-start';
const text = document.createElement('div');
text.textContent = item.prompt;
text.style.flex = '1';
text.style.wordBreak = 'break-word';
text.style.fontSize = '14px';
const editBtn = document.createElement('button');
editBtn.textContent = '\u{1F589}';
editBtn.title = 'Edit';
editBtn.style.cursor = 'pointer';
editBtn.style.color = '#7dd3fc';
editBtn.style.display = 'none';
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '\u2715';
deleteBtn.title = 'Delete';
deleteBtn.style.cursor = 'pointer';
deleteBtn.style.color = '#ff6b6b';
deleteBtn.style.display = 'none';
row.appendChild(text);
row.appendChild(editBtn);
row.appendChild(deleteBtn);
const dragHandle = document.createElement('span');
dragHandle.textContent = '\u2630';
dragHandle.title = 'Drag to reorder';
dragHandle.style.cursor = 'grab';
dragHandle.style.userSelect = 'none';
dragHandle.style.alignSelf = 'center';
dragHandle.style.marginLeft = '6px';
dragHandle.style.display = 'none';
dragHandle.addEventListener('dragstart', (e) => {
queueState.draggedId = item.id;
try {
e.dataTransfer.setData('text/plain', item.id);
e.dataTransfer.effectAllowed = 'move';
} catch (err) {
error('Drag start dataTransfer error:', err);
}
li.style.opacity = '0.6';
});
dragHandle.addEventListener('dragend', () => {
queueState.draggedId = null;
li.style.opacity = '';
});
row.appendChild(dragHandle);
li.appendChild(row);
li.addEventListener('mouseenter', () => {
editBtn.style.display = 'inline-block';
deleteBtn.style.display = 'inline-block';
dragHandle.style.display = 'inline-block';
});
li.addEventListener('mouseleave', () => {
if (queueState.editingId === item.id) return;
editBtn.style.display = 'none';
deleteBtn.style.display = 'none';
if (queueState.draggedId === item.id) return;
dragHandle.style.display = 'none';
});
li.addEventListener('dragover', (e) => {
e.preventDefault();
try {
e.dataTransfer.dropEffect = 'move';
} catch (err) {
error('Drag over dataTransfer error:', err);
}
li.style.borderTop = '2px solid #7dd3fc';
});
li.addEventListener('dragleave', () => {
li.style.borderTop = '';
});
li.addEventListener('drop', (e) => {
e.preventDefault();
li.style.borderTop = '';
const draggedId =
queueState.draggedId ||
(e.dataTransfer && e.dataTransfer.getData && e.dataTransfer.getData('text/plain'));
if (draggedId && draggedId !== item.id) {
moveQueueItem(draggedId, item.id, queueState.queue, renderQueue, saveQueue2);
}
});
return { li, text, editBtn, deleteBtn };
}
// AI Queue/core/storage.js
var GLOBAL_CHAT_KEY = '__global__';
var DEFAULT_ITEM_STATUS = 'queued';
var DEFAULT_FAILED_STATUS = 'failed';
function toChatCode(value) {
if (typeof value !== 'string') return null;
const normalized = value.trim();
return normalized ? normalized : null;
}
function resolveScope(currentScope = null) {
if (typeof currentScope === 'string') {
return {
chatId: toChatCode(currentScope),
groupId: null,
};
}
if (!currentScope || typeof currentScope !== 'object') {
return {
chatId: null,
groupId: null,
};
}
const chatId = toChatCode(currentScope.chatId || currentScope.chatCode || null);
const groupId = toChatCode(currentScope.groupId || null);
return {
chatId,
groupId,
};
}
function resolveScopeKeys(currentScope = null) {
const scope = resolveScope(currentScope);
return [...new Set([scope.groupId, scope.chatId].filter(Boolean))];
}
function hasItemScope(item) {
return !!(item?.chatId || item?.chatCode || item?.groupId);
}
function applyScopeToQueuedItems(queue, failedQueue, currentScope = null) {
const scope = resolveScope(currentScope);
if (!scope.chatId && !scope.groupId) return false;
let updated = false;
const applyScope = (item) => {
if (!item || hasItemScope(item)) return;
if (scope.chatId) {
item.chatId = scope.chatId;
item.chatCode = scope.chatId;
}
if (scope.groupId) {
item.groupId = scope.groupId;
}
updated = true;
};
(queue || []).forEach(applyScope);
if (Array.isArray(failedQueue)) {
failedQueue.forEach(applyScope);
}
return updated;
}
function toChatKey(value) {
return toChatCode(value) || GLOBAL_CHAT_KEY;
}
function generateId() {
if (globalThis.crypto?.randomUUID) {
return globalThis.crypto.randomUUID();
}
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
}
function toCreatedAt(value) {
const timestamp = Number(value);
return Number.isFinite(timestamp) ? timestamp : Date.now();
}
function sanitizeItem(item, defaultStatus = DEFAULT_ITEM_STATUS) {
if (!item || typeof item.prompt !== 'string') return null;
const chatId = toChatCode(item.chatId || item.chatCode || null);
const groupId = toChatCode(item.groupId || null);
const normalized = {
id: typeof item.id === 'string' && item.id ? item.id : generateId(),
prompt: item.prompt,
attempts: Number.isFinite(Number(item.attempts)) ? Number(item.attempts) : 0,
status:
typeof item.status === 'string' && item.status.trim() ? item.status.trim() : defaultStatus,
createdAt: toCreatedAt(item.createdAt),
};
if (chatId) {
normalized.chatId = chatId;
normalized.chatCode = chatId;
}
if (groupId) {
normalized.groupId = groupId;
}
return normalized;
}
function sanitizeItems(items, defaultStatus = DEFAULT_ITEM_STATUS) {
if (!Array.isArray(items)) return [];
return items.map((item) => sanitizeItem(item, defaultStatus)).filter(Boolean);
}
function emptyChats() {
return {};
}
function sanitizeChatBuckets(chats) {
const normalized = emptyChats();
if (!chats || typeof chats !== 'object' || Array.isArray(chats)) {
return normalized;
}
for (const [chatKey, bucket] of Object.entries(chats)) {
normalized[toChatKey(chatKey)] = {
items: sanitizeItems(bucket?.items, DEFAULT_ITEM_STATUS),
failedItems: sanitizeItems(bucket?.failedItems, DEFAULT_FAILED_STATUS),
};
}
return normalized;
}
function addLegacyItem(chats, item, defaultStatus, bucketName) {
const normalizedItem = sanitizeItem(item, defaultStatus);
if (!normalizedItem) return;
const chatKey = toChatKey(item?.chatCode);
if (!chats[chatKey]) {
chats[chatKey] = { items: [], failedItems: [] };
}
chats[chatKey][bucketName].push(normalizedItem);
}
function mergeUniqueItems(itemsA, itemsB) {
const merged = /* @__PURE__ */ new Map();
[...(itemsA || []), ...(itemsB || [])].forEach((item) => {
if (!item || typeof item.id !== 'string') return;
if (!merged.has(item.id)) {
merged.set(item.id, { ...item });
}
});
return [...merged.values()];
}
function buildLegacyData(parsed) {
const chats = emptyChats();
if (Array.isArray(parsed?.items)) {
parsed.items.forEach((item) => addLegacyItem(chats, item, DEFAULT_ITEM_STATUS, 'items'));
} else if (Array.isArray(parsed?.queue)) {
parsed.queue.forEach((item) => addLegacyItem(chats, item, DEFAULT_ITEM_STATUS, 'items'));
}
if (Array.isArray(parsed?.failedItems)) {
parsed.failedItems.forEach((item) =>
addLegacyItem(chats, item, DEFAULT_FAILED_STATUS, 'failedItems')
);
} else if (Array.isArray(parsed?.failedQueue)) {
parsed.failedQueue.forEach((item) =>
addLegacyItem(chats, item, DEFAULT_FAILED_STATUS, 'failedItems')
);
}
return { chats };
}
function normalizeData(parsed) {
if (!parsed || typeof parsed !== 'object') {
return { chats: emptyChats() };
}
if (parsed.chats && typeof parsed.chats === 'object' && !Array.isArray(parsed.chats)) {
return {
chats: sanitizeChatBuckets(parsed.chats),
};
}
return buildLegacyData(parsed);
}
function readScopedQueueData(storageKey = 'pq-queue-state') {
try {
const stored = localStorage.getItem(storageKey);
if (!stored) {
return { chats: emptyChats() };
}
const parsed = JSON.parse(stored);
return normalizeData(parsed);
} catch (err) {
error('Failed to read queue storage:', err);
return { chats: emptyChats() };
}
}
function writeScopedQueueData(storageKey = 'pq-queue-state', data = {}) {
try {
const normalized = normalizeData(data);
localStorage.setItem(storageKey, JSON.stringify(normalized));
} catch (err) {
error('Failed to write queue storage:', err);
}
}
function saveQueue(queue, failedQueue, storageKey = 'pq-queue-state', currentScope = null) {
try {
const scope = resolveScope(currentScope);
const scopeKeys = resolveScopeKeys(currentScope);
const data = readScopedQueueData(storageKey);
const existingBuckets = scopeKeys.map(
(scopeKey) => data.chats[scopeKey] || { items: [], failedItems: [] }
);
const nextItems = sanitizeItems(queue || [], DEFAULT_ITEM_STATUS).map((item) => ({
...item,
...(scope.chatId
? {
chatId: scope.chatId,
chatCode: scope.chatId,
}
: {}),
...(scope.groupId ? { groupId: scope.groupId } : {}),
}));
const nextFailedItems = Array.isArray(failedQueue)
? sanitizeItems(failedQueue || [], DEFAULT_FAILED_STATUS).map((item) => ({
...item,
...(scope.chatId
? {
chatId: scope.chatId,
chatCode: scope.chatId,
}
: {}),
...(scope.groupId ? { groupId: scope.groupId } : {}),
}))
: mergeUniqueItems(
...existingBuckets.map((bucket) =>
sanitizeItems(bucket.failedItems, DEFAULT_FAILED_STATUS)
)
);
const nextBucket = {
items: nextItems,
failedItems: nextFailedItems,
};
const nextChats = { ...data.chats };
scopeKeys.forEach((scopeKey) => {
nextChats[scopeKey] = {
items: nextItems.map((item) => ({ ...item })),
failedItems: nextFailedItems.map((item) => ({ ...item })),
};
});
writeScopedQueueData(storageKey, {
chats: nextChats,
});
log('queue saved to storage', {
storageKey,
currentChatCode: scopeKeys,
visibleItems: nextBucket.items.length,
});
} catch (err) {
error('Failed to save queue:', err);
}
}
function loadQueue(queue, failedQueue, storageKey = 'pq-queue-state', currentScope = null) {
try {
const scopeKeys = resolveScopeKeys(currentScope);
const data = readScopedQueueData(storageKey);
const buckets = scopeKeys.map(
(scopeKey) => data.chats[scopeKey] || { items: [], failedItems: [] }
);
const visibleQueueItems = mergeUniqueItems(...buckets.map((bucket) => bucket.items));
queue.push(...visibleQueueItems.map((item) => ({ ...item })));
if (Array.isArray(failedQueue)) {
const visibleFailedItems = mergeUniqueItems(...buckets.map((bucket) => bucket.failedItems));
failedQueue.push(...visibleFailedItems.map((item) => ({ ...item })));
}
log('queue loaded from storage', {
storageKey,
currentChatCode: scopeKeys,
visibleItems: visibleQueueItems.length,
});
} catch (err) {
error('Failed to load queue:', err);
}
}
// AI Queue/core/panel-controls.js
var boundPanels = /* @__PURE__ */ new WeakSet();
function setupPanelControls({
createItem,
renderQueue,
saveQueue: saveQueue2,
processQueue,
openChatManager,
}) {
const panel = getPanel();
if (!panel) return;
if (boundPanels.has(panel)) return;
const input = panel.querySelector('#pq-input');
const addBtn = panel.querySelector('#pq-add');
const manageChatsBtn = panel.querySelector('#pq-manage-chats');
const startBtn = panel.querySelector('#pq-start');
const getToolbarButton = () => document.querySelector('#pq-toolbar-button');
const handleAddClick = () => {
const text = input.value.trim();
if (!text) {
log('Empty prompt, not adding to queue');
return;
}
if (queueState.editingId !== null) {
const item = queueState.queue.find((item2) => item2.id === queueState.editingId);
if (!item) {
log('Editing item not found in queue:', queueState.editingId);
queueState.queue.push(createItem(text));
} else {
item.prompt = text;
}
queueState.editingId = null;
addBtn.textContent = 'Add To Queue';
} else {
queueState.queue.push(createItem(text));
}
updateToolbarButton(getToolbarButton(), queueState.queue, queueState.running);
input.value = '';
renderQueue();
saveQueue2();
};
addBtn.addEventListener('click', handleAddClick);
if (manageChatsBtn) {
manageChatsBtn.addEventListener('click', () => {
openChatManager?.();
});
}
input.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
handleAddClick();
}
});
function updateStartStopButtons() {
if (!startBtn) return;
startBtn.textContent = queueState.running ? 'Stop Queue' : 'Start Queue';
startBtn.disabled = false;
}
startBtn.addEventListener('click', async () => {
if (queueState.running) {
queueState.running = false;
setStatus(panel, 'Stopped');
updateStartStopButtons();
updateToolbarButton(getToolbarButton(), queueState.queue, queueState.running);
return;
}
queueState.running = true;
updateStartStopButtons();
updateToolbarButton(getToolbarButton(), queueState.queue, queueState.running);
try {
await processQueue();
} catch (err) {
error('Queue processor error:', formatError(err));
} finally {
queueState.running = false;
updateStartStopButtons();
updateToolbarButton(getToolbarButton(), queueState.queue, queueState.running);
}
});
boundPanels.add(panel);
updateStartStopButtons();
}
// AI Queue/core/drag.js
var dragBoundPanel = null;
var listenersBound = false;
var dragging = false;
var dragStartX = 0;
var dragStartY = 0;
var panelStartX = 0;
var panelStartY = 0;
function onMouseMove(e) {
if (!dragging) return;
const panel = getPanel();
if (!panel) return;
const deltaX = e.clientX - dragStartX;
const deltaY = e.clientY - dragStartY;
panel.style.left = panelStartX + deltaX + 'px';
panel.style.top = panelStartY + deltaY + 'px';
panel.style.right = 'auto';
panel.style.bottom = 'auto';
}
function onMouseUp() {
if (dragging) {
dragging = false;
log('panel drag ended');
}
}
function bindDocumentListeners() {
if (listenersBound) return;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
listenersBound = true;
}
function setupPanelDrag() {
const panel = getPanel();
if (!panel) return;
bindDocumentListeners();
if (dragBoundPanel === panel) return;
dragBoundPanel = panel;
panel.addEventListener(
'mousedown',
(e) => {
if (e.button !== 2) return;
e.preventDefault();
e.stopPropagation();
dragging = true;
dragStartX = e.clientX;
dragStartY = e.clientY;
panelStartX = panel.offsetLeft;
panelStartY = panel.offsetTop;
log('panel drag started');
},
true
);
panel.addEventListener('contextmenu', (e) => {
if (dragging) {
e.preventDefault();
}
});
}
// AI Queue/core/dom.js
function waitForCondition(
predicate,
{ timeoutMs = 1e4, intervalMs = 100, description = 'condition' } = {}
) {
const startedAt = Date.now();
return new Promise((resolve, reject) => {
const check = async () => {
try {
const result = await Promise.resolve(predicate());
if (result) {
resolve(result);
return;
}
} catch (err) {
error('waitForCondition check error:', err);
}
const elapsed = Date.now() - startedAt;
if (elapsed > timeoutMs) {
error(`Timeout waiting for ${description} (${elapsed}ms)`);
reject(new Error(`Timeout waiting for ${description} (${elapsed}ms`));
return;
}
setTimeout(check, intervalMs);
};
check();
});
}
function waitForElement(getter, options = {}) {
return waitForCondition(() => getter(), options);
}
function safeClick(element) {
if (!isAttached(element) || !isVisible(element)) return false;
element.focus?.({ preventScroll: true });
if (typeof element.click === 'function') {
element.click();
return true;
}
element.dispatchEvent(
new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window })
);
element.dispatchEvent(
new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window })
);
element.dispatchEvent(
new MouseEvent('click', { bubbles: true, cancelable: true, view: window })
);
return true;
}
function getEditorText(editor) {
if (!editor) return '';
if ('value' in editor) return String(editor.value || '');
return String(editor.textContent || '');
}
function isEditableCandidate(element) {
if (!element) return false;
if (!(element instanceof HTMLElement)) return false;
if (!isAttached(element)) return false;
if (!isVisible(element)) return false;
const isTextarea = element instanceof HTMLTextAreaElement;
const isContentEditable =
element.isContentEditable || element.getAttribute('contenteditable') === 'true';
if (!isTextarea && !isContentEditable) return false;
if (element.matches('button, [role="button"], input[type="button"], input[type="submit"]'))
return false;
if (element.disabled || element.getAttribute('aria-disabled') === 'true') return false;
if (element.closest('#pq-panel')) return false;
return true;
}
function scoreEditor(editor) {
const rect = editor.getBoundingClientRect();
let score = rect.top;
if (editor === document.activeElement) score += 1e3;
if (editor.matches('textarea')) score += 100;
if (editor.matches('[contenteditable="true"]')) score += 80;
if ((editor.getAttribute('role') || '').toLowerCase() === 'textbox') score += 60;
if (editor.closest('form')) score += 30;
if (editor.closest('[role="form"]')) score += 20;
if (rect.bottom > window.innerHeight * 0.5) score += 50;
return score;
}
function getComposerEditor() {
const activeElement = document.activeElement;
if (isEditableCandidate(activeElement)) {
if (isActionButtonElement(activeElement)) return null;
log('editor found', activeElement);
return activeElement;
}
const candidates = [
...document.querySelectorAll(
'textarea:not(#pq-input), [contenteditable="true"][role="textbox"], [contenteditable="true"]'
),
]
.filter(isEditableCandidate)
.sort((left, right) => scoreEditor(right) - scoreEditor(left));
candidates.forEach((candidate) => log('editor candidate', candidate.tagName, candidate));
const editor = candidates[0] || null;
if (editor && editor.matches('button, [role="button"]')) return null;
if (editor) log('editor found', editor);
return editor;
}
function getComposerHost(editor = getComposerEditor()) {
if (!editor) return null;
const host =
editor.closest(
'form, [role="form"], [aria-label*="prompt" i], [aria-label*="composer" i], [aria-label*="message" i], [data-testid*="prompt" i], [data-testid*="composer" i]'
) ||
editor.parentElement ||
null;
if (!host || host === editor || editor.contains(host) || host.contains?.(editor)) return null;
if (
host.isContentEditable ||
host.matches?.(
'[contenteditable="true"], textarea, input, button, [role="button"], input[type="button"], input[type="submit"]'
)
)
return null;
return host;
}
function getButtonLabel(button) {
return [button.getAttribute('aria-label'), button.getAttribute('title'), button.textContent]
.filter(Boolean)
.join(' ')
.replace(/\s+/g, ' ')
.trim();
}
function isActionButtonVisible(button) {
return isAttached(button) && isVisible(button);
}
function getSendButton({ includeDisabled = false } = {}) {
const host = getComposerHost();
const selectors = [
'button[data-testid="send-button"]',
'[role="button"][data-testid="send-button"]',
'button[aria-label*="Send" i]',
'button[title*="Send" i]',
'[role="button"][aria-label*="Send" i]',
'[role="button"][title*="Send" i]',
];
const candidates = [];
for (const selector of selectors) {
candidates.push(...document.querySelectorAll(selector));
}
if (host) {
candidates.push(...host.querySelectorAll('button, [role="button"]'));
}
const button =
candidates.find((candidate) => {
if (!candidate || !(candidate instanceof HTMLElement)) return false;
if (!isActionButtonVisible(candidate)) return false;
const label = getButtonLabel(candidate);
const exactSend = candidate.matches('[data-testid="send-button"]');
const looksLikeSend = /\bsend\b/i.test(label) || /\bsubmit\b/i.test(label);
if (!exactSend && !looksLikeSend) return false;
if (!includeDisabled && candidate.disabled) return false;
return true;
}) || null;
if (button) log('send button found', button);
return button;
}
function findStopButton() {
const selectors = [
'button[data-testid="stop-button"]',
'[role="button"][data-testid="stop-button"]',
'button[aria-label*="Stop" i]',
'button[title*="Stop" i]',
'[role="button"][aria-label*="Stop" i]',
'[role="button"][title*="Stop" i]',
];
for (const selector of selectors) {
const button = [...document.querySelectorAll(selector)].find(isActionButtonVisible) || null;
if (button) {
log('stop button found', button);
return button;
}
}
return null;
}
function hasBusyIndicators() {
return [
...document.querySelectorAll(
'[aria-busy="true"], [data-loading="true"], [role="progressbar"]'
),
].some(isActionButtonVisible);
}
// AI Queue/core/generation.js
var lastGenerationLabel = '';
function getGenerationState() {
const editor = getComposerEditor();
const sendButton = getSendButton({ includeDisabled: true });
const stopButton = findStopButton();
const hasPrompt = !!getEditorText(editor).trim();
const busyIndicators = hasBusyIndicators();
const generating = Boolean(
stopButton || busyIndicators || (sendButton && sendButton.disabled && hasPrompt)
);
const label = JSON.stringify({
generating,
hasPrompt,
busyIndicators,
sendDisabled: !!(sendButton && sendButton.disabled),
stopButton: !!stopButton,
});
if (label !== lastGenerationLabel) {
lastGenerationLabel = label;
log('generation state', {
generating,
hasPrompt,
busyIndicators,
sendDisabled: !!(sendButton && sendButton.disabled),
stopButton: !!stopButton,
});
}
return { generating, editor, sendButton, stopButton, busyIndicators, hasPrompt };
}
async function waitForIdle({ timeoutMs = 6e4, intervalMs = 200 } = {}) {
try {
await waitForCondition(
async () => {
const { generating } = getGenerationState();
return !generating;
},
{
timeoutMs,
intervalMs,
description: 'AI to become idle',
}
);
await sleep(300);
} catch (err) {
log('waitForIdle timed out:', formatError(err));
await sleep(300);
}
}
async function waitForGenerationStart({ timeoutMs = 8e3, intervalMs = 100 } = {}) {
return waitForCondition(() => getGenerationState().generating, {
timeoutMs,
intervalMs,
description: 'Generation to start',
});
}
async function waitForPromptProcessing() {
try {
await waitForGenerationStart();
} catch (err) {
log('Generation did not start:', formatError(err));
}
await waitForIdle();
}
// AI Queue/core/keyboard.js
function isScrollableElement(element) {
if (!element || !(element instanceof HTMLElement)) return false;
const style = window.getComputedStyle(element);
if (!style) return false;
const overflowY = style.overflowY;
const overflowX = style.overflowX;
const canScrollY =
/(auto|scroll|overlay)/i.test(overflowY) && element.scrollHeight > element.clientHeight;
const canScrollX =
/(auto|scroll|overlay)/i.test(overflowX) && element.scrollWidth > element.clientWidth;
return canScrollY || canScrollX;
}
function getScrollTargets(element) {
const targets = [];
const seen = /* @__PURE__ */ new Set();
const addTarget = (target) => {
if (!target || seen.has(target)) return;
seen.add(target);
targets.push(target);
};
addTarget(window);
const scrollingElement = document.scrollingElement || document.documentElement;
if (scrollingElement) {
addTarget(scrollingElement);
}
if (document.body) {
addTarget(document.body);
}
let current = element?.parentElement || null;
while (current) {
if (isScrollableElement(current)) {
addTarget(current);
}
current = current.parentElement;
}
const root = document.body || document.documentElement;
if (root) {
for (const candidate of root.querySelectorAll('*')) {
if (isScrollableElement(candidate)) {
addTarget(candidate);
}
}
}
return targets;
}
function getScrollPosition(target) {
if (target === window) {
return { x: window.scrollX, y: window.scrollY };
}
return {
x: target.scrollLeft,
y: target.scrollTop,
};
}
function restoreScrollPosition(target, position) {
if (target === window) {
window.scrollTo(position.x, position.y);
return;
}
target.scrollLeft = position.x;
target.scrollTop = position.y;
}
function createScrollApiSuppressor() {
const restorers = [];
const patch = (target, key, replacement) => {
if (!target || typeof target[key] !== 'function') return;
const original = target[key];
target[key] = replacement;
restorers.push(() => {
target[key] = original;
});
};
patch(Element.prototype, 'scrollIntoView', function () {});
patch(window, 'scrollTo', function () {});
patch(window, 'scrollBy', function () {});
patch(Element.prototype, 'scroll', function () {});
patch(Element.prototype, 'scrollTo', function () {});
patch(Element.prototype, 'scrollBy', function () {});
return () => {
for (let index = restorers.length - 1; index >= 0; index -= 1) {
restorers[index]();
}
};
}
async function withPreservedViewport(action, targets = []) {
const scrollTargets = targets.length > 0 ? targets : [window];
const scrollPositions = new Map(
scrollTargets.map((target) => [target, getScrollPosition(target)])
);
const originalAnchors = /* @__PURE__ */ new Map();
let restoring = false;
const restoreScrollApis = createScrollApiSuppressor();
for (const target of scrollTargets) {
if (target instanceof HTMLElement) {
originalAnchors.set(target, target.style.overflowAnchor);
target.style.overflowAnchor = 'none';
}
}
const restoreScroll = () => {
if (restoring || userInteracting) return;
restoring = true;
for (const target of scrollTargets) {
const position = scrollPositions.get(target);
if (!position) continue;
const currentPosition = getScrollPosition(target);
if (currentPosition.x === position.x && currentPosition.y === position.y) continue;
restoreScrollPosition(target, position);
}
restoring = false;
};
const keepViewportStable = () => {
restoreScroll();
};
let userInteracting = false;
let userInteractTimer = null;
const onUserInteraction = (ev) => {
userInteracting = true;
if (userInteractTimer) clearTimeout(userInteractTimer);
userInteractTimer = setTimeout(() => {
userInteracting = false;
userInteractTimer = null;
try {
for (const t of scrollTargets) {
scrollPositions.set(t, getScrollPosition(t));
}
} catch (err) {
error('Error updating scroll positions after user interaction:', formatError(err));
}
}, 300);
};
window.addEventListener('wheel', onUserInteraction, { passive: true, capture: true });
window.addEventListener('touchstart', onUserInteraction, { passive: true, capture: true });
window.addEventListener('touchmove', onUserInteraction, { passive: true, capture: true });
window.addEventListener('pointerdown', onUserInteraction, { passive: true, capture: true });
window.addEventListener('keydown', onUserInteraction, true);
window.addEventListener('scroll', keepViewportStable, true);
window.addEventListener('resize', keepViewportStable);
const restoreTimer = window.setInterval(restoreScroll, 50);
try {
return await action();
} finally {
window.clearInterval(restoreTimer);
window.removeEventListener('scroll', keepViewportStable, true);
window.removeEventListener('resize', keepViewportStable);
window.removeEventListener('wheel', onUserInteraction, true);
window.removeEventListener('touchstart', onUserInteraction, true);
window.removeEventListener('touchmove', onUserInteraction, true);
window.removeEventListener('pointerdown', onUserInteraction, true);
window.removeEventListener('keydown', onUserInteraction, true);
if (userInteractTimer) {
clearTimeout(userInteractTimer);
try {
for (const t of scrollTargets) {
scrollPositions.set(t, getScrollPosition(t));
}
} catch (err) {
error('Error updating scroll positions during cleanup:', formatError(err));
}
userInteractTimer = null;
}
restoreScrollApis();
for (const target of scrollTargets) {
if (target instanceof HTMLElement && originalAnchors.has(target)) {
target.style.overflowAnchor = originalAnchors.get(target);
}
}
restoreScroll();
}
}
function dispatchEnterKey(target) {
const eventInit = {
bubbles: true,
cancelable: true,
key: 'Enter',
code: 'Enter',
keyCode: 13,
which: 13,
};
for (const type of ['keydown', 'keypress', 'keyup']) {
target.dispatchEvent(new KeyboardEvent(type, eventInit));
}
}
function setEditorValue(editor, prompt) {
if (!editor) throwError('Editor not found');
if (!isAttached(editor)) throwError('Editor is detached');
editor.focus?.({ preventScroll: true });
if ('value' in editor) {
const setter =
Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set ||
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
if (setter) {
setter.call(editor, prompt);
} else {
editor.value = prompt;
}
if ('selectionStart' in editor) {
editor.selectionStart = editor.selectionEnd = editor.value.length;
}
editor.dispatchEvent(
new InputEvent('beforeinput', {
bubbles: true,
cancelable: true,
inputType: 'insertText',
data: prompt,
})
);
editor.dispatchEvent(
new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertText',
data: prompt,
})
);
editor.dispatchEvent(new Event('change', { bubbles: true }));
return;
}
if (editor.isContentEditable) {
editor.focus?.({ preventScroll: true });
const selection = window.getSelection();
if (selection) {
const range = document.createRange();
range.selectNodeContents(editor);
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('delete', false);
}
editor.textContent = prompt;
editor.dispatchEvent(
new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertText',
data: prompt,
})
);
editor.dispatchEvent(new Event('change', { bubbles: true }));
return;
}
throwError('Unsupported editor type');
}
async function sendPrompt(prompt) {
const editor = await waitForElement(() => getComposerEditor(), {
timeoutMs: 15e3,
intervalMs: 200,
description: 'Composer editor',
});
const scrollTargets = getScrollTargets(editor);
return withPreservedViewport(async () => {
setEditorValue(editor, prompt);
try {
await waitForCondition(
() => {
const btn = getSendButton();
return btn && !btn.disabled;
},
{
timeoutMs: 5e3,
intervalMs: 100,
description: 'send button to enable',
}
);
const sendButton = getSendButton();
if (sendButton) {
safeClick(sendButton);
await new Promise((resolve) => setTimeout(resolve, 100));
await waitForPromptProcessing();
return;
}
} catch (err) {
error('send button unavailable, falling back to Enter', formatError(err));
}
dispatchEnterKey(editor);
if (editor.form && typeof editor.form.requestSubmit === 'function') {
editor.form.requestSubmit();
}
await waitForPromptProcessing();
}, scrollTargets);
}
// AI Queue/styles/chat-manager.css
var chat_manager_default =
':root {\r\n color-scheme: dark;\r\n --pq-manager-bg: #0b1220;\r\n --pq-manager-panel: rgba(17, 24, 39, 0.96);\r\n --pq-manager-card: rgba(31, 41, 55, 0.96);\r\n --pq-manager-text: #f3f4f6;\r\n --pq-manager-muted: #9ca3af;\r\n --pq-manager-border: #374151;\r\n --pq-manager-accent: #60a5fa;\r\n --pq-manager-accent-strong: #22c55e;\r\n}\r\n\r\n#pq-chat-manager-panel {\r\n position: fixed;\r\n top: 6vh;\r\n left: 50%;\r\n transform: translateX(-50%);\r\n width: min(1100px, calc(100vw - 32px));\r\n height: min(760px, calc(100vh - 32px));\r\n z-index: 2147483647;\r\n display: flex;\r\n flex-direction: column;\r\n background: radial-gradient(circle at top right, rgba(31, 41, 55, 0.95), rgba(11, 18, 32, 0.98) 60%);\r\n color: var(--pq-manager-text);\r\n border: 1px solid var(--pq-manager-border);\r\n border-radius: 16px;\r\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);\r\n overflow: hidden;\r\n}\r\n\r\n.pq-manager-shell {\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n min-height: 0;\r\n}\r\n\r\n.pq-manager-header {\r\n display: flex;\r\n align-items: flex-start;\r\n justify-content: space-between;\r\n gap: 16px;\r\n padding: 16px 18px 12px;\r\n border-bottom: 1px solid var(--pq-manager-border);\r\n background: linear-gradient(180deg, rgba(17, 24, 39, 0.95), rgba(17, 24, 39, 0.82));\r\n}\r\n\r\n.pq-manager-title {\r\n font-size: 18px;\r\n font-weight: 700;\r\n line-height: 1.2;\r\n margin: 0;\r\n}\r\n\r\n.pq-manager-subtitle {\r\n margin-top: 4px;\r\n color: var(--pq-manager-muted);\r\n font-size: 13px;\r\n line-height: 1.4;\r\n max-width: 72ch;\r\n}\r\n\r\n.pq-manager-actions {\r\n display: flex;\r\n gap: 8px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.pq-manager-actions button {\r\n appearance: none;\r\n border: 1px solid var(--pq-manager-border);\r\n background: rgba(31, 41, 55, 0.95);\r\n color: var(--pq-manager-text);\r\n border-radius: 999px;\r\n padding: 8px 12px;\r\n font: inherit;\r\n cursor: pointer;\r\n}\r\n\r\n.pq-manager-actions button:hover {\r\n border-color: var(--pq-manager-accent);\r\n}\r\n\r\n.pq-manager-body {\r\n display: flex;\r\n flex-direction: column;\r\n min-height: 0;\r\n padding: 16px 18px 18px;\r\n gap: 12px;\r\n overflow: hidden;\r\n}\r\n\r\n.pq-manager-grid {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\r\n gap: 12px;\r\n align-items: start;\r\n flex: 1;\r\n min-height: 0;\r\n overflow: auto;\r\n padding-right: 4px;\r\n}\r\n\r\n.chat-card {\r\n background: linear-gradient(180deg, var(--pq-manager-panel), var(--pq-manager-card));\r\n border: 1px solid var(--pq-manager-border);\r\n border-radius: 12px;\r\n overflow: hidden;\r\n min-height: 140px;\r\n}\r\n\r\n.chat-title {\r\n padding: 10px 12px;\r\n border-bottom: 1px solid var(--pq-manager-border);\r\n font-size: 12px;\r\n letter-spacing: 0.2px;\r\n text-transform: uppercase;\r\n color: #d1d5db;\r\n display: flex;\r\n justify-content: space-between;\r\n gap: 8px;\r\n}\r\n\r\n.chat-title .chat-controls {\r\n display: inline-flex;\r\n gap: 8px;\r\n align-items: center;\r\n}\r\n\r\n.chat-delete {\r\n appearance: none;\r\n border: 1px solid transparent;\r\n background: transparent;\r\n color: var(--pq-manager-muted);\r\n border-radius: 8px;\r\n padding: 4px 8px;\r\n font-size: 12px;\r\n cursor: pointer;\r\n}\r\n\r\n.chat-delete:hover {\r\n color: var(--pq-manager-accent);\r\n border-color: rgba(96, 165, 250, 0.12);\r\n background: rgba(96, 165, 250, 0.03);\r\n}\r\n\r\n.chat-list {\r\n list-style: none;\r\n margin: 0;\r\n padding: 8px;\r\n min-height: 90px;\r\n}\r\n\r\n.chat-item {\r\n background: rgba(17, 24, 39, 0.7);\r\n border: 1px solid #334155;\r\n border-radius: 8px;\r\n padding: 8px;\r\n margin-bottom: 8px;\r\n cursor: grab;\r\n user-select: none;\r\n font-size: 13px;\r\n line-height: 1.35;\r\n word-break: break-word;\r\n}\r\n\r\n.chat-item.dragging {\r\n opacity: 0.5;\r\n}\r\n\r\n.chat-list.drag-over,\r\n.chat-item.drag-over {\r\n outline: 2px dashed var(--pq-manager-accent);\r\n outline-offset: 2px;\r\n}\r\n\r\n.empty {\r\n color: var(--pq-manager-muted);\r\n font-size: 12px;\r\n padding: 8px;\r\n border: 1px dashed var(--pq-manager-border);\r\n border-radius: 8px;\r\n text-align: center;\r\n}\r\n\r\n.pq-manager-footer {\r\n color: var(--pq-manager-muted);\r\n font-size: 12px;\r\n border-top: 1px solid var(--pq-manager-border);\r\n padding-top: 12px;\r\n}';
// AI Queue/core/chat-manager.js
var GLOBAL_CHAT_KEY2 = '__global__';
var MANAGER_PANEL_ID = 'pq-chat-manager-panel';
var MANAGER_GRID_ID = 'pq-chat-manager-grid';
var MANAGER_BODY_ID = 'pq-chat-manager-body';
var activeManagers = /* @__PURE__ */ new Map();
function toChatCode2(chatKey) {
return chatKey === GLOBAL_CHAT_KEY2 ? null : chatKey;
}
function chatLabel(chatKey) {
if (chatKey === GLOBAL_CHAT_KEY2) {
return 'Global (all chats)';
}
return chatKey;
}
function cloneItem(item) {
return { ...item };
}
function cloneItems(items) {
return Array.isArray(items) ? items.map((item) => cloneItem(item)) : [];
}
function groupItems(chats) {
const grouped = { [GLOBAL_CHAT_KEY2]: [] };
Object.entries(chats || {}).forEach(([chatKey, bucket]) => {
if (!grouped[chatKey]) {
grouped[chatKey] = [];
}
grouped[chatKey].push(...cloneItems(bucket?.items));
});
return grouped;
}
function orderedKeys(groups) {
const keys = Object.keys(groups).filter((key) => groups[key]?.length > 0);
const nonGlobal = keys
.filter((key) => key !== GLOBAL_CHAT_KEY2)
.sort((a, b) => a.localeCompare(b));
if (groups[GLOBAL_CHAT_KEY2]?.length > 0) {
return [GLOBAL_CHAT_KEY2, ...nonGlobal];
}
return nonGlobal;
}
function flattenGroups(groups, existingChats = {}) {
const nextChats = {};
orderedKeys(groups).forEach((key) => {
nextChats[key] = {
items: cloneItems(groups[key]),
failedItems: cloneItems(existingChats[key]?.failedItems),
};
});
Object.entries(existingChats).forEach(([chatKey, bucket]) => {
if (nextChats[chatKey]) return;
if (Array.isArray(bucket?.failedItems) && bucket.failedItems.length > 0) {
nextChats[chatKey] = {
items: [],
failedItems: cloneItems(bucket.failedItems),
};
}
});
return nextChats;
}
function findItemIndex(groups, chatKey, itemId) {
const list = groups[chatKey] || [];
return list.findIndex((item) => item.id === itemId);
}
function ensureManagerStyles(doc) {
if (doc.querySelector('#pq-chat-manager-styles')) return;
const style = doc.createElement('style');
style.id = 'pq-chat-manager-styles';
style.textContent = chat_manager_default;
doc.head.appendChild(style);
}
function ensureManagerShell(doc, title, mountTarget) {
ensureManagerStyles(doc);
let panel = doc.getElementById(MANAGER_PANEL_ID);
if (!panel) {
panel = doc.createElement('section');
panel.id = MANAGER_PANEL_ID;
const shell = doc.createElement('div');
shell.className = 'pq-manager-shell';
const header = doc.createElement('div');
header.className = 'pq-manager-header';
const titleWrap = doc.createElement('div');
const heading = doc.createElement('div');
heading.className = 'pq-manager-title';
const subtitle = doc.createElement('div');
subtitle.className = 'pq-manager-subtitle';
titleWrap.appendChild(heading);
titleWrap.appendChild(subtitle);
const actions = doc.createElement('div');
actions.className = 'pq-manager-actions';
const refreshButton = doc.createElement('button');
refreshButton.type = 'button';
refreshButton.id = 'pq-chat-manager-refresh';
refreshButton.textContent = 'Refresh';
const closeButton = doc.createElement('button');
closeButton.type = 'button';
closeButton.id = 'pq-chat-manager-close';
closeButton.textContent = 'Close';
actions.appendChild(refreshButton);
actions.appendChild(closeButton);
header.appendChild(titleWrap);
header.appendChild(actions);
const body = doc.createElement('div');
body.className = 'pq-manager-body';
body.id = MANAGER_BODY_ID;
const grid = doc.createElement('div');
grid.className = 'pq-manager-grid';
grid.id = MANAGER_GRID_ID;
const footer = doc.createElement('div');
footer.className = 'pq-manager-footer';
footer.textContent =
'Drag prompts between chats. Changes are saved immediately to localStorage.';
body.appendChild(grid);
body.appendChild(footer);
shell.appendChild(header);
shell.appendChild(body);
panel.appendChild(shell);
closeButton.addEventListener('click', () => {
panel.hidden = true;
panel.style.display = 'none';
});
}
const titleNode = panel.querySelector('.pq-manager-title');
const subtitleNode = panel.querySelector('.pq-manager-subtitle');
if (titleNode) {
titleNode.textContent = title;
}
if (subtitleNode) {
subtitleNode.textContent =
'Reorder prompts within a chat or move them into another chat card. This panel stays inside the page instead of opening a popup.';
}
const root = mountTarget || doc.documentElement || doc.body;
if (root && !root.contains(panel)) {
root.appendChild(panel);
}
panel.hidden = false;
panel.style.display = 'flex';
return panel;
}
function moveByDrop(state, fromChatKey, itemId, toChatKey2, toIndex) {
const fromList = state.groups[fromChatKey] || [];
const fromIndex = findItemIndex(state.groups, fromChatKey, itemId);
if (fromIndex === -1) {
return false;
}
const [movedItem] = fromList.splice(fromIndex, 1);
if (!state.groups[toChatKey2]) {
state.groups[toChatKey2] = [];
}
const targetList = state.groups[toChatKey2];
let normalizedIndex = Number.isInteger(toIndex) ? toIndex : targetList.length;
if (normalizedIndex < 0) normalizedIndex = 0;
if (normalizedIndex > targetList.length) normalizedIndex = targetList.length;
if (fromChatKey === toChatKey2 && normalizedIndex > fromIndex) {
normalizedIndex -= 1;
}
movedItem.chatCode = toChatCode2(toChatKey2) || void 0;
targetList.splice(normalizedIndex, 0, movedItem);
if (fromChatKey !== GLOBAL_CHAT_KEY2 && (state.groups[fromChatKey] || []).length === 0) {
delete state.groups[fromChatKey];
}
return true;
}
function persistState(storageKey, state) {
state.data.chats = flattenGroups(state.groups, state.data.chats);
writeScopedQueueData(storageKey, state.data);
}
function renderCards(grid, storageKey, state, rerender) {
if (!grid) return;
grid.replaceChildren();
const keys = orderedKeys(state.groups);
if (keys.length === 0) {
const doc2 = grid.ownerDocument;
const empty = doc2.createElement('div');
empty.className = 'empty';
empty.textContent = 'No prompts found in storage.';
grid.appendChild(empty);
return;
}
const doc = grid.ownerDocument;
keys.forEach((chatKey) => {
const card = doc.createElement('section');
card.className = 'chat-card';
const title = doc.createElement('div');
title.className = 'chat-title';
const label = doc.createElement('span');
label.textContent = chatLabel(chatKey);
const count = doc.createElement('span');
count.textContent = String(state.groups[chatKey].length);
const controls = doc.createElement('span');
controls.className = 'chat-controls';
const deleteButton = doc.createElement('button');
deleteButton.type = 'button';
deleteButton.className = 'chat-delete';
deleteButton.textContent = 'Delete';
deleteButton.title = 'Delete all prompts in this chat';
deleteButton.addEventListener('click', () => {
const chatName = chatLabel(chatKey);
if (
!doc.defaultView?.confirm(`Delete all prompts in "${chatName}"? This cannot be undone.`)
)
return;
if (state.groups[chatKey]) {
delete state.groups[chatKey];
}
persistState(storageKey, state);
rerender();
});
controls.appendChild(count);
controls.appendChild(deleteButton);
title.appendChild(label);
title.appendChild(controls);
const list = doc.createElement('ul');
list.className = 'chat-list';
list.dataset.chatKey = chatKey;
const items = state.groups[chatKey];
if (!items || items.length === 0) {
const empty = doc.createElement('div');
empty.className = 'empty';
empty.textContent = 'Drop a prompt here.';
list.appendChild(empty);
} else {
items.forEach((item, index) => {
const entry = doc.createElement('li');
entry.className = 'chat-item';
entry.draggable = true;
entry.dataset.chatKey = chatKey;
entry.dataset.itemId = item.id;
entry.dataset.index = String(index);
entry.textContent = item.prompt;
entry.addEventListener('dragstart', (event) => {
state.drag = {
itemId: item.id,
fromChatKey: chatKey,
};
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', item.id);
entry.classList.add('dragging');
});
entry.addEventListener('dragend', () => {
state.drag = null;
entry.classList.remove('dragging');
doc.querySelectorAll(`#${MANAGER_PANEL_ID} .drag-over`).forEach((element) => {
element.classList.remove('drag-over');
});
});
entry.addEventListener('dragover', (event) => {
event.preventDefault();
entry.classList.add('drag-over');
});
entry.addEventListener('dragleave', () => {
entry.classList.remove('drag-over');
});
entry.addEventListener('drop', (event) => {
event.preventDefault();
entry.classList.remove('drag-over');
if (!state.drag) return;
const moved = moveByDrop(
state,
state.drag.fromChatKey,
state.drag.itemId,
chatKey,
Number(entry.dataset.index)
);
if (!moved) return;
persistState(storageKey, state);
rerender();
});
list.appendChild(entry);
});
}
list.addEventListener('dragover', (event) => {
event.preventDefault();
list.classList.add('drag-over');
});
list.addEventListener('dragleave', () => {
list.classList.remove('drag-over');
});
list.addEventListener('drop', (event) => {
event.preventDefault();
list.classList.remove('drag-over');
if (!state.drag) return;
const moved = moveByDrop(
state,
state.drag.fromChatKey,
state.drag.itemId,
chatKey,
state.groups[chatKey]?.length
);
if (!moved) return;
persistState(storageKey, state);
rerender();
});
card.appendChild(title);
card.appendChild(list);
grid.appendChild(card);
});
}
function openChatManagerWindow(storageKey, title = 'Prompt Queue Chat Manager', mountTarget) {
const data = readScopedQueueData(storageKey);
const state = {
data,
groups: groupItems(data.chats),
drag: null,
};
const doc = mountTarget?.ownerDocument || document;
const panel = ensureManagerShell(doc, title, mountTarget);
panel.dataset.storageKey = storageKey;
const rerender = () => {
const grid = panel.querySelector(`#${MANAGER_GRID_ID}`);
renderCards(grid, storageKey, state, rerender);
};
rerender();
const refreshButton = panel.querySelector('#pq-chat-manager-refresh');
if (refreshButton) {
refreshButton.onclick = () => {
const refreshedData = readScopedQueueData(storageKey);
state.data = refreshedData;
state.groups = groupItems(refreshedData.chats);
state.drag = null;
rerender();
};
}
panel.scrollIntoView?.({ block: 'start', inline: 'nearest', behavior: 'smooth' });
log('chat manager opened in-page', { storageKey });
activeManagers.set(storageKey, {
panel,
state,
title,
rerender,
});
}
function refreshChatManager(storageKey) {
const manager = activeManagers.get(storageKey);
if (!manager || !manager.panel || manager.panel.hidden) return false;
const refreshedData = readScopedQueueData(storageKey);
manager.state.data = refreshedData;
manager.state.groups = groupItems(refreshedData.chats);
manager.state.drag = null;
const grid = manager.panel.querySelector(`#${MANAGER_GRID_ID}`);
renderCards(grid, storageKey, manager.state, manager.rerender);
return true;
}
// AI Queue/core/bootstrap.js
function bootstrapQueueApp(provider) {
globalThis.aiQueue = queueState;
log('AI Queue running', true);
const storageKey = provider.storageKey;
const syncFromStorage = () => {
resetQueueState({ includeFailedQueue: !!provider.includeFailedQueue });
provider.loadQueue?.();
provider.renderQueue?.();
provider.ensureToolbarButton?.();
if (storageKey) {
refreshChatManager(storageKey);
}
};
const refreshForCurrentUrl = (previousUrl = location.href, currentUrl = location.href) => {
const getScope = provider.getCurrentScope;
const previousScope = typeof getScope === 'function' ? getScope(previousUrl) : null;
const currentScope = typeof getScope === 'function' ? getScope(currentUrl) : null;
if (
queueState.running &&
queueState.awaitingChatScopeSync &&
!previousScope &&
currentScope
) {
const updated = applyScopeToQueuedItems(
queueState.queue,
queueState.failedQueue,
currentScope
);
if (updated) {
provider.saveQueue?.();
provider.renderQueue?.();
provider.ensureToolbarButton?.();
if (storageKey) {
refreshChatManager(storageKey);
}
}
queueState.awaitingChatScopeSync = false;
return;
}
if (queueState.running) {
queueState.running = false;
}
syncFromStorage();
};
syncFromStorage();
provider.createPanel();
provider.setupPanelControls?.({
createItem: provider.createItem,
renderQueue: provider.renderQueue,
saveQueue: provider.saveQueue,
processQueue: provider.processQueue,
openChatManager: provider.openChatManager,
});
provider.setupPanelDrag?.();
provider.renderQueue?.();
provider.ensureToolbarButton?.();
if (storageKey) {
window.addEventListener('storage', (event) => {
if (event.storageArea !== localStorage) return;
if (event.key !== storageKey) return;
syncFromStorage();
});
}
startDomObserver(
provider.createPanel,
() =>
provider.setupPanelControls?.({
createItem: provider.createItem,
renderQueue: provider.renderQueue,
saveQueue: provider.saveQueue,
processQueue: provider.processQueue,
openChatManager: provider.openChatManager,
}),
provider.setupPanelDrag,
provider.ensureToolbarButton,
provider.isOwnMutation
);
startUrlWatcher(
provider.createPanel,
() =>
provider.setupPanelControls?.({
createItem: provider.createItem,
renderQueue: provider.renderQueue,
saveQueue: provider.saveQueue,
processQueue: provider.processQueue,
openChatManager: provider.openChatManager,
}),
provider.setupPanelDrag,
provider.ensureToolbarButton,
refreshForCurrentUrl
);
}
// AI Queue/styles/selection-menu.css
var selection_menu_default =
'#pq-selection-menu {\r\n position: fixed;\r\n z-index: 2147483647;\r\n display: none;\r\n min-width: 180px;\r\n padding: 6px;\r\n border: 1px solid #444;\r\n border-radius: 12px;\r\n background: #202123;\r\n box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);\r\n}\r\n\r\n#pq-selection-menu button {\r\n appearance: none;\r\n display: block;\r\n width: 100%;\r\n padding: 8px 12px;\r\n border: 1px solid #555;\r\n border-radius: 10px;\r\n background: #2a2a2a;\r\n color: #fff;\r\n cursor: pointer;\r\n font: inherit;\r\n text-align: left;\r\n}\r\n\r\n#pq-selection-menu button:hover {\r\n background: #343434;\r\n}';
// AI Queue/core/selection-menu.js
var SELECTION_MENU_ID = 'pq-selection-menu';
var installed = false;
function getSelectedPageText() {
const selection = window.getSelection?.();
if (!selection || selection.isCollapsed) return '';
return selection.toString().trim();
}
function hideSelectionMenu() {
const menu = document.querySelector(`#${SELECTION_MENU_ID}`);
if (!menu) return;
menu.hidden = true;
menu.style.display = 'none';
}
function ensureSelectionMenu(onAddSelection) {
let menu = document.querySelector(`#${SELECTION_MENU_ID}`);
if (menu) return menu;
if (!document.querySelector('#pq-selection-menu-styles')) {
const style = document.createElement('style');
style.id = 'pq-selection-menu-styles';
style.textContent = selection_menu_default;
document.head.appendChild(style);
}
menu = document.createElement('div');
menu.id = SELECTION_MENU_ID;
menu.hidden = true;
const button = document.createElement('button');
button.type = 'button';
button.textContent = 'Add to Prompt Queue';
button.addEventListener('click', () => {
const prompt = menu.dataset.prompt || '';
if (prompt) {
onAddSelection(prompt);
}
hideSelectionMenu();
});
menu.appendChild(button);
document.body.appendChild(menu);
return menu;
}
function showSelectionMenu(selectionText, x, y, onAddSelection) {
const menu = ensureSelectionMenu(onAddSelection);
menu.dataset.prompt = selectionText;
const margin = 12;
const left = Math.min(x + margin, window.innerWidth - 200);
const top = Math.min(y + margin, window.innerHeight - 64);
menu.style.left = `${Math.max(8, left)}px`;
menu.style.top = `${Math.max(8, top)}px`;
menu.hidden = false;
menu.style.display = 'block';
}
function addSelectionToQueue(createItem, renderQueue, saveQueue2, updateToolbarButton2) {
return (selectionText) => {
const prompt = selectionText.trim();
if (!prompt) return;
queueState.queue.push(createItem(prompt));
updateToolbarButton2(
document.querySelector('#pq-toolbar-button'),
queueState.queue,
queueState.running
);
renderQueue?.();
saveQueue2();
};
}
function installSelectionPromptMenu({
createItem,
renderQueue,
saveQueue: saveQueue2,
updateToolbarButton: updateToolbarButton2,
}) {
if (installed) return;
installed = true;
const onAddSelection = addSelectionToQueue(
createItem,
renderQueue,
saveQueue2,
updateToolbarButton2
);
document.addEventListener(
'contextmenu',
(event) => {
const target = event.target;
if (!(target instanceof Element)) return;
if (target.closest('#pq-panel') || target.closest(`#${SELECTION_MENU_ID}`)) {
hideSelectionMenu();
return;
}
const selectionText = getSelectedPageText();
if (!selectionText) {
hideSelectionMenu();
return;
}
showSelectionMenu(selectionText, event.clientX, event.clientY, onAddSelection);
},
true
);
document.addEventListener('click', (event) => {
const menu = document.querySelector(`#${SELECTION_MENU_ID}`);
if (!menu) return;
if (menu.contains(event.target)) return;
hideSelectionMenu();
});
window.addEventListener('blur', hideSelectionMenu);
window.addEventListener('scroll', hideSelectionMenu, true);
window.addEventListener('resize', hideSelectionMenu);
}
// AI Queue/providers/chatgpt.js
var STORAGE_KEY = 'pq-chatgpt-queue';
var DOMAINS = ['chatgpt.com', 'chat.openai.com'];
function normalizeCode(value) {
if (typeof value !== 'string') return null;
const trimmed = value.trim();
return trimmed ? trimmed : null;
}
function getCurrentChatGPTScope(url = globalThis.location?.href || '') {
try {
const parsedUrl = new URL(url, globalThis.location?.origin || 'https://example.com');
const host = parsedUrl.hostname.toLowerCase();
if (!DOMAINS.includes(host)) {
return null;
}
const segments = parsedUrl.pathname.split('/').filter(Boolean);
if (segments[0] === 'g' && segments[2] === 'c') {
return {
groupId: normalizeCode(segments[1]),
chatId: normalizeCode(segments[3]),
};
}
if (segments[0] === 'c') {
return {
chatId: normalizeCode(segments[1]),
};
}
return null;
} catch (err) {
error('Failed to parse URL for chat code:', err.message);
return null;
}
}
function getCurrentChatGPTChatCode(url = globalThis.location?.href || '') {
const scope = getCurrentChatGPTScope(url);
return scope?.groupId || scope?.chatId || null;
}
function queryPanel() {
return document.querySelector('#pq-panel');
}
function queryInput() {
return queryPanel()?.querySelector('#pq-input');
}
function queryAddButton() {
return queryPanel()?.querySelector('#pq-add');
}
function createChatGPTPanel() {
return createBasePanel('ChatGPT Prompt Queue', false);
}
function renderChatGPTQueue() {
const panel = queryPanel();
if (!panel) return;
const list = panel.querySelector('#pq-list');
if (!list) return;
while (list.firstChild) {
list.removeChild(list.firstChild);
}
queueState.queue.forEach((item) => {
const { li, text, editBtn, deleteBtn } = createQueueItemElement(item, {
renderQueue: renderChatGPTQueue,
saveQueue: saveChatGPTQueue,
});
if (queueState.editingId == item.id) {
li.querySelector('div').style.backgroundColor = '#333';
li.querySelector('div').style.padding = '4px';
li.querySelector('div').style.borderRadius = '4px';
}
text.addEventListener('dblclick', () => {
editQueueItem(item.id, queueState.queue, (id, prompt) => {
queueState.editingId = id;
const input = queryInput();
const addButton = queryAddButton();
if (input && addButton) {
input.value = prompt;
addButton.textContent = 'Save Changes';
input.focus();
input.selectionStart = input.selectionEnd = input.value.length;
}
editBtn.style.display = 'inline-block';
deleteBtn.style.display = 'inline-block';
});
});
editBtn.addEventListener('click', () => {
editQueueItem(item.id, queueState.queue, (id, prompt) => {
queueState.editingId = id;
const input = queryInput();
const addButton = queryAddButton();
if (input && addButton) {
input.value = prompt;
addButton.textContent = 'Save Changes';
input.focus();
input.selectionStart = input.selectionEnd = input.value.length;
}
});
});
deleteBtn.addEventListener('click', () => {
deleteQueueItem(item.id, queueState.queue, renderChatGPTQueue, saveChatGPTQueue);
});
list.appendChild(li);
});
updateToolbarButton(
document.querySelector('#pq-toolbar-button'),
queueState.queue,
queueState.running
);
}
function saveChatGPTQueue() {
saveQueue(queueState.queue, null, STORAGE_KEY, getCurrentChatGPTScope());
}
function syncQueuedItemsToCurrentChatScope(scope) {
if (!scope) return false;
const updated = applyScopeToQueuedItems(queueState.queue, null, scope);
if (!updated) return false;
saveChatGPTQueue();
refreshChatManager(STORAGE_KEY);
return true;
}
function loadChatGPTQueue() {
loadQueue(queueState.queue, null, STORAGE_KEY, getCurrentChatGPTScope());
}
function openChatGPTChatManager() {
openChatManagerWindow(
STORAGE_KEY,
'ChatGPT Chat Prompt Manager',
document.querySelector('#pq-panel')
);
}
async function processChatGPTQueue() {
const panel = queryPanel();
if (!panel) return;
setStatus(panel, 'Running');
while (queueState.queue.length > 0 && queueState.running) {
await waitForIdle();
const item = queueState.queue.shift();
const prompt = item.prompt;
const beforeScope = getCurrentChatGPTScope();
queueState.awaitingChatScopeSync = !beforeScope;
updateToolbarButton(
document.querySelector('#pq-toolbar-button'),
queueState.queue,
queueState.running
);
renderChatGPTQueue();
setStatus(panel, `Sending: ${prompt.slice(0, 40)}...`);
try {
await sendPrompt(prompt);
const afterScope = getCurrentChatGPTScope();
if (!beforeScope && afterScope) {
syncQueuedItemsToCurrentChatScope(afterScope);
}
if (afterScope) {
queueState.awaitingChatScopeSync = false;
}
} catch (err) {
queueState.awaitingChatScopeSync = false;
error('Failed to send prompt:', formatError(err));
}
if (beforeScope) {
queueState.awaitingChatScopeSync = false;
}
saveChatGPTQueue();
}
setStatus(panel, queueState.running ? 'Finished' : 'Stopped');
queueState.running = false;
updateToolbarButton(
document.querySelector('#pq-toolbar-button'),
queueState.queue,
queueState.running
);
}
function ensureChatGPTToolbarButton() {
ensureToolbarStyles();
installSelectionPromptMenu({
createItem: chatgptProvider.createItem,
renderQueue: renderChatGPTQueue,
saveQueue: saveChatGPTQueue,
updateToolbarButton,
});
let button = document.querySelector('#pq-toolbar-button');
if (!button) {
button = document.createElement('button');
button.id = 'pq-toolbar-button';
button.type = 'button';
button.textContent = 'Queue';
button.addEventListener('click', () => showPanel(() => createChatGPTPanel()));
}
Object.assign(button.style, {
position: 'fixed',
bottom: '24px',
right: '24px',
padding: '10px 14px',
borderRadius: '9999px',
background: '#1f1f1f',
color: '#fff',
border: '1px solid #555',
boxShadow: '0 10px 30px rgba(0,0,0,0.35)',
zIndex: '2147483647',
cursor: 'pointer',
});
if (button.parentElement !== document.body) {
document.body.appendChild(button);
}
}
var chatgptProvider = {
storageKey: STORAGE_KEY,
includeFailedQueue: false,
getCurrentScope: getCurrentChatGPTScope,
createItem(text) {
const scope = getCurrentChatGPTScope();
return {
id: crypto.randomUUID(),
prompt: text,
attempts: 0,
status: 'queued',
createdAt: Date.now(),
...(scope?.chatId
? {
chatId: scope.chatId,
chatCode: scope.chatId,
}
: {}),
...(scope?.groupId ? { groupId: scope.groupId } : {}),
};
},
createPanel: createChatGPTPanel,
renderQueue: renderChatGPTQueue,
saveQueue: saveChatGPTQueue,
loadQueue: loadChatGPTQueue,
processQueue: processChatGPTQueue,
setupPanelControls,
setupPanelDrag,
ensureToolbarButton: ensureChatGPTToolbarButton,
openChatManager: openChatGPTChatManager,
isOwnMutation(target) {
return !!target && (target.closest?.('#pq-panel') || target.closest?.('.pq-toolbar'));
},
};
bootstrapQueueApp(chatgptProvider);
})();
//# sourceMappingURL=chatgpt.user.js.map