Files
wwcompanion/wwcompanion-extension/settings.js

266 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const apiKeyInput = document.getElementById("apiKey");
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 addTaskBtn = document.getElementById("addTaskBtn");
const tasksContainer = document.getElementById("tasks");
const statusEl = document.getElementById("status");
const toggleKeyBtn = document.getElementById("toggleKey");
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 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 moveUpBtn = document.createElement("button");
moveUpBtn.type = "button";
moveUpBtn.className = "ghost icon-btn move-up";
moveUpBtn.textContent = "↑";
moveUpBtn.setAttribute("aria-label", "Move task up");
moveUpBtn.setAttribute("title", "Move up");
const moveDownBtn = document.createElement("button");
moveDownBtn.type = "button";
moveDownBtn.className = "ghost icon-btn move-down";
moveDownBtn.textContent = "↓";
moveDownBtn.setAttribute("aria-label", "Move task down");
moveDownBtn.setAttribute("title", "Move down");
const addBelowBtn = document.createElement("button");
addBelowBtn.type = "button";
addBelowBtn.className = "ghost icon-btn add-below";
addBelowBtn.textContent = "";
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 icon-btn delete";
deleteBtn.textContent = "🗑";
deleteBtn.setAttribute("aria-label", "Delete task");
deleteBtn.setAttribute("title", "Delete");
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(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 moveUpBtn = card.querySelector(".move-up");
const moveDownBtn = card.querySelector(".move-down");
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 = "",
apiBaseUrl = "",
apiKeyHeader = "",
apiKeyPrefix = "",
model = "",
systemPrompt = "",
resume = "",
tasks = [],
theme = "system"
} = await getStorage([
"apiKey",
"apiBaseUrl",
"apiKeyHeader",
"apiKeyPrefix",
"model",
"systemPrompt",
"resume",
"tasks",
"theme"
]);
apiKeyInput.value = apiKey;
apiBaseUrlInput.value = apiBaseUrl;
apiKeyHeaderInput.value = apiKeyHeader;
apiKeyPrefixInput.value = apiKeyPrefix;
modelInput.value = model;
systemPromptInput.value = systemPrompt;
resumeInput.value = resume;
themeSelect.value = theme;
applyTheme(theme);
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();
await chrome.storage.local.set({
apiKey: apiKeyInput.value.trim(),
apiBaseUrl: apiBaseUrlInput.value.trim(),
apiKeyHeader: apiKeyHeaderInput.value.trim(),
apiKeyPrefix: apiKeyPrefixInput.value,
model: modelInput.value.trim(),
systemPrompt: systemPromptInput.value,
resume: resumeInput.value,
tasks,
theme: themeSelect.value
});
setStatus("Saved.");
}
saveBtn.addEventListener("click", () => void saveSettings());
addTaskBtn.addEventListener("click", () => {
tasksContainer.appendChild(buildTaskCard({ id: newTaskId(), name: "", text: "" }));
updateTaskControls();
});
toggleKeyBtn.addEventListener("click", () => {
const isPassword = apiKeyInput.type === "password";
apiKeyInput.type = isPassword ? "text" : "password";
toggleKeyBtn.textContent = isPassword ? "Hide" : "Show";
});
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();