@soba-so/react 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ConnectButton.d.ts +38 -0
- package/dist/ConnectCompute.d.ts +13 -7
- package/dist/ConnectModal.d.ts +23 -0
- package/dist/SobaProvider.d.ts +15 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +209 -6
- package/dist/styles.css +204 -1
- package/dist/types.d.ts +22 -0
- package/package.json +8 -9
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { Machine } from "./types.js";
|
|
3
|
+
export interface ConnectButtonProps {
|
|
4
|
+
/** Override the label. Defaults to "Connect compute", or to what they have. */
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* `outline` is the same control at lower volume, for a settings row that
|
|
8
|
+
* already has a primary action on it. The design shows both.
|
|
9
|
+
*/
|
|
10
|
+
variant?: "primary" | "outline";
|
|
11
|
+
className?: string;
|
|
12
|
+
/** From `?code=` on a `verification_uri_complete`. Implies `defaultOpen`. */
|
|
13
|
+
initialCode?: string;
|
|
14
|
+
onConnected?: (machines: Machine[]) => void;
|
|
15
|
+
/** Open on mount. */
|
|
16
|
+
defaultOpen?: boolean;
|
|
17
|
+
/** Controlled. Pass both or neither. */
|
|
18
|
+
open?: boolean;
|
|
19
|
+
onOpenChange?: (open: boolean) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the button reports what is connected instead of saying "Connect
|
|
22
|
+
* compute". Left true: a button that still asks after they have connected is
|
|
23
|
+
* the screen telling them it did not work.
|
|
24
|
+
*/
|
|
25
|
+
showConnected?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* ONE CONTROL: IT OPENS THE MODAL, THEN REPORTS WHAT IT OPENED.
|
|
29
|
+
*
|
|
30
|
+
* The whole connect flow behind a single button a host can place and style,
|
|
31
|
+
* which is the shape every payment and auth product converged on — and the
|
|
32
|
+
* reason is not fashion. The flow has seven steps and needs the screen; the
|
|
33
|
+
* settings page that hosts it has one line to spare.
|
|
34
|
+
*
|
|
35
|
+
* Nothing here fetches. The connected label is read from the machines the
|
|
36
|
+
* provider is already polling.
|
|
37
|
+
*/
|
|
38
|
+
export declare function ConnectButton({ children, variant, className, initialCode, onConnected, defaultOpen, open: controlledOpen, onOpenChange, showConnected, }: ConnectButtonProps): import("react").JSX.Element;
|
package/dist/ConnectCompute.d.ts
CHANGED
|
@@ -5,17 +5,23 @@ export interface ConnectComputeProps {
|
|
|
5
5
|
onConnected?: (machines: Machine[]) => void;
|
|
6
6
|
className?: string;
|
|
7
7
|
/** False when you place `<ComputeStatus />` yourself, so the machines are not
|
|
8
|
-
* told twice. Left true
|
|
8
|
+
* told twice. Left true this draws them above the button. */
|
|
9
9
|
showStatus?: boolean;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* THE COMPUTE SECTION: what this person has, and the way to add one.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* The flow itself moved into a modal, which is the change. It used to unfold in
|
|
15
|
+
* place — seven steps, a code to read, a machine to wait for — inside whatever
|
|
16
|
+
* page a customer had put it on. That made the page's height a function of how
|
|
17
|
+
* far through pairing somebody was, and it put the most demanding moment in the
|
|
18
|
+
* product in a column it had to share with everything else on a settings screen.
|
|
17
19
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
+
* What is left here is the part that belongs on a page: the machines, and one
|
|
21
|
+
* control. `<ConnectButton />` is that control alone, for an app that places
|
|
22
|
+
* the status itself.
|
|
23
|
+
*
|
|
24
|
+
* `showStatus` keeps the meaning it has always had, so every existing caller
|
|
25
|
+
* carries on working untouched.
|
|
20
26
|
*/
|
|
21
27
|
export declare function ConnectCompute({ initialCode, onConnected, className, showStatus, }: ConnectComputeProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Machine } from "./types.js";
|
|
2
|
+
export interface ConnectModalProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
/** From `?code=` on a `verification_uri_complete`, when they came from a
|
|
6
|
+
* terminal that already has one. */
|
|
7
|
+
initialCode?: string;
|
|
8
|
+
onConnected?: (machines: Machine[]) => void;
|
|
9
|
+
/** Whether the panel draws the machines they already have, or the app does. */
|
|
10
|
+
showStatus?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* THE CONNECT FLOW, IN A DIALOG.
|
|
14
|
+
*
|
|
15
|
+
* Exported for a host that wants its own trigger — a menu item, a row in a
|
|
16
|
+
* table, a prompt raised by a run that failed for want of a machine. Most apps
|
|
17
|
+
* want `<ConnectButton />`, which is this plus the control.
|
|
18
|
+
*
|
|
19
|
+
* Every failure renders INSIDE the sheet. They used to render where the panel
|
|
20
|
+
* was, which is now where the button is: an error that replaces the thing you
|
|
21
|
+
* were about to press is worse than one inside the thing you just opened.
|
|
22
|
+
*/
|
|
23
|
+
export declare function ConnectModal({ open, onOpenChange, initialCode, onConnected, showStatus, }: ConnectModalProps): import("react").JSX.Element;
|
package/dist/SobaProvider.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type { CurrentPlan, Plan } from "./types.js";
|
|
2
|
+
import type { ConnectContext, ConnectTheme, CurrentPlan, Machine, Plan } from "./types.js";
|
|
3
3
|
export interface SobaProviderProps {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
/**
|
|
@@ -41,9 +41,23 @@ export interface SobaProviderProps {
|
|
|
41
41
|
* replacing the fetch: the provider still refreshes in the background, so a
|
|
42
42
|
* price changed since the page was rendered still corrects itself.
|
|
43
43
|
*/
|
|
44
|
+
/**
|
|
45
|
+
* WHAT THIS USER HAS CONNECTED, and the app's connect context, fetched by the
|
|
46
|
+
* server. The compute half of `initialPlans`, and it matters more: a panel
|
|
47
|
+
* waits on BOTH before it draws, so it is three round trips from mount —
|
|
48
|
+
* mint a session, then read the two — and every one of them is a spinner.
|
|
49
|
+
*/
|
|
50
|
+
initialMachines?: Machine[];
|
|
51
|
+
initialConnect?: ConnectContext | null;
|
|
44
52
|
initialPlans?: Plan[];
|
|
45
53
|
/** What that user is on, if the server asked. Pairs with `initialPlans`. */
|
|
46
54
|
initialCurrentPlan?: CurrentPlan | null;
|
|
55
|
+
/**
|
|
56
|
+
* Overrides what the app's own settings say, for a page that knows better —
|
|
57
|
+
* a docs example rendering both, or an app whose theme is per-user rather
|
|
58
|
+
* than per-app. Most apps set this once in the dashboard instead.
|
|
59
|
+
*/
|
|
60
|
+
theme?: ConnectTheme;
|
|
47
61
|
pollMs?: number;
|
|
48
62
|
/** The stylesheet injects itself once. Turn it off to ship your own. */
|
|
49
63
|
injectStyles?: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { ComputeStatus, type ComputeStatusProps } from "./ComputeStatus.js";
|
|
2
|
+
export { ConnectButton, type ConnectButtonProps } from "./ConnectButton.js";
|
|
2
3
|
export { ConnectCompute, type ConnectComputeProps } from "./ConnectCompute.js";
|
|
4
|
+
export { ConnectModal, type ConnectModalProps } from "./ConnectModal.js";
|
|
3
5
|
export { type ComputeState, type ConnectState, type PlanState, type RunRequest, type SobaContextValue, useSoba, } from "./context.js";
|
|
4
6
|
export { collectText, parseEvent, readEvents } from "./events.js";
|
|
5
7
|
export { SobaCheckout, type SobaCheckoutProps } from "./SobaCheckout.js";
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
|
-
var te=class extends Error{status;constructor(t,n){super(t),this.name="ConnectError",this.status=n}},Q=class{session;baseUrl;doFetch;constructor(t){this.session=t.session,this.baseUrl=(t.baseUrl??"").replace(/\/+$/,""),this.doFetch=t.fetchImpl??fetch.bind(globalThis)}context(){return this.request("GET","/v1/connect")}async machines(){return(await this.request("GET","/v1/machines")).machines}lookup(t){return this.request("GET",`/v1/device/lookup?code=${encodeURIComponent(t)}`)}approve(t){return this.request("POST","/v1/device/approve",{user_code:t})}deny(t){return this.request("POST","/v1/device/deny",{user_code:t})}plans(){return this.request("GET","/v1/plans")}selectPlan(t){return this.request("POST","/v1/plans/select",{plan:t})}checkoutPlan(t,n){return this.request("POST","/v1/plans/checkout",{plan:t,...n?.successUrl?{success_url:n.successUrl}:{},...n?.cancelUrl?{cancel_url:n.cancelUrl}:{}})}leavePlan(){return this.request("DELETE","/v1/plans/select")}approveRuntime(t,n){return this.request("POST","/v1/runtimes/approve",{machine_id:t,runtime_id:n})}async request(t,n,c){let s=await this.doFetch(`${this.baseUrl}${n}`,{method:t,headers:{authorization:`Bearer ${this.session}`,...c?{"content-type":"application/json"}:{}},...c?{body:JSON.stringify(c)}:{}}),r={};try{r=await s.json()}catch{}if(!s.ok){let u=typeof r.error=="string"?r.error:`Request failed (${s.status})`;throw new te(u,s.status)}return r}};function ce(e){return e.authenticated===!0?{tone:"ok",text:"signed in"}:e.authenticated===!1?{tone:"warn",text:e.auth_hint??"signed out on that machine"}:{tone:"unknown",text:"could not tell"}}function le(e){return e.state==="busy"?{tone:"busy",text:`serving ${e.active} of ${e.capacity}`}:e.state==="online"?{tone:"ok",text:"connected"}:{tone:"off",text:"asleep or offline"}}import{jsx as E,jsxs as V}from"react/jsx-runtime";function W({machines:e,onApproveRuntime:t}){return e.length?E("div",{children:e.map(n=>{let c=le(n);return V("div",{className:"sc-machine",children:[V("div",{className:"sc-row-between",children:[V("div",{className:"sc-row",children:[E("span",{className:`sc-dot sc-dot-${c.tone}`,"aria-hidden":"true"}),E("strong",{children:n.label??"This machine"})]}),E("span",{className:"sc-note",children:c.text})]}),E("p",{className:"sc-note",children:[n.platform,n.arch,n.version&&`worker ${n.version}`,n.always_on&&"always on"].filter(Boolean).join(" \xB7 ")}),V("ul",{className:"sc-runtimes",children:[n.runtimes.map(s=>{let r=ce(s);return s.quarantined?V("li",{className:"sc-row-between",children:[E("span",{children:s.display_name}),t?E("button",{type:"button",className:"sc-btn",onClick:()=>t(n.id,s.id),children:"New here. Let it serve"}):E("span",{className:"sc-tone-warn",children:"new, not serving yet"})]},s.id):V("li",{className:"sc-row-between",children:[E("span",{children:s.display_name}),E("span",{className:`sc-tone-${r.tone}`,children:r.text})]},s.id)}),!n.runtimes.length&&E("li",{className:"sc-note",children:"No runtime advertised. Install Claude Code, Codex or Ollama on that machine."})]})]},n.id)})}):E("p",{className:"sc-note",children:"Nothing connected yet."})}import{useCallback as ne,useEffect as pe,useMemo as De,useRef as Be,useState as U}from"react";var ue=[{id:"claude",title:"The Claude plan you already pay for",cost:"Nothing beyond the plan",prerequisite:"Claude Code installed, and signed in",hint:"Install it from claude.ai/code, then run `claude` once and sign in.",expects:["claude-code"]},{id:"chatgpt",title:"The ChatGPT plan you already pay for",cost:"Nothing beyond the plan",prerequisite:"Codex installed, and signed in",hint:"Install Codex, then run `codex` once and sign in.",expects:["codex"]},{id:"local",title:"A model on your own machine",cost:"Free. You bought the hardware",prerequisite:"Ollama running, with a model pulled",hint:"Install Ollama, then `ollama pull` a model before pairing.",expects:["ollama","openai-api"]}];import{Fragment as ke,jsx as a,jsxs as h}from"react/jsx-runtime";var Ge=2e3,He=1e4;function de({client:e,initialCode:t,onConnected:n,showStatus:c=!0}){let[s,r]=U("loading"),[u,y]=U(null),[f,g]=U([]),[b,o]=U(null),[m,w]=U(t??""),[_,R]=U(null),[A,P]=U(null),[D,T]=U(!1),B=Be(null);pe(()=>{let l=!0;return Promise.all([e.context(),e.machines()]).then(([S,v])=>{l&&(y(S),g(v),B.current=new Set(v.map(I=>I.id)),t?r("code"):c&&v.length>0?r("manage"):r("choose"))}).catch(S=>{l&&(P(S.message),r("choose"))}),()=>{l=!1}},[e,t,c]),pe(()=>{if(s!=="manage")return;let l=!0,S=setInterval(async()=>{try{let v=await e.machines();l&&g(v)}catch{}},He);return()=>{l=!1,clearInterval(S)}},[s,e]),pe(()=>{if(s!=="waiting")return;let l=!0,S=setInterval(async()=>{try{let v=await e.machines();if(!l)return;g(v);let I=v.filter(O=>!B.current?.has(O.id));(I.find(O=>O.state!=="offline")??I[0])&&(r("connected"),n?.(v))}catch{}},Ge);return()=>{l=!1,clearInterval(S)}},[s,e,n]);let Z=ne(async()=>{P(null),T(!0);try{let l=await e.lookup(m);R(l),r("confirm")}catch(l){P(l.message)}finally{T(!1)}},[e,m]),G=ne(async()=>{P(null),T(!0);try{await e.approve(m),r("waiting")}catch(l){P(l.message)}finally{T(!1)}},[e,m]),H=ne(async()=>{T(!0);try{await e.deny(m),r("denied")}catch(l){P(l.message)}finally{T(!1)}},[e,m]),ee=De(()=>u?.connect_url??null,[u]),Y=ne(async(l,S)=>{try{await e.approveRuntime(l,S),g(await e.machines())}catch(v){P(v.message)}},[e]);return s==="loading"?a("div",{className:"soba-connect",children:a("div",{className:"sc-card",children:a("p",{className:"sc-note",children:"Loading\u2026"})})}):h("div",{className:"soba-connect",children:[s==="manage"&&h("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Your compute"}),h("p",{className:"sc-sub",children:[u?.app.name??"This app"," runs on these machines, while they are awake."]}),a(W,{machines:f,onApproveRuntime:Y}),A&&a("p",{className:"sc-error",children:A}),a("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>r("choose"),children:"Connect another machine"})]}),s==="choose"&&h("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Use the AI you already pay for"}),h("p",{className:"sc-sub",children:[u?.app.name??"This app"," can run on your machine instead of selling you credits. Pick what you already have."]}),a("div",{className:"sc-choices",children:ue.map(l=>h("button",{type:"button",className:"sc-choice","aria-pressed":b?.id===l.id,onClick:()=>o(l),children:[a("span",{className:"sc-choice-title",children:l.title}),a("br",{}),h("span",{className:"sc-choice-meta",children:[l.cost," \xB7 ",l.prerequisite]})]},l.id))}),b&&h("div",{className:"sc-stack",style:{marginTop:16},children:[a("p",{className:"sc-note",children:b.hint}),ee?h(ke,{children:[a("p",{className:"sc-label",children:"Install Soba Connect on the machine you want to use"}),a("a",{className:"sc-btn sc-btn-primary",href:"https://soba.so/soba-connect",target:"_blank",rel:"noreferrer noopener",children:"Get Soba Connect"}),a("p",{className:"sc-label",children:"Then paste this link into it"}),a("code",{className:"sc-code",children:ee}),a("p",{className:"sc-note",children:"It will show you a short code. Nothing is pasted back: the credential is written straight to that machine."}),a("button",{type:"button",className:"sc-btn",onClick:()=>r("code"),children:"I have a code"})]}):a("p",{className:"sc-error",children:"This app has no publishable key yet, so there is nothing to pair against."})]})]}),s==="code"&&h("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Enter the code from that machine"}),a("p",{className:"sc-sub",children:"Eight characters, shown by Soba Connect once you paste the link."}),h("form",{className:"sc-stack",onSubmit:l=>{l.preventDefault(),Z()},children:[a("input",{className:"sc-input",value:m,onChange:l=>w(l.target.value),placeholder:"ACDE-F234",autoComplete:"off",autoFocus:!0,"aria-label":"Pairing code"}),A&&a("p",{className:"sc-error",children:A}),a("button",{type:"submit",className:"sc-btn sc-btn-primary",disabled:D||m.length<8,children:D?"Checking\u2026":"Continue"})]})]}),s==="confirm"&&_&&h("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Attach this machine to your account?"}),h("p",{className:"sc-sub",children:["Once you approve, ",u?.app.name??"this app"," may send your runs to it. It can never see the account or the keys on it."]}),h("ul",{className:"sc-facts",children:[a(X,{k:"Code",v:_.user_code}),a(X,{k:"Machine",v:_.hostname??"not stated"}),a(X,{k:"System",v:[_.platform,_.arch].filter(Boolean).join(" / ")||"not stated"}),a(X,{k:"Asked by",v:_.client??"not stated"}),_.labels.length>0&&a(X,{k:"Labels",v:_.labels.join(", ")})]}),a("p",{className:"sc-note",style:{marginTop:12},children:"Every line above is what that machine said about itself. If none of it looks like a computer of yours, say no."}),A&&a("p",{className:"sc-error",children:A}),h("div",{className:"sc-row",style:{marginTop:16},children:[a("button",{type:"button",className:"sc-btn sc-btn-primary",onClick:()=>void G(),disabled:D,children:"Approve"}),a("button",{type:"button",className:"sc-btn",onClick:()=>void H(),disabled:D,children:"No, deny it"})]})]}),s==="waiting"&&h("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Approved. Waiting for it to connect\u2026"}),a("p",{className:"sc-sub",children:"The worker is starting on that machine. This page will change on its own the moment it arrives."}),a("p",{className:"sc-note",children:"If nothing happens, look at Soba Connect on that machine: it says what it is doing."})]}),s==="connected"&&h("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Connected"}),h("p",{className:"sc-sub",children:[u?.app.name??"This app"," will use this machine from now on, while it is awake."]}),c&&h(ke,{children:[a(W,{machines:f,onApproveRuntime:Y}),a("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>r("manage"),children:"Done"})]})]}),s==="denied"&&h("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Declined"}),a("p",{className:"sc-sub",children:"That machine was not attached to your account, and the terminal has been told so."})]})]})}function X({k:e,v:t}){return h("li",{className:"sc-fact",children:[a("span",{className:"sc-fact-key",children:e}),a("span",{children:t})]})}import{createContext as Ve,useContext as We}from"react";var me=Ve(null);function M(){let e=We(me);if(!e)throw new Error("useSoba() must be used inside a <SobaProvider>.");return e}import{jsx as J}from"react/jsx-runtime";function Je({className:e}){let{compute:t}=M(),n=e?`soba-connect ${e}`:"soba-connect";return t.loading&&!t.machines.length?J("div",{className:n,children:J("p",{className:"sc-note",children:"Loading\u2026"})}):t.error&&!t.machines.length?J("div",{className:n,children:J("p",{className:"sc-error",children:t.error.message})}):J("div",{className:n,children:J(W,{machines:t.machines})})}import{createContext as Ye,useContext as ze}from"react";var he=Ye(null);function _e(){return ze(he)}import{jsx as q}from"react/jsx-runtime";function Ke({initialCode:e,onConnected:t,className:n,showStatus:c}){let{error:s}=M(),r=_e();return s?q("div",{className:Re(n),children:q("div",{className:"sc-card",children:q("p",{className:"sc-error",children:s.message})})}):r?q("div",{className:n,children:q(de,{client:r,...e?{initialCode:e}:{},...t?{onConnected:t}:{},...c===void 0?{}:{showStatus:c}})}):q("div",{className:Re(n),children:q("div",{className:"sc-card",children:q("p",{className:"sc-note",children:"Loading\u2026"})})})}function Re(e){return e?`soba-connect ${e}`:"soba-connect"}var Qe=new Set(["delta","thinking","status","done","error"]);function se(e){let t;try{t=JSON.parse(e)}catch{return null}if(!t||typeof t!="object")return null;let n=t,c=n.type==="event"?n.event:n;if(!c||typeof c!="object")return null;let s=c;if(typeof s.type!="string"||!Qe.has(s.type))return null;let r={type:s.type};return typeof s.text=="string"&&(r.text=s.text),typeof s.message=="string"&&(r.message=s.message),typeof s.tool=="string"&&(r.tool=s.tool),typeof s.tier=="string"&&(r.tier=s.tier),s.usage&&typeof s.usage=="object"&&(r.usage=s.usage),r}async function*fe(e){let t=e.body;if(!t)throw new Error("That run response had no body to stream.");let n=(e.headers.get("content-type")??"").includes("ndjson"),c=t.getReader(),s=new TextDecoder,r="",u=[];try{for(;;){let f=await c.read();if(f.done)break;for(r+=s.decode(f.value,{stream:!0});;){let g=r.indexOf(`
|
|
3
|
-
`);if(
|
|
4
|
-
`);if(
|
|
5
|
-
`);if(
|
|
2
|
+
var ye=class extends Error{status;constructor(t,n){super(t),this.name="ConnectError",this.status=n}},de=class{session;baseUrl;doFetch;constructor(t){this.session=t.session,this.baseUrl=(t.baseUrl??"").replace(/\/+$/,""),this.doFetch=t.fetchImpl??fetch.bind(globalThis)}context(){return this.request("GET","/v1/connect")}async machines(){return(await this.request("GET","/v1/machines")).machines}lookup(t){return this.request("GET",`/v1/device/lookup?code=${encodeURIComponent(t)}`)}approve(t){return this.request("POST","/v1/device/approve",{user_code:t})}deny(t){return this.request("POST","/v1/device/deny",{user_code:t})}plans(){return this.request("GET","/v1/plans")}selectPlan(t){return this.request("POST","/v1/plans/select",{plan:t})}checkoutPlan(t,n){return this.request("POST","/v1/plans/checkout",{plan:t,...n?.successUrl?{success_url:n.successUrl}:{},...n?.cancelUrl?{cancel_url:n.cancelUrl}:{}})}leavePlan(){return this.request("DELETE","/v1/plans/select")}approveRuntime(t,n){return this.request("POST","/v1/runtimes/approve",{machine_id:t,runtime_id:n})}async request(t,n,o){let s=await this.doFetch(`${this.baseUrl}${n}`,{method:t,headers:{authorization:`Bearer ${this.session}`,...o?{"content-type":"application/json"}:{}},...o?{body:JSON.stringify(o)}:{}}),r={};try{r=await s.json()}catch{}if(!s.ok){let m=typeof r.error=="string"?r.error:`Request failed (${s.status})`;throw new ye(m,s.status)}return r}};function Ee(e){return e.authenticated===!0?{tone:"ok",text:"signed in"}:e.authenticated===!1?{tone:"warn",text:e.auth_hint??"signed out on that machine"}:{tone:"unknown",text:"could not tell"}}function _e(e){return e.state==="busy"?{tone:"busy",text:`serving ${e.active} of ${e.capacity}`}:e.state==="online"?{tone:"ok",text:"connected"}:{tone:"off",text:"asleep or offline"}}import{jsx as O,jsxs as se}from"react/jsx-runtime";function G({machines:e,onApproveRuntime:t}){return e.length?O("div",{children:e.map(n=>{let o=_e(n);return se("div",{className:"sc-machine",children:[se("div",{className:"sc-row-between",children:[se("div",{className:"sc-row",children:[O("span",{className:`sc-dot sc-dot-${o.tone}`,"aria-hidden":"true"}),O("strong",{children:n.label??"This machine"})]}),O("span",{className:"sc-note",children:o.text})]}),O("p",{className:"sc-note",children:[n.platform,n.arch,n.version&&`worker ${n.version}`,n.always_on&&"always on"].filter(Boolean).join(" \xB7 ")}),se("ul",{className:"sc-runtimes",children:[n.runtimes.map(s=>{let r=Ee(s);return s.quarantined?se("li",{className:"sc-row-between",children:[O("span",{children:s.display_name}),t?O("button",{type:"button",className:"sc-btn",onClick:()=>t(n.id,s.id),children:"New here. Let it serve"}):O("span",{className:"sc-tone-warn",children:"new, not serving yet"})]},s.id):se("li",{className:"sc-row-between",children:[O("span",{children:s.display_name}),O("span",{className:`sc-tone-${r.tone}`,children:r.text})]},s.id)}),!n.runtimes.length&&O("li",{className:"sc-note",children:"No runtime advertised. Install Claude Code, Codex or Ollama on that machine."})]})]},n.id)})}):O("p",{className:"sc-note",children:"Nothing connected yet."})}import{useCallback as ve,useEffect as W,useId as St,useMemo as me,useRef as Pt,useState as L}from"react";import{jsx as z,jsxs as xt}from"react/jsx-runtime";function Me({className:e}){return xt("svg",{viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",className:e,children:[z("path",{d:"M18.125 7.039A8.6 8.6 0 1 0 18.125 16.961A6 6 0 0 1 18.125 7.039Z",fill:"currentColor"}),z("path",{d:"M19.925 7.039A8.6 8.6 0 0 1 19.925 16.961A6 6 0 0 0 19.925 7.039Z",fill:"currentColor"})]})}function Xe({className:e}){return z("svg",{viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",className:e,children:z("path",{fill:"currentColor",d:"M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z"})})}function Qe({className:e}){return z("svg",{viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",className:e,children:z("path",{fill:"currentColor",d:"M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"})})}function et({className:e}){return z("svg",{viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",className:e,children:z("path",{d:"M4 5h16v11H4zM9 20h6M12 16v4",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})}var tt={claude:Xe,chatgpt:Qe,local:et};var pe=[{id:"claude",label:"Continue with Claude",title:"The Claude plan you already pay for",cost:"Nothing beyond the plan",prerequisite:"Claude Code installed, and signed in",hint:"Install it from claude.ai/code, then run `claude` once and sign in.",expects:["claude-code"]},{id:"chatgpt",label:"Continue with ChatGPT",title:"The ChatGPT plan you already pay for",cost:"Nothing beyond the plan",prerequisite:"Codex installed, and signed in",hint:"Install Codex, then run `codex` once and sign in.",expects:["codex"]},{id:"local",label:"Use a model on this machine",title:"A model on your own machine",cost:"Free. You bought the hardware",prerequisite:"Ollama running, with a model pulled",hint:"Install Ollama, then `ollama pull` a model before pairing.",expects:["ollama","openai-api"]}];var nt="soba.connect.choice",Nt=180*24*60*60*1e3;function kt(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return(t>>>0).toString(36)}function Re(e,t){return e?t?`${nt}.${e}.${kt(t)}`:`${nt}.${e}`:null}function Te(e){if(!e||typeof localStorage>"u")return null;try{let t=localStorage.getItem(e);if(!t)return null;let n=JSON.parse(t);if(Date.now()-Date.parse(n.at)>Nt)return null;let o=pe.find(s=>s.id===n.choice);return o?o.id:null}catch{return null}}function Ie(e,t){if(!(!e||typeof localStorage>"u"))try{localStorage.setItem(e,JSON.stringify({choice:t,at:new Date().toISOString()}))}catch{}}function Ae(e,t){if(!e)return{kind:"unasked"};if(!t)return{kind:"bare"};let n=t.runtimes??[];if(n.length===0)return{kind:"bare"};let o=n.find(s=>e.expects.includes(s.id));return o?o.quarantined?{kind:"held",runtime:o}:o.authenticated===!1?{kind:"signed-out",runtime:o}:o.authenticated===null||o.authenticated===void 0?{kind:"unsure",runtime:o}:{kind:"matched",runtime:o}:{kind:"missing",found:n}}function Oe(e){return e.kind!=="bare"&&e.kind!=="missing"}function Le(e){return e.kind==="matched"||e.kind==="signed-out"||e.kind==="unsure"||e.kind==="held"}import{Fragment as ot,jsx as c,jsxs as b}from"react/jsx-runtime";var st=2e3,Et=1e4,_t=15e3,Mt=9e4;function Rt(e){let t=Math.max(0,Math.round(e/1e3));return`${Math.floor(t/60)}:${String(t%60).padStart(2,"0")}`}function $e({client:e,initialCode:t,onConnected:n,showStatus:o=!0,initialContext:s=null,initialMachines:r,onClose:m,headingId:h,theme:u}){let v=!!s&&r!==void 0,[d,a]=L(v?t?"code":o&&(r?.length??0)>0?"manage":"choose":"loading"),[f,R]=L(s),[w,T]=L(r??[]),[x,I]=L(null),[N,Y]=L(t??""),[g,B]=L(null),[j,U]=L(null),[V,q]=L(!1),[H,ke]=L(0),[K,ae]=L(()=>Date.now()),[ie,fe]=L(null),[X,Se]=L(null),Q=St(),ee=h??Q,te=Pt(null);W(()=>{if(!e||v)return;let i=!0;return Promise.all([e.context(),e.machines()]).then(([C,P])=>{i&&(R(C),T(P),t?a("code"):o&&P.length>0?a("manage"):a("choose"))}).catch(C=>{i&&(U(C.message),a("choose"))}),()=>{i=!1}},[e,t,o,v]),W(()=>{r&&T(r)},[r]),W(()=>{if(!e||d!=="manage")return;let i=!0,C=setInterval(async()=>{try{let P=await e.machines();i&&T(P)}catch{}},Et);return()=>{i=!1,clearInterval(C)}},[d,e]),W(()=>{if(!e||d!=="waiting")return;let i=!0,C=setInterval(async()=>{try{let P=await e.machines();if(!i)return;T(P);let ne=te.current;if(!ne)return;let l=P.filter(A=>!ne.has(A.id)),k=l.find(A=>A.state!=="offline")??l[0];k&&(Se(k.id),a("connected"),n?.(P))}catch{}},st);return()=>{i=!1,clearInterval(C)}},[d,e,n]);let ce=ve(async()=>{if(e){U(null),q(!0);try{let i=await e.lookup(N);B(i),a("confirm")}catch(i){U(i.message)}finally{q(!1)}}},[e,N]),ge=ve(async()=>{if(e){U(null),q(!0);try{try{te.current=new Set((await e.machines()).map(i=>i.id))}catch{te.current=new Set(w.map(i=>i.id))}await e.approve(N),a("waiting"),ke(Date.now())}catch(i){U(i.message)}finally{q(!1)}}},[e,N,w]),be=ve(async()=>{if(e){q(!0);try{await e.deny(N),a("denied")}catch(i){U(i.message)}finally{q(!1)}}},[e,N]),le=me(()=>f?.connect_url??null,[f]),D=me(()=>Re(f?.publishable_key??null),[f?.publishable_key]);W(()=>fe(Te(D)),[D]);let S=me(()=>w.find(i=>i.id===X),[w,X]),p=me(()=>Ae(x,S),[x,S]);W(()=>{if(!e||d!=="connected"||Oe(p))return;let i=Date.now()+_t,C=setInterval(async()=>{if(Date.now()>i)return clearInterval(C);try{T(await e.machines())}catch{}},st);return()=>clearInterval(C)},[e,d,p]),W(()=>{d!=="connected"||!x||Le(p)&&Ie(D,x.id)},[d,x,p,D]);let E=me(()=>{if(!g)return null;if(g.approved||g.denied)return"answered";let i=g.expires_at?Date.parse(g.expires_at):Number.NaN;return Number.isFinite(i)&&i<=K?"expired":"live"},[g,K]);function F({verdict:i,choice:C}){if(i.kind==="unasked"||i.kind==="matched")return null;if(i.kind==="signed-out")return b("p",{className:"sc-note sc-tone-warn",children:[i.runtime.display_name," is on that machine but"," ",i.runtime.auth_hint??"signed out",". ",C?.hint]});if(i.kind==="unsure")return b("p",{className:"sc-note",children:[i.runtime.display_name," is there. We could not tell whether it is signed in \u2014 the first run will say for certain."]});if(i.kind==="held")return b("p",{className:"sc-note sc-tone-warn",children:[i.runtime.display_name," appeared after pairing, so it is waiting for you to let it serve."]});if(i.kind==="missing"){let P=i.found.map(ne=>ne.display_name).join(", ");return b("p",{className:"sc-note sc-tone-warn",children:["That machine is not advertising ",C?.prerequisite.split(",")[0]??"what you picked",". It has ",P,", and runs will go there instead. ",C?.hint]})}return c("p",{className:"sc-note sc-tone-warn",children:"No runtime advertised yet. Install Claude Code, Codex or Ollama on that machine and it will appear here."})}W(()=>{if(!(d==="confirm"&&E==="live"||d==="waiting"))return;let C=setInterval(()=>ae(Date.now()),1e3);return()=>clearInterval(C)},[d,E]);let ue=ve(async(i,C)=>{if(e)try{await e.approveRuntime(i,C),T(await e.machines())}catch(P){U(P.message)}},[e]);return d==="loading"?c("div",{className:"soba-connect",...u?{"data-theme":u}:{},children:c("div",{className:"sc-card",children:c("p",{className:"sc-note",children:"Loading\u2026"})})}):b("div",{className:"soba-connect",...u?{"data-theme":u}:{},children:[m&&c("button",{type:"button",className:"sc-modal-close",onClick:m,"aria-label":"Close",children:"\xD7"}),d==="manage"&&b("div",{className:"sc-card",children:[c("h2",{className:"sc-title",children:"Your compute"}),b("p",{className:"sc-sub",children:[f?.app.name??"This app"," runs on these machines, while they are awake."]}),c(G,{machines:w,onApproveRuntime:ue}),j&&c("p",{className:"sc-error",children:j}),c("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>a("choose"),children:"Connect another machine"})]}),d==="choose"&&b("div",{className:"sc-card",children:[c("span",{className:"sc-mark","aria-hidden":"true",children:c(Me,{})}),c("h2",{className:"sc-h",id:ee,children:"Connect your AI"}),c("p",{className:"sc-p",children:"Use access you already have. Nothing here is charged to you."}),c("div",{className:"sc-opts",children:pe.map(i=>{let C=tt[i.id];return b("button",{type:"button",className:"sc-opt","aria-pressed":x?.id===i.id,onClick:()=>I(i),children:[C&&c(C,{className:"sc-opt-mark"}),c("span",{className:"sc-opt-label",children:i.label}),ie===i.id&&c("span",{className:"sc-opt-last",children:"last used"})]},i.id)})}),x&&b("div",{className:"sc-stack",style:{marginTop:16},children:[c("p",{className:"sc-note",children:x.hint}),le?b(ot,{children:[c("p",{className:"sc-label",children:"Install Soba Connect on the machine you want to use"}),c("a",{className:"sc-btn sc-btn-primary",href:"https://soba.so/soba-connect",target:"_blank",rel:"noreferrer noopener",children:"Get Soba Connect"}),c("p",{className:"sc-label",children:"Then paste this link into it"}),c("code",{className:"sc-code",children:le}),c("p",{className:"sc-note",children:"It will show you a short code. Nothing is pasted back: the credential is written straight to that machine."}),c("button",{type:"button",className:"sc-btn",onClick:()=>a("code"),children:"I have a code"})]}):c("p",{className:"sc-error",children:"This app has no publishable key yet, so there is nothing to pair against."})]})]}),d==="code"&&b("div",{className:"sc-card",children:[c("h2",{className:"sc-title",children:"Enter the code from that machine"}),c("p",{className:"sc-sub",children:"Eight characters, shown by Soba Connect once you paste the link."}),b("form",{className:"sc-stack",onSubmit:i=>{i.preventDefault(),ce()},children:[c("input",{className:"sc-input",value:N,onChange:i=>Y(i.target.value),placeholder:"ACDE-F234",autoComplete:"off",autoFocus:!0,"aria-label":"Pairing code"}),j&&c("p",{className:"sc-error",children:j}),c("button",{type:"submit",className:"sc-btn sc-btn-primary",disabled:V||N.length<8,children:V?"Checking\u2026":"Continue"})]})]}),d==="confirm"&&g&&b("div",{className:"sc-card",children:[c("h2",{className:"sc-title",children:"Attach this machine to your account?"}),b("p",{className:"sc-sub",children:["Once you approve, ",f?.app.name??"this app"," may send your runs to it. It can never see the account or the keys on it."]}),b("ul",{className:"sc-facts",children:[c(he,{k:"Code",v:g.user_code}),c(he,{k:"Machine",v:g.hostname??"not stated"}),c(he,{k:"System",v:[g.platform,g.arch].filter(Boolean).join(" / ")||"not stated"}),c(he,{k:"Asked by",v:g.client??"not stated"}),g.labels.length>0&&c(he,{k:"Labels",v:g.labels.join(", ")})]}),c("p",{className:"sc-note",style:{marginTop:12},children:"Every line above is what that machine said about itself. If none of it looks like a computer of yours, say no."}),E==="expired"&&c("p",{className:"sc-error",children:"That code has expired. Ask the machine for a new one \u2014 it prints a fresh code each time it starts."}),E==="answered"&&c("p",{className:"sc-error",children:"That code has already been answered. If it was not you, deny the next one."}),j&&c("p",{className:"sc-error",children:j}),b("div",{className:"sc-row",style:{marginTop:16},children:[c("button",{type:"button",className:"sc-btn sc-btn-primary",onClick:()=>void ge(),disabled:V||E!=="live",children:"Approve"}),c("button",{type:"button",className:"sc-btn",onClick:()=>void be(),disabled:V||E==="answered",children:"No, deny it"}),c("button",{type:"button",className:"sc-btn sc-btn-quiet",onClick:()=>a("code"),disabled:V,children:"Back"})]}),E==="live"&&g.expires_at&&b("p",{className:"sc-note",style:{marginTop:10},children:["This code expires in ",Rt(Date.parse(g.expires_at)-K),"."]})]}),d==="waiting"&&b("div",{className:"sc-card",children:[c("h2",{className:"sc-title",id:ee,children:"Approved. Waiting for it to connect\u2026"}),c("p",{className:"sc-sub",children:"The worker is starting on that machine. This page will change on its own the moment it arrives."}),c("p",{className:"sc-note",children:"If nothing happens, look at Soba Connect on that machine: it says what it is doing."}),H>0&&K-H>Mt&&b("div",{className:"sc-stack",style:{marginTop:14},children:[c("p",{className:"sc-note sc-tone-warn",children:"Still nothing. Soba Connect may not be running on that machine, or it may not have finished starting. This keeps watching either way."}),c("button",{type:"button",className:"sc-btn",onClick:()=>a("choose"),children:"Start over"})]})]}),d==="connected"&&b("div",{className:"sc-card",children:[c("h2",{className:"sc-title",id:ee,children:"Connected"}),b("p",{className:"sc-sub",children:[f?.app.name??"This app"," will use this machine from now on, while it is awake."]}),c(F,{verdict:p,choice:x}),o&&b(ot,{children:[c(G,{machines:w,onApproveRuntime:ue}),c("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>a("manage"),children:"Done"})]})]}),d==="denied"&&b("div",{className:"sc-card",children:[c("h2",{className:"sc-title",id:ee,children:"Declined"}),c("p",{className:"sc-sub",children:"That machine was not attached to your account, and the terminal has been told so."}),c("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>a("choose"),children:"Back to the start"})]})]})}function he({k:e,v:t}){return b("li",{className:"sc-fact",children:[c("span",{className:"sc-fact-key",children:e}),c("span",{children:t})]})}import{useEffect as Tt,useRef as qe}from"react";var Ce=0,Ue=null;function rt(){if(typeof document>"u")return()=>{};Ce===0&&(Ue=document.body.style.overflow,document.body.style.overflow="hidden"),Ce+=1;let e=!1;return()=>{e||(e=!0,Ce-=1,Ce===0&&(document.body.style.overflow=Ue??"",Ue=null))}}import{jsx as at}from"react/jsx-runtime";function Be({open:e,onClose:t,labelledBy:n,theme:o,children:s}){let r=qe(null),m=qe(null),h=qe(null);return Tt(()=>{let u=r.current;if(u){if(!e){u.open&&u.close();return}return u.open||u.showModal(),document.activeElement===u&&m.current?.focus(),rt()}},[e]),at("dialog",{ref:r,className:"soba-connect sc-modal","aria-labelledby":n,...o?{"data-theme":o}:{},onCancel:u=>{u.preventDefault(),t()},onPointerDown:u=>{h.current=u.target},onClick:u=>{u.target===u.currentTarget&&h.current===u.currentTarget&&t()},children:at("div",{className:"sc-modal-sheet",ref:m,tabIndex:-1,children:s})})}import{createContext as It,useContext as At}from"react";var De=It(null);function _(){let e=At(De);if(!e)throw new Error("useSoba() must be used inside a <SobaProvider>.");return e}import{jsx as oe}from"react/jsx-runtime";function Ot({className:e}){let{compute:t}=_(),n=e?`soba-connect ${e}`:"soba-connect";return t.loading&&!t.machines.length?oe("div",{className:n,children:oe("p",{className:"sc-note",children:"Loading\u2026"})}):t.error&&!t.machines.length?oe("div",{className:n,children:oe("p",{className:"sc-error",children:t.error.message})}):oe("div",{className:n,children:oe(G,{machines:t.machines})})}import{useEffect as $t,useRef as dt,useState as Ut}from"react";import{useId as Lt}from"react";import{createContext as it,useContext as ct}from"react";var Fe=it(null);function lt(){return ct(Fe)}var je=it({context:null,machines:void 0,theme:"light"});function ut(){return ct(je)}import{jsx as re,jsxs as Ve}from"react/jsx-runtime";function He({open:e,onOpenChange:t,initialCode:n,onConnected:o,showStatus:s}){let{error:r}=_(),m=lt(),h=ut(),u=()=>t(!1),v=Lt();return Ve(Be,{open:e,onClose:u,labelledBy:v,theme:h.theme,children:[r?Ve("div",{className:"sc-card",children:[re("h2",{className:"sc-title",id:v,children:"Could not start"}),re("p",{className:"sc-error",children:r.message})]}):!m&&!h.context?Ve("div",{className:"sc-card",children:[re("h2",{className:"sc-title",id:v,children:"One moment"}),re("p",{className:"sc-note",children:"Getting your compute\u2026"})]}):re($e,{client:m,onClose:u,headingId:v,theme:h.theme,initialContext:h.context,...h.machines!==void 0?{initialMachines:h.machines}:{},...n?{initialCode:n}:{},...o?{onConnected:o}:{},...s===void 0?{}:{showStatus:s}}),re("p",{className:"sc-modal-foot",children:"Secured by Soba"})]})}import{Fragment as pt,jsx as Ge,jsxs as mt}from"react/jsx-runtime";function ze({children:e,variant:t,className:n,initialCode:o,onConnected:s,defaultOpen:r,open:m,onOpenChange:h,showConnected:u=!0}){let{compute:v}=_(),[d,a]=Ut(r??!!o),f=dt(null),R=m!==void 0,w=R?m:d,T=B=>{R||a(B),h?.(B)},x=dt(w);$t(()=>{x.current&&!w&&f.current?.focus(),x.current=w},[w]);let I=v.machines.find(B=>B.state!=="offline")??v.machines[0],N=I?.runtimes?.find(B=>!B.quarantined)??I?.runtimes?.[0],Y=u&&I!==void 0,g=I?.state==="online"?"ok":I?.state==="busy"?"busy":"off";return mt(pt,{children:[Ge("button",{ref:f,type:"button",className:["sc-btn",Y?"sc-btn-connected":t==="outline"?"":"sc-btn-primary",n].filter(Boolean).join(" "),onClick:()=>T(!0),children:e??(Y?mt(pt,{children:[Ge("i",{className:`sc-dot sc-dot-${g}`,"aria-hidden":"true"}),N?.display_name??"Connected",I?.label?` \xB7 ${I.label}`:""]}):"Connect compute")}),Ge(He,{open:w,onOpenChange:T,...o?{initialCode:o}:{},...s?{onConnected:s}:{}})]})}import{jsx as We,jsxs as Bt}from"react/jsx-runtime";function qt({initialCode:e,onConnected:t,className:n,showStatus:o=!0}){let{compute:s}=_(),r=s.machines.length>0;return Bt("div",{className:n?`soba-connect ${n}`:"soba-connect",children:[o&&We(G,{machines:s.machines}),We("div",{style:{marginTop:o&&r?14:0},children:We(ze,{variant:o&&r?"outline":"primary",...e?{initialCode:e}:{},...t?{onConnected:t}:{}})})]})}var Dt=new Set(["delta","thinking","status","done","error"]);function we(e){let t;try{t=JSON.parse(e)}catch{return null}if(!t||typeof t!="object")return null;let n=t,o=n.type==="event"?n.event:n;if(!o||typeof o!="object")return null;let s=o;if(typeof s.type!="string"||!Dt.has(s.type))return null;let r={type:s.type};return typeof s.text=="string"&&(r.text=s.text),typeof s.message=="string"&&(r.message=s.message),typeof s.tool=="string"&&(r.tool=s.tool),typeof s.tier=="string"&&(r.tier=s.tier),s.usage&&typeof s.usage=="object"&&(r.usage=s.usage),r}async function*Ke(e){let t=e.body;if(!t)throw new Error("That run response had no body to stream.");let n=(e.headers.get("content-type")??"").includes("ndjson"),o=t.getReader(),s=new TextDecoder,r="",m=[];try{for(;;){let u=await o.read();if(u.done)break;for(r+=s.decode(u.value,{stream:!0});;){let v=r.indexOf(`
|
|
3
|
+
`);if(v===-1)break;let d=ft(r.slice(0,v));if(r=r.slice(v+1),n){let a=d.trim()?we(d):null;a&&(yield a);continue}if(d===""){let a=m.join(`
|
|
4
|
+
`);if(m=[],a==="[DONE]")return;let f=a?we(a):null;f&&(yield f);continue}ht(d,m)}}!n&&r&&ht(ft(r),m);let h=n?r.trim():m.join(`
|
|
5
|
+
`);if(h&&h!=="[DONE]"){let u=we(h);u&&(yield u)}}finally{o.cancel().catch(()=>{})}}function ht(e,t){if(!e||e.startsWith(":"))return;let n=e.indexOf(":");if(n===-1||e.slice(0,n)!=="data")return;let o=e.slice(n+1);t.push(o.startsWith(" ")?o.slice(1):o)}function ft(e){return e.endsWith("\r")?e.slice(0,-1):e}async function Ft(e){let t="";for await(let n of e)if(n.type==="delta"&&n.text&&(t+=n.text),n.type==="error")throw new Error(n.message??"The run ended with an error.");return t}import{useState as gt}from"react";import{jsx as bt,jsxs as Vt}from"react/jsx-runtime";function jt({plan:e,successUrl:t,cancelUrl:n,label:o,className:s}){let{plan:r}=_(),[m,h]=gt(!1),[u,v]=gt(null);async function d(){h(!0),v(null);try{let a=await r.checkout(e.id,{...t?{successUrl:t}:{},...n?{cancelUrl:n}:{}});window.location.assign(a)}catch(a){v(a.message),h(!1)}}return Vt("div",{className:s?`soba-connect ${s}`:"soba-connect",children:[bt("button",{type:"button",className:"sc-btn sc-btn-primary",disabled:m,onClick:()=>void d(),children:m?"Opening\u2026":o??`Subscribe \u2014 $${e.price.toLocaleString()}/${e.interval}`}),u&&bt("p",{className:"sc-error",children:u})]})}import{useState as yt}from"react";import{Fragment as Wt,jsx as y,jsxs as M}from"react/jsx-runtime";var Ht={"user-hardware":"your own machine","user-subscription":"your own AI subscription","open-weights":"open models we host",frontier:"frontier models"},Gt=new Set(["user-hardware","user-subscription"]);function xe(e){return e?`soba-connect ${e}`:"soba-connect"}function zt({onSelect:e,successUrl:t,cancelUrl:n,showCompute:o=!0,className:s}){let{plan:r}=_(),[m,h]=yt(null),[u,v]=yt(null);if(r.error)return y("div",{className:xe(s),children:y("div",{className:"sc-card",children:y("p",{className:"sc-error",children:r.error.message})})});if(r.loading)return y("div",{className:xe(s),children:y("div",{className:"sc-card",children:y("p",{className:"sc-note",children:"Loading plans\u2026"})})});if(r.plans.length===0)return y("div",{className:xe(s),children:y("div",{className:"sc-card",children:y("p",{className:"sc-note",children:"This app has no plans yet."})})});async function d(a){h(a.id),v(null);try{if(a.selectable)await r.select(a.id);else if(e)e(a);else{let f=await r.checkout(a.id,{...t?{successUrl:t}:{},...n?{cancelUrl:n}:{}});window.location.assign(f);return}}catch(f){v(f.message)}finally{h(null)}}return M("div",{className:xe(s),children:[y("div",{className:"sc-plans",children:r.plans.map(a=>{let f=r.current?.plan_id===a.id;return M("div",{className:f?"sc-plan sc-plan-current":"sc-plan",children:[M("div",{className:"sc-row-between",children:[y("span",{className:"sc-title",children:a.name}),f&&y("span",{className:"sc-plan-badge",children:"Current"})]}),a.description&&y("p",{className:"sc-sub",children:a.description}),y("p",{className:"sc-plan-price",children:a.price===0?y("span",{className:"sc-plan-amount",children:"Free"}):M(Wt,{children:[M("span",{className:"sc-plan-amount",children:["$",a.price.toLocaleString()]}),M("span",{className:"sc-plan-interval",children:["/",a.interval]})]})}),M("div",{className:"sc-facts",children:[M("div",{className:"sc-fact",children:[y("span",{className:"sc-fact-key",children:"Included"}),y("span",{children:a.included_units===null?"Unlimited":`${a.included_units.toLocaleString()} ${a.unit}${a.included_units===1?"":"s"}`})]}),a.overage_per_unit>0&&M("div",{className:"sc-fact",children:[y("span",{className:"sc-fact-key",children:"Then"}),M("span",{children:["$",a.overage_per_unit," / ",a.unit]})]}),a.trial_days>0&&M("div",{className:"sc-fact",children:[y("span",{className:"sc-fact-key",children:"Trial"}),M("span",{children:[a.trial_days," days"]})]}),o&&M("div",{className:"sc-plan-compute",children:[y("span",{className:"sc-fact-key",children:"Runs on"}),a.route_allow===null||a.route_allow.length===0?y("span",{children:a.route_allow===null?"any compute":"no compute"}):y("ul",{className:"sc-compute",children:a.route_allow.map(R=>y("li",{className:Gt.has(R)?"sc-compute-own":void 0,children:Ht[R]??R},R))})]})]}),y("button",{type:"button",className:f?"sc-btn":"sc-btn sc-btn-primary",disabled:f||m!==null,onClick:()=>void d(a),children:f?"Your plan":m===a.id?a.selectable?"Switching\u2026":"Opening\u2026":a.selectable?"Choose":`Subscribe \u2014 $${a.price.toLocaleString()}/${a.interval}`})]},a.id)})}),u&&y("p",{className:"sc-error",children:u})]})}import{useCallback as J,useEffect as Z,useMemo as Ze,useRef as Jt,useState as $}from"react";var Ne=class{#t;#s=null;#n=null;#e;#o=new Set;constructor(t){this.#e=t,this.#t=t.token??null}get token(){return this.#t}get expiresAt(){return this.#s}get canRefresh(){return!!this.#e.endpoint}get refreshAt(){return!this.canRefresh||this.#s===null?null:Math.max(Date.now()+1e4,this.#s-6e4)}subscribe(t){return this.#o.add(t),()=>this.#o.delete(t)}async ensure(){return this.#t?this.#t:this.refresh()}async refresh(){if(this.#n)return this.#n;let t=this.#e.endpoint;if(!t)throw new Error("This Soba session cannot be refreshed. Pass `sessionEndpoint` so the provider can mint a new one.");let n=this.#e.fetchImpl??fetch.bind(globalThis);this.#n=(async()=>{let o=await n(t,{method:"POST",headers:{"content-type":"application/json"},credentials:"same-origin",body:JSON.stringify(this.#e.user?{user:this.#e.user}:{})});if(!o.ok)throw new Error(`Could not start a Soba session (${o.status} from ${t}).`);let s=await o.json(),r=s.session??s.token;if(!r)throw new Error(`${t} answered without a \`session\`. See @soba-so/react's README.`);let m=s.expires_in??s.expiresIn;this.#t=r,this.#s=typeof m=="number"?Date.now()+m*1e3:null;for(let h of this.#o)h();return r})();try{return await this.#n}finally{this.#n=null}}};function vt(e,t=fetch.bind(globalThis)){return async(n,o)=>{let s=await t(n,o);if(s.status!==401||!e.canRefresh)return s;let r;try{r=await e.refresh()}catch{return s}return t(n,Kt(o,r))}}function Kt(e,t){let n=new Headers(e?.headers);return n.set("authorization",`Bearer ${t}`),{...e,headers:n}}var Ct="data-soba-react",wt=!1;function Je(){if(wt||typeof document>"u"||(wt=!0,document.querySelector(`style[${Ct}]`)))return;let e=document.createElement("style");e.setAttribute(Ct,""),e.textContent=`.soba-connect {
|
|
6
6
|
--sc-ground: #ffffff;
|
|
7
7
|
--sc-sheet: #ffffff;
|
|
8
8
|
--sc-raised: #f6f6f7;
|
|
@@ -16,6 +16,8 @@ var te=class extends Error{status;constructor(t,n){super(t),this.name="ConnectEr
|
|
|
16
16
|
--sc-ok: #22a06b;
|
|
17
17
|
--sc-warn: #b06f14;
|
|
18
18
|
--sc-r: 10px;
|
|
19
|
+
|
|
20
|
+
--sc-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
19
21
|
|
|
20
22
|
box-sizing: border-box;
|
|
21
23
|
max-width: 560px;
|
|
@@ -236,8 +238,13 @@ a.sc-btn {
|
|
|
236
238
|
font-size: 14px;
|
|
237
239
|
}
|
|
238
240
|
|
|
241
|
+
.soba-connect[data-theme="dark"],
|
|
242
|
+
.soba-connect[data-theme="system"] {
|
|
243
|
+
color-scheme: dark;
|
|
244
|
+
}
|
|
245
|
+
|
|
239
246
|
@media (prefers-color-scheme: dark) {
|
|
240
|
-
.soba-connect
|
|
247
|
+
.soba-connect[data-theme="system"] {
|
|
241
248
|
--sc-ground: #0f0f11;
|
|
242
249
|
--sc-sheet: #161618;
|
|
243
250
|
--sc-raised: #1d1d20;
|
|
@@ -251,6 +258,19 @@ a.sc-btn {
|
|
|
251
258
|
}
|
|
252
259
|
}
|
|
253
260
|
|
|
261
|
+
.soba-connect[data-theme="dark"] {
|
|
262
|
+
--sc-ground: #0f0f11;
|
|
263
|
+
--sc-sheet: #161618;
|
|
264
|
+
--sc-raised: #1d1d20;
|
|
265
|
+
--sc-line: #26262a;
|
|
266
|
+
--sc-line-2: #34343a;
|
|
267
|
+
--sc-text: #f2f2f3;
|
|
268
|
+
--sc-text-2: #c9c9cd;
|
|
269
|
+
--sc-muted: #8d8d95;
|
|
270
|
+
--sc-accent: #f2f2f3;
|
|
271
|
+
--sc-accent-fg: #131316;
|
|
272
|
+
}
|
|
273
|
+
|
|
254
274
|
.sc-plans {
|
|
255
275
|
display: grid;
|
|
256
276
|
gap: 12px;
|
|
@@ -374,4 +394,187 @@ a.sc-btn {
|
|
|
374
394
|
border-bottom: 1px solid var(--sc-line, #e9e9eb);
|
|
375
395
|
margin-bottom: 2px;
|
|
376
396
|
}
|
|
377
|
-
|
|
397
|
+
|
|
398
|
+
.sc-modal {
|
|
399
|
+
|
|
400
|
+
position: fixed;
|
|
401
|
+
inset: 0;
|
|
402
|
+
width: 100%;
|
|
403
|
+
max-width: 100%;
|
|
404
|
+
height: 100%;
|
|
405
|
+
max-height: 100%;
|
|
406
|
+
margin: 0;
|
|
407
|
+
padding: 16px;
|
|
408
|
+
border: 0;
|
|
409
|
+
background: transparent;
|
|
410
|
+
overflow: auto;
|
|
411
|
+
|
|
412
|
+
display: grid;
|
|
413
|
+
place-items: center;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.sc-modal::backdrop {
|
|
417
|
+
background: rgb(15 15 17 / 55%);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.sc-modal-sheet {
|
|
421
|
+
position: relative;
|
|
422
|
+
width: 100%;
|
|
423
|
+
|
|
424
|
+
max-width: 420px;
|
|
425
|
+
background: var(--sc-sheet, #ffffff);
|
|
426
|
+
border: 1.5px solid var(--sc-line, #e9e9eb);
|
|
427
|
+
border-radius: 14px;
|
|
428
|
+
box-shadow: 0 24px 60px -12px rgb(15 15 17 / 22%);
|
|
429
|
+
|
|
430
|
+
outline: none;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.sc-modal-close {
|
|
434
|
+
position: absolute;
|
|
435
|
+
top: 10px;
|
|
436
|
+
right: 12px;
|
|
437
|
+
z-index: 1;
|
|
438
|
+
border: 0;
|
|
439
|
+
background: transparent;
|
|
440
|
+
color: var(--sc-muted, #6b6b73);
|
|
441
|
+
font: inherit;
|
|
442
|
+
font-size: 20px;
|
|
443
|
+
line-height: 1;
|
|
444
|
+
padding: 4px 8px;
|
|
445
|
+
border-radius: 6px;
|
|
446
|
+
cursor: pointer;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.sc-modal-close:hover {
|
|
450
|
+
background: var(--sc-raised, #f6f6f7);
|
|
451
|
+
color: var(--sc-text, #1a1a1c);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.sc-modal-sheet .sc-card {
|
|
455
|
+
padding: 26px 24px 22px;
|
|
456
|
+
border: 0;
|
|
457
|
+
border-radius: 0;
|
|
458
|
+
background: transparent;
|
|
459
|
+
box-shadow: none;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.sc-modal-foot {
|
|
463
|
+
padding: 11px 14px;
|
|
464
|
+
border-top: 1.5px solid var(--sc-line, #e9e9eb);
|
|
465
|
+
text-align: center;
|
|
466
|
+
font-size: 12.5px;
|
|
467
|
+
color: var(--sc-muted, #6b6b73);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.sc-mark {
|
|
471
|
+
width: 44px;
|
|
472
|
+
height: 44px;
|
|
473
|
+
margin: 0 auto 14px;
|
|
474
|
+
border-radius: 11px;
|
|
475
|
+
|
|
476
|
+
background: var(--sc-accent, #18181b);
|
|
477
|
+
display: grid;
|
|
478
|
+
place-items: center;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.sc-mark svg {
|
|
482
|
+
width: 24px;
|
|
483
|
+
height: 24px;
|
|
484
|
+
color: var(--sc-accent-fg, #ffffff);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.sc-h {
|
|
488
|
+
font-size: 19px;
|
|
489
|
+
font-weight: 700;
|
|
490
|
+
letter-spacing: -0.02em;
|
|
491
|
+
margin: 0;
|
|
492
|
+
text-align: center;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.sc-p {
|
|
496
|
+
font-size: 14px;
|
|
497
|
+
color: var(--sc-muted, #6b6b73);
|
|
498
|
+
margin: 7px auto 0;
|
|
499
|
+
max-width: 40ch;
|
|
500
|
+
text-align: center;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.sc-opts {
|
|
504
|
+
display: grid;
|
|
505
|
+
gap: 10px;
|
|
506
|
+
margin-top: 20px;
|
|
507
|
+
text-align: left;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.sc-opt {
|
|
511
|
+
display: flex;
|
|
512
|
+
align-items: center;
|
|
513
|
+
gap: 10px;
|
|
514
|
+
width: 100%;
|
|
515
|
+
padding: 12px 14px;
|
|
516
|
+
border: 1.5px solid var(--sc-line-2, #dcdce0);
|
|
517
|
+
border-radius: 9px;
|
|
518
|
+
background: var(--sc-sheet, #ffffff);
|
|
519
|
+
color: var(--sc-text, #1a1a1c);
|
|
520
|
+
font: inherit;
|
|
521
|
+
font-size: 14.5px;
|
|
522
|
+
font-weight: 600;
|
|
523
|
+
text-align: left;
|
|
524
|
+
cursor: pointer;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.sc-opt:hover {
|
|
528
|
+
background: var(--sc-raised, #f6f6f7);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.sc-opt[aria-pressed="true"] {
|
|
532
|
+
border-color: var(--sc-accent, #18181b);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.sc-opt-mark {
|
|
536
|
+
width: 17px;
|
|
537
|
+
height: 17px;
|
|
538
|
+
flex: none;
|
|
539
|
+
|
|
540
|
+
color: currentColor;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.sc-opt-label {
|
|
544
|
+
min-width: 0;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.sc-opt-last {
|
|
548
|
+
margin-left: auto;
|
|
549
|
+
flex: none;
|
|
550
|
+
font-size: 11px;
|
|
551
|
+
font-weight: 600;
|
|
552
|
+
color: var(--sc-muted, #6b6b73);
|
|
553
|
+
background: var(--sc-raised, #f6f6f7);
|
|
554
|
+
border-radius: 5px;
|
|
555
|
+
padding: 3px 7px;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.sc-btn-quiet {
|
|
559
|
+
border-color: transparent;
|
|
560
|
+
background: transparent;
|
|
561
|
+
color: var(--sc-muted, #6b6b73);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.sc-btn-quiet:hover {
|
|
565
|
+
background: var(--sc-raised, #f6f6f7);
|
|
566
|
+
color: var(--sc-text, #1a1a1c);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.sc-btn-connected {
|
|
570
|
+
display: inline-flex;
|
|
571
|
+
align-items: center;
|
|
572
|
+
gap: 9px;
|
|
573
|
+
font-family: var(--sc-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
574
|
+
font-size: 13px;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.sc-btn-connected .sc-dot {
|
|
578
|
+
flex: none;
|
|
579
|
+
}
|
|
580
|
+
`,document.head.appendChild(e)}import{jsx as Ye}from"react/jsx-runtime";var Zt="https://www.soba.so",Yt=15e3;function Xt(e){let{children:t,session:n,sessionEndpoint:o,user:s,runEndpoint:r,theme:m,initialMachines:h,initialConnect:u=null,initialPlans:v,initialCurrentPlan:d=null,baseUrl:a=Zt,pollMs:f=Yt,injectStyles:R=!0,fetchImpl:w,onError:T}=e,[x,I]=$(n??null),[N,Y]=$(null),[g,B]=$(u),[j,U]=$(null),[V,q]=$(u===null),[H,ke]=$(h??[]),[K,ae]=$(null),[ie,fe]=$(h===void 0),[X,Se]=$(v??[]),[Q,ee]=$(d),[te,ce]=$(null),[ge,be]=$(v===void 0),le=Jt(T);le.current=T;let D=J(l=>{Y(l),le.current?.(l)},[]);Z(()=>{R&&Je()},[R]);let S=Ze(()=>new Ne({...n?{token:n}:{},...o?{endpoint:o}:{},...s?{user:s}:{},...w?{fetchImpl:w}:{}}),[n,o,s,w]);Z(()=>{let l=!0,k=S.subscribe(()=>{l&&I(S.token)});return!S.token&&!S.canRefresh?D(new Error("<SobaProvider> needs a `session` or a `sessionEndpoint`. A browser cannot mint one: that takes your secret key.")):S.ensure().then(()=>l&&I(S.token),A=>l&&D(A)),()=>{l=!1,k()}},[S,D]),Z(()=>{N&&(q(!1),fe(!1),be(!1),U(l=>l??N),ae(l=>l??N),ce(l=>l??N))},[N]),Z(()=>{let l=S.refreshAt;if(l===null)return;let k=setTimeout(()=>{S.refresh().catch(A=>D(A))},Math.max(0,l-Date.now()));return()=>clearTimeout(k)},[S,D,x]);let p=Ze(()=>x?new de({session:x,baseUrl:a,fetchImpl:vt(S,w??fetch.bind(globalThis))}):null,[x,a,S,w]);Z(()=>{if(!p)return;let l=!0;return q(!0),p.context().then(k=>{l&&(B(k),U(null),q(!1))},k=>{l&&(U(k),q(!1))}),()=>{l=!1}},[p]);let E=J(async()=>{if(p)try{let l=await p.machines();ke(l),ae(null)}catch(l){ae(l)}finally{fe(!1)}},[p]),F=J(async()=>{if(p)try{let l=await p.plans();Se(l.plans),ee(l.current),ce(null)}catch(l){ce(l)}finally{be(!1)}},[p]);Z(()=>{p&&F()},[p,F]);let ue=J(async l=>{if(!p)throw new Error("No session yet.");await p.selectPlan(l),await F()},[p,F]),i=J(async(l,k)=>{if(!p)throw new Error("No session yet.");let A=await p.checkoutPlan(l,k);if(!A.url)throw new Error("Checkout could not be started.");return A.url},[p]),C=J(async()=>{if(!p)throw new Error("No session yet.");await p.leavePlan(),await F()},[p,F]);Z(()=>{if(!p)return;let l=!0;E();let k=setInterval(()=>{typeof document<"u"&&document.hidden||l&&E()},f);return()=>{l=!1,clearInterval(k)}},[p,E,f]);let P=J(async l=>{if(!r)throw new Error("Pass `runEndpoint` to <SobaProvider> to start runs from the browser. It is your own route: it holds your secret key, calls soba.run(), and forwards the stream.");let{signal:k,...A}=l,Pe=await(w??fetch.bind(globalThis))(r,{method:"POST",headers:{"content-type":"application/json",accept:"text/event-stream"},credentials:"same-origin",body:JSON.stringify(A),...k?{signal:k}:{}});if(!Pe.ok)throw new Error(`${r} answered ${Pe.status}.`);return Ke(Pe)},[r,w]),ne=Ze(()=>({session:x,client:p,error:N,plan:{plans:X,current:Q,plan:Q?X.find(l=>l.id===Q.plan_id)??null:null,loading:ge,error:te,select:ue,checkout:i,leave:C,refresh:F},compute:{machines:H,connected:H.length>0,online:H.some(l=>l.state!=="offline"),loading:ie,error:K,refresh:E},connect:{app:g?.app??null,command:g?.command??null,publishableKey:g?.publishable_key??null,loading:V,error:j},run:P}),[x,p,N,H,ie,K,E,X,Q,ge,te,ue,i,C,F,g,V,j,P]);return Ye(Fe.Provider,{value:p,children:Ye(je.Provider,{value:{context:g,machines:ie?void 0:H,theme:m??g?.theme??"light"},children:Ye(De.Provider,{value:ne,children:t})})})}export{Ot as ComputeStatus,ze as ConnectButton,qt as ConnectCompute,He as ConnectModal,jt as SobaCheckout,zt as SobaPlans,Xt as SobaProvider,Ft as collectText,Je as injectStyles,we as parseEvent,Ke as readEvents,_ as useSoba};
|
package/dist/styles.css
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
--sc-ok: #22a06b;
|
|
13
13
|
--sc-warn: #b06f14;
|
|
14
14
|
--sc-r: 10px;
|
|
15
|
+
|
|
16
|
+
--sc-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
15
17
|
|
|
16
18
|
box-sizing: border-box;
|
|
17
19
|
max-width: 560px;
|
|
@@ -232,8 +234,13 @@ a.sc-btn {
|
|
|
232
234
|
font-size: 14px;
|
|
233
235
|
}
|
|
234
236
|
|
|
237
|
+
.soba-connect[data-theme="dark"],
|
|
238
|
+
.soba-connect[data-theme="system"] {
|
|
239
|
+
color-scheme: dark;
|
|
240
|
+
}
|
|
241
|
+
|
|
235
242
|
@media (prefers-color-scheme: dark) {
|
|
236
|
-
.soba-connect
|
|
243
|
+
.soba-connect[data-theme="system"] {
|
|
237
244
|
--sc-ground: #0f0f11;
|
|
238
245
|
--sc-sheet: #161618;
|
|
239
246
|
--sc-raised: #1d1d20;
|
|
@@ -247,6 +254,19 @@ a.sc-btn {
|
|
|
247
254
|
}
|
|
248
255
|
}
|
|
249
256
|
|
|
257
|
+
.soba-connect[data-theme="dark"] {
|
|
258
|
+
--sc-ground: #0f0f11;
|
|
259
|
+
--sc-sheet: #161618;
|
|
260
|
+
--sc-raised: #1d1d20;
|
|
261
|
+
--sc-line: #26262a;
|
|
262
|
+
--sc-line-2: #34343a;
|
|
263
|
+
--sc-text: #f2f2f3;
|
|
264
|
+
--sc-text-2: #c9c9cd;
|
|
265
|
+
--sc-muted: #8d8d95;
|
|
266
|
+
--sc-accent: #f2f2f3;
|
|
267
|
+
--sc-accent-fg: #131316;
|
|
268
|
+
}
|
|
269
|
+
|
|
250
270
|
.sc-plans {
|
|
251
271
|
display: grid;
|
|
252
272
|
gap: 12px;
|
|
@@ -370,3 +390,186 @@ a.sc-btn {
|
|
|
370
390
|
border-bottom: 1px solid var(--sc-line, #e9e9eb);
|
|
371
391
|
margin-bottom: 2px;
|
|
372
392
|
}
|
|
393
|
+
|
|
394
|
+
.sc-modal {
|
|
395
|
+
|
|
396
|
+
position: fixed;
|
|
397
|
+
inset: 0;
|
|
398
|
+
width: 100%;
|
|
399
|
+
max-width: 100%;
|
|
400
|
+
height: 100%;
|
|
401
|
+
max-height: 100%;
|
|
402
|
+
margin: 0;
|
|
403
|
+
padding: 16px;
|
|
404
|
+
border: 0;
|
|
405
|
+
background: transparent;
|
|
406
|
+
overflow: auto;
|
|
407
|
+
|
|
408
|
+
display: grid;
|
|
409
|
+
place-items: center;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.sc-modal::backdrop {
|
|
413
|
+
background: rgb(15 15 17 / 55%);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.sc-modal-sheet {
|
|
417
|
+
position: relative;
|
|
418
|
+
width: 100%;
|
|
419
|
+
|
|
420
|
+
max-width: 420px;
|
|
421
|
+
background: var(--sc-sheet, #ffffff);
|
|
422
|
+
border: 1.5px solid var(--sc-line, #e9e9eb);
|
|
423
|
+
border-radius: 14px;
|
|
424
|
+
box-shadow: 0 24px 60px -12px rgb(15 15 17 / 22%);
|
|
425
|
+
|
|
426
|
+
outline: none;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.sc-modal-close {
|
|
430
|
+
position: absolute;
|
|
431
|
+
top: 10px;
|
|
432
|
+
right: 12px;
|
|
433
|
+
z-index: 1;
|
|
434
|
+
border: 0;
|
|
435
|
+
background: transparent;
|
|
436
|
+
color: var(--sc-muted, #6b6b73);
|
|
437
|
+
font: inherit;
|
|
438
|
+
font-size: 20px;
|
|
439
|
+
line-height: 1;
|
|
440
|
+
padding: 4px 8px;
|
|
441
|
+
border-radius: 6px;
|
|
442
|
+
cursor: pointer;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.sc-modal-close:hover {
|
|
446
|
+
background: var(--sc-raised, #f6f6f7);
|
|
447
|
+
color: var(--sc-text, #1a1a1c);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.sc-modal-sheet .sc-card {
|
|
451
|
+
padding: 26px 24px 22px;
|
|
452
|
+
border: 0;
|
|
453
|
+
border-radius: 0;
|
|
454
|
+
background: transparent;
|
|
455
|
+
box-shadow: none;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.sc-modal-foot {
|
|
459
|
+
padding: 11px 14px;
|
|
460
|
+
border-top: 1.5px solid var(--sc-line, #e9e9eb);
|
|
461
|
+
text-align: center;
|
|
462
|
+
font-size: 12.5px;
|
|
463
|
+
color: var(--sc-muted, #6b6b73);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.sc-mark {
|
|
467
|
+
width: 44px;
|
|
468
|
+
height: 44px;
|
|
469
|
+
margin: 0 auto 14px;
|
|
470
|
+
border-radius: 11px;
|
|
471
|
+
|
|
472
|
+
background: var(--sc-accent, #18181b);
|
|
473
|
+
display: grid;
|
|
474
|
+
place-items: center;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.sc-mark svg {
|
|
478
|
+
width: 24px;
|
|
479
|
+
height: 24px;
|
|
480
|
+
color: var(--sc-accent-fg, #ffffff);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.sc-h {
|
|
484
|
+
font-size: 19px;
|
|
485
|
+
font-weight: 700;
|
|
486
|
+
letter-spacing: -0.02em;
|
|
487
|
+
margin: 0;
|
|
488
|
+
text-align: center;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.sc-p {
|
|
492
|
+
font-size: 14px;
|
|
493
|
+
color: var(--sc-muted, #6b6b73);
|
|
494
|
+
margin: 7px auto 0;
|
|
495
|
+
max-width: 40ch;
|
|
496
|
+
text-align: center;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.sc-opts {
|
|
500
|
+
display: grid;
|
|
501
|
+
gap: 10px;
|
|
502
|
+
margin-top: 20px;
|
|
503
|
+
text-align: left;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.sc-opt {
|
|
507
|
+
display: flex;
|
|
508
|
+
align-items: center;
|
|
509
|
+
gap: 10px;
|
|
510
|
+
width: 100%;
|
|
511
|
+
padding: 12px 14px;
|
|
512
|
+
border: 1.5px solid var(--sc-line-2, #dcdce0);
|
|
513
|
+
border-radius: 9px;
|
|
514
|
+
background: var(--sc-sheet, #ffffff);
|
|
515
|
+
color: var(--sc-text, #1a1a1c);
|
|
516
|
+
font: inherit;
|
|
517
|
+
font-size: 14.5px;
|
|
518
|
+
font-weight: 600;
|
|
519
|
+
text-align: left;
|
|
520
|
+
cursor: pointer;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.sc-opt:hover {
|
|
524
|
+
background: var(--sc-raised, #f6f6f7);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.sc-opt[aria-pressed="true"] {
|
|
528
|
+
border-color: var(--sc-accent, #18181b);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.sc-opt-mark {
|
|
532
|
+
width: 17px;
|
|
533
|
+
height: 17px;
|
|
534
|
+
flex: none;
|
|
535
|
+
|
|
536
|
+
color: currentColor;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.sc-opt-label {
|
|
540
|
+
min-width: 0;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.sc-opt-last {
|
|
544
|
+
margin-left: auto;
|
|
545
|
+
flex: none;
|
|
546
|
+
font-size: 11px;
|
|
547
|
+
font-weight: 600;
|
|
548
|
+
color: var(--sc-muted, #6b6b73);
|
|
549
|
+
background: var(--sc-raised, #f6f6f7);
|
|
550
|
+
border-radius: 5px;
|
|
551
|
+
padding: 3px 7px;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.sc-btn-quiet {
|
|
555
|
+
border-color: transparent;
|
|
556
|
+
background: transparent;
|
|
557
|
+
color: var(--sc-muted, #6b6b73);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.sc-btn-quiet:hover {
|
|
561
|
+
background: var(--sc-raised, #f6f6f7);
|
|
562
|
+
color: var(--sc-text, #1a1a1c);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.sc-btn-connected {
|
|
566
|
+
display: inline-flex;
|
|
567
|
+
align-items: center;
|
|
568
|
+
gap: 9px;
|
|
569
|
+
font-family: var(--sc-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
570
|
+
font-size: 13px;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.sc-btn-connected .sc-dot {
|
|
574
|
+
flex: none;
|
|
575
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -22,6 +22,18 @@ export interface Runtime {
|
|
|
22
22
|
*/
|
|
23
23
|
authenticated: boolean | null;
|
|
24
24
|
auth_hint: string | null;
|
|
25
|
+
/**
|
|
26
|
+
* A runtime that appeared on a machine that was ALREADY paired, so it waits
|
|
27
|
+
* for the person to say so — what arrived with the pairing was approved by
|
|
28
|
+
* the pairing, and this did not.
|
|
29
|
+
*
|
|
30
|
+
* Optional, which is how it went missing from this copy: `types-parity.ts`
|
|
31
|
+
* compares the two shapes both ways, and an interface with an extra OPTIONAL
|
|
32
|
+
* property still extends one without it, in both directions. So the guard
|
|
33
|
+
* passes on a drift of exactly this kind. Worth knowing before trusting it.
|
|
34
|
+
*/
|
|
35
|
+
quarantined?: boolean;
|
|
36
|
+
first_seen_at?: string;
|
|
25
37
|
}
|
|
26
38
|
export interface Machine {
|
|
27
39
|
id: string;
|
|
@@ -50,7 +62,17 @@ export interface PendingCode {
|
|
|
50
62
|
denied: boolean;
|
|
51
63
|
}
|
|
52
64
|
/** Everything the approval page needs, in one request. */
|
|
65
|
+
export type ConnectTheme = "light" | "dark" | "system";
|
|
53
66
|
export interface ConnectContext {
|
|
67
|
+
/**
|
|
68
|
+
* How this app wants the component to look, set on its Settings page.
|
|
69
|
+
*
|
|
70
|
+
* Light unless the app says otherwise, and `system` is the opt-in — a
|
|
71
|
+
* component inside somebody else's page should not follow the reader's OS
|
|
72
|
+
* into dark on its own. Optional so an older server that does not send it
|
|
73
|
+
* still parses.
|
|
74
|
+
*/
|
|
75
|
+
theme?: ConnectTheme;
|
|
54
76
|
app: {
|
|
55
77
|
name: string;
|
|
56
78
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soba-so/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Connect the AI your users already pay for, from your own React app.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://soba.so/docs/components",
|
|
@@ -19,18 +19,13 @@
|
|
|
19
19
|
"!dist/**/*.map",
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "node ../../scripts/build-react.mjs",
|
|
24
|
-
"typecheck": "tsc -p tsconfig.json",
|
|
25
|
-
"prepublishOnly": "node ../../scripts/build-react.mjs"
|
|
26
|
-
},
|
|
27
22
|
"peerDependencies": {
|
|
28
23
|
"react": ">=18"
|
|
29
24
|
},
|
|
30
25
|
"devDependencies": {
|
|
31
|
-
"@soba-so/connect-ui": "workspace:*",
|
|
32
26
|
"@types/react": "^19.0.0",
|
|
33
|
-
"typescript": "^7.0.2"
|
|
27
|
+
"typescript": "^7.0.2",
|
|
28
|
+
"@soba-so/connect-ui": "0.1.0"
|
|
34
29
|
},
|
|
35
30
|
"engines": {
|
|
36
31
|
"node": ">=22"
|
|
@@ -44,5 +39,9 @@
|
|
|
44
39
|
],
|
|
45
40
|
"publishConfig": {
|
|
46
41
|
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "node ../../scripts/build-react.mjs",
|
|
45
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
47
46
|
}
|
|
48
|
-
}
|
|
47
|
+
}
|