@soba-so/react 0.0.6 → 0.0.8
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/SobaProvider.d.ts +26 -0
- package/dist/index.mjs +6 -6
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/SobaProvider.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
+
import type { ConnectContext, CurrentPlan, Machine, Plan } from "./types.js";
|
|
2
3
|
export interface SobaProviderProps {
|
|
3
4
|
children: ReactNode;
|
|
4
5
|
/**
|
|
@@ -26,6 +27,31 @@ export interface SobaProviderProps {
|
|
|
26
27
|
/** Your route that starts a run with `@soba-so/sdk` and forwards the stream. */
|
|
27
28
|
runEndpoint?: string;
|
|
28
29
|
baseUrl?: string;
|
|
30
|
+
/**
|
|
31
|
+
* PLANS THE SERVER ALREADY FETCHED, so the first paint has prices in it.
|
|
32
|
+
*
|
|
33
|
+
* Without these the component cannot draw a price until two round trips have
|
|
34
|
+
* finished in sequence — mint an `est_` session, then read `/v1/plans` with
|
|
35
|
+
* it — and both start after mount, so every pricing page opened on a spinner.
|
|
36
|
+
* Neither trip can move to the server, because the session is per end user
|
|
37
|
+
* and the browser is the only party that has one.
|
|
38
|
+
*
|
|
39
|
+
* A server holding the app's secret key can read the same list directly
|
|
40
|
+
* (`soba.plans.list()`), and passing it here seeds the state rather than
|
|
41
|
+
* replacing the fetch: the provider still refreshes in the background, so a
|
|
42
|
+
* price changed since the page was rendered still corrects itself.
|
|
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;
|
|
52
|
+
initialPlans?: Plan[];
|
|
53
|
+
/** What that user is on, if the server asked. Pairs with `initialPlans`. */
|
|
54
|
+
initialCurrentPlan?: CurrentPlan | null;
|
|
29
55
|
pollMs?: number;
|
|
30
56
|
/** The stylesheet injects itself once. Turn it off to ship your own. */
|
|
31
57
|
injectStyles?: boolean;
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
|
-
var ee=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 ee(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 x,jsxs as V}from"react/jsx-runtime";function W({machines:e,onApproveRuntime:t}){return e.length?x("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:[x("span",{className:`sc-dot sc-dot-${c.tone}`,"aria-hidden":"true"}),x("strong",{children:n.label??"This machine"})]}),x("span",{className:"sc-note",children:c.text})]}),x("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:[x("span",{children:s.display_name}),t?x("button",{type:"button",className:"sc-btn",onClick:()=>t(n.id,s.id),children:"New here. Let it serve"}):x("span",{className:"sc-tone-warn",children:"new, not serving yet"})]},s.id):V("li",{className:"sc-row-between",children:[x("span",{children:s.display_name}),x("span",{className:`sc-tone-${r.tone}`,children:r.text})]},s.id)}),!n.runtimes.length&&x("li",{className:"sc-note",children:"No runtime advertised. Install Claude Code, Codex or Ollama on that machine."})]})]},n.id)})}):x("p",{className:"sc-note",children:"Nothing connected yet."})}import{useCallback as te,useEffect as pe,useMemo as Fe,useRef as je,useState as $}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 Ee,jsx as a,jsxs as f}from"react/jsx-runtime";var De=2e3,Be=1e4;function de({client:e,initialCode:t,onConnected:n,showStatus:c=!0}){let[s,r]=$("loading"),[u,y]=$(null),[g,h]=$([]),[b,o]=$(null),[m,v]=$(t??""),[k,D]=$(null),[A,_]=$(null),[I,R]=$(!1),q=je(null);pe(()=>{let l=!0;return Promise.all([e.context(),e.machines()]).then(([w,N])=>{l&&(y(w),h(N),q.current=new Set(N.map(T=>T.id)),t?r("code"):c&&N.length>0?r("manage"):r("choose"))}).catch(w=>{l&&(_(w.message),r("choose"))}),()=>{l=!1}},[e,t,c]),pe(()=>{if(s!=="manage")return;let l=!0,w=setInterval(async()=>{try{let N=await e.machines();l&&h(N)}catch{}},Be);return()=>{l=!1,clearInterval(w)}},[s,e]),pe(()=>{if(s!=="waiting")return;let l=!0,w=setInterval(async()=>{try{let N=await e.machines();if(!l)return;h(N);let T=N.filter(H=>!q.current?.has(H.id));(T.find(H=>H.state!=="offline")??T[0])&&(r("connected"),n?.(N))}catch{}},De);return()=>{l=!1,clearInterval(w)}},[s,e,n]);let B=te(async()=>{_(null),R(!0);try{let l=await e.lookup(m);D(l),r("confirm")}catch(l){_(l.message)}finally{R(!1)}},[e,m]),oe=te(async()=>{_(null),R(!0);try{await e.approve(m),r("waiting")}catch(l){_(l.message)}finally{R(!1)}},[e,m]),Z=te(async()=>{R(!0);try{await e.deny(m),r("denied")}catch(l){_(l.message)}finally{R(!1)}},[e,m]),G=Fe(()=>u?.connect_url??null,[u]),Y=te(async(l,w)=>{try{await e.approveRuntime(l,w),h(await e.machines())}catch(N){_(N.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"})})}):f("div",{className:"soba-connect",children:[s==="manage"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Your compute"}),f("p",{className:"sc-sub",children:[u?.app.name??"This app"," runs on these machines, while they are awake."]}),a(W,{machines:g,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"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Use the AI you already pay for"}),f("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=>f("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",{}),f("span",{className:"sc-choice-meta",children:[l.cost," \xB7 ",l.prerequisite]})]},l.id))}),b&&f("div",{className:"sc-stack",style:{marginTop:16},children:[a("p",{className:"sc-note",children:b.hint}),G?f(Ee,{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:G}),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"&&f("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."}),f("form",{className:"sc-stack",onSubmit:l=>{l.preventDefault(),B()},children:[a("input",{className:"sc-input",value:m,onChange:l=>v(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:I||m.length<8,children:I?"Checking\u2026":"Continue"})]})]}),s==="confirm"&&k&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Attach this machine to your account?"}),f("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."]}),f("ul",{className:"sc-facts",children:[a(X,{k:"Code",v:k.user_code}),a(X,{k:"Machine",v:k.hostname??"not stated"}),a(X,{k:"System",v:[k.platform,k.arch].filter(Boolean).join(" / ")||"not stated"}),a(X,{k:"Asked by",v:k.client??"not stated"}),k.labels.length>0&&a(X,{k:"Labels",v:k.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}),f("div",{className:"sc-row",style:{marginTop:16},children:[a("button",{type:"button",className:"sc-btn sc-btn-primary",onClick:()=>void oe(),disabled:I,children:"Approve"}),a("button",{type:"button",className:"sc-btn",onClick:()=>void Z(),disabled:I,children:"No, deny it"})]})]}),s==="waiting"&&f("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"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Connected"}),f("p",{className:"sc-sub",children:[u?.app.name??"This app"," will use this machine from now on, while it is awake."]}),c&&f(Ee,{children:[a(W,{machines:g,onApproveRuntime:Y}),a("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>r("manage"),children:"Done"})]})]}),s==="denied"&&f("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 f("li",{className:"sc-fact",children:[a("span",{className:"sc-fact-key",children:e}),a("span",{children:t})]})}import{createContext as Ge,useContext as He}from"react";var me=Ge(null);function M(){let e=He(me);if(!e)throw new Error("useSoba() must be used inside a <SobaProvider>.");return e}import{jsx as J}from"react/jsx-runtime";function Ve({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 We,useContext as Je}from"react";var he=We(null);function ke(){return Je(he)}import{jsx as U}from"react/jsx-runtime";function Ye({initialCode:e,onConnected:t,className:n,showStatus:c}){let{error:s}=M(),r=ke();return s?U("div",{className:_e(n),children:U("div",{className:"sc-card",children:U("p",{className:"sc-error",children:s.message})})}):r?U("div",{className:n,children:U(de,{client:r,...e?{initialCode:e}:{},...t?{onConnected:t}:{},...c===void 0?{}:{showStatus:c}})}):U("div",{className:_e(n),children:U("div",{className:"sc-card",children:U("p",{className:"sc-note",children:"Loading\u2026"})})})}function _e(e){return e?`soba-connect ${e}`:"soba-connect"}var ze=new Set(["delta","thinking","status","done","error"]);function ne(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"||!ze.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 g=await c.read();if(g.done)break;for(r+=s.decode(g.value,{stream:!0});;){let h=r.indexOf(`
|
|
3
|
-
`);if(
|
|
4
|
-
`);if(u=[],o==="[DONE]")return;let
|
|
5
|
-
`);if(
|
|
2
|
+
var se=class extends Error{status;constructor(t,n){super(t),this.name="ConnectError",this.status=n}},Z=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 se(u,s.status)}return r}};function me(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 he(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 _,jsxs as H}from"react/jsx-runtime";function V({machines:e,onApproveRuntime:t}){return e.length?_("div",{children:e.map(n=>{let c=he(n);return H("div",{className:"sc-machine",children:[H("div",{className:"sc-row-between",children:[H("div",{className:"sc-row",children:[_("span",{className:`sc-dot sc-dot-${c.tone}`,"aria-hidden":"true"}),_("strong",{children:n.label??"This machine"})]}),_("span",{className:"sc-note",children:c.text})]}),_("p",{className:"sc-note",children:[n.platform,n.arch,n.version&&`worker ${n.version}`,n.always_on&&"always on"].filter(Boolean).join(" \xB7 ")}),H("ul",{className:"sc-runtimes",children:[n.runtimes.map(s=>{let r=me(s);return s.quarantined?H("li",{className:"sc-row-between",children:[_("span",{children:s.display_name}),t?_("button",{type:"button",className:"sc-btn",onClick:()=>t(n.id,s.id),children:"New here. Let it serve"}):_("span",{className:"sc-tone-warn",children:"new, not serving yet"})]},s.id):H("li",{className:"sc-row-between",children:[_("span",{children:s.display_name}),_("span",{className:`sc-tone-${r.tone}`,children:r.text})]},s.id)}),!n.runtimes.length&&_("li",{className:"sc-note",children:"No runtime advertised. Install Claude Code, Codex or Ollama on that machine."})]})]},n.id)})}):_("p",{className:"sc-note",children:"Nothing connected yet."})}import{useCallback as re,useEffect as oe,useMemo as Je,useRef as Ye,useState as L}from"react";var fe=[{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 Ae,jsx as a,jsxs as g}from"react/jsx-runtime";var ze=2e3,Ke=1e4;function ge({client:e,initialCode:t,onConnected:n,showStatus:c=!0,initialContext:s=null,initialMachines:r}){let u=!!s&&r!==void 0,[p,d]=L(u?t?"code":c&&(r?.length??0)>0?"manage":"choose":"loading"),[y,S]=L(s),[o,f]=L(r??[]),[b,te]=L(null),[C,ne]=L(t??""),[v,le]=L(null),[P,M]=L(null),[U,T]=L(!1),J=Ye(null);oe(()=>{if(!e||u)return;let l=!0;return Promise.all([e.context(),e.machines()]).then(([E,w])=>{l&&(S(E),f(w),J.current=new Set(w.map(G=>G.id)),t?d("code"):c&&w.length>0?d("manage"):d("choose"))}).catch(E=>{l&&(M(E.message),d("choose"))}),()=>{l=!1}},[e,t,c,u]),oe(()=>{r&&f(r)},[r]),oe(()=>{if(!e||p!=="manage")return;let l=!0,E=setInterval(async()=>{try{let w=await e.machines();l&&f(w)}catch{}},Ke);return()=>{l=!1,clearInterval(E)}},[p,e]),oe(()=>{if(!e||p!=="waiting")return;let l=!0,E=setInterval(async()=>{try{let w=await e.machines();if(!l)return;f(w);let G=w.filter(K=>!J.current?.has(K.id));(G.find(K=>K.state!=="offline")??G[0])&&(d("connected"),n?.(w))}catch{}},ze);return()=>{l=!1,clearInterval(E)}},[p,e,n]);let D=re(async()=>{if(e){M(null),T(!0);try{let l=await e.lookup(C);le(l),d("confirm")}catch(l){M(l.message)}finally{T(!1)}}},[e,C]),q=re(async()=>{if(e){M(null),T(!0);try{await e.approve(C),d("waiting")}catch(l){M(l.message)}finally{T(!1)}}},[e,C]),ue=re(async()=>{if(e){T(!0);try{await e.deny(C),d("denied")}catch(l){M(l.message)}finally{T(!1)}}},[e,C]),Y=Je(()=>y?.connect_url??null,[y]),B=re(async(l,E)=>{if(e)try{await e.approveRuntime(l,E),f(await e.machines())}catch(w){M(w.message)}},[e]);return p==="loading"?a("div",{className:"soba-connect",children:a("div",{className:"sc-card",children:a("p",{className:"sc-note",children:"Loading\u2026"})})}):g("div",{className:"soba-connect",children:[p==="manage"&&g("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Your compute"}),g("p",{className:"sc-sub",children:[y?.app.name??"This app"," runs on these machines, while they are awake."]}),a(V,{machines:o,onApproveRuntime:B}),P&&a("p",{className:"sc-error",children:P}),a("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>d("choose"),children:"Connect another machine"})]}),p==="choose"&&g("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Use the AI you already pay for"}),g("p",{className:"sc-sub",children:[y?.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:fe.map(l=>g("button",{type:"button",className:"sc-choice","aria-pressed":b?.id===l.id,onClick:()=>te(l),children:[a("span",{className:"sc-choice-title",children:l.title}),a("br",{}),g("span",{className:"sc-choice-meta",children:[l.cost," \xB7 ",l.prerequisite]})]},l.id))}),b&&g("div",{className:"sc-stack",style:{marginTop:16},children:[a("p",{className:"sc-note",children:b.hint}),Y?g(Ae,{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:Y}),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:()=>d("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."})]})]}),p==="code"&&g("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."}),g("form",{className:"sc-stack",onSubmit:l=>{l.preventDefault(),D()},children:[a("input",{className:"sc-input",value:C,onChange:l=>ne(l.target.value),placeholder:"ACDE-F234",autoComplete:"off",autoFocus:!0,"aria-label":"Pairing code"}),P&&a("p",{className:"sc-error",children:P}),a("button",{type:"submit",className:"sc-btn sc-btn-primary",disabled:U||C.length<8,children:U?"Checking\u2026":"Continue"})]})]}),p==="confirm"&&v&&g("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Attach this machine to your account?"}),g("p",{className:"sc-sub",children:["Once you approve, ",y?.app.name??"this app"," may send your runs to it. It can never see the account or the keys on it."]}),g("ul",{className:"sc-facts",children:[a(ee,{k:"Code",v:v.user_code}),a(ee,{k:"Machine",v:v.hostname??"not stated"}),a(ee,{k:"System",v:[v.platform,v.arch].filter(Boolean).join(" / ")||"not stated"}),a(ee,{k:"Asked by",v:v.client??"not stated"}),v.labels.length>0&&a(ee,{k:"Labels",v: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."}),P&&a("p",{className:"sc-error",children:P}),g("div",{className:"sc-row",style:{marginTop:16},children:[a("button",{type:"button",className:"sc-btn sc-btn-primary",onClick:()=>void q(),disabled:U,children:"Approve"}),a("button",{type:"button",className:"sc-btn",onClick:()=>void ue(),disabled:U,children:"No, deny it"})]})]}),p==="waiting"&&g("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."})]}),p==="connected"&&g("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Connected"}),g("p",{className:"sc-sub",children:[y?.app.name??"This app"," will use this machine from now on, while it is awake."]}),c&&g(Ae,{children:[a(V,{machines:o,onApproveRuntime:B}),a("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>d("manage"),children:"Done"})]})]}),p==="denied"&&g("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 ee({k:e,v:t}){return g("li",{className:"sc-fact",children:[a("span",{className:"sc-fact-key",children:e}),a("span",{children:t})]})}import{createContext as Qe,useContext as Xe}from"react";var ye=Qe(null);function A(){let e=Xe(ye);if(!e)throw new Error("useSoba() must be used inside a <SobaProvider>.");return e}import{jsx as W}from"react/jsx-runtime";function Ze({className:e}){let{compute:t}=A(),n=e?`soba-connect ${e}`:"soba-connect";return t.loading&&!t.machines.length?W("div",{className:n,children:W("p",{className:"sc-note",children:"Loading\u2026"})}):t.error&&!t.machines.length?W("div",{className:n,children:W("p",{className:"sc-error",children:t.error.message})}):W("div",{className:n,children:W(V,{machines:t.machines})})}import{createContext as Ie,useContext as Oe}from"react";var be=Ie(null);function Le(){return Oe(be)}var ve=Ie({context:null,machines:void 0});function $e(){return Oe(ve)}import{jsx as $}from"react/jsx-runtime";function et({initialCode:e,onConnected:t,className:n,showStatus:c}){let{error:s}=A(),r=Le(),u=$e();return s?$("div",{className:Ue(n),children:$("div",{className:"sc-card",children:$("p",{className:"sc-error",children:s.message})})}):!r&&!u.context?$("div",{className:Ue(n),children:$("div",{className:"sc-card",children:$("p",{className:"sc-note",children:"Loading\u2026"})})}):$("div",{className:n,children:$(ge,{client:r,initialContext:u.context,initialMachines:u.machines,...e?{initialCode:e}:{},...t?{onConnected:t}:{},...c===void 0?{}:{showStatus:c}})})}function Ue(e){return e?`soba-connect ${e}`:"soba-connect"}var tt=new Set(["delta","thinking","status","done","error"]);function ae(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"||!tt.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*Ce(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 d=await c.read();if(d.done)break;for(r+=s.decode(d.value,{stream:!0});;){let y=r.indexOf(`
|
|
3
|
+
`);if(y===-1)break;let S=Fe(r.slice(0,y));if(r=r.slice(y+1),n){let o=S.trim()?ae(S):null;o&&(yield o);continue}if(S===""){let o=u.join(`
|
|
4
|
+
`);if(u=[],o==="[DONE]")return;let f=o?ae(o):null;f&&(yield f);continue}qe(S,u)}}!n&&r&&qe(Fe(r),u);let p=n?r.trim():u.join(`
|
|
5
|
+
`);if(p&&p!=="[DONE]"){let d=ae(p);d&&(yield d)}}finally{c.cancel().catch(()=>{})}}function qe(e,t){if(!e||e.startsWith(":"))return;let n=e.indexOf(":");if(n===-1||e.slice(0,n)!=="data")return;let c=e.slice(n+1);t.push(c.startsWith(" ")?c.slice(1):c)}function Fe(e){return e.endsWith("\r")?e.slice(0,-1):e}async function nt(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 je}from"react";import{jsx as De,jsxs as rt}from"react/jsx-runtime";function st({plan:e,successUrl:t,cancelUrl:n,label:c,className:s}){let{plan:r}=A(),[u,p]=je(!1),[d,y]=je(null);async function S(){p(!0),y(null);try{let o=await r.checkout(e.id,{...t?{successUrl:t}:{},...n?{cancelUrl:n}:{}});window.location.assign(o)}catch(o){y(o.message),p(!1)}}return rt("div",{className:s?`soba-connect ${s}`:"soba-connect",children:[De("button",{type:"button",className:"sc-btn sc-btn-primary",disabled:u,onClick:()=>void S(),children:u?"Opening\u2026":c??`Subscribe \u2014 $${e.price.toLocaleString()}/${e.interval}`}),d&&De("p",{className:"sc-error",children:d})]})}import{useState as Be}from"react";import{Fragment as ct,jsx as h,jsxs as N}from"react/jsx-runtime";var ot={"user-hardware":"your own machine","user-subscription":"your own AI subscription","open-weights":"open models we host",frontier:"frontier models"},at=new Set(["user-hardware","user-subscription"]);function ie(e){return e?`soba-connect ${e}`:"soba-connect"}function it({onSelect:e,successUrl:t,cancelUrl:n,showCompute:c=!0,className:s}){let{plan:r}=A(),[u,p]=Be(null),[d,y]=Be(null);if(r.error)return h("div",{className:ie(s),children:h("div",{className:"sc-card",children:h("p",{className:"sc-error",children:r.error.message})})});if(r.loading)return h("div",{className:ie(s),children:h("div",{className:"sc-card",children:h("p",{className:"sc-note",children:"Loading plans\u2026"})})});if(r.plans.length===0)return h("div",{className:ie(s),children:h("div",{className:"sc-card",children:h("p",{className:"sc-note",children:"This app has no plans yet."})})});async function S(o){p(o.id),y(null);try{if(o.selectable)await r.select(o.id);else if(e)e(o);else{let f=await r.checkout(o.id,{...t?{successUrl:t}:{},...n?{cancelUrl:n}:{}});window.location.assign(f);return}}catch(f){y(f.message)}finally{p(null)}}return N("div",{className:ie(s),children:[h("div",{className:"sc-plans",children:r.plans.map(o=>{let f=r.current?.plan_id===o.id;return N("div",{className:f?"sc-plan sc-plan-current":"sc-plan",children:[N("div",{className:"sc-row-between",children:[h("span",{className:"sc-title",children:o.name}),f&&h("span",{className:"sc-plan-badge",children:"Current"})]}),o.description&&h("p",{className:"sc-sub",children:o.description}),h("p",{className:"sc-plan-price",children:o.price===0?h("span",{className:"sc-plan-amount",children:"Free"}):N(ct,{children:[N("span",{className:"sc-plan-amount",children:["$",o.price.toLocaleString()]}),N("span",{className:"sc-plan-interval",children:["/",o.interval]})]})}),N("div",{className:"sc-facts",children:[N("div",{className:"sc-fact",children:[h("span",{className:"sc-fact-key",children:"Included"}),h("span",{children:o.included_units===null?"Unlimited":`${o.included_units.toLocaleString()} ${o.unit}${o.included_units===1?"":"s"}`})]}),o.overage_per_unit>0&&N("div",{className:"sc-fact",children:[h("span",{className:"sc-fact-key",children:"Then"}),N("span",{children:["$",o.overage_per_unit," / ",o.unit]})]}),o.trial_days>0&&N("div",{className:"sc-fact",children:[h("span",{className:"sc-fact-key",children:"Trial"}),N("span",{children:[o.trial_days," days"]})]}),c&&N("div",{className:"sc-plan-compute",children:[h("span",{className:"sc-fact-key",children:"Runs on"}),o.route_allow===null||o.route_allow.length===0?h("span",{children:o.route_allow===null?"any compute":"no compute"}):h("ul",{className:"sc-compute",children:o.route_allow.map(b=>h("li",{className:at.has(b)?"sc-compute-own":void 0,children:ot[b]??b},b))})]})]}),h("button",{type:"button",className:f?"sc-btn":"sc-btn sc-btn-primary",disabled:f||u!==null,onClick:()=>void S(o),children:f?"Your plan":u===o.id?o.selectable?"Switching\u2026":"Opening\u2026":o.selectable?"Choose":`Subscribe \u2014 $${o.price.toLocaleString()}/${o.interval}`})]},o.id)})}),d&&h("p",{className:"sc-error",children:d})]})}import{useCallback as F,useEffect as j,useMemo as xe,useRef as ut,useState as R}from"react";var ce=class{#t;#s=null;#n=null;#e;#r=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.#r.add(t),()=>this.#r.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 c=await n(t,{method:"POST",headers:{"content-type":"application/json"},credentials:"same-origin",body:JSON.stringify(this.#e.user?{user:this.#e.user}:{})});if(!c.ok)throw new Error(`Could not start a Soba session (${c.status} from ${t}).`);let s=await c.json(),r=s.session??s.token;if(!r)throw new Error(`${t} answered without a \`session\`. See @soba-so/react's README.`);let u=s.expires_in??s.expiresIn;this.#t=r,this.#s=typeof u=="number"?Date.now()+u*1e3:null;for(let p of this.#r)p();return r})();try{return await this.#n}finally{this.#n=null}}};function Ge(e,t=fetch.bind(globalThis)){return async(n,c)=>{let s=await t(n,c);if(s.status!==401||!e.canRefresh)return s;let r;try{r=await e.refresh()}catch{return s}return t(n,lt(c,r))}}function lt(e,t){let n=new Headers(e?.headers);return n.set("authorization",`Bearer ${t}`),{...e,headers:n}}var He="data-soba-react",Ve=!1;function we(){if(Ve||typeof document>"u"||(Ve=!0,document.querySelector(`style[${He}]`)))return;let e=document.createElement("style");e.setAttribute(He,""),e.textContent=`.soba-connect {
|
|
6
6
|
--sc-ground: #ffffff;
|
|
7
7
|
--sc-sheet: #ffffff;
|
|
8
8
|
--sc-raised: #f6f6f7;
|
|
@@ -256,7 +256,7 @@ a.sc-btn {
|
|
|
256
256
|
gap: 12px;
|
|
257
257
|
align-items: stretch;
|
|
258
258
|
|
|
259
|
-
grid-template-columns: repeat(auto-fit, minmax(
|
|
259
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
.sc-plan {
|
|
@@ -374,4 +374,4 @@ a.sc-btn {
|
|
|
374
374
|
border-bottom: 1px solid var(--sc-line, #e9e9eb);
|
|
375
375
|
margin-bottom: 2px;
|
|
376
376
|
}
|
|
377
|
-
`,document.head.appendChild(e)}import{jsx as
|
|
377
|
+
`,document.head.appendChild(e)}import{jsx as Se}from"react/jsx-runtime";var pt="https://www.soba.so",dt=15e3;function mt(e){let{children:t,session:n,sessionEndpoint:c,user:s,runEndpoint:r,initialMachines:u,initialConnect:p=null,initialPlans:d,initialCurrentPlan:y=null,baseUrl:S=pt,pollMs:o=dt,injectStyles:f=!0,fetchImpl:b,onError:te}=e,[C,ne]=R(n??null),[v,le]=R(null),[P,M]=R(p),[U,T]=R(null),[J,D]=R(p===null),[q,ue]=R(u??[]),[Y,B]=R(null),[l,E]=R(u===void 0),[w,G]=R(d??[]),[z,K]=R(y),[Ne,pe]=R(null),[Pe,Ee]=R(d===void 0),ke=ut(te);ke.current=te;let Q=F(i=>{le(i),ke.current?.(i)},[]);j(()=>{f&&we()},[f]);let k=xe(()=>new ce({...n?{token:n}:{},...c?{endpoint:c}:{},...s?{user:s}:{},...b?{fetchImpl:b}:{}}),[n,c,s,b]);j(()=>{let i=!0,x=k.subscribe(()=>{i&&ne(k.token)});return!k.token&&!k.canRefresh?Q(new Error("<SobaProvider> needs a `session` or a `sessionEndpoint`. A browser cannot mint one: that takes your secret key.")):k.ensure().then(()=>i&&ne(k.token),O=>i&&Q(O)),()=>{i=!1,x()}},[k,Q]),j(()=>{v&&(D(!1),E(!1),Ee(!1),T(i=>i??v),B(i=>i??v),pe(i=>i??v))},[v]),j(()=>{let i=k.refreshAt;if(i===null)return;let x=setTimeout(()=>{k.refresh().catch(O=>Q(O))},Math.max(0,i-Date.now()));return()=>clearTimeout(x)},[k,Q,C]);let m=xe(()=>C?new Z({session:C,baseUrl:S,fetchImpl:Ge(k,b??fetch.bind(globalThis))}):null,[C,S,k,b]);j(()=>{if(!m)return;let i=!0;return D(!0),m.context().then(x=>{i&&(M(x),T(null),D(!1))},x=>{i&&(T(x),D(!1))}),()=>{i=!1}},[m]);let X=F(async()=>{if(m)try{let i=await m.machines();ue(i),B(null)}catch(i){B(i)}finally{E(!1)}},[m]),I=F(async()=>{if(m)try{let i=await m.plans();G(i.plans),K(i.current),pe(null)}catch(i){pe(i)}finally{Ee(!1)}},[m]);j(()=>{m&&I()},[m,I]);let _e=F(async i=>{if(!m)throw new Error("No session yet.");await m.selectPlan(i),await I()},[m,I]),Re=F(async(i,x)=>{if(!m)throw new Error("No session yet.");let O=await m.checkoutPlan(i,x);if(!O.url)throw new Error("Checkout could not be started.");return O.url},[m]),Te=F(async()=>{if(!m)throw new Error("No session yet.");await m.leavePlan(),await I()},[m,I]);j(()=>{if(!m)return;let i=!0;X();let x=setInterval(()=>{typeof document<"u"&&document.hidden||i&&X()},o);return()=>{i=!1,clearInterval(x)}},[m,X,o]);let Me=F(async i=>{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:x,...O}=i,de=await(b??fetch.bind(globalThis))(r,{method:"POST",headers:{"content-type":"application/json",accept:"text/event-stream"},credentials:"same-origin",body:JSON.stringify(O),...x?{signal:x}:{}});if(!de.ok)throw new Error(`${r} answered ${de.status}.`);return Ce(de)},[r,b]),We=xe(()=>({session:C,client:m,error:v,plan:{plans:w,current:z,plan:z?w.find(i=>i.id===z.plan_id)??null:null,loading:Pe,error:Ne,select:_e,checkout:Re,leave:Te,refresh:I},compute:{machines:q,connected:q.length>0,online:q.some(i=>i.state!=="offline"),loading:l,error:Y,refresh:X},connect:{app:P?.app??null,command:P?.command??null,publishableKey:P?.publishable_key??null,loading:J,error:U},run:Me}),[C,m,v,q,l,Y,X,w,z,Pe,Ne,_e,Re,Te,I,P,J,U,Me]);return Se(be.Provider,{value:m,children:Se(ve.Provider,{value:{context:P,machines:l?void 0:q},children:Se(ye.Provider,{value:We,children:t})})})}export{Ze as ComputeStatus,et as ConnectCompute,st as SobaCheckout,it as SobaPlans,mt as SobaProvider,nt as collectText,we as injectStyles,ae as parseEvent,Ce as readEvents,A as useSoba};
|
package/dist/styles.css
CHANGED