feat(keys): paste-your-key field — snippets carry the real key instantly

Next to the copy button: a key input and an «Insert key» button (Enter works
too). The pasted key substitutes into every harness/SDK snippet immediately
and survives tab and OS switches; a freshly created key pre-fills the field.
No more copying a snippet and then hand-editing the placeholder. 2 strings
×7 languages.
This commit is contained in:
infosave2007
2026-08-08 14:07:11 +03:00
parent 239ddc767c
commit 9607a38792
8 changed files with 36 additions and 8 deletions
+2
View File
@@ -644,4 +644,6 @@ export default {
"keys.budgetOf": "Budget {b}/Mon.", "keys.budgetOf": "Budget {b}/Mon.",
"dash.kpi.orBalance": "OpenRouter-Guthaben", "dash.kpi.orBalance": "OpenRouter-Guthaben",
"keys.h.install": "Installieren:", "keys.h.install": "Installieren:",
"keys.snip.keyInputPh": "API-Schlüssel einfügen",
"keys.snip.apply": "Schlüssel einsetzen",
}; };
+2
View File
@@ -637,4 +637,6 @@ export default {
"keys.budgetOf": "budget {b}/mo", "keys.budgetOf": "budget {b}/mo",
"dash.kpi.orBalance": "OpenRouter balance", "dash.kpi.orBalance": "OpenRouter balance",
"keys.h.install": "Install:", "keys.h.install": "Install:",
"keys.snip.keyInputPh": "paste your API key",
"keys.snip.apply": "Insert key",
}; };
+2
View File
@@ -644,4 +644,6 @@ export default {
"keys.budgetOf": "presupuesto {b}/mes", "keys.budgetOf": "presupuesto {b}/mes",
"dash.kpi.orBalance": "Saldo OpenRouter", "dash.kpi.orBalance": "Saldo OpenRouter",
"keys.h.install": "Instalar:", "keys.h.install": "Instalar:",
"keys.snip.keyInputPh": "pega tu clave API",
"keys.snip.apply": "Insertar clave",
}; };
+2
View File
@@ -644,4 +644,6 @@ export default {
"keys.budgetOf": "budget {b}/mois", "keys.budgetOf": "budget {b}/mois",
"dash.kpi.orBalance": "Solde OpenRouter", "dash.kpi.orBalance": "Solde OpenRouter",
"keys.h.install": "Installer :", "keys.h.install": "Installer :",
"keys.snip.keyInputPh": "collez votre clé API",
"keys.snip.apply": "Insérer la clé",
}; };
+2
View File
@@ -636,4 +636,6 @@ export default {
"keys.budgetOf": "бюджет {b}/мес", "keys.budgetOf": "бюджет {b}/мес",
"dash.kpi.orBalance": "Баланс OpenRouter", "dash.kpi.orBalance": "Баланс OpenRouter",
"keys.h.install": "Установить:", "keys.h.install": "Установить:",
"keys.snip.keyInputPh": "вставьте ваш API-ключ",
"keys.snip.apply": "Вставить ключ",
}; };
+2
View File
@@ -644,4 +644,6 @@ export default {
"keys.budgetOf": "bütçe {b}/ay", "keys.budgetOf": "bütçe {b}/ay",
"dash.kpi.orBalance": "OpenRouter bakiyesi", "dash.kpi.orBalance": "OpenRouter bakiyesi",
"keys.h.install": "Kurulum:", "keys.h.install": "Kurulum:",
"keys.snip.keyInputPh": "API anahtarınızı yapıştırın",
"keys.snip.apply": "Anahtarı ekle",
}; };
+2
View File
@@ -644,4 +644,6 @@ export default {
"keys.budgetOf": "预算 {b}/月", "keys.budgetOf": "预算 {b}/月",
"dash.kpi.orBalance": "OpenRouter 余额", "dash.kpi.orBalance": "OpenRouter 余额",
"keys.h.install": "安装:", "keys.h.install": "安装:",
"keys.snip.keyInputPh": "粘贴你的 API 密钥",
"keys.snip.apply": "插入密钥",
}; };
+22 -8
View File
@@ -54,7 +54,7 @@ export async function renderKeys() {
function snippetsCard(openMode) { function snippetsCard(openMode) {
const origin = location.origin; const origin = location.origin;
const base = origin + "/v1"; const base = origin + "/v1";
const key = lastCreatedKey || (openMode ? "sk-anything" : "<" + t("keys.snip.keyPh") + ">"); let key = lastCreatedKey || (openMode ? "sk-anything" : "<" + t("keys.snip.keyPh") + ">");
// Every scripty snippet exists in two OS flavors: bash heredocs for // Every scripty snippet exists in two OS flavors: bash heredocs for
// Linux/macOS and PowerShell here-strings for Windows. SDK snippets // Linux/macOS and PowerShell here-strings for Windows. SDK snippets
// (python/js) and GUI instructions are OS-independent. // (python/js) and GUI instructions are OS-independent.
@@ -195,11 +195,15 @@ const resp = await client.chat.completions.create({
}); });
console.log(resp.choices[0].message.content);`, console.log(resp.choices[0].message.content);`,
}; };
const shSnips = buildSnips("sh"); let shSnips, psSnips, snips;
const psSnips = buildSnips("ps"); function rebuildSnips() {
// python/js/Cline одинаковы — дополняем ps-набор из sh shSnips = buildSnips("sh");
for (const k of Object.keys(shSnips)) if (!psSnips[k]) psSnips[k] = shSnips[k]; psSnips = buildSnips("ps");
let snips = os === "ps" ? psSnips : shSnips; // python/js/Cline одинаковы — дополняем ps-набор из sh
for (const k of Object.keys(shSnips)) if (!psSnips[k]) psSnips[k] = shSnips[k];
snips = os === "ps" ? psSnips : shSnips;
}
rebuildSnips();
let cur = "Claude Code"; let cur = "Claude Code";
const pre = h("pre", { class: "conv-log snippet" }, snips[cur]); const pre = h("pre", { class: "conv-log snippet" }, snips[cur]);
const seg = h( const seg = h(
@@ -289,12 +293,22 @@ console.log(resp.choices[0].message.content);`,
osSeg.querySelectorAll("button").forEach((b, i) => osSeg.querySelectorAll("button").forEach((b, i) =>
b.addEventListener("click", () => { b.addEventListener("click", () => {
os = i === 0 ? "sh" : "ps"; os = i === 0 ? "sh" : "ps";
snips = os === "ps" ? psSnips : shSnips; rebuildSnips();
osSeg.querySelectorAll("button").forEach((x, j) => x.classList.toggle("active", j === (os === "sh" ? 0 : 1))); osSeg.querySelectorAll("button").forEach((x, j) => x.classList.toggle("active", j === (os === "sh" ? 0 : 1)));
pre.textContent = snips[cur]; pre.textContent = snips[cur];
paintStatus(); paintStatus();
})); }));
// paste-your-key: substitutes the key into every snippet immediately
const keyIn = h("input", { class: "mono", placeholder: t("keys.snip.keyInputPh"), style: "max-width:230px; font-size:12px", value: lastCreatedKey || "" });
const applyKey = () => {
key = keyIn.value.trim() || (openMode ? "sk-anything" : "<" + t("keys.snip.keyPh") + ">");
rebuildSnips();
pre.textContent = snips[cur];
};
const applyBtn = h("button", { class: "btn ghost sm", onClick: applyKey }, "⤵ " + t("keys.snip.apply"));
keyIn.addEventListener("keydown", (e) => { if (e.key === "Enter") applyKey(); });
const copy = h("button", { class: "btn ghost sm" }, "⧉ " + t("common.copy")); const copy = h("button", { class: "btn ghost sm" }, "⧉ " + t("common.copy"));
copy.addEventListener("click", async () => { copy.addEventListener("click", async () => {
await navigator.clipboard.writeText(pre.textContent); await navigator.clipboard.writeText(pre.textContent);
@@ -306,7 +320,7 @@ console.log(resp.choices[0].message.content);`,
h("div", { class: "card-title" }, "🔌 " + t("keys.snip.title")), h("div", { class: "card-title" }, "🔌 " + t("keys.snip.title")),
h("div", { class: "muted small", style: "margin-bottom:8px" }, h("div", { class: "muted small", style: "margin-bottom:8px" },
lastCreatedKey ? t("keys.snip.withKey") : openMode ? t("keys.snip.openMode") : t("keys.snip.hint")), lastCreatedKey ? t("keys.snip.withKey") : openMode ? t("keys.snip.openMode") : t("keys.snip.hint")),
h("div", { class: "flex", style: "gap:8px; margin-bottom:8px; flex-wrap:wrap" }, seg, h("div", { class: "grow" }), osSeg, copy), h("div", { class: "flex", style: "gap:8px; margin-bottom:8px; flex-wrap:wrap" }, seg, h("div", { class: "grow" }), keyIn, applyBtn, osSeg, copy),
pre, pre,
statusHost, statusHost,
protoWarn, protoWarn,