diff --git a/wwcompanion-extension/background.js b/wwcompanion-extension/background.js index 82a9250..873b18c 100644 --- a/wwcompanion-extension/background.js +++ b/wwcompanion-extension/background.js @@ -242,6 +242,30 @@ chrome.runtime.onInstalled.addListener(async () => { } } + const resolvedEnvConfigs = updates.envConfigs || stored.envConfigs || []; + const defaultEnvId = + resolvedEnvConfigs[0]?.id || + updates.activeEnvConfigId || + stored.activeEnvConfigId || + ""; + const taskSource = Array.isArray(updates.tasks) + ? updates.tasks + : Array.isArray(stored.tasks) + ? stored.tasks + : []; + if (taskSource.length) { + const normalizedTasks = taskSource.map((task) => ({ + ...task, + defaultEnvId: task.defaultEnvId || defaultEnvId + })); + const needsTaskUpdate = normalizedTasks.some( + (task, index) => task.defaultEnvId !== taskSource[index]?.defaultEnvId + ); + if (needsTaskUpdate) { + updates.tasks = normalizedTasks; + } + } + if (Object.keys(updates).length) { await chrome.storage.local.set(updates); } diff --git a/wwcompanion-extension/popup.css b/wwcompanion-extension/popup.css index 20f3a18..20bfa5b 100644 --- a/wwcompanion-extension/popup.css +++ b/wwcompanion-extension/popup.css @@ -127,28 +127,33 @@ select { font-size: 12px; } -.button-row { - display: grid; - grid-template-columns: minmax(64px, 0.8fr) minmax(0, 1.4fr) minmax(64px, 0.8fr); +.env-row { + display: flex; + align-items: flex-end; +} + +.env-row .env-field { + flex: 1; + margin: 0; +} + +.task-row { + display: flex; + align-items: flex-end; gap: 8px; } -.button-row button { - white-space: nowrap; +.task-row button { + padding: 6px 15px; } -.button-row .primary { - padding: 6px 8px; +.task-row .task-field { + flex: 1; + margin: 0; } -.stop-row { - display: flex; - justify-content: stretch; - margin-top: 4px; -} - -.stop-row button { - width: 100%; +.task-row select { + min-width: 0; } .hidden { @@ -192,7 +197,7 @@ button:active { border: 1px solid var(--border); } -.stop-row .ghost { +.stop-btn { background: #c0392b; border-color: #c0392b; color: #fff6f2; diff --git a/wwcompanion-extension/popup.html b/wwcompanion-extension/popup.html index 8b807c6..67d3ca9 100644 --- a/wwcompanion-extension/popup.html +++ b/wwcompanion-extension/popup.html @@ -17,17 +17,19 @@
-
- - +
+
+ + +
-
- - - -
-
diff --git a/wwcompanion-extension/popup.js b/wwcompanion-extension/popup.js index d9af489..b5086c9 100644 --- a/wwcompanion-extension/popup.js +++ b/wwcompanion-extension/popup.js @@ -1,10 +1,7 @@ -const extractBtn = document.getElementById("extractBtn"); -const analyzeBtn = document.getElementById("analyzeBtn"); +const runBtn = document.getElementById("runBtn"); const abortBtn = document.getElementById("abortBtn"); -const extractRunBtn = document.getElementById("extractRunBtn"); -const stopRow = document.getElementById("stopRow"); -const buttonRow = document.querySelector(".button-row"); const taskSelect = document.getElementById("taskSelect"); +const envSelect = document.getElementById("envSelect"); const outputEl = document.getElementById("output"); const statusEl = document.getElementById("status"); const postingCountEl = document.getElementById("postingCount"); @@ -16,14 +13,19 @@ const clearOutputBtn = document.getElementById("clearOutputBtn"); const OUTPUT_STORAGE_KEY = "lastOutput"; const AUTO_RUN_KEY = "autoRunDefaultTask"; +const LAST_TASK_KEY = "lastSelectedTaskId"; +const LAST_ENV_KEY = "lastSelectedEnvId"; const state = { postingText: "", tasks: [], + envs: [], port: null, isAnalyzing: false, outputRaw: "", - autoRunPending: false + autoRunPending: false, + selectedTaskId: "", + selectedEnvId: "" }; function getStorage(keys) { @@ -245,15 +247,12 @@ function applyTheme(theme) { function setAnalyzing(isAnalyzing) { state.isAnalyzing = isAnalyzing; - analyzeBtn.disabled = isAnalyzing; + runBtn.disabled = isAnalyzing; abortBtn.disabled = !isAnalyzing; - extractBtn.disabled = isAnalyzing; - extractRunBtn.disabled = isAnalyzing; - if (buttonRow && stopRow) { - buttonRow.classList.toggle("hidden", isAnalyzing); - stopRow.classList.toggle("hidden", !isAnalyzing); - } + runBtn.classList.toggle("hidden", isAnalyzing); + abortBtn.classList.toggle("hidden", !isAnalyzing); updateTaskSelectState(); + updateEnvSelectState(); } function updatePostingCount() { @@ -286,11 +285,70 @@ function renderTasks(tasks) { updateTaskSelectState(); } +function renderEnvironments(envs) { + state.envs = envs; + envSelect.innerHTML = ""; + + if (!envs.length) { + const option = document.createElement("option"); + option.textContent = "No environments configured"; + option.value = ""; + envSelect.appendChild(option); + updateEnvSelectState(); + return; + } + + for (const env of envs) { + const option = document.createElement("option"); + option.value = env.id; + option.textContent = env.name || "Default"; + envSelect.appendChild(option); + } + updateEnvSelectState(); +} + function updateTaskSelectState() { const hasTasks = state.tasks.length > 0; taskSelect.disabled = state.isAnalyzing || !hasTasks; } +function updateEnvSelectState() { + const hasEnvs = state.envs.length > 0; + envSelect.disabled = state.isAnalyzing || !hasEnvs; +} + +function getTaskDefaultEnvId(task) { + return task?.defaultEnvId || state.envs[0]?.id || ""; +} + +function setEnvironmentSelection(envId) { + const target = + envId && state.envs.some((env) => env.id === envId) + ? envId + : state.envs[0]?.id || ""; + if (target) { + envSelect.value = target; + } + state.selectedEnvId = target; +} + +function selectTask(taskId, { resetEnv } = { resetEnv: false }) { + if (!taskId) return; + taskSelect.value = taskId; + state.selectedTaskId = taskId; + const task = state.tasks.find((item) => item.id === taskId); + if (resetEnv) { + setEnvironmentSelection(getTaskDefaultEnvId(task)); + } +} + +async function persistSelections() { + await chrome.storage.local.set({ + [LAST_TASK_KEY]: state.selectedTaskId, + [LAST_ENV_KEY]: state.selectedEnvId + }); +} + function isWaterlooWorksUrl(url) { try { return new URL(url).hostname === "waterlooworks.uwaterloo.ca"; @@ -377,9 +435,45 @@ function ensurePort() { return port; } -async function loadTasks() { - const { tasks = [] } = await getStorage(["tasks"]); +async function loadConfig() { + const stored = await getStorage([ + "tasks", + "envConfigs", + LAST_TASK_KEY, + LAST_ENV_KEY + ]); + const tasks = Array.isArray(stored.tasks) ? stored.tasks : []; + const envs = Array.isArray(stored.envConfigs) ? stored.envConfigs : []; renderTasks(tasks); + renderEnvironments(envs); + + if (!tasks.length) { + state.selectedTaskId = ""; + state.selectedEnvId = envs[0]?.id || ""; + return; + } + + const storedTaskId = stored[LAST_TASK_KEY]; + const storedEnvId = stored[LAST_ENV_KEY]; + const initialTaskId = tasks.some((task) => task.id === storedTaskId) + ? storedTaskId + : tasks[0].id; + selectTask(initialTaskId, { resetEnv: false }); + + const task = tasks.find((item) => item.id === initialTaskId); + if (storedEnvId && envs.some((env) => env.id === storedEnvId)) { + setEnvironmentSelection(storedEnvId); + } else { + setEnvironmentSelection(getTaskDefaultEnvId(task)); + } + + if ( + storedTaskId !== state.selectedTaskId || + storedEnvId !== state.selectedEnvId + ) { + await persistSelections(); + } + maybeRunDefaultTask(); } @@ -434,7 +528,6 @@ async function handleAnalyze() { apiConfigs = [], activeApiConfigId = "", envConfigs = [], - activeEnvConfigId = "", apiBaseUrl, apiKeyHeader, apiKeyPrefix, @@ -447,7 +540,6 @@ async function handleAnalyze() { "apiConfigs", "activeApiConfigId", "envConfigs", - "activeEnvConfigId", "apiBaseUrl", "apiKeyHeader", "apiKeyPrefix", @@ -458,13 +550,18 @@ async function handleAnalyze() { const resolvedConfigs = Array.isArray(apiConfigs) ? apiConfigs : []; const resolvedEnvs = Array.isArray(envConfigs) ? envConfigs : []; + const selectedEnvId = envSelect.value; const activeEnv = - resolvedEnvs.find((entry) => entry.id === activeEnvConfigId) || + resolvedEnvs.find((entry) => entry.id === selectedEnvId) || resolvedEnvs[0]; + if (!activeEnv) { + setStatus("Add an environment in Settings."); + return; + } const resolvedSystemPrompt = - activeEnv?.systemPrompt ?? systemPrompt ?? ""; + activeEnv.systemPrompt ?? systemPrompt ?? ""; const resolvedApiConfigId = - activeEnv?.apiConfigId || activeApiConfigId || resolvedConfigs[0]?.id || ""; + activeEnv.apiConfigId || activeApiConfigId || resolvedConfigs[0]?.id || ""; const activeConfig = resolvedConfigs.find((entry) => entry.id === resolvedApiConfigId) || resolvedConfigs[0]; @@ -593,20 +690,26 @@ function handleCopyRaw() { void copyTextToClipboard(text, "Markdown"); } -extractBtn.addEventListener("click", handleExtract); -analyzeBtn.addEventListener("click", handleAnalyze); -extractRunBtn.addEventListener("click", handleExtractAndAnalyze); +runBtn.addEventListener("click", handleExtractAndAnalyze); abortBtn.addEventListener("click", handleAbort); settingsBtn.addEventListener("click", () => chrome.runtime.openOptionsPage()); copyRenderedBtn.addEventListener("click", handleCopyRendered); copyRawBtn.addEventListener("click", handleCopyRaw); clearOutputBtn.addEventListener("click", () => void handleClearOutput()); +taskSelect.addEventListener("change", () => { + selectTask(taskSelect.value, { resetEnv: true }); + void persistSelections(); +}); +envSelect.addEventListener("change", () => { + setEnvironmentSelection(envSelect.value); + void persistSelections(); +}); updatePostingCount(); updatePromptCount(0); renderOutput(); setAnalyzing(false); -loadTasks(); +loadConfig(); loadTheme(); async function loadSavedOutput() { @@ -628,7 +731,8 @@ function maybeRunDefaultTask() { if (!state.autoRunPending) return; if (state.isAnalyzing) return; if (!state.tasks.length) return; - taskSelect.value = state.tasks[0].id; + selectTask(state.tasks[0].id, { resetEnv: true }); + void persistSelections(); state.autoRunPending = false; void handleExtractAndAnalyze(); } diff --git a/wwcompanion-extension/settings.html b/wwcompanion-extension/settings.html index 3ae9916..b0ef5e5 100644 --- a/wwcompanion-extension/settings.html +++ b/wwcompanion-extension/settings.html @@ -78,17 +78,16 @@ -

Environment

+
+

Environment

+ API configuration and system prompt go here +
-
- - -
@@ -99,7 +98,10 @@ -

Resume

+
+

Resume

+ Text to your profile goes here +
diff --git a/wwcompanion-extension/settings.js b/wwcompanion-extension/settings.js index 510720e..61ea3fb 100644 --- a/wwcompanion-extension/settings.js +++ b/wwcompanion-extension/settings.js @@ -6,7 +6,6 @@ const addApiKeyBtn = document.getElementById("addApiKeyBtn"); const apiKeysContainer = document.getElementById("apiKeys"); const addEnvConfigBtn = document.getElementById("addEnvConfigBtn"); const envConfigsContainer = document.getElementById("envConfigs"); -const activeEnvConfigSelect = document.getElementById("activeEnvConfigSelect"); const addTaskBtn = document.getElementById("addTaskBtn"); const tasksContainer = document.getElementById("tasks"); const statusEl = document.getElementById("status"); @@ -91,6 +90,10 @@ function ensureUniqueName(desired, existingNames) { return buildUniqueDefaultName(existingNames); } +function getTopEnvId() { + return collectEnvConfigs()[0]?.id || ""; +} + function setApiConfigAdvanced(card, isAdvanced) { card.classList.toggle("is-advanced", isAdvanced); card.dataset.mode = isAdvanced ? "advanced" : "basic"; @@ -250,6 +253,46 @@ function buildApiConfigCard(config) { const actions = document.createElement("div"); actions.className = "api-config-actions"; + const moveTopBtn = document.createElement("button"); + moveTopBtn.type = "button"; + moveTopBtn.className = "ghost move-top"; + moveTopBtn.textContent = "Top"; + const moveUpBtn = document.createElement("button"); + moveUpBtn.type = "button"; + moveUpBtn.className = "ghost move-up"; + moveUpBtn.textContent = "Up"; + const moveDownBtn = document.createElement("button"); + moveDownBtn.type = "button"; + moveDownBtn.className = "ghost move-down"; + moveDownBtn.textContent = "Down"; + + moveTopBtn.addEventListener("click", () => { + const first = apiConfigsContainer.firstElementChild; + if (!first || first === card) return; + apiConfigsContainer.insertBefore(card, first); + updateApiConfigControls(); + updateEnvApiOptions(); + }); + + moveUpBtn.addEventListener("click", () => { + const previous = card.previousElementSibling; + if (!previous) return; + apiConfigsContainer.insertBefore(card, previous); + updateApiConfigControls(); + updateEnvApiOptions(); + }); + + moveDownBtn.addEventListener("click", () => { + const next = card.nextElementSibling; + if (!next) return; + apiConfigsContainer.insertBefore(card, next.nextElementSibling); + updateApiConfigControls(); + updateEnvApiOptions(); + }); + + actions.appendChild(moveTopBtn); + actions.appendChild(moveUpBtn); + actions.appendChild(moveDownBtn); const advancedBtn = document.createElement("button"); advancedBtn.type = "button"; advancedBtn.className = "ghost advanced-toggle"; @@ -312,6 +355,7 @@ function buildApiConfigCard(config) { deleteBtn.addEventListener("click", () => { card.remove(); updateEnvApiOptions(); + updateApiConfigControls(); }); actions.appendChild(deleteBtn); @@ -344,6 +388,18 @@ function collectApiConfigs() { return cards.map((card) => readApiConfigFromCard(card)); } +function updateApiConfigControls() { + const cards = [...apiConfigsContainer.querySelectorAll(".api-config-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 buildApiKeyCard(entry) { const card = document.createElement("div"); card.className = "api-key-card"; @@ -388,6 +444,46 @@ function buildApiKeyCard(entry) { const actions = document.createElement("div"); actions.className = "api-key-actions"; + const moveTopBtn = document.createElement("button"); + moveTopBtn.type = "button"; + moveTopBtn.className = "ghost move-top"; + moveTopBtn.textContent = "Top"; + const moveUpBtn = document.createElement("button"); + moveUpBtn.type = "button"; + moveUpBtn.className = "ghost move-up"; + moveUpBtn.textContent = "Up"; + const moveDownBtn = document.createElement("button"); + moveDownBtn.type = "button"; + moveDownBtn.className = "ghost move-down"; + moveDownBtn.textContent = "Down"; + + moveTopBtn.addEventListener("click", () => { + const first = apiKeysContainer.firstElementChild; + if (!first || first === card) return; + apiKeysContainer.insertBefore(card, first); + updateApiKeyControls(); + updateApiConfigKeyOptions(); + }); + + moveUpBtn.addEventListener("click", () => { + const previous = card.previousElementSibling; + if (!previous) return; + apiKeysContainer.insertBefore(card, previous); + updateApiKeyControls(); + updateApiConfigKeyOptions(); + }); + + moveDownBtn.addEventListener("click", () => { + const next = card.nextElementSibling; + if (!next) return; + apiKeysContainer.insertBefore(card, next.nextElementSibling); + updateApiKeyControls(); + updateApiConfigKeyOptions(); + }); + + actions.appendChild(moveTopBtn); + actions.appendChild(moveUpBtn); + actions.appendChild(moveDownBtn); const deleteBtn = document.createElement("button"); deleteBtn.type = "button"; deleteBtn.className = "ghost delete"; @@ -395,6 +491,7 @@ function buildApiKeyCard(entry) { deleteBtn.addEventListener("click", () => { card.remove(); updateApiConfigKeyOptions(); + updateApiKeyControls(); }); actions.appendChild(deleteBtn); @@ -422,6 +519,18 @@ function collectApiKeys() { }); } +function updateApiKeyControls() { + const cards = [...apiKeysContainer.querySelectorAll(".api-key-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 updateApiConfigKeyOptions() { const keys = collectApiKeys(); const selects = apiConfigsContainer.querySelectorAll(".api-config-key-select"); @@ -494,6 +603,46 @@ function buildEnvConfigCard(config) { const actions = document.createElement("div"); actions.className = "env-config-actions"; + const moveTopBtn = document.createElement("button"); + moveTopBtn.type = "button"; + moveTopBtn.className = "ghost move-top"; + moveTopBtn.textContent = "Top"; + const moveUpBtn = document.createElement("button"); + moveUpBtn.type = "button"; + moveUpBtn.className = "ghost move-up"; + moveUpBtn.textContent = "Up"; + const moveDownBtn = document.createElement("button"); + moveDownBtn.type = "button"; + moveDownBtn.className = "ghost move-down"; + moveDownBtn.textContent = "Down"; + + moveTopBtn.addEventListener("click", () => { + const first = envConfigsContainer.firstElementChild; + if (!first || first === card) return; + envConfigsContainer.insertBefore(card, first); + updateEnvControls(); + updateTaskEnvOptions(); + }); + + moveUpBtn.addEventListener("click", () => { + const previous = card.previousElementSibling; + if (!previous) return; + envConfigsContainer.insertBefore(card, previous); + updateEnvControls(); + updateTaskEnvOptions(); + }); + + moveDownBtn.addEventListener("click", () => { + const next = card.nextElementSibling; + if (!next) return; + envConfigsContainer.insertBefore(card, next.nextElementSibling); + updateEnvControls(); + updateTaskEnvOptions(); + }); + + actions.appendChild(moveTopBtn); + actions.appendChild(moveUpBtn); + actions.appendChild(moveDownBtn); const duplicateBtn = document.createElement("button"); duplicateBtn.type = "button"; @@ -515,7 +664,8 @@ function buildEnvConfigCard(config) { }); card.insertAdjacentElement("afterend", newCard); updateEnvApiOptions(); - updateEnvConfigSelect(newCard.dataset.id); + updateEnvControls(); + updateTaskEnvOptions(); }); const deleteBtn = document.createElement("button"); @@ -524,15 +674,13 @@ function buildEnvConfigCard(config) { deleteBtn.textContent = "Delete"; deleteBtn.addEventListener("click", () => { card.remove(); - updateEnvConfigSelect(activeEnvConfigSelect.value); + updateEnvControls(); + updateTaskEnvOptions(); }); actions.appendChild(duplicateBtn); actions.appendChild(deleteBtn); - - nameInput.addEventListener("input", () => - updateEnvConfigSelect(activeEnvConfigSelect.value) - ); + nameInput.addEventListener("input", () => updateEnvApiOptions()); card.appendChild(nameField); card.appendChild(apiField); @@ -542,6 +690,51 @@ function buildEnvConfigCard(config) { return card; } +function updateEnvControls() { + const cards = [...envConfigsContainer.querySelectorAll(".env-config-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 updateTaskEnvOptions() { + const envs = collectEnvConfigs(); + const selects = tasksContainer.querySelectorAll(".task-env-select"); + selects.forEach((select) => { + const preferred = select.dataset.preferred || select.value; + select.innerHTML = ""; + if (!envs.length) { + const option = document.createElement("option"); + option.value = ""; + option.textContent = "No environments configured"; + select.appendChild(option); + select.disabled = true; + return; + } + + select.disabled = false; + for (const env of envs) { + const option = document.createElement("option"); + option.value = env.id; + option.textContent = env.name || "Default"; + select.appendChild(option); + } + + if (preferred && envs.some((env) => env.id === preferred)) { + select.value = preferred; + } else { + select.value = envs[0].id; + } + + select.dataset.preferred = select.value; + }); +} + function collectEnvConfigs() { const cards = [...envConfigsContainer.querySelectorAll(".env-config-card")]; return cards.map((card) => { @@ -557,35 +750,6 @@ function collectEnvConfigs() { }); } -function updateEnvConfigSelect(preferredId) { - const configs = collectEnvConfigs(); - activeEnvConfigSelect.innerHTML = ""; - - if (!configs.length) { - const option = document.createElement("option"); - option.value = ""; - option.textContent = "No environments configured"; - activeEnvConfigSelect.appendChild(option); - activeEnvConfigSelect.disabled = true; - return; - } - - activeEnvConfigSelect.disabled = false; - const selectedId = - preferredId && configs.some((config) => config.id === preferredId) - ? preferredId - : configs[0].id; - - for (const config of configs) { - const option = document.createElement("option"); - option.value = config.id; - option.textContent = config.name || "Default"; - activeEnvConfigSelect.appendChild(option); - } - - activeEnvConfigSelect.value = selectedId; -} - function updateEnvApiOptions() { const apiConfigs = collectApiConfigs(); const selects = envConfigsContainer.querySelectorAll(".env-config-api-select"); @@ -617,6 +781,7 @@ function updateEnvApiOptions() { select.dataset.preferred = select.value; }); + updateTaskEnvOptions(); } function buildTaskCard(task) { @@ -635,6 +800,16 @@ function buildTaskCard(task) { nameField.appendChild(nameLabel); nameField.appendChild(nameInput); + const envField = document.createElement("div"); + envField.className = "field"; + const envLabel = document.createElement("label"); + envLabel.textContent = "Default environment"; + const envSelect = document.createElement("select"); + envSelect.className = "task-env-select"; + envSelect.dataset.preferred = task.defaultEnvId || ""; + envField.appendChild(envLabel); + envField.appendChild(envSelect); + const textField = document.createElement("div"); textField.className = "field"; const textLabel = document.createElement("label"); @@ -710,9 +885,15 @@ function buildTaskCard(task) { const name = buildUniqueDefaultName( collectNames(tasksContainer, ".task-name") ); - const newCard = buildTaskCard({ id: newTaskId(), name, text: "" }); + const newCard = buildTaskCard({ + id: newTaskId(), + name, + text: "", + defaultEnvId: getTopEnvId() + }); card.insertAdjacentElement("afterend", newCard); updateTaskControls(); + updateTaskEnvOptions(); }); duplicateBtn.addEventListener("click", () => { @@ -722,11 +903,13 @@ function buildTaskCard(task) { `${nameInput.value || "Untitled"} Copy`, collectNames(tasksContainer, ".task-name") ), - text: textArea.value + text: textArea.value, + defaultEnvId: envSelect.value || "" }; const newCard = buildTaskCard(copy); card.insertAdjacentElement("afterend", newCard); updateTaskControls(); + updateTaskEnvOptions(); }); deleteBtn.addEventListener("click", () => { @@ -742,6 +925,7 @@ function buildTaskCard(task) { actions.appendChild(deleteBtn); card.appendChild(nameField); + card.appendChild(envField); card.appendChild(textField); card.appendChild(actions); @@ -765,10 +949,12 @@ function collectTasks() { return cards.map((card) => { const nameInput = card.querySelector(".task-name"); const textArea = card.querySelector(".task-text"); + const envSelect = card.querySelector(".task-env-select"); return { id: card.dataset.id || newTaskId(), name: (nameInput?.value || "Untitled Task").trim(), - text: (textArea?.value || "").trim() + text: (textArea?.value || "").trim(), + defaultEnvId: envSelect?.value || "" }; }); } @@ -841,6 +1027,7 @@ async function loadSettings() { apiKeysContainer.appendChild(buildApiKeyCard(entry)); } } + updateApiKeyControls(); let resolvedConfigs = Array.isArray(apiConfigs) ? apiConfigs : []; let resolvedActiveConfigId = activeApiConfigId; @@ -891,9 +1078,9 @@ async function loadSettings() { apiConfigsContainer.appendChild(buildApiConfigCard(config)); } updateApiConfigKeyOptions(); + updateApiConfigControls(); let resolvedEnvConfigs = Array.isArray(envConfigs) ? envConfigs : []; - let resolvedActiveEnvId = activeEnvConfigId; const fallbackApiConfigId = resolvedActiveConfigId || resolvedConfigs[0]?.id || ""; @@ -905,10 +1092,9 @@ async function loadSettings() { systemPrompt: systemPrompt || DEFAULT_SYSTEM_PROMPT }; resolvedEnvConfigs = [migrated]; - resolvedActiveEnvId = migrated.id; await chrome.storage.local.set({ envConfigs: resolvedEnvConfigs, - activeEnvConfigId: resolvedActiveEnvId + activeEnvConfigId: migrated.id }); } else { const withDefaults = resolvedEnvConfigs.map((config) => ({ @@ -928,11 +1114,12 @@ async function loadSettings() { await chrome.storage.local.set({ envConfigs: resolvedEnvConfigs }); } const hasActive = resolvedEnvConfigs.some( - (config) => config.id === resolvedActiveEnvId + (config) => config.id === activeEnvConfigId ); - if (!hasActive) { - resolvedActiveEnvId = resolvedEnvConfigs[0].id; - await chrome.storage.local.set({ activeEnvConfigId: resolvedActiveEnvId }); + if (!hasActive && resolvedEnvConfigs.length) { + await chrome.storage.local.set({ + activeEnvConfigId: resolvedEnvConfigs[0].id + }); } } @@ -941,21 +1128,44 @@ async function loadSettings() { envConfigsContainer.appendChild(buildEnvConfigCard(config)); } updateEnvApiOptions(); - updateEnvConfigSelect(resolvedActiveEnvId); + updateEnvControls(); tasksContainer.innerHTML = ""; - if (!tasks.length) { + const defaultEnvId = resolvedEnvConfigs[0]?.id || ""; + const normalizedTasks = Array.isArray(tasks) + ? tasks.map((task) => ({ + ...task, + defaultEnvId: task.defaultEnvId || defaultEnvId + })) + : []; + if ( + normalizedTasks.length && + normalizedTasks.some( + (task, index) => task.defaultEnvId !== tasks[index]?.defaultEnvId + ) + ) { + await chrome.storage.local.set({ tasks: normalizedTasks }); + } + + if (!normalizedTasks.length) { tasksContainer.appendChild( - buildTaskCard({ id: newTaskId(), name: "", text: "" }) + buildTaskCard({ + id: newTaskId(), + name: "", + text: "", + defaultEnvId + }) ); updateTaskControls(); + updateTaskEnvOptions(); return; } - for (const task of tasks) { + for (const task of normalizedTasks) { tasksContainer.appendChild(buildTaskCard(task)); } updateTaskControls(); + updateTaskEnvOptions(); } async function saveSettings() { @@ -963,11 +1173,8 @@ async function saveSettings() { const apiKeys = collectApiKeys(); const apiConfigs = collectApiConfigs(); const envConfigs = collectEnvConfigs(); - const activeEnvConfigId = - envConfigs.find((entry) => entry.id === activeEnvConfigSelect.value)?.id || - envConfigs[0]?.id || - ""; - const activeEnv = envConfigs.find((entry) => entry.id === activeEnvConfigId); + const activeEnvConfigId = envConfigs[0]?.id || ""; + const activeEnv = envConfigs[0]; const activeApiConfigId = activeEnv?.apiConfigId || apiConfigs[0]?.id || ""; const activeConfig = apiConfigs.find((entry) => entry.id === activeApiConfigId); @@ -995,7 +1202,12 @@ addTaskBtn.addEventListener("click", () => { const name = buildUniqueDefaultName( collectNames(tasksContainer, ".task-name") ); - const newCard = buildTaskCard({ id: newTaskId(), name, text: "" }); + const newCard = buildTaskCard({ + id: newTaskId(), + name, + text: "", + defaultEnvId: getTopEnvId() + }); const first = tasksContainer.firstElementChild; if (first) { tasksContainer.insertBefore(newCard, first); @@ -1003,6 +1215,7 @@ addTaskBtn.addEventListener("click", () => { tasksContainer.appendChild(newCard); } updateTaskControls(); + updateTaskEnvOptions(); }); addApiKeyBtn.addEventListener("click", () => { @@ -1017,6 +1230,7 @@ addApiKeyBtn.addEventListener("click", () => { apiKeysContainer.appendChild(newCard); } updateApiConfigKeyOptions(); + updateApiKeyControls(); }); addApiConfigBtn.addEventListener("click", () => { @@ -1042,6 +1256,7 @@ addApiConfigBtn.addEventListener("click", () => { } updateApiConfigKeyOptions(); updateEnvApiOptions(); + updateApiConfigControls(); }); addEnvConfigBtn.addEventListener("click", () => { @@ -1062,11 +1277,8 @@ addEnvConfigBtn.addEventListener("click", () => { envConfigsContainer.appendChild(newCard); } updateEnvApiOptions(); - updateEnvConfigSelect(newCard.dataset.id); -}); - -activeEnvConfigSelect.addEventListener("change", () => { - updateEnvConfigSelect(activeEnvConfigSelect.value); + updateEnvControls(); + updateTaskEnvOptions(); }); themeSelect.addEventListener("change", () => applyTheme(themeSelect.value));