reworked UI, moved environment selection to tasks

This commit is contained in:
2026-01-17 17:36:44 -05:00
parent e3c7cbba95
commit df80aee8eb
6 changed files with 467 additions and 118 deletions

View File

@@ -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) { if (Object.keys(updates).length) {
await chrome.storage.local.set(updates); await chrome.storage.local.set(updates);
} }

View File

@@ -127,28 +127,33 @@ select {
font-size: 12px; font-size: 12px;
} }
.button-row { .env-row {
display: grid; display: flex;
grid-template-columns: minmax(64px, 0.8fr) minmax(0, 1.4fr) minmax(64px, 0.8fr); align-items: flex-end;
}
.env-row .env-field {
flex: 1;
margin: 0;
}
.task-row {
display: flex;
align-items: flex-end;
gap: 8px; gap: 8px;
} }
.button-row button { .task-row button {
white-space: nowrap; padding: 6px 15px;
} }
.button-row .primary { .task-row .task-field {
padding: 6px 8px; flex: 1;
margin: 0;
} }
.stop-row { .task-row select {
display: flex; min-width: 0;
justify-content: stretch;
margin-top: 4px;
}
.stop-row button {
width: 100%;
} }
.hidden { .hidden {
@@ -192,7 +197,7 @@ button:active {
border: 1px solid var(--border); border: 1px solid var(--border);
} }
.stop-row .ghost { .stop-btn {
background: #c0392b; background: #c0392b;
border-color: #c0392b; border-color: #c0392b;
color: #fff6f2; color: #fff6f2;

View File

@@ -17,17 +17,19 @@
<section class="panel"> <section class="panel">
<div class="controls-block"> <div class="controls-block">
<div class="config-block"> <div class="config-block">
<div class="field inline-field"> <div class="env-row">
<label for="taskSelect">Task</label> <div class="field inline-field env-field">
<select id="taskSelect"></select> <label for="envSelect">Environment</label>
<select id="envSelect"></select>
</div>
</div> </div>
<div class="button-row"> <div class="task-row">
<button id="extractBtn" class="primary">Extract</button> <div class="field inline-field task-field">
<button id="extractRunBtn" class="accent">Extract &amp; Run</button> <label for="taskSelect">Task</label>
<button id="analyzeBtn" class="ghost">Run Task</button> <select id="taskSelect"></select>
</div> </div>
<div id="stopRow" class="stop-row hidden"> <button id="runBtn" class="accent">Run</button>
<button id="abortBtn" class="ghost" disabled>Stop</button> <button id="abortBtn" class="ghost stop-btn hidden" disabled>Stop</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,10 +1,7 @@
const extractBtn = document.getElementById("extractBtn"); const runBtn = document.getElementById("runBtn");
const analyzeBtn = document.getElementById("analyzeBtn");
const abortBtn = document.getElementById("abortBtn"); 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 taskSelect = document.getElementById("taskSelect");
const envSelect = document.getElementById("envSelect");
const outputEl = document.getElementById("output"); const outputEl = document.getElementById("output");
const statusEl = document.getElementById("status"); const statusEl = document.getElementById("status");
const postingCountEl = document.getElementById("postingCount"); const postingCountEl = document.getElementById("postingCount");
@@ -16,14 +13,19 @@ const clearOutputBtn = document.getElementById("clearOutputBtn");
const OUTPUT_STORAGE_KEY = "lastOutput"; const OUTPUT_STORAGE_KEY = "lastOutput";
const AUTO_RUN_KEY = "autoRunDefaultTask"; const AUTO_RUN_KEY = "autoRunDefaultTask";
const LAST_TASK_KEY = "lastSelectedTaskId";
const LAST_ENV_KEY = "lastSelectedEnvId";
const state = { const state = {
postingText: "", postingText: "",
tasks: [], tasks: [],
envs: [],
port: null, port: null,
isAnalyzing: false, isAnalyzing: false,
outputRaw: "", outputRaw: "",
autoRunPending: false autoRunPending: false,
selectedTaskId: "",
selectedEnvId: ""
}; };
function getStorage(keys) { function getStorage(keys) {
@@ -245,15 +247,12 @@ function applyTheme(theme) {
function setAnalyzing(isAnalyzing) { function setAnalyzing(isAnalyzing) {
state.isAnalyzing = isAnalyzing; state.isAnalyzing = isAnalyzing;
analyzeBtn.disabled = isAnalyzing; runBtn.disabled = isAnalyzing;
abortBtn.disabled = !isAnalyzing; abortBtn.disabled = !isAnalyzing;
extractBtn.disabled = isAnalyzing; runBtn.classList.toggle("hidden", isAnalyzing);
extractRunBtn.disabled = isAnalyzing; abortBtn.classList.toggle("hidden", !isAnalyzing);
if (buttonRow && stopRow) {
buttonRow.classList.toggle("hidden", isAnalyzing);
stopRow.classList.toggle("hidden", !isAnalyzing);
}
updateTaskSelectState(); updateTaskSelectState();
updateEnvSelectState();
} }
function updatePostingCount() { function updatePostingCount() {
@@ -286,11 +285,70 @@ function renderTasks(tasks) {
updateTaskSelectState(); 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() { function updateTaskSelectState() {
const hasTasks = state.tasks.length > 0; const hasTasks = state.tasks.length > 0;
taskSelect.disabled = state.isAnalyzing || !hasTasks; 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) { function isWaterlooWorksUrl(url) {
try { try {
return new URL(url).hostname === "waterlooworks.uwaterloo.ca"; return new URL(url).hostname === "waterlooworks.uwaterloo.ca";
@@ -377,9 +435,45 @@ function ensurePort() {
return port; return port;
} }
async function loadTasks() { async function loadConfig() {
const { tasks = [] } = await getStorage(["tasks"]); 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); 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(); maybeRunDefaultTask();
} }
@@ -434,7 +528,6 @@ async function handleAnalyze() {
apiConfigs = [], apiConfigs = [],
activeApiConfigId = "", activeApiConfigId = "",
envConfigs = [], envConfigs = [],
activeEnvConfigId = "",
apiBaseUrl, apiBaseUrl,
apiKeyHeader, apiKeyHeader,
apiKeyPrefix, apiKeyPrefix,
@@ -447,7 +540,6 @@ async function handleAnalyze() {
"apiConfigs", "apiConfigs",
"activeApiConfigId", "activeApiConfigId",
"envConfigs", "envConfigs",
"activeEnvConfigId",
"apiBaseUrl", "apiBaseUrl",
"apiKeyHeader", "apiKeyHeader",
"apiKeyPrefix", "apiKeyPrefix",
@@ -458,13 +550,18 @@ async function handleAnalyze() {
const resolvedConfigs = Array.isArray(apiConfigs) ? apiConfigs : []; const resolvedConfigs = Array.isArray(apiConfigs) ? apiConfigs : [];
const resolvedEnvs = Array.isArray(envConfigs) ? envConfigs : []; const resolvedEnvs = Array.isArray(envConfigs) ? envConfigs : [];
const selectedEnvId = envSelect.value;
const activeEnv = const activeEnv =
resolvedEnvs.find((entry) => entry.id === activeEnvConfigId) || resolvedEnvs.find((entry) => entry.id === selectedEnvId) ||
resolvedEnvs[0]; resolvedEnvs[0];
if (!activeEnv) {
setStatus("Add an environment in Settings.");
return;
}
const resolvedSystemPrompt = const resolvedSystemPrompt =
activeEnv?.systemPrompt ?? systemPrompt ?? ""; activeEnv.systemPrompt ?? systemPrompt ?? "";
const resolvedApiConfigId = const resolvedApiConfigId =
activeEnv?.apiConfigId || activeApiConfigId || resolvedConfigs[0]?.id || ""; activeEnv.apiConfigId || activeApiConfigId || resolvedConfigs[0]?.id || "";
const activeConfig = const activeConfig =
resolvedConfigs.find((entry) => entry.id === resolvedApiConfigId) || resolvedConfigs.find((entry) => entry.id === resolvedApiConfigId) ||
resolvedConfigs[0]; resolvedConfigs[0];
@@ -593,20 +690,26 @@ function handleCopyRaw() {
void copyTextToClipboard(text, "Markdown"); void copyTextToClipboard(text, "Markdown");
} }
extractBtn.addEventListener("click", handleExtract); runBtn.addEventListener("click", handleExtractAndAnalyze);
analyzeBtn.addEventListener("click", handleAnalyze);
extractRunBtn.addEventListener("click", handleExtractAndAnalyze);
abortBtn.addEventListener("click", handleAbort); abortBtn.addEventListener("click", handleAbort);
settingsBtn.addEventListener("click", () => chrome.runtime.openOptionsPage()); settingsBtn.addEventListener("click", () => chrome.runtime.openOptionsPage());
copyRenderedBtn.addEventListener("click", handleCopyRendered); copyRenderedBtn.addEventListener("click", handleCopyRendered);
copyRawBtn.addEventListener("click", handleCopyRaw); copyRawBtn.addEventListener("click", handleCopyRaw);
clearOutputBtn.addEventListener("click", () => void handleClearOutput()); clearOutputBtn.addEventListener("click", () => void handleClearOutput());
taskSelect.addEventListener("change", () => {
selectTask(taskSelect.value, { resetEnv: true });
void persistSelections();
});
envSelect.addEventListener("change", () => {
setEnvironmentSelection(envSelect.value);
void persistSelections();
});
updatePostingCount(); updatePostingCount();
updatePromptCount(0); updatePromptCount(0);
renderOutput(); renderOutput();
setAnalyzing(false); setAnalyzing(false);
loadTasks(); loadConfig();
loadTheme(); loadTheme();
async function loadSavedOutput() { async function loadSavedOutput() {
@@ -628,7 +731,8 @@ function maybeRunDefaultTask() {
if (!state.autoRunPending) return; if (!state.autoRunPending) return;
if (state.isAnalyzing) return; if (state.isAnalyzing) return;
if (!state.tasks.length) return; if (!state.tasks.length) return;
taskSelect.value = state.tasks[0].id; selectTask(state.tasks[0].id, { resetEnv: true });
void persistSelections();
state.autoRunPending = false; state.autoRunPending = false;
void handleExtractAndAnalyze(); void handleExtractAndAnalyze();
} }

View File

@@ -78,17 +78,16 @@
<span class="caret-closed"></span> <span class="caret-closed"></span>
<span class="caret-open"></span> <span class="caret-open"></span>
</span> </span>
<h2>Environment</h2> <div class="row-title">
<h2>Environment</h2>
<span class="hint hint-accent">API configuration and system prompt go here</span>
</div>
</summary> </summary>
<div class="panel-body"> <div class="panel-body">
<div class="row"> <div class="row">
<div></div> <div></div>
<button id="addEnvConfigBtn" class="ghost" type="button">Add Config</button> <button id="addEnvConfigBtn" class="ghost" type="button">Add Config</button>
</div> </div>
<div class="field">
<label for="activeEnvConfigSelect">Active environment</label>
<select id="activeEnvConfigSelect"></select>
</div>
<div id="envConfigs" class="env-configs"></div> <div id="envConfigs" class="env-configs"></div>
</div> </div>
</details> </details>
@@ -99,7 +98,10 @@
<span class="caret-closed"></span> <span class="caret-closed"></span>
<span class="caret-open"></span> <span class="caret-open"></span>
</span> </span>
<h2>Resume</h2> <div class="row-title">
<h2>Resume</h2>
<span class="hint hint-accent">Text to your profile goes here</span>
</div>
</summary> </summary>
<div class="panel-body"> <div class="panel-body">
<textarea id="resume" rows="10" placeholder="Paste your resume text..."></textarea> <textarea id="resume" rows="10" placeholder="Paste your resume text..."></textarea>

View File

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