1import { parseResponse } from "hono/client";
2import { createResource, createSignal, For, Show } from "solid-js";
3import { api, reason } from "../api.ts";
4import { Copy } from "../components/Copy.tsx";
5import { Loaded } from "../components/Loaded.tsx";
6import { toast } from "../components/Toast.tsx";
7
8export function AI(props: { viewing: boolean }) {
9 const [info, { refetch }] = createResource(() => parseResponse(api.ai.$get()));
10 const [key, setKey] = createSignal("");
11 const [busy, setBusy] = createSignal(false);
12 return <Loaded data={info} what="Local AI" retry={refetch}>{(data) => <>
13 <section class="mcp-endpoint">
14 <p>{data().model} on Snow Globe · {Math.round(data().context / 1024)}K context</p>
15 <div class="mcp-section-heading"><h2>API endpoint</h2><Copy value={data().endpoint} label="API endpoint" /></div>
16 <p class="muted">OpenAI Responses and Chat Completions use /v1. Anthropic Messages uses this base URL.</p>
17 </section>
18 <section class="mcp-panel">
19 <h2>API key</h2>
20 <p>Use this key with the endpoint or enter it when a Snow Globe wrapper first starts.</p>
21 <div class="mcp-actions">
22 <Show when={key()} fallback={<button class="button" disabled={busy() || props.viewing}
23 title={props.viewing ? "Switch back to your account to access the key" : undefined} onClick={async () => {
24 setBusy(true);
25 try { setKey((await parseResponse(api.ai.key.$post())).key); }
26 catch (error) { toast(reason(error)); }
27 finally { setBusy(false); }
28 }}>Access API key</button>}>
29 <Copy value={key()} label="API key">Copy API key</Copy>
30 <button class="button" onClick={() => setKey("")}>Hide key</button>
31 </Show>
32 </div>
33 <Show when={key()}><details class="mcp-install"><summary>Show key value</summary><code class="ai-key">{key()}</code></details></Show>
34 </section>
35 <section class="mcp-panel">
36 <h2>Coding clients</h2>
37 <p>Shell wrappers for installed Codex and Claude Code. Both start with automatic approval and use the local model.</p>
38 <p>Download a wrapper, then run its install command. Codex or Claude Code must already be installed.</p>
39 <div class="ai-downloads"><For each={["codex", "claude"]}>{(client) => <div class="mcp-actions">
40 <a class="button" href={`/api/ai/snow-${client}`} download={`snow-${client}`}>Download snow-{client}</a>
41 <Copy value={`mkdir -p ~/.local/bin && install -m 755 ~/Downloads/snow-${client} ~/.local/bin/snow-${client}`} label={`install snow-${client}`} />
42 </div>}</For></div>
43 <p>Run snow-codex or snow-claude from a project. The first run asks for your API key.</p>
44 <p class="muted">API waits allow eight hours. Stock Codex approval reviews retain their 90-second deadline.</p>
45 </section>
46 </>}</Loaded>;
47}