ignore: zen

This commit is contained in:
Jay V
2025-09-10 17:59:03 -10:00
parent 30aae66320
commit c2fa28c1be
6 changed files with 327 additions and 33 deletions

View File

@@ -15,7 +15,7 @@
cursor: pointer; cursor: pointer;
transition: all 0.15s ease; transition: all 0.15s ease;
&:hover { &:hover:not(:disabled) {
background-color: var(--color-surface-hover); background-color: var(--color-surface-hover);
border-color: var(--color-accent); border-color: var(--color-accent);
} }
@@ -26,21 +26,15 @@
&:disabled { &:disabled {
opacity: 0.5; opacity: 0.5;
cursor: not-allowed;
&:hover {
background-color: var(--color-bg);
border-color: var(--color-border);
transform: none; transform: none;
} }
}
&[data-color="primary"] { &[data-color="primary"] {
background-color: var(--color-primary); background-color: var(--color-primary);
border-color: var(--color-primary); border-color: var(--color-primary);
color: var(--color-primary-text); color: var(--color-primary-text);
&:hover { &:hover:not(:disabled) {
background-color: var(--color-primary-hover); background-color: var(--color-primary-hover);
border-color: var(--color-primary-hover); border-color: var(--color-primary-hover);
} }
@@ -51,7 +45,7 @@
border-color: transparent; border-color: transparent;
color: var(--color-text-muted); color: var(--color-text-muted);
&:hover { &:hover:not(:disabled) {
background-color: var(--color-surface-hover); background-color: var(--color-surface-hover);
border-color: var(--color-border); border-color: var(--color-border);
color: var(--color-text); color: var(--color-text);

View File

@@ -47,12 +47,6 @@
font-size: var(--font-size-md); font-size: var(--font-size-md);
} }
} }
p {
line-height: 1.4;
font-size: var(--font-size-sm);
color: var(--color-text-muted);
}
} }
} }
section:not(:last-child) { section:not(:last-child) {
@@ -192,11 +186,35 @@
&[data-slot="key-value"] { &[data-slot="key-value"] {
font-family: var(--font-mono); font-family: var(--font-mono);
div { button {
cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-2); gap: var(--space-2);
padding: var(--space-2) var(--space-3);
font-size: var(--font-size-sm);
font-weight: 400;
border: none;
background-color: transparent;
color: var(--color-text-muted);
font-family: var(--font-mono);
border-radius: var(--border-radius-sm);
cursor: pointer;
transition: all 0.15s ease;
text-transform: none;
&:hover:not(:disabled) {
background-color: var(--color-bg-surface);
color: var(--color-text);
}
&:disabled {
cursor: default;
color: var(--color-text);
}
span {
font-family: inherit;
}
} }
} }
@@ -262,6 +280,9 @@
[data-slot="value"] { [data-slot="value"] {
color: var(--color-danger); color: var(--color-danger);
} }
[data-slot="currency"] {
color: var(--color-danger);
}
} }
[data-slot="currency"] { [data-slot="currency"] {
@@ -428,4 +449,186 @@
} }
} }
} }
[data-slot="new-user-sections"] {
display: flex;
flex-direction: column;
gap: var(--space-16);
@media (max-width: 30rem) {
gap: var(--space-8);
}
[data-component="feature-grid"] {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--space-6);
@media (max-width: 30rem) {
grid-template-columns: 1fr;
gap: var(--space-4);
}
[data-slot="feature"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg-surface);
h3 {
font-size: var(--font-size-sm);
font-weight: 600;
margin: 0;
color: var(--color-text);
text-transform: uppercase;
letter-spacing: -0.025rem;
}
p {
font-size: var(--font-size-sm);
line-height: 1.5;
margin: 0;
color: var(--color-text-muted);
}
}
}
[data-component="api-key-highlight"] {
display: flex;
flex-direction: column;
gap: var(--space-6);
[data-slot="section-title"] {
display: flex;
flex-direction: column;
gap: var(--space-1);
h2 {
font-size: var(--font-size-md);
font-weight: 600;
line-height: 1.2;
letter-spacing: -0.03125rem;
margin: 0;
color: var(--color-text-secondary);
text-transform: uppercase;
@media (max-width: 30rem) {
font-size: var(--font-size-md);
}
}
}
[data-slot="key-display"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
[data-slot="key-container"] {
display: flex;
gap: var(--space-3);
padding: var(--space-4);
border: 2px solid var(--color-accent);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg-surface);
align-items: center;
@media (max-width: 40rem) {
flex-direction: column;
gap: var(--space-3);
align-items: stretch;
}
[data-slot="key-value"] {
flex: 1;
font-family: var(--font-mono);
font-size: var(--font-size-sm);
color: var(--color-text);
background-color: var(--color-bg);
padding: var(--space-3);
border-radius: var(--border-radius-sm);
border: 1px solid var(--color-border);
word-break: break-all;
line-height: 1.4;
@media (max-width: 40rem) {
font-size: var(--font-size-xs);
padding: var(--space-2-5);
}
}
button {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-4);
font-size: var(--font-size-sm);
font-weight: 500;
white-space: nowrap;
min-width: 130px;
@media (max-width: 40rem) {
justify-content: center;
padding: var(--space-2-5) var(--space-3);
font-size: var(--font-size-xs);
min-width: 96px;
}
}
}
}
}
[data-component="next-steps"] {
display: flex;
flex-direction: column;
gap: var(--space-6);
[data-slot="section-title"] {
display: flex;
flex-direction: column;
gap: var(--space-1);
h2 {
font-size: var(--font-size-md);
font-weight: 600;
line-height: 1.2;
letter-spacing: -0.03125rem;
margin: 0;
color: var(--color-text-secondary);
text-transform: uppercase;
@media (max-width: 30rem) {
font-size: var(--font-size-md);
}
}
}
ol {
margin: 0;
padding-left: 0;
display: flex;
flex-direction: column;
gap: var(--space-2);
list-style-position: inside;
li {
font-size: var(--font-size-sm);
line-height: 1.5;
color: var(--color-text-muted);
code {
font-family: var(--font-mono);
font-size: var(--font-size-xs);
padding: var(--space-1) var(--space-2);
background-color: var(--color-bg-surface);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
color: var(--color-text);
}
}
}
}
}
} }

