| 1 | import { parseResponse } from "hono/client"; |
| 2 | import { createResource, createSignal, For, Show } from "solid-js"; |
| 3 | import { api, reason } from "../api.ts"; |
| 4 | import { Copy } from "../components/Copy.tsx"; |
| 5 | import { Loaded } from "../components/Loaded.tsx"; |
| 6 | import { toast } from "../components/Toast.tsx"; |
| 7 | |
| 8 | export 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 | } |