diff --git a/wwcompanion-extension/background.js b/wwcompanion-extension/background.js
index 6b92c07..82a9250 100644
--- a/wwcompanion-extension/background.js
+++ b/wwcompanion-extension/background.js
@@ -19,6 +19,8 @@ const DEFAULT_SETTINGS = {
activeApiKeyId: "",
apiConfigs: [],
activeApiConfigId: "",
+ envConfigs: [],
+ activeEnvConfigId: "",
apiBaseUrl: "https://api.openai.com/v1",
apiKeyHeader: "Authorization",
apiKeyPrefix: "Bearer ",
@@ -188,6 +190,58 @@ chrome.runtime.onInstalled.addListener(async () => {
}
}
+ const resolvedApiConfigs = updates.apiConfigs || stored.apiConfigs || [];
+ const resolvedActiveApiConfigId =
+ updates.activeApiConfigId ||
+ stored.activeApiConfigId ||
+ resolvedApiConfigs[0]?.id ||
+ "";
+ const hasEnvConfigs =
+ Array.isArray(stored.envConfigs) && stored.envConfigs.length > 0;
+
+ if (!hasEnvConfigs) {
+ const id = crypto?.randomUUID
+ ? crypto.randomUUID()
+ : `env-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
+ updates.envConfigs = [
+ {
+ id,
+ name: "Default",
+ apiConfigId: resolvedActiveApiConfigId,
+ systemPrompt: stored.systemPrompt || DEFAULT_SETTINGS.systemPrompt
+ }
+ ];
+ updates.activeEnvConfigId = id;
+ } else {
+ const normalizedEnvs = stored.envConfigs.map((config) => ({
+ ...config,
+ apiConfigId: config.apiConfigId || resolvedActiveApiConfigId,
+ systemPrompt: config.systemPrompt ?? ""
+ }));
+ const envNeedsUpdate = normalizedEnvs.some((config, index) => {
+ const original = stored.envConfigs[index];
+ return (
+ config.apiConfigId !== original.apiConfigId ||
+ (config.systemPrompt || "") !== (original.systemPrompt || "")
+ );
+ });
+ if (envNeedsUpdate) {
+ updates.envConfigs = normalizedEnvs;
+ }
+
+ const envActiveId = updates.activeEnvConfigId || stored.activeEnvConfigId;
+ if (envActiveId) {
+ const exists = stored.envConfigs.some(
+ (config) => config.id === envActiveId
+ );
+ if (!exists) {
+ updates.activeEnvConfigId = stored.envConfigs[0].id;
+ }
+ } else {
+ updates.activeEnvConfigId = stored.envConfigs[0].id;
+ }
+ }
+
if (Object.keys(updates).length) {
await chrome.storage.local.set(updates);
}
@@ -307,6 +361,10 @@ async function handleAnalysisRequest(port, payload, signal) {
safePost(port, { type: "ERROR", message: "Missing request template." });
return;
}
+ if (apiKeyHeader && !apiKey) {
+ safePost(port, { type: "ERROR", message: "Missing API key." });
+ return;
+ }
} else {
if (!apiBaseUrl) {
safePost(port, { type: "ERROR", message: "Missing API base URL." });
@@ -345,6 +403,10 @@ async function handleAnalysisRequest(port, payload, signal) {
apiKey,
apiUrl,
requestTemplate,
+ apiKeyHeader,
+ apiKeyPrefix,
+ apiBaseUrl,
+ model,
systemPrompt: systemPrompt || "",
userMessage,
signal,
@@ -511,6 +573,10 @@ async function streamCustomCompletion({
apiKey,
apiUrl,
requestTemplate,
+ apiKeyHeader,
+ apiKeyPrefix,
+ apiBaseUrl,
+ model,
systemPrompt,
userMessage,
signal,
@@ -519,16 +585,24 @@ async function streamCustomCompletion({
const replacements = {
PROMPT_GOES_HERE: userMessage,
SYSTEM_PROMPT_GOES_HERE: systemPrompt,
- API_KEY_GOES_HERE: apiKey
+ API_KEY_GOES_HERE: apiKey,
+ MODEL_GOES_HERE: model || "",
+ API_BASE_URL_GOES_HERE: apiBaseUrl || ""
};
const resolvedUrl = replaceUrlTokens(apiUrl, replacements);
const body = buildTemplateBody(requestTemplate, replacements);
+ const headers = {
+ "Content-Type": "application/json"
+ };
+ const authHeader = buildAuthHeader(apiKeyHeader, apiKeyPrefix, apiKey);
+ if (authHeader) {
+ headers[authHeader.name] = authHeader.value;
+ }
+
const response = await fetch(resolvedUrl, {
method: "POST",
- headers: {
- "Content-Type": "application/json"
- },
+ headers,
body: JSON.stringify(body),
signal
});
diff --git a/wwcompanion-extension/popup.js b/wwcompanion-extension/popup.js
index 3be90d0..d9af489 100644
--- a/wwcompanion-extension/popup.js
+++ b/wwcompanion-extension/popup.js
@@ -433,6 +433,8 @@ async function handleAnalyze() {
activeApiKeyId = "",
apiConfigs = [],
activeApiConfigId = "",
+ envConfigs = [],
+ activeEnvConfigId = "",
apiBaseUrl,
apiKeyHeader,
apiKeyPrefix,
@@ -444,6 +446,8 @@ async function handleAnalyze() {
"activeApiKeyId",
"apiConfigs",
"activeApiConfigId",
+ "envConfigs",
+ "activeEnvConfigId",
"apiBaseUrl",
"apiKeyHeader",
"apiKeyPrefix",
@@ -453,22 +457,28 @@ async function handleAnalyze() {
]);
const resolvedConfigs = Array.isArray(apiConfigs) ? apiConfigs : [];
+ const resolvedEnvs = Array.isArray(envConfigs) ? envConfigs : [];
+ const activeEnv =
+ resolvedEnvs.find((entry) => entry.id === activeEnvConfigId) ||
+ resolvedEnvs[0];
+ const resolvedSystemPrompt =
+ activeEnv?.systemPrompt ?? systemPrompt ?? "";
+ const resolvedApiConfigId =
+ activeEnv?.apiConfigId || activeApiConfigId || resolvedConfigs[0]?.id || "";
const activeConfig =
- resolvedConfigs.find((entry) => entry.id === activeApiConfigId) ||
+ resolvedConfigs.find((entry) => entry.id === resolvedApiConfigId) ||
resolvedConfigs[0];
+ if (!activeConfig) {
+ setStatus("Add an API configuration in Settings.");
+ return;
+ }
const isAdvanced = Boolean(activeConfig?.advanced);
const resolvedApiUrl = activeConfig?.apiUrl || "";
const resolvedTemplate = activeConfig?.requestTemplate || "";
- const resolvedApiBaseUrl = isAdvanced
- ? ""
- : activeConfig?.apiBaseUrl || apiBaseUrl || "";
- const resolvedApiKeyHeader = isAdvanced
- ? ""
- : activeConfig?.apiKeyHeader ?? apiKeyHeader ?? "";
- const resolvedApiKeyPrefix = isAdvanced
- ? ""
- : activeConfig?.apiKeyPrefix ?? apiKeyPrefix ?? "";
- const resolvedModel = isAdvanced ? "" : activeConfig?.model || model || "";
+ const resolvedApiBaseUrl = activeConfig?.apiBaseUrl || apiBaseUrl || "";
+ const resolvedApiKeyHeader = activeConfig?.apiKeyHeader ?? apiKeyHeader ?? "";
+ const resolvedApiKeyPrefix = activeConfig?.apiKeyPrefix ?? apiKeyPrefix ?? "";
+ const resolvedModel = activeConfig?.model || model || "";
const resolvedKeys = Array.isArray(apiKeys) ? apiKeys : [];
const resolvedKeyId =
@@ -485,7 +495,10 @@ async function handleAnalyze() {
setStatus("Set a request template in Settings.");
return;
}
- if (resolvedTemplate.includes("API_KEY_GOES_HERE") && !apiKey) {
+ const needsKey =
+ Boolean(resolvedApiKeyHeader) ||
+ resolvedTemplate.includes("API_KEY_GOES_HERE");
+ if (needsKey && !apiKey) {
setStatus("Add an API key in Settings.");
return;
}
@@ -524,7 +537,7 @@ async function handleAnalyze() {
apiKeyHeader: resolvedApiKeyHeader,
apiKeyPrefix: resolvedApiKeyPrefix,
model: resolvedModel,
- systemPrompt: systemPrompt || "",
+ systemPrompt: resolvedSystemPrompt,
resume: resume || "",
taskText: task.text || "",
postingText: state.postingText,
diff --git a/wwcompanion-extension/settings.css b/wwcompanion-extension/settings.css
index 2326294..21f63bf 100644
--- a/wwcompanion-extension/settings.css
+++ b/wwcompanion-extension/settings.css
@@ -256,6 +256,7 @@ button:active {
.api-key-actions .delete,
.api-config-actions .delete,
+.env-config-actions .delete,
.task-actions .delete {
background: #c0392b;
border-color: #c0392b;
@@ -290,6 +291,26 @@ button:active {
justify-content: flex-end;
}
+.env-configs {
+ display: grid;
+ gap: 12px;
+}
+
+.env-config-card {
+ padding: 12px;
+ border-radius: 12px;
+ border: 1px solid var(--border);
+ background: var(--card-bg);
+ display: grid;
+ gap: 8px;
+}
+
+.env-config-actions {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+}
+
@media (prefers-color-scheme: dark) {
:root:not([data-theme]),
:root[data-theme="system"] {
diff --git a/wwcompanion-extension/settings.html b/wwcompanion-extension/settings.html
index 9c3d605..3ae9916 100644
--- a/wwcompanion-extension/settings.html
+++ b/wwcompanion-extension/settings.html
@@ -16,46 +16,6 @@
-
-
-
- ▸
- ▾
-
- API
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ▸
- ▾
-
- API KEYS
-
-
-
-
@@ -82,14 +42,54 @@
▸
▾
- System Prompt
+ API KEYS
+
+
+
+
+
+ ▸
+ ▾
+
+ API
+
+
+
+
+
+
+
+ ▸
+ ▾
+
+ Environment
+
+
+
+
+
+
+
+
diff --git a/wwcompanion-extension/settings.js b/wwcompanion-extension/settings.js
index 0983954..510720e 100644
--- a/wwcompanion-extension/settings.js
+++ b/wwcompanion-extension/settings.js
@@ -1,11 +1,12 @@
-const systemPromptInput = document.getElementById("systemPrompt");
const resumeInput = document.getElementById("resume");
const saveBtn = document.getElementById("saveBtn");
const addApiConfigBtn = document.getElementById("addApiConfigBtn");
const apiConfigsContainer = document.getElementById("apiConfigs");
-const activeApiConfigSelect = document.getElementById("activeApiConfigSelect");
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");
@@ -17,6 +18,8 @@ const OPENAI_DEFAULTS = {
apiKeyPrefix: "Bearer "
};
const DEFAULT_MODEL = "gpt-4o-mini";
+const DEFAULT_SYSTEM_PROMPT =
+ "You are a precise, honest assistant. Be concise and avoid inventing details, be critical about evaluations. You should put in a small summary of all the sections at the end. You should answer in no longer than 3 sections including the summary. And remember to bold or italicize key points.";
function getStorage(keys) {
return new Promise((resolve) => chrome.storage.local.get(keys, resolve));
@@ -50,6 +53,18 @@ function newApiConfigId() {
return `config-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
}
+function newEnvConfigId() {
+ if (crypto?.randomUUID) return crypto.randomUUID();
+ return `env-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
+}
+
+function buildChatUrlFromBase(baseUrl) {
+ const trimmed = (baseUrl || "").trim().replace(/\/+$/, "");
+ if (!trimmed) return "";
+ if (trimmed.endsWith("/chat/completions")) return trimmed;
+ return `${trimmed}/chat/completions`;
+}
+
function collectNames(container, selector) {
if (!container) return [];
return [...container.querySelectorAll(selector)]
@@ -94,10 +109,10 @@ function setApiConfigAdvanced(card, isAdvanced) {
});
const resetBtn = card.querySelector(".reset-openai");
- if (resetBtn) resetBtn.disabled = isAdvanced;
+ if (resetBtn) resetBtn.disabled = false;
const advancedBtn = card.querySelector(".advanced-toggle");
- if (advancedBtn && isAdvanced) advancedBtn.remove();
+ if (advancedBtn) advancedBtn.disabled = isAdvanced;
}
function readApiConfigFromCard(card) {
@@ -165,7 +180,7 @@ function buildApiConfigCard(config) {
baseField.appendChild(baseInput);
const headerField = document.createElement("div");
- headerField.className = "field basic-only";
+ headerField.className = "field advanced-only";
const headerLabel = document.createElement("label");
headerLabel.textContent = "API Key Header";
const headerInput = document.createElement("input");
@@ -177,7 +192,7 @@ function buildApiConfigCard(config) {
headerField.appendChild(headerInput);
const prefixField = document.createElement("div");
- prefixField.className = "field basic-only";
+ prefixField.className = "field advanced-only";
const prefixLabel = document.createElement("label");
prefixLabel.textContent = "API Key Prefix";
const prefixInput = document.createElement("input");
@@ -235,17 +250,28 @@ function buildApiConfigCard(config) {
const actions = document.createElement("div");
actions.className = "api-config-actions";
- if (!isAdvanced) {
- const advancedBtn = document.createElement("button");
- advancedBtn.type = "button";
- advancedBtn.className = "ghost advanced-toggle";
- advancedBtn.textContent = "Advanced Mode";
- advancedBtn.addEventListener("click", () => {
- setApiConfigAdvanced(card, true);
- updateApiConfigSelect(activeApiConfigSelect.value);
- });
- actions.appendChild(advancedBtn);
- }
+ const advancedBtn = document.createElement("button");
+ advancedBtn.type = "button";
+ advancedBtn.className = "ghost advanced-toggle";
+ advancedBtn.textContent = "Advanced Mode";
+ advancedBtn.addEventListener("click", () => {
+ if (card.classList.contains("is-advanced")) return;
+ urlInput.value = buildChatUrlFromBase(baseInput.value);
+ templateInput.value = [
+ "{",
+ ` \"model\": \"${modelInput.value || DEFAULT_MODEL}\",`,
+ " \"stream\": true,",
+ " \"messages\": [",
+ " { \"role\": \"system\", \"content\": \"SYSTEM_PROMPT_GOES_HERE\" },",
+ " { \"role\": \"user\", \"content\": \"PROMPT_GOES_HERE\" }",
+ " ],",
+ " \"api_key\": \"API_KEY_GOES_HERE\"",
+ "}"
+ ].join("\n");
+ setApiConfigAdvanced(card, true);
+ updateEnvApiOptions();
+ });
+ actions.appendChild(advancedBtn);
const duplicateBtn = document.createElement("button");
duplicateBtn.type = "button";
@@ -259,7 +285,7 @@ function buildApiConfigCard(config) {
const newCard = buildApiConfigCard(copy);
card.insertAdjacentElement("afterend", newCard);
updateApiConfigKeyOptions();
- updateApiConfigSelect(newCard.dataset.id);
+ updateEnvApiOptions();
});
actions.appendChild(duplicateBtn);
@@ -268,14 +294,14 @@ function buildApiConfigCard(config) {
resetBtn.className = "ghost reset-openai";
resetBtn.textContent = "Reset to OpenAI";
resetBtn.addEventListener("click", () => {
- if (card.classList.contains("is-advanced")) {
- setStatus("Advanced mode cannot be reset to OpenAI.");
- return;
- }
baseInput.value = OPENAI_DEFAULTS.apiBaseUrl;
headerInput.value = OPENAI_DEFAULTS.apiKeyHeader;
prefixInput.value = OPENAI_DEFAULTS.apiKeyPrefix;
- updateApiConfigSelect(activeApiConfigSelect.value);
+ modelInput.value = DEFAULT_MODEL;
+ urlInput.value = "";
+ templateInput.value = "";
+ setApiConfigAdvanced(card, false);
+ updateEnvApiOptions();
});
actions.appendChild(resetBtn);
@@ -285,11 +311,11 @@ function buildApiConfigCard(config) {
deleteBtn.textContent = "Delete";
deleteBtn.addEventListener("click", () => {
card.remove();
- updateApiConfigSelect(activeApiConfigSelect.value);
+ updateEnvApiOptions();
});
actions.appendChild(deleteBtn);
- const updateSelect = () => updateApiConfigSelect(activeApiConfigSelect.value);
+ const updateSelect = () => updateEnvApiOptions();
nameInput.addEventListener("input", updateSelect);
baseInput.addEventListener("input", updateSelect);
headerInput.addEventListener("input", updateSelect);
@@ -318,35 +344,6 @@ function collectApiConfigs() {
return cards.map((card) => readApiConfigFromCard(card));
}
-function updateApiConfigSelect(preferredId) {
- const configs = collectApiConfigs();
- activeApiConfigSelect.innerHTML = "";
-
- if (!configs.length) {
- const option = document.createElement("option");
- option.value = "";
- option.textContent = "No configs configured";
- activeApiConfigSelect.appendChild(option);
- activeApiConfigSelect.disabled = true;
- return;
- }
-
- activeApiConfigSelect.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";
- activeApiConfigSelect.appendChild(option);
- }
-
- activeApiConfigSelect.value = selectedId;
-}
-
function buildApiKeyCard(entry) {
const card = document.createElement("div");
card.className = "api-key-card";
@@ -458,6 +455,170 @@ function updateApiConfigKeyOptions() {
});
}
+function buildEnvConfigCard(config) {
+ const card = document.createElement("div");
+ card.className = "env-config-card";
+ card.dataset.id = config.id || newEnvConfigId();
+
+ 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 = config.name || "";
+ nameInput.className = "env-config-name";
+ nameField.appendChild(nameLabel);
+ nameField.appendChild(nameInput);
+
+ const apiField = document.createElement("div");
+ apiField.className = "field";
+ const apiLabel = document.createElement("label");
+ apiLabel.textContent = "API config";
+ const apiSelect = document.createElement("select");
+ apiSelect.className = "env-config-api-select";
+ apiSelect.dataset.preferred = config.apiConfigId || "";
+ apiField.appendChild(apiLabel);
+ apiField.appendChild(apiSelect);
+
+ const promptField = document.createElement("div");
+ promptField.className = "field";
+ const promptLabel = document.createElement("label");
+ promptLabel.textContent = "System prompt";
+ const promptInput = document.createElement("textarea");
+ promptInput.rows = 8;
+ promptInput.value = config.systemPrompt || "";
+ promptInput.className = "env-config-prompt";
+ promptField.appendChild(promptLabel);
+ promptField.appendChild(promptInput);
+
+ const actions = document.createElement("div");
+ actions.className = "env-config-actions";
+
+ const duplicateBtn = document.createElement("button");
+ duplicateBtn.type = "button";
+ duplicateBtn.className = "ghost duplicate";
+ duplicateBtn.textContent = "Duplicate";
+ duplicateBtn.addEventListener("click", () => {
+ const names = collectNames(envConfigsContainer, ".env-config-name");
+ const copy = collectEnvConfigs().find((entry) => entry.id === card.dataset.id) || {
+ id: card.dataset.id,
+ name: nameInput.value || "Default",
+ apiConfigId: apiSelect.value || "",
+ systemPrompt: promptInput.value || ""
+ };
+ const newCard = buildEnvConfigCard({
+ id: newEnvConfigId(),
+ name: ensureUniqueName(`${copy.name || "Default"} Copy`, names),
+ apiConfigId: copy.apiConfigId,
+ systemPrompt: copy.systemPrompt
+ });
+ card.insertAdjacentElement("afterend", newCard);
+ updateEnvApiOptions();
+ updateEnvConfigSelect(newCard.dataset.id);
+ });
+
+ const deleteBtn = document.createElement("button");
+ deleteBtn.type = "button";
+ deleteBtn.className = "ghost delete";
+ deleteBtn.textContent = "Delete";
+ deleteBtn.addEventListener("click", () => {
+ card.remove();
+ updateEnvConfigSelect(activeEnvConfigSelect.value);
+ });
+
+ actions.appendChild(duplicateBtn);
+ actions.appendChild(deleteBtn);
+
+ nameInput.addEventListener("input", () =>
+ updateEnvConfigSelect(activeEnvConfigSelect.value)
+ );
+
+ card.appendChild(nameField);
+ card.appendChild(apiField);
+ card.appendChild(promptField);
+ card.appendChild(actions);
+
+ return card;
+}
+
+function collectEnvConfigs() {
+ const cards = [...envConfigsContainer.querySelectorAll(".env-config-card")];
+ return cards.map((card) => {
+ const nameInput = card.querySelector(".env-config-name");
+ const apiSelect = card.querySelector(".env-config-api-select");
+ const promptInput = card.querySelector(".env-config-prompt");
+ return {
+ id: card.dataset.id || newEnvConfigId(),
+ name: (nameInput?.value || "Default").trim(),
+ apiConfigId: apiSelect?.value || "",
+ systemPrompt: (promptInput?.value || "").trim()
+ };
+ });
+}
+
+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");
+ selects.forEach((select) => {
+ const preferred = select.dataset.preferred || select.value;
+ select.innerHTML = "";
+ if (!apiConfigs.length) {
+ const option = document.createElement("option");
+ option.value = "";
+ option.textContent = "No API configs configured";
+ select.appendChild(option);
+ select.disabled = true;
+ return;
+ }
+
+ select.disabled = false;
+ for (const config of apiConfigs) {
+ const option = document.createElement("option");
+ option.value = config.id;
+ option.textContent = config.name || "Default";
+ select.appendChild(option);
+ }
+
+ if (preferred && apiConfigs.some((config) => config.id === preferred)) {
+ select.value = preferred;
+ } else {
+ select.value = apiConfigs[0].id;
+ }
+
+ select.dataset.preferred = select.value;
+ });
+}
+
function buildTaskCard(task) {
const card = document.createElement("div");
card.className = "task-card";
@@ -619,6 +780,8 @@ async function loadSettings() {
activeApiKeyId = "",
apiConfigs = [],
activeApiConfigId = "",
+ envConfigs = [],
+ activeEnvConfigId = "",
apiBaseUrl = "",
apiKeyHeader = "",
apiKeyPrefix = "",
@@ -633,6 +796,8 @@ async function loadSettings() {
"activeApiKeyId",
"apiConfigs",
"activeApiConfigId",
+ "envConfigs",
+ "activeEnvConfigId",
"apiBaseUrl",
"apiKeyHeader",
"apiKeyPrefix",
@@ -643,7 +808,6 @@ async function loadSettings() {
"theme"
]);
- systemPromptInput.value = systemPrompt;
resumeInput.value = resume;
themeSelect.value = theme;
applyTheme(theme);
@@ -727,7 +891,57 @@ async function loadSettings() {
apiConfigsContainer.appendChild(buildApiConfigCard(config));
}
updateApiConfigKeyOptions();
- updateApiConfigSelect(resolvedActiveConfigId);
+
+ let resolvedEnvConfigs = Array.isArray(envConfigs) ? envConfigs : [];
+ let resolvedActiveEnvId = activeEnvConfigId;
+ const fallbackApiConfigId =
+ resolvedActiveConfigId || resolvedConfigs[0]?.id || "";
+
+ if (!resolvedEnvConfigs.length) {
+ const migrated = {
+ id: newEnvConfigId(),
+ name: "Default",
+ apiConfigId: fallbackApiConfigId,
+ systemPrompt: systemPrompt || DEFAULT_SYSTEM_PROMPT
+ };
+ resolvedEnvConfigs = [migrated];
+ resolvedActiveEnvId = migrated.id;
+ await chrome.storage.local.set({
+ envConfigs: resolvedEnvConfigs,
+ activeEnvConfigId: resolvedActiveEnvId
+ });
+ } else {
+ const withDefaults = resolvedEnvConfigs.map((config) => ({
+ ...config,
+ apiConfigId: config.apiConfigId || fallbackApiConfigId,
+ systemPrompt: config.systemPrompt ?? ""
+ }));
+ const needsUpdate = withDefaults.some((config, index) => {
+ const original = resolvedEnvConfigs[index];
+ return (
+ config.apiConfigId !== original.apiConfigId ||
+ (config.systemPrompt || "") !== (original.systemPrompt || "")
+ );
+ });
+ if (needsUpdate) {
+ resolvedEnvConfigs = withDefaults;
+ await chrome.storage.local.set({ envConfigs: resolvedEnvConfigs });
+ }
+ const hasActive = resolvedEnvConfigs.some(
+ (config) => config.id === resolvedActiveEnvId
+ );
+ if (!hasActive) {
+ resolvedActiveEnvId = resolvedEnvConfigs[0].id;
+ await chrome.storage.local.set({ activeEnvConfigId: resolvedActiveEnvId });
+ }
+ }
+
+ envConfigsContainer.innerHTML = "";
+ for (const config of resolvedEnvConfigs) {
+ envConfigsContainer.appendChild(buildEnvConfigCard(config));
+ }
+ updateEnvApiOptions();
+ updateEnvConfigSelect(resolvedActiveEnvId);
tasksContainer.innerHTML = "";
if (!tasks.length) {
@@ -748,10 +962,14 @@ async function saveSettings() {
const tasks = collectTasks();
const apiKeys = collectApiKeys();
const apiConfigs = collectApiConfigs();
- const activeApiConfigId =
- apiConfigs.find((entry) => entry.id === activeApiConfigSelect.value)?.id ||
- apiConfigs[0]?.id ||
+ 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 activeApiConfigId =
+ activeEnv?.apiConfigId || apiConfigs[0]?.id || "";
const activeConfig = apiConfigs.find((entry) => entry.id === activeApiConfigId);
const activeApiKeyId =
activeConfig?.apiKeyId ||
@@ -762,7 +980,9 @@ async function saveSettings() {
activeApiKeyId,
apiConfigs,
activeApiConfigId,
- systemPrompt: systemPromptInput.value,
+ envConfigs,
+ activeEnvConfigId,
+ systemPrompt: activeEnv?.systemPrompt || "",
resume: resumeInput.value,
tasks,
theme: themeSelect.value
@@ -821,11 +1041,32 @@ addApiConfigBtn.addEventListener("click", () => {
apiConfigsContainer.appendChild(newCard);
}
updateApiConfigKeyOptions();
- updateApiConfigSelect(activeApiConfigSelect.value);
+ updateEnvApiOptions();
});
-activeApiConfigSelect.addEventListener("change", () => {
- updateApiConfigSelect(activeApiConfigSelect.value);
+addEnvConfigBtn.addEventListener("click", () => {
+ const name = buildUniqueDefaultName(
+ collectNames(envConfigsContainer, ".env-config-name")
+ );
+ const fallbackApiConfigId = collectApiConfigs()[0]?.id || "";
+ const newCard = buildEnvConfigCard({
+ id: newEnvConfigId(),
+ name,
+ apiConfigId: fallbackApiConfigId,
+ systemPrompt: DEFAULT_SYSTEM_PROMPT
+ });
+ const first = envConfigsContainer.firstElementChild;
+ if (first) {
+ envConfigsContainer.insertBefore(newCard, first);
+ } else {
+ envConfigsContainer.appendChild(newCard);
+ }
+ updateEnvApiOptions();
+ updateEnvConfigSelect(newCard.dataset.id);
+});
+
+activeEnvConfigSelect.addEventListener("change", () => {
+ updateEnvConfigSelect(activeEnvConfigSelect.value);
});
themeSelect.addEventListener("change", () => applyTheme(themeSelect.value));