View File

@@ -237,7 +237,12 @@ function KeysSection() {
<tr> <tr>
<td data-slot="key-name">{key.name}</td> <td data-slot="key-name">{key.name}</td>
<td data-slot="key-value"> <td data-slot="key-value">
<div onClick={() => copyKeyToClipboard(key.key, key.id)} title="Click to copy API key"> <button
data-color="ghost"
disabled={copiedId() === key.id}
onClick={() => copyKeyToClipboard(key.key, key.id)}
title="Copy API key"
>
<span>{formatKey(key.key)}</span> <span>{formatKey(key.key)}</span>
<Show <Show
when={copiedId() === key.id} when={copiedId() === key.id}
@@ -245,7 +250,7 @@ function KeysSection() {
> >
<IconCheck style={{ width: "14px", height: "14px" }} /> <IconCheck style={{ width: "14px", height: "14px" }} />
</Show> </Show>
</div> </button>
</td> </td>
<td data-slot="key-date" title={formatDateUTC(key.timeCreated)}> <td data-slot="key-date" title={formatDateUTC(key.timeCreated)}>
{formatDateForTable(key.timeCreated)} {formatDateForTable(key.timeCreated)}
@@ -464,7 +469,99 @@ function PaymentsSection() {
) )
} }
function NewUserSection() {
const params = useParams()
const keys = createAsync(() => listKeys(params.id))
const [copiedKey, setCopiedKey] = createSignal(false)
async function copyKeyToClipboard(text: string) {
try {
await navigator.clipboard.writeText(text)
setCopiedKey(true)
setTimeout(() => setCopiedKey(false), 2000)
} catch (error) {
console.error("Failed to copy to clipboard:", error)
}
}
return (
<div data-slot="new-user-sections">
<div data-component="feature-grid">
<div data-slot="feature">
<h3>Tested & Verified Models</h3>
<p>We've benchmarked and tested models specifically for coding agents to ensure the best performance.</p>
</div>
<div data-slot="feature">
<h3>Highest Quality</h3>
<p>Access models configured for optimal performance - no downgrades or routing to cheaper providers.</p>
</div>
<div data-slot="feature">
<h3>No Lock-in</h3>
<p>Use Zen with any coding agent, and continue using other providers with opencode whenever you want.</p>
</div>
</div>
<div data-component="api-key-highlight">
<div data-slot="section-title">
<h2>Your API Key</h2>
</div>
<Show when={keys()?.length}>
<div data-slot="key-display">
<div data-slot="key-container">
<code data-slot="key-value">{keys()![0].key}</code>
<button
data-color="primary"
disabled={copiedKey()}
onClick={() => copyKeyToClipboard(keys()![0].key)}
title="Copy API key"
>
<Show
when={copiedKey()}
fallback={
<>
<IconCopy style={{ width: "16px", height: "16px" }} /> Copy Key
</>
}
>
<IconCheck style={{ width: "16px", height: "16px" }} /> Copied!
</Show>
</button>
</div>
</div>
</Show>
</div>
<div data-component="next-steps">
<div data-slot="section-title">
<h2>Next Steps</h2>
</div>
<ol>
<li>Copy your API key above</li>
<li>
Run <code>opencode auth login</code> and select opencode
</li>
<li>Paste your API key when prompted</li>
<li>
Run <code>/models</code> to see available models
</li>
</ol>
</div>
</div>
)
}
export default function() { export default function() {
const params = useParams()
const keys = createAsync(() => listKeys(params.id))
const usage = createAsync(() => getUsageInfo(params.id))
const isNewUser = createMemo(() => {
const keysList = keys()
const usageList = usage()
return keysList?.length === 1 && (!usageList || usageList.length === 0)
})
return ( return (
<div data-page="workspace-[id]"> <div data-page="workspace-[id]">
<section data-component="title-section"> <section data-component="title-section">
@@ -478,12 +575,14 @@ export default function () {
</p> </p>
</section> </section>
<Show when={!isNewUser()} fallback={<NewUserSection />}>
<div data-slot="sections"> <div data-slot="sections">
<KeysSection /> <KeysSection />
<BalanceSection /> <BalanceSection />
<UsageSection /> <UsageSection />
<PaymentsSection /> <PaymentsSection />
</div> </div>
</Show>
</div> </div>
) )
} }

View File

@@ -79,7 +79,7 @@ $ opencode auth login
┌ Add credential ┌ Add credential
◆ Select provider ◆ Select provider
│ ● Anthropic (recommended) │ ● Anthropic
│ ○ OpenAI │ ○ OpenAI
│ ○ Google │ ○ Google
│ ○ Amazon Bedrock │ ○ Amazon Bedrock

View File

@@ -58,7 +58,7 @@ If you are new, we recommend starting with opencode zen.
::: :::
1. You sign in to **<a href={console}>opencode zen</a>** and get your API key. 1. You sign in to **<a href={console}>opencode zen</a>** and get your API key.
2. You run `opencode auth login` and select opencode zen and add your API key. 2. You run `opencode auth login` and select opencode and add your API key.
3. Run `/models` in the TUI to see the list of models we recommend. 3. Run `/models` in the TUI to see the list of models we recommend.
It works like any other provider in opencode. And is completely optional to use It works like any other provider in opencode. And is completely optional to use
@@ -131,9 +131,7 @@ $ opencode auth login
┌ Add credential ┌ Add credential
◆ Select provider ◆ Select provider
│ ● Anthropic (recommended) │ ● Anthropic
│ ○ OpenAI
│ ○ Google
│ ... │ ...
``` ```

View File

@@ -50,7 +50,7 @@ opencode zen is an AI gateway that gives you access to these models.
opencode zen works like any other provider in opencode. opencode zen works like any other provider in opencode.
1. You sign in to **<a href={console}>opencode zen</a>** and get your API key. 1. You sign in to **<a href={console}>opencode zen</a>** and get your API key.
2. You run `opencode auth login` and select opencode zen and add your API key. 2. You run `opencode auth login` and select opencode and add your API key.
3. Run `/models` in the TUI to see the list of models we recommend. 3. Run `/models` in the TUI to see the list of models we recommend.
You are charged per request and you can add credits to your account. You are charged per request and you can add credits to your account.