Stage: CI fixes, pricing buttons, API key provisioning #90
+84
-16
@@ -494,12 +494,26 @@ fn account_css() -> String {
|
||||
.roadmap-items li { font-family: var(--body); font-size: .875rem; font-weight: 300; color: var(--t2); line-height: 1.6; padding-left: 1rem; position: relative; }
|
||||
.roadmap-items li::before { content: \"-\"; position: absolute; left: 0; color: var(--navy-65); }
|
||||
.signout-section { padding-top: 1rem; display: flex; justify-content: flex-end; }
|
||||
.api-key-list { display: flex; flex-direction: column; gap: 1rem; }
|
||||
.api-key-row { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
|
||||
.api-key-provider { display: flex; flex-direction: column; gap: .2rem; min-width: 120px; }
|
||||
.api-key-list { display: flex; flex-direction: column; gap: 1.5rem; }
|
||||
.api-key-entry { border-bottom: 1px solid var(--border); padding-bottom: 1.25rem; }
|
||||
.api-key-entry:last-child { border-bottom: none; padding-bottom: 0; }
|
||||
.api-key-header { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: .625rem; }
|
||||
.api-key-name { font-size: .875rem; font-weight: 500; color: var(--t1); }
|
||||
.api-key-masked { font-size: .75rem; font-family: monospace; color: var(--t3); }
|
||||
.api-key-actions { display: flex; gap: .5rem; }"
|
||||
.api-key-row { display: flex; align-items: center; gap: .75rem; }
|
||||
.api-key-actions { display: flex; gap: .5rem; flex-shrink: 0; }
|
||||
.api-key-help { margin-top: .625rem; }
|
||||
.api-key-help summary { font-size: .75rem; font-weight: 500; letter-spacing: .03em; color: var(--navy-65); cursor: pointer; list-style: none; padding: .25rem 0; user-select: none; }
|
||||
.api-key-help summary::-webkit-details-marker { display: none; }
|
||||
.api-key-help summary::before { content: \"\\25B8 \"; font-size: .6rem; }
|
||||
.api-key-help[open] summary::before { content: \"\\25BE \"; }
|
||||
.api-key-help-body { padding: .75rem 0 .125rem; }
|
||||
.api-key-help-body ol { padding-left: 1.25rem; display: flex; flex-direction: column; gap: .375rem; }
|
||||
.api-key-help-body li { font-size: .8125rem; font-weight: 300; color: var(--t2); line-height: 1.55; }
|
||||
.api-key-help-body a { color: var(--navy-65); text-decoration: underline; text-underline-offset: 2px; }
|
||||
.api-key-help-body a:hover { color: var(--navy); }
|
||||
.api-key-note { font-size: .75rem; font-weight: 300; color: var(--t3); margin-top: .625rem; line-height: 1.55; padding: .5rem .75rem; background: var(--bg2); border-left: 2px solid var(--navy-b); }
|
||||
.api-key-note a { color: var(--navy-65); text-decoration: underline; text-underline-offset: 2px; }"
|
||||
"<style>" + css + "</style>"
|
||||
}
|
||||
|
||||
@@ -810,30 +824,84 @@ fn account_devices_card() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
fn api_key_provider_row(provider_id: String, provider_name: String, placeholder: String) -> String {
|
||||
fn api_key_provider_row(provider_id: String, provider_name: String, placeholder: String, instructions: String) -> String {
|
||||
el_div(
|
||||
"class=\"api-key-row\"",
|
||||
"class=\"api-key-entry\"",
|
||||
el_div(
|
||||
"class=\"api-key-provider\"",
|
||||
"class=\"api-key-header\"",
|
||||
el_span("class=\"api-key-name\"", provider_name) +
|
||||
el_span("class=\"api-key-masked\" id=\"apikey-masked-" + provider_id + "\"", "Not configured")
|
||||
) +
|
||||
"<input type=\"password\" id=\"apikey-input-" + provider_id + "\" class=\"acct-input\" placeholder=\"" + placeholder + "\" autocomplete=\"off\" style=\"margin-bottom:0;flex:1\">" +
|
||||
el_div(
|
||||
"class=\"api-key-actions\"",
|
||||
el_button("type=\"button\" class=\"btn-primary\" style=\"padding:.5rem 1rem;font-size:.75rem\" onclick=\"saveApiKey('" + provider_id + "')\"", "Save") +
|
||||
el_button("type=\"button\" class=\"btn-ghost\" style=\"padding:.5rem 1rem;font-size:.75rem;display:none\" id=\"apikey-del-" + provider_id + "\" onclick=\"deleteApiKey('" + provider_id + "')\"", "Remove")
|
||||
)
|
||||
"class=\"api-key-row\"",
|
||||
"<input type=\"password\" id=\"apikey-input-" + provider_id + "\" class=\"acct-input\" placeholder=\"" + placeholder + "\" autocomplete=\"off\" style=\"margin-bottom:0;flex:1\">" +
|
||||
el_div(
|
||||
"class=\"api-key-actions\"",
|
||||
el_button("type=\"button\" class=\"btn-primary\" style=\"padding:.5rem 1rem;font-size:.75rem\" onclick=\"saveApiKey('" + provider_id + "')\"", "Save") +
|
||||
el_button("type=\"button\" class=\"btn-ghost\" style=\"padding:.5rem 1rem;font-size:.75rem;display:none\" id=\"apikey-del-" + provider_id + "\" onclick=\"deleteApiKey('" + provider_id + "')\"", "Remove")
|
||||
)
|
||||
) +
|
||||
instructions
|
||||
)
|
||||
}
|
||||
|
||||
fn account_api_keys_section() -> String {
|
||||
let openai_help: String =
|
||||
"<details class=\"api-key-help\">" +
|
||||
"<summary>How to get an OpenAI key</summary>" +
|
||||
"<div class=\"api-key-help-body\"><ol>" +
|
||||
"<li>Go to <a href=\"https://platform.openai.com/api-keys\" target=\"_blank\" rel=\"noopener\">platform.openai.com/api-keys ↗</a></li>" +
|
||||
"<li>Click <strong>Create new secret key</strong></li>" +
|
||||
"<li>Give it a name (e.g. “Neuron”), then click <strong>Create secret key</strong></li>" +
|
||||
"<li>Copy the key immediately — it is only shown once</li>" +
|
||||
"</ol>" +
|
||||
"<p class=\"api-key-note\">You need billing set up before making API calls. Add a payment method at <a href=\"https://platform.openai.com/settings/organization/billing\" target=\"_blank\" rel=\"noopener\">platform.openai.com ↗</a></p>" +
|
||||
"</div></details>"
|
||||
|
||||
let anthropic_help: String =
|
||||
"<details class=\"api-key-help\">" +
|
||||
"<summary>How to get an Anthropic key</summary>" +
|
||||
"<div class=\"api-key-help-body\"><ol>" +
|
||||
"<li>Go to <a href=\"https://console.anthropic.com/settings/keys\" target=\"_blank\" rel=\"noopener\">console.anthropic.com/settings/keys ↗</a></li>" +
|
||||
"<li>Click <strong>Create Key</strong></li>" +
|
||||
"<li>Give it a name (e.g. “Neuron”), then click <strong>Create Key</strong></li>" +
|
||||
"<li>Copy the key immediately — it is only shown once</li>" +
|
||||
"</ol>" +
|
||||
"<p class=\"api-key-note\">You need to add credits before making API calls. Go to <a href=\"https://console.anthropic.com/settings/plans\" target=\"_blank\" rel=\"noopener\">console.anthropic.com → Plans & Billing ↗</a> to add credits.</p>" +
|
||||
"</div></details>"
|
||||
|
||||
let gemini_help: String =
|
||||
"<details class=\"api-key-help\">" +
|
||||
"<summary>How to get a Gemini key</summary>" +
|
||||
"<div class=\"api-key-help-body\"><ol>" +
|
||||
"<li>Go to <a href=\"https://aistudio.google.com/apikey\" target=\"_blank\" rel=\"noopener\">aistudio.google.com/apikey ↗</a></li>" +
|
||||
"<li>Sign in with your Google account if prompted</li>" +
|
||||
"<li>Click <strong>Create API key</strong></li>" +
|
||||
"<li>Select an existing Google Cloud project or create a new one</li>" +
|
||||
"<li>Copy the key</li>" +
|
||||
"</ol>" +
|
||||
"<p class=\"api-key-note\">The free tier (Gemini 1.5 Flash) has generous rate limits. Paid usage is billed through your Google Cloud account.</p>" +
|
||||
"</div></details>"
|
||||
|
||||
let grok_help: String =
|
||||
"<details class=\"api-key-help\">" +
|
||||
"<summary>How to get a Grok key</summary>" +
|
||||
"<div class=\"api-key-help-body\"><ol>" +
|
||||
"<li>Go to <a href=\"https://console.x.ai/\" target=\"_blank\" rel=\"noopener\">console.x.ai ↗</a></li>" +
|
||||
"<li>Sign in with your X (Twitter) account</li>" +
|
||||
"<li>In the left sidebar, click <strong>API Keys</strong></li>" +
|
||||
"<li>Click <strong>Create API Key</strong>, give it a name</li>" +
|
||||
"<li>Copy the key immediately — it is only shown once</li>" +
|
||||
"</ol>" +
|
||||
"<p class=\"api-key-note\">xAI offers free monthly credits to new accounts. Usage beyond the free tier is billed per token.</p>" +
|
||||
"</div></details>"
|
||||
|
||||
let providers: String = el_div(
|
||||
"class=\"api-key-list\"",
|
||||
api_key_provider_row("openai", "OpenAI", "sk-...") +
|
||||
api_key_provider_row("anthropic", "Anthropic", "sk-ant-...") +
|
||||
api_key_provider_row("gemini", "Gemini", "AIza...") +
|
||||
api_key_provider_row("grok", "Grok", "xai-...")
|
||||
api_key_provider_row("openai", "OpenAI", "sk-...", openai_help) +
|
||||
api_key_provider_row("anthropic", "Anthropic", "sk-ant-...", anthropic_help) +
|
||||
api_key_provider_row("gemini", "Gemini", "AIza...", gemini_help) +
|
||||
api_key_provider_row("grok", "Grok", "xai-...", grok_help)
|
||||
)
|
||||
el_div(
|
||||
"id=\"api-keys-section\" style=\"display:none\"",
|
||||
|
||||
Reference in New Issue
Block a user