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

View File

@@ -47,12 +47,6 @@
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) {
@@ -192,11 +186,35 @@
&[data-slot="key-value"] {
font-family: var(--font-mono);
div {
cursor: pointer;
button {
display: flex;
align-items: center;
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"] {
color: var(--color-danger);
}
[data-slot="currency"] {
color: var(--color-danger);
}
}
[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>
<td data-slot="key-name">{key.name}</td>
<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>
<Show
when={copiedId() === key.id}
@@ -245,7 +250,7 @@ function KeysSection() {
>
<IconCheck style={{ width: "14px", height: "14px" }} />
</Show>
</div>
</button>
</td>
<td data-slot="key-date" title={formatDateUTC(key.timeCreated)}>
{formatDateForTable(key.timeCreated)}
@@ -464,7 +469,99 @@ function PaymentsSection() {
)
}
export default function () {
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() {
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 (
<div data-page="workspace-[id]">
<section data-component="title-section">
@@ -478,12 +575,14 @@ export default function () {
</p>
</section>
<Show when={!isNewUser()} fallback={<NewUserSection />}>
<div data-slot="sections">
<KeysSection />
<BalanceSection />
<UsageSection />
<PaymentsSection />
</div>
</Show>
</div>
)
}

View File

@@ -79,7 +79,7 @@ $ opencode auth login
┌ Add credential
◆ Select provider
│ ● Anthropic (recommended)
│ ● Anthropic
│ ○ OpenAI
│ ○ Google
│ ○ 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.
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.
It works like any other provider in opencode. And is completely optional to use
@@ -131,9 +131,7 @@ $ opencode auth login
┌ Add credential
◆ Select provider
│ ● Anthropic (recommended)
│ ○ OpenAI
│ ○ Google
│ ● Anthropic
│ ...
```

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.
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.
You are charged per request and you can add credits to your account.