266 lines
8.3 KiB
JavaScript
266 lines
8.3 KiB
JavaScript
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();
|