const apiBaseUrlInput = document.getElementById("apiBaseUrl"); const apiKeyHeaderInput = document.getElementById("apiKeyHeader"); const apiKeyPrefixInput = document.getElementById("apiKeyPrefix"); const modelInput = document.getElementById("model"); const systemPromptInput = document.getElementById("systemPrompt"); const resumeInput = document.getElementById("resume"); const saveBtn = document.getElementById("saveBtn"); const addApiKeyBtn = document.getElementById("addApiKeyBtn"); const apiKeysContainer = document.getElementById("apiKeys"); const activeApiKeySelect = document.getElementById("activeApiKeySelect"); const addTaskBtn = document.getElementById("addTaskBtn"); const tasksContainer = document.getElementById("tasks"); const statusEl = document.getElementById("status"); const themeSelect = document.getElementById("themeSelect"); const resetApiBtn = document.getElementById("resetApiBtn"); const OPENAI_DEFAULTS = { apiBaseUrl: "https://api.openai.com/v1", apiKeyHeader: "Authorization", apiKeyPrefix: "Bearer " }; function getStorage(keys) { return new Promise((resolve) => chrome.storage.local.get(keys, resolve)); } function setStatus(message) { statusEl.textContent = message; if (!message) return; setTimeout(() => { if (statusEl.textContent === message) statusEl.textContent = ""; }, 2000); } function applyTheme(theme) { const value = theme || "system"; document.documentElement.dataset.theme = value; } function newTaskId() { if (crypto?.randomUUID) return crypto.randomUUID(); return `task-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; } function newApiKeyId() { if (crypto?.randomUUID) return crypto.randomUUID(); return `key-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; } function buildApiKeyCard(entry) { const card = document.createElement("div"); card.className = "api-key-card"; card.dataset.id = entry.id || newApiKeyId(); const nameField = document.createElement("div"); nameField.className = "field"; const nameLabel = document.createElement("label"); nameLabel.textContent = "Name"; const nameInput = document.createElement("input"); nameInput.type = "text"; nameInput.value = entry.name || ""; nameInput.className = "api-key-name"; nameField.appendChild(nameLabel); nameField.appendChild(nameInput); const keyField = document.createElement("div"); keyField.className = "field"; const keyLabel = document.createElement("label"); keyLabel.textContent = "Key"; const keyInline = document.createElement("div"); keyInline.className = "inline"; const keyInput = document.createElement("input"); keyInput.type = "password"; keyInput.autocomplete = "off"; keyInput.placeholder = "sk-..."; keyInput.value = entry.key || ""; keyInput.className = "api-key-value"; const showBtn = document.createElement("button"); showBtn.type = "button"; showBtn.className = "ghost"; showBtn.textContent = "Show"; showBtn.addEventListener("click", () => { const isPassword = keyInput.type === "password"; keyInput.type = isPassword ? "text" : "password"; showBtn.textContent = isPassword ? "Hide" : "Show"; }); keyInline.appendChild(keyInput); keyInline.appendChild(showBtn); keyField.appendChild(keyLabel); keyField.appendChild(keyInline); const actions = document.createElement("div"); actions.className = "api-key-actions"; const deleteBtn = document.createElement("button"); deleteBtn.type = "button"; deleteBtn.className = "ghost delete"; deleteBtn.textContent = "Delete"; deleteBtn.addEventListener("click", () => { card.remove(); updateApiKeySelect(); }); actions.appendChild(deleteBtn); const updateSelect = () => updateApiKeySelect(activeApiKeySelect.value); nameInput.addEventListener("input", updateSelect); keyInput.addEventListener("input", updateSelect); card.appendChild(nameField); card.appendChild(keyField); card.appendChild(actions); return card; } function collectApiKeys() { const cards = [...apiKeysContainer.querySelectorAll(".api-key-card")]; return cards.map((card) => { const nameInput = card.querySelector(".api-key-name"); const keyInput = card.querySelector(".api-key-value"); return { id: card.dataset.id || newApiKeyId(), name: (nameInput?.value || "Default").trim(), key: (keyInput?.value || "").trim() }; }); } function updateApiKeySelect(preferredId) { const keys = collectApiKeys(); activeApiKeySelect.innerHTML = ""; if (!keys.length) { const option = document.createElement("option"); option.value = ""; option.textContent = "No keys configured"; activeApiKeySelect.appendChild(option); activeApiKeySelect.disabled = true; return; } activeApiKeySelect.disabled = false; const selectedId = preferredId && keys.some((key) => key.id === preferredId) ? preferredId : keys[0].id; for (const key of keys) { const option = document.createElement("option"); option.value = key.id; option.textContent = key.name || "Default"; activeApiKeySelect.appendChild(option); } activeApiKeySelect.value = selectedId; } function buildTaskCard(task) { const card = document.createElement("div"); card.className = "task-card"; card.dataset.id = task.id || newTaskId(); const nameField = document.createElement("div"); nameField.className = "field"; const nameLabel = document.createElement("label"); nameLabel.textContent = "Name"; const nameInput = document.createElement("input"); nameInput.type = "text"; nameInput.value = task.name || ""; nameInput.className = "task-name"; nameField.appendChild(nameLabel); nameField.appendChild(nameInput); const textField = document.createElement("div"); textField.className = "field"; const textLabel = document.createElement("label"); textLabel.textContent = "Task prompt"; const textArea = document.createElement("textarea"); textArea.rows = 6; textArea.value = task.text || ""; textArea.className = "task-text"; textField.appendChild(textLabel); textField.appendChild(textArea); const actions = document.createElement("div"); actions.className = "task-actions"; const moveTopBtn = document.createElement("button"); moveTopBtn.type = "button"; moveTopBtn.className = "ghost move-top"; moveTopBtn.textContent = "Top"; moveTopBtn.setAttribute("aria-label", "Move task to top"); moveTopBtn.setAttribute("title", "Move to top"); const moveUpBtn = document.createElement("button"); moveUpBtn.type = "button"; moveUpBtn.className = "ghost move-up"; moveUpBtn.textContent = "Up"; moveUpBtn.setAttribute("aria-label", "Move task up"); moveUpBtn.setAttribute("title", "Move up"); const moveDownBtn = document.createElement("button"); moveDownBtn.type = "button"; moveDownBtn.className = "ghost move-down"; moveDownBtn.textContent = "Down"; moveDownBtn.setAttribute("aria-label", "Move task down"); moveDownBtn.setAttribute("title", "Move down"); const addBelowBtn = document.createElement("button"); addBelowBtn.type = "button"; addBelowBtn.className = "ghost add-below"; addBelowBtn.textContent = "Add"; addBelowBtn.setAttribute("aria-label", "Add task below"); addBelowBtn.setAttribute("title", "Add below"); const duplicateBtn = document.createElement("button"); duplicateBtn.type = "button"; duplicateBtn.className = "ghost duplicate"; duplicateBtn.textContent = "Duplicate"; duplicateBtn.setAttribute("aria-label", "Duplicate task"); duplicateBtn.setAttribute("title", "Duplicate"); const deleteBtn = document.createElement("button"); deleteBtn.type = "button"; deleteBtn.className = "ghost delete"; deleteBtn.textContent = "Delete"; deleteBtn.setAttribute("aria-label", "Delete task"); deleteBtn.setAttribute("title", "Delete"); moveTopBtn.addEventListener("click", () => { const first = tasksContainer.firstElementChild; if (!first || first === card) return; tasksContainer.insertBefore(card, first); updateTaskControls(); }); moveUpBtn.addEventListener("click", () => { const previous = card.previousElementSibling; if (!previous) return; tasksContainer.insertBefore(card, previous); updateTaskControls(); }); moveDownBtn.addEventListener("click", () => { const next = card.nextElementSibling; if (!next) return; tasksContainer.insertBefore(card, next.nextElementSibling); updateTaskControls(); }); addBelowBtn.addEventListener("click", () => { const newCard = buildTaskCard({ id: newTaskId(), name: "", text: "" }); card.insertAdjacentElement("afterend", newCard); updateTaskControls(); }); duplicateBtn.addEventListener("click", () => { const copy = { id: newTaskId(), name: `${nameInput.value || "Untitled"} Copy`, text: textArea.value }; const newCard = buildTaskCard(copy); card.insertAdjacentElement("afterend", newCard); updateTaskControls(); }); deleteBtn.addEventListener("click", () => { card.remove(); updateTaskControls(); }); actions.appendChild(moveTopBtn); actions.appendChild(moveUpBtn); actions.appendChild(moveDownBtn); actions.appendChild(addBelowBtn); actions.appendChild(duplicateBtn); actions.appendChild(deleteBtn); card.appendChild(nameField); card.appendChild(textField); card.appendChild(actions); return card; } function updateTaskControls() { const cards = [...tasksContainer.querySelectorAll(".task-card")]; cards.forEach((card, index) => { const moveTopBtn = card.querySelector(".move-top"); const moveUpBtn = card.querySelector(".move-up"); const moveDownBtn = card.querySelector(".move-down"); if (moveTopBtn) moveTopBtn.disabled = index === 0; if (moveUpBtn) moveUpBtn.disabled = index === 0; if (moveDownBtn) moveDownBtn.disabled = index === cards.length - 1; }); } function collectTasks() { const cards = [...tasksContainer.querySelectorAll(".task-card")]; return cards.map((card) => { const nameInput = card.querySelector(".task-name"); const textArea = card.querySelector(".task-text"); return { id: card.dataset.id || newTaskId(), name: (nameInput?.value || "Untitled Task").trim(), text: (textArea?.value || "").trim() }; }); } async function loadSettings() { const { apiKey = "", apiKeys = [], activeApiKeyId = "", apiBaseUrl = "", apiKeyHeader = "", apiKeyPrefix = "", model = "", systemPrompt = "", resume = "", tasks = [], theme = "system" } = await getStorage([ "apiKey", "apiKeys", "activeApiKeyId", "apiBaseUrl", "apiKeyHeader", "apiKeyPrefix", "model", "systemPrompt", "resume", "tasks", "theme" ]); apiBaseUrlInput.value = apiBaseUrl; apiKeyHeaderInput.value = apiKeyHeader; apiKeyPrefixInput.value = apiKeyPrefix; modelInput.value = model; systemPromptInput.value = systemPrompt; resumeInput.value = resume; themeSelect.value = theme; applyTheme(theme); let resolvedKeys = Array.isArray(apiKeys) ? apiKeys : []; let resolvedActiveId = activeApiKeyId; if (!resolvedKeys.length && apiKey) { const migrated = { id: newApiKeyId(), name: "Default", key: apiKey }; resolvedKeys = [migrated]; resolvedActiveId = migrated.id; await chrome.storage.local.set({ apiKeys: resolvedKeys, activeApiKeyId: resolvedActiveId }); } apiKeysContainer.innerHTML = ""; if (!resolvedKeys.length) { apiKeysContainer.appendChild(buildApiKeyCard({ id: newApiKeyId(), name: "", key: "" })); } else { for (const entry of resolvedKeys) { apiKeysContainer.appendChild(buildApiKeyCard(entry)); } } updateApiKeySelect(resolvedActiveId); tasksContainer.innerHTML = ""; if (!tasks.length) { tasksContainer.appendChild( buildTaskCard({ id: newTaskId(), name: "", text: "" }) ); updateTaskControls(); return; } for (const task of tasks) { tasksContainer.appendChild(buildTaskCard(task)); } updateTaskControls(); } async function saveSettings() { const tasks = collectTasks(); const apiKeys = collectApiKeys(); const activeApiKeyId = apiKeys.find((entry) => entry.id === activeApiKeySelect.value)?.id || apiKeys[0]?.id || ""; await chrome.storage.local.set({ apiBaseUrl: apiBaseUrlInput.value.trim(), apiKeyHeader: apiKeyHeaderInput.value.trim(), apiKeyPrefix: apiKeyPrefixInput.value, apiKeys, activeApiKeyId, model: modelInput.value.trim(), systemPrompt: systemPromptInput.value, resume: resumeInput.value, tasks, theme: themeSelect.value }); setStatus("Saved."); } saveBtn.addEventListener("click", () => void saveSettings()); addTaskBtn.addEventListener("click", () => { const newCard = buildTaskCard({ id: newTaskId(), name: "", text: "" }); const first = tasksContainer.firstElementChild; if (first) { tasksContainer.insertBefore(newCard, first); } else { tasksContainer.appendChild(newCard); } updateTaskControls(); }); addApiKeyBtn.addEventListener("click", () => { const newCard = buildApiKeyCard({ id: newApiKeyId(), name: "", key: "" }); const first = apiKeysContainer.firstElementChild; if (first) { apiKeysContainer.insertBefore(newCard, first); } else { apiKeysContainer.appendChild(newCard); } updateApiKeySelect(activeApiKeySelect.value); }); activeApiKeySelect.addEventListener("change", () => { updateApiKeySelect(activeApiKeySelect.value); }); themeSelect.addEventListener("change", () => applyTheme(themeSelect.value)); resetApiBtn.addEventListener("click", async () => { apiBaseUrlInput.value = OPENAI_DEFAULTS.apiBaseUrl; apiKeyHeaderInput.value = OPENAI_DEFAULTS.apiKeyHeader; apiKeyPrefixInput.value = OPENAI_DEFAULTS.apiKeyPrefix; await chrome.storage.local.set({ apiBaseUrl: OPENAI_DEFAULTS.apiBaseUrl, apiKeyHeader: OPENAI_DEFAULTS.apiKeyHeader, apiKeyPrefix: OPENAI_DEFAULTS.apiKeyPrefix }); setStatus("OpenAI defaults restored."); }); loadSettings();