Added multi-key support
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
const apiKeyInput = document.getElementById("apiKey");
|
||||
const apiBaseUrlInput = document.getElementById("apiBaseUrl");
|
||||
const apiKeyHeaderInput = document.getElementById("apiKeyHeader");
|
||||
const apiKeyPrefixInput = document.getElementById("apiKeyPrefix");
|
||||
@@ -6,10 +5,12 @@ const modelInput = document.getElementById("model");
|
||||
const systemPromptInput = document.getElementById("systemPrompt");
|
||||
const resumeInput = document.getElementById("resume");
|
||||
const saveBtn = document.getElementById("saveBtn");
|
||||
const addApiKeyBtn = document.getElementById("addApiKeyBtn");
|
||||
const apiKeysContainer = document.getElementById("apiKeys");
|
||||
const activeApiKeySelect = document.getElementById("activeApiKeySelect");
|
||||
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");
|
||||
|
||||
@@ -41,6 +42,118 @@ function newTaskId() {
|
||||
return `task-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
function newApiKeyId() {
|
||||
if (crypto?.randomUUID) return crypto.randomUUID();
|
||||
return `key-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
function buildApiKeyCard(entry) {
|
||||
const card = document.createElement("div");
|
||||
card.className = "api-key-card";
|
||||
card.dataset.id = entry.id || newApiKeyId();
|
||||
|
||||
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 = entry.name || "";
|
||||
nameInput.className = "api-key-name";
|
||||
nameField.appendChild(nameLabel);
|
||||
nameField.appendChild(nameInput);
|
||||
|
||||
const keyField = document.createElement("div");
|
||||
keyField.className = "field";
|
||||
const keyLabel = document.createElement("label");
|
||||
keyLabel.textContent = "Key";
|
||||
const keyInline = document.createElement("div");
|
||||
keyInline.className = "inline";
|
||||
const keyInput = document.createElement("input");
|
||||
keyInput.type = "password";
|
||||
keyInput.autocomplete = "off";
|
||||
keyInput.placeholder = "sk-...";
|
||||
keyInput.value = entry.key || "";
|
||||
keyInput.className = "api-key-value";
|
||||
const showBtn = document.createElement("button");
|
||||
showBtn.type = "button";
|
||||
showBtn.className = "ghost";
|
||||
showBtn.textContent = "Show";
|
||||
showBtn.addEventListener("click", () => {
|
||||
const isPassword = keyInput.type === "password";
|
||||
keyInput.type = isPassword ? "text" : "password";
|
||||
showBtn.textContent = isPassword ? "Hide" : "Show";
|
||||
});
|
||||
keyInline.appendChild(keyInput);
|
||||
keyInline.appendChild(showBtn);
|
||||
keyField.appendChild(keyLabel);
|
||||
keyField.appendChild(keyInline);
|
||||
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "api-key-actions";
|
||||
const deleteBtn = document.createElement("button");
|
||||
deleteBtn.type = "button";
|
||||
deleteBtn.className = "ghost delete";
|
||||
deleteBtn.textContent = "Delete";
|
||||
deleteBtn.addEventListener("click", () => {
|
||||
card.remove();
|
||||
updateApiKeySelect();
|
||||
});
|
||||
actions.appendChild(deleteBtn);
|
||||
|
||||
const updateSelect = () => updateApiKeySelect(activeApiKeySelect.value);
|
||||
nameInput.addEventListener("input", updateSelect);
|
||||
keyInput.addEventListener("input", updateSelect);
|
||||
|
||||
card.appendChild(nameField);
|
||||
card.appendChild(keyField);
|
||||
card.appendChild(actions);
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
function collectApiKeys() {
|
||||
const cards = [...apiKeysContainer.querySelectorAll(".api-key-card")];
|
||||
return cards.map((card) => {
|
||||
const nameInput = card.querySelector(".api-key-name");
|
||||
const keyInput = card.querySelector(".api-key-value");
|
||||
return {
|
||||
id: card.dataset.id || newApiKeyId(),
|
||||
name: (nameInput?.value || "Default").trim(),
|
||||
key: (keyInput?.value || "").trim()
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function updateApiKeySelect(preferredId) {
|
||||
const keys = collectApiKeys();
|
||||
activeApiKeySelect.innerHTML = "";
|
||||
|
||||
if (!keys.length) {
|
||||
const option = document.createElement("option");
|
||||
option.value = "";
|
||||
option.textContent = "No keys configured";
|
||||
activeApiKeySelect.appendChild(option);
|
||||
activeApiKeySelect.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
activeApiKeySelect.disabled = false;
|
||||
const selectedId =
|
||||
preferredId && keys.some((key) => key.id === preferredId)
|
||||
? preferredId
|
||||
: keys[0].id;
|
||||
|
||||
for (const key of keys) {
|
||||
const option = document.createElement("option");
|
||||
option.value = key.id;
|
||||
option.textContent = key.name || "Default";
|
||||
activeApiKeySelect.appendChild(option);
|
||||
}
|
||||
|
||||
activeApiKeySelect.value = selectedId;
|
||||
}
|
||||
|
||||
function buildTaskCard(task) {
|
||||
const card = document.createElement("div");
|
||||
card.className = "task-card";
|
||||
@@ -192,6 +305,8 @@ function collectTasks() {
|
||||
async function loadSettings() {
|
||||
const {
|
||||
apiKey = "",
|
||||
apiKeys = [],
|
||||
activeApiKeyId = "",
|
||||
apiBaseUrl = "",
|
||||
apiKeyHeader = "",
|
||||
apiKeyPrefix = "",
|
||||
@@ -202,6 +317,8 @@ async function loadSettings() {
|
||||
theme = "system"
|
||||
} = await getStorage([
|
||||
"apiKey",
|
||||
"apiKeys",
|
||||
"activeApiKeyId",
|
||||
"apiBaseUrl",
|
||||
"apiKeyHeader",
|
||||
"apiKeyPrefix",
|
||||
@@ -212,7 +329,6 @@ async function loadSettings() {
|
||||
"theme"
|
||||
]);
|
||||
|
||||
apiKeyInput.value = apiKey;
|
||||
apiBaseUrlInput.value = apiBaseUrl;
|
||||
apiKeyHeaderInput.value = apiKeyHeader;
|
||||
apiKeyPrefixInput.value = apiKeyPrefix;
|
||||
@@ -222,6 +338,29 @@ async function loadSettings() {
|
||||
themeSelect.value = theme;
|
||||
applyTheme(theme);
|
||||
|
||||
let resolvedKeys = Array.isArray(apiKeys) ? apiKeys : [];
|
||||
let resolvedActiveId = activeApiKeyId;
|
||||
|
||||
if (!resolvedKeys.length && apiKey) {
|
||||
const migrated = { id: newApiKeyId(), name: "Default", key: apiKey };
|
||||
resolvedKeys = [migrated];
|
||||
resolvedActiveId = migrated.id;
|
||||
await chrome.storage.local.set({
|
||||
apiKeys: resolvedKeys,
|
||||
activeApiKeyId: resolvedActiveId
|
||||
});
|
||||
}
|
||||
|
||||
apiKeysContainer.innerHTML = "";
|
||||
if (!resolvedKeys.length) {
|
||||
apiKeysContainer.appendChild(buildApiKeyCard({ id: newApiKeyId(), name: "", key: "" }));
|
||||
} else {
|
||||
for (const entry of resolvedKeys) {
|
||||
apiKeysContainer.appendChild(buildApiKeyCard(entry));
|
||||
}
|
||||
}
|
||||
updateApiKeySelect(resolvedActiveId);
|
||||
|
||||
tasksContainer.innerHTML = "";
|
||||
if (!tasks.length) {
|
||||
tasksContainer.appendChild(
|
||||
@@ -239,11 +378,17 @@ async function loadSettings() {
|
||||
|
||||
async function saveSettings() {
|
||||
const tasks = collectTasks();
|
||||
const apiKeys = collectApiKeys();
|
||||
const activeApiKeyId =
|
||||
apiKeys.find((entry) => entry.id === activeApiKeySelect.value)?.id ||
|
||||
apiKeys[0]?.id ||
|
||||
"";
|
||||
await chrome.storage.local.set({
|
||||
apiKey: apiKeyInput.value.trim(),
|
||||
apiBaseUrl: apiBaseUrlInput.value.trim(),
|
||||
apiKeyHeader: apiKeyHeaderInput.value.trim(),
|
||||
apiKeyPrefix: apiKeyPrefixInput.value,
|
||||
apiKeys,
|
||||
activeApiKeyId,
|
||||
model: modelInput.value.trim(),
|
||||
systemPrompt: systemPromptInput.value,
|
||||
resume: resumeInput.value,
|
||||
@@ -265,10 +410,19 @@ addTaskBtn.addEventListener("click", () => {
|
||||
updateTaskControls();
|
||||
});
|
||||
|
||||
toggleKeyBtn.addEventListener("click", () => {
|
||||
const isPassword = apiKeyInput.type === "password";
|
||||
apiKeyInput.type = isPassword ? "text" : "password";
|
||||
toggleKeyBtn.textContent = isPassword ? "Hide" : "Show";
|
||||
addApiKeyBtn.addEventListener("click", () => {
|
||||
const newCard = buildApiKeyCard({ id: newApiKeyId(), name: "", key: "" });
|
||||
const first = apiKeysContainer.firstElementChild;
|
||||
if (first) {
|
||||
apiKeysContainer.insertBefore(newCard, first);
|
||||
} else {
|
||||
apiKeysContainer.appendChild(newCard);
|
||||
}
|
||||
updateApiKeySelect(activeApiKeySelect.value);
|
||||
});
|
||||
|
||||
activeApiKeySelect.addEventListener("change", () => {
|
||||
updateApiKeySelect(activeApiKeySelect.value);
|
||||
});
|
||||
|
||||
themeSelect.addEventListener("change", () => applyTheme(themeSelect.value));
|
||||
|
||||
Reference in New Issue
Block a user