522 lines · 18.4 KB · TypeScript
RawDownload
// Compute — where runs execute: BYOK GPU providers (Modal, TensorPool, Lambda// Labs, Prime Intellect, Vast.ai, RunPod), your own SSH hosts, and local/remote// model endpoints.//// Backend: routes/settings/compute.ts. Provider keys are encrypted at rest// under ~/.openscience/ (AES-256-GCM, machine-local key) and never returned to the// browser — the panel only ever sees connection state + metadata.import { Component, For, Show, createResource, createSignal, type JSX } from "solid-js"import { Button } from "@synsci/ui/button"import { Select } from "@synsci/ui/select"import { Icon } from "@synsci/ui/icon"import { showToast } from "@synsci/ui/toast"import { useGlobalSDK } from "@/context/global-sdk"import { usePlatform } from "@/context/platform"import { StatusDot } from "@/thesis/shared/StatusDot"import { settingsApi } from "./api" interface SshHost {  id: string  label: string  host: string  user?: string  port?: number}interface Endpoint {  id: string  label: string  url: string  kind: "local" | "remote"}interface Provider {  id: string  name: string  verified: boolean  placeholder: string  hint: string  connected: boolean  connected_at: string | null  last_used: string | null}interface ComputeInfo {  providers: Provider[]  ssh_hosts: SshHost[]  endpoints: Endpoint[]} const fmtDate = (iso: string | null) => {  if (!iso) return null  const d = new Date(iso)  if (Number.isNaN(d.getTime())) return null  return d.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" })} const Compute: Component = () => {  const sdk = useGlobalSDK()  const platform = usePlatform()  const fetchFn = platform.fetch ?? fetch  const call = <T,>(path: string, init?: RequestInit) =>    settingsApi<T>(sdk.url, fetchFn, `/settings/compute${path}`, init)   const [info, { mutate, refetch }] = createResource(() => call<ComputeInfo>(""))   const [busy, setBusy] = createSignal(false)  const run = async (fn: () => Promise<ComputeInfo>, failure: string) => {    setBusy(true)    try {      mutate(await fn())    } catch (err) {      showToast({ title: failure, description: err instanceof Error ? err.message : String(err) })      refetch()    }    setBusy(false)  }   // ── Provider connect ──  const [connecting, setConnecting] = createSignal<string>()  const [keyValue, setKeyValue] = createSignal("")  const openConnect = (id: string) => {    setKeyValue("")    setConnecting((v) => (v === id ? undefined : id))  }  const connectProvider = async (id: string) => {    if (!keyValue().trim()) return    await run(      () => call<ComputeInfo>(`/provider/${id}`, { method: "POST", body: JSON.stringify({ key: keyValue().trim() }) }),      "Failed to connect provider",    )    setKeyValue("")    setConnecting(undefined)  }  const removeProvider = (id: string) =>    run(() => call<ComputeInfo>(`/provider/${id}`, { method: "DELETE" }), "Failed to remove provider")   // ── SSH host add form ──  const [addingHost, setAddingHost] = createSignal(false)  const [hLabel, setHLabel] = createSignal("")  const [hHost, setHHost] = createSignal("")  const [hUser, setHUser] = createSignal("")  const [hPort, setHPort] = createSignal("")  const resetHost = () => {    setHLabel("")    setHHost("")    setHUser("")    setHPort("")    setAddingHost(false)  }  const saveHost = async () => {    if (!hLabel().trim() || !hHost().trim()) return    await run(      () =>        call<ComputeInfo>("/ssh", {          method: "POST",          body: JSON.stringify({            label: hLabel().trim(),            host: hHost().trim(),            user: hUser().trim() || undefined,            port: hPort().trim() ? Number(hPort().trim()) : undefined,          }),        }),      "Failed to add SSH host",    )    resetHost()  }   // ── Endpoint add form ──  const [addingEp, setAddingEp] = createSignal(false)  const [epLabel, setEpLabel] = createSignal("")  const [epUrl, setEpUrl] = createSignal("")  const [epKind, setEpKind] = createSignal<"local" | "remote">("remote")  const resetEp = () => {    setEpLabel("")    setEpUrl("")    setEpKind("remote")    setAddingEp(false)  }  const saveEp = async () => {    if (!epLabel().trim() || !epUrl().trim()) return    await run(      () =>        call<ComputeInfo>("/endpoint", {          method: "POST",          body: JSON.stringify({ label: epLabel().trim(), url: epUrl().trim(), kind: epKind() }),        }),      "Failed to add model endpoint",    )    resetEp()  }   const kindOptions: { value: "local" | "remote"; label: string }[] = [    { value: "remote", label: "Remote" },    { value: "local", label: "Local" },  ]   return (    <div class="flex flex-col h-full overflow-y-auto no-scrollbar">      <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">        <div class="flex flex-col gap-1 px-4 py-8 sm:p-8 max-w-[820px]">          <h2 class="text-16-medium text-text-strong">Compute</h2>          <p class="text-13-regular text-text-weak">            GPU providers for sandboxes and training. Add your own key to run on your account for free — or skip the key            and provision from the Compute tab, funded by your CLI wallet.          </p>        </div>      </div>       <div class="flex flex-col gap-8 px-4 pb-12 sm:px-8 max-w-[820px]">        {/* ── GPU providers (BYOK) ── */}        <Section title="GPU providers" subtitle="Bring your own key — connect once and work runs under your account.">          <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">            <For each={info()?.providers}>              {(p) => (                <ProviderCard                  provider={p}                  busy={busy()}                  connecting={connecting() === p.id}                  keyValue={keyValue()}                  onKeyInput={setKeyValue}                  onOpen={() => openConnect(p.id)}                  onConnect={() => connectProvider(p.id)}                  onRemove={() => removeProvider(p.id)}                />              )}            </For>          </div>          <p class="text-11-regular text-text-weak/70 leading-relaxed">            Everything is BYOK — connect a provider once and work runs under your account. Your provider bills you            directly — we never store a key in plaintext.            <span class="text-text-weak"> encrypted at rest · your provider, your bill · revoke anytime.</span>          </p>        </Section>         {/* ── SSH hosts ── */}        <Section          title="SSH hosts"          subtitle="Machines the agent can dispatch runs to over SSH."          action={            <Button              size="small"              variant="secondary"              icon="plus-small"              disabled={busy()}              onClick={() => setAddingHost((v) => !v)}            >              add SSH host            </Button>          }        >          <div class="border border-border-weak-base rounded-[4px] overflow-hidden bg-surface-base/40">            <Show              when={(info()?.ssh_hosts.length ?? 0) > 0}              fallback={<Empty when={!addingHost()} text="No SSH hosts yet." />}            >              <For each={info()?.ssh_hosts}>                {(h) => (                  <Row title={h.label} subtitle={`${h.user ? `${h.user}@` : ""}${h.host}${h.port ? `:${h.port}` : ""}`}>                    <RemoveButton                      disabled={busy()}                      onClick={() =>                        run(() => call<ComputeInfo>(`/ssh/${h.id}`, { method: "DELETE" }), "Failed to remove host")                      }                    />                  </Row>                )}              </For>            </Show>            <Show when={addingHost()}>              <div class="flex flex-col gap-3 p-4 border-t border-border-weak-base">                <div class="grid grid-cols-2 gap-3">                  <TextField label="Label" value={hLabel()} onInput={setHLabel} placeholder="lab-gpu-01" />                  <TextField                    label="Host"                    value={hHost()}                    onInput={setHHost}                    placeholder="10.0.0.4 or gpu.lab.internal"                  />                  <TextField label="User (optional)" value={hUser()} onInput={setHUser} placeholder="ubuntu" />                  <TextField label="Port (optional)" value={hPort()} onInput={setHPort} placeholder="22" />                </div>                <FormActions                  onCancel={resetHost}                  onSave={saveHost}                  saveDisabled={busy() || !hLabel().trim() || !hHost().trim()}                />              </div>            </Show>          </div>        </Section>         {/* ── Model endpoints ── */}        <Section          title="Model endpoints"          subtitle="Local or remote inference URLs the agent can route requests to."          action={            <Button              size="small"              variant="secondary"              icon="plus-small"              disabled={busy()}              onClick={() => setAddingEp((v) => !v)}            >              add model endpoint            </Button>          }        >          <div class="border border-border-weak-base rounded-[4px] overflow-hidden bg-surface-base/40">            <Show              when={(info()?.endpoints.length ?? 0) > 0}              fallback={<Empty when={!addingEp()} text="No model endpoints yet." />}            >              <For each={info()?.endpoints}>                {(e) => (                  <Row title={e.label} subtitle={`${e.kind} · ${e.url}`}>                    <RemoveButton                      disabled={busy()}                      onClick={() =>                        run(                          () => call<ComputeInfo>(`/endpoint/${e.id}`, { method: "DELETE" }),                          "Failed to remove endpoint",                        )                      }                    />                  </Row>                )}              </For>            </Show>            <Show when={addingEp()}>              <div class="flex flex-col gap-3 p-4 border-t border-border-weak-base">                <div class="grid grid-cols-2 gap-3">                  <TextField label="Label" value={epLabel()} onInput={setEpLabel} placeholder="local-vllm" />                  <div class="flex flex-col gap-1.5">                    <span class="text-12-medium text-text-weak">Kind</span>                    <Select                      options={kindOptions}                      current={kindOptions.find((o) => o.value === epKind())}                      value={(o) => o.value}                      label={(o) => o.label}                      onSelect={(o) => o && setEpKind(o.value)}                      variant="secondary"                      size="small"                      triggerVariant="settings"                    />                  </div>                </div>                <TextField label="URL" value={epUrl()} onInput={setEpUrl} placeholder="http://localhost:8000/v1" />                <FormActions                  onCancel={resetEp}                  onSave={saveEp}                  saveDisabled={busy() || !epLabel().trim() || !epUrl().trim()}                />              </div>            </Show>          </div>        </Section>      </div>    </div>  )} export default Compute // ── Provider card ─────────────────────────────────────────────────────────── const ProviderCard: Component<{  provider: Provider  busy: boolean  connecting: boolean  keyValue: string  onKeyInput: (v: string) => void  onOpen: () => void  onConnect: () => void  onRemove: () => void}> = (props) => {  const p = () => props.provider  return (    <div class="flex flex-col gap-3 p-4 rounded-[4px] border border-border-weak-base bg-surface-base/40">      <div class="flex items-start justify-between gap-3">        <div class="flex items-center gap-2.5 min-w-0">          <StatusDot status={p().connected ? "active" : "muted"} />          <span class="text-14-medium text-text-strong truncate">{p().name}</span>        </div>        <Badge connected={p().connected} verified={p().verified} />      </div>       <p class="text-12-regular text-text-weak leading-relaxed min-h-[2.4em]">{p().hint}</p>       <Show when={p().connected}>        <div class="flex flex-col gap-0.5">          <Show when={fmtDate(p().connected_at)}>            {(d) => <span class="text-11-regular text-text-weak/70">Connected {d()}</span>}          </Show>          <span class="text-11-regular text-text-weak/70">Last used {fmtDate(p().last_used) ?? "never"}</span>        </div>      </Show>       <Show when={props.connecting}>        <div class="flex flex-col gap-2">          <TextField            label="API key"            value={props.keyValue}            onInput={props.onKeyInput}            placeholder={p().placeholder}            secret          />          <p class="text-11-regular text-text-weak/70">            Encrypted at rest under ~/.openscience/ · never returned to the browser.          </p>        </div>      </Show>       <div class="flex items-center justify-end gap-2 mt-auto pt-1">        <Show          when={p().connected}          fallback={            <Show              when={props.connecting}              fallback={                <Button size="small" variant="primary" disabled={props.busy} onClick={props.onOpen}>                  connect                </Button>              }            >              <Button size="small" variant="ghost" disabled={props.busy} onClick={props.onOpen}>                cancel              </Button>              <Button                size="small"                variant="primary"                disabled={props.busy || !props.keyValue.trim()}                onClick={props.onConnect}              >                connect              </Button>            </Show>          }        >          <Show            when={props.connecting}            fallback={              <>                <Button size="small" variant="ghost" disabled={props.busy} onClick={props.onRemove}>                  remove                </Button>                <Button size="small" variant="secondary" disabled={props.busy} onClick={props.onOpen}>                  re-add key                </Button>              </>            }          >            <Button size="small" variant="ghost" disabled={props.busy} onClick={props.onOpen}>              cancel            </Button>            <Button              size="small"              variant="primary"              disabled={props.busy || !props.keyValue.trim()}              onClick={props.onConnect}            >              save key            </Button>          </Show>        </Show>      </div>    </div>  )} const Badge: Component<{ connected: boolean; verified: boolean }> = (props) => {  const label = () => (props.connected ? (props.verified ? "verified" : "connected") : "not connected")  return (    <span      class="flex-shrink-0 px-2 py-0.5 rounded-full text-11-regular border"      style={{        color: props.connected ? "var(--color-success)" : "var(--color-text-faint)",        "border-color": props.connected          ? "color-mix(in srgb, var(--color-success) 40%, transparent)"          : "var(--color-border)",        background: props.connected ? "color-mix(in srgb, var(--color-success) 12%, transparent)" : "transparent",      }}    >      {label()}    </span>  )} // ── Shared building blocks ────────────────────────────────────────────────── const Section: Component<{ title: string; subtitle: string; action?: JSX.Element; children: JSX.Element }> = (  props,) => (  <div class="flex flex-col gap-3">    <div class="flex items-end justify-between gap-4">      <div class="flex flex-col gap-0.5">        <h3 class="text-13-medium text-text-weak tracking-wide">{props.title}</h3>        <p class="text-12-regular text-text-weak">{props.subtitle}</p>      </div>      <Show when={props.action}>{props.action}</Show>    </div>    {props.children}  </div>) const Row: Component<{ title: string; subtitle: string; children: JSX.Element }> = (props) => (  <div class="flex flex-wrap items-center justify-between gap-4 px-4 py-3.5 border-b border-border-weak-base last:border-none">    <div class="flex flex-col gap-0.5 min-w-0">      <span class="text-14-medium text-text-strong truncate">{props.title}</span>      <span class="text-12-regular text-text-weak truncate">{props.subtitle}</span>    </div>    <div class="flex-shrink-0">{props.children}</div>  </div>) const Empty: Component<{ when: boolean; text: string }> = (props) => (  <Show when={props.when}>    <div class="px-4 py-4 text-12-regular text-text-weak">{props.text}</div>  </Show>) const RemoveButton: Component<{ disabled?: boolean; onClick: () => void }> = (props) => (  <button    class="flex items-center justify-center size-7 rounded-xs text-icon-weak-base hover:text-text-strong hover:bg-surface-base disabled:opacity-40"    disabled={props.disabled}    onClick={props.onClick}    aria-label="Remove"  >    <Icon name="close-small" size="small" />  </button>) const FormActions: Component<{  onCancel: () => void  onSave: () => void  saveLabel?: string  saveDisabled?: boolean}> = (props) => (  <div class="flex items-center justify-end gap-2">    <Button size="small" variant="ghost" onClick={props.onCancel}>      cancel    </Button>    <Button size="small" variant="primary" disabled={props.saveDisabled} onClick={props.onSave}>      {props.saveLabel ?? "add"}

Lines 1–500 of 522.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t