@soba-so/react 0.0.13 → 0.0.15
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/README.md +9 -2
- package/dist/RunError.d.ts +66 -0
- package/dist/SobaProvider.d.ts +5 -2
- package/dist/context.d.ts +10 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +405 -146
- package/dist/session.d.ts +3 -0
- package/dist/styles.css +400 -141
- package/dist/types.d.ts +15 -1
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ export async function POST() {
|
|
|
32
32
|
authorization: `Bearer ${process.env.SOBA_KEY}`,
|
|
33
33
|
"content-type": "application/json",
|
|
34
34
|
},
|
|
35
|
-
body: JSON.stringify({ user: session.user.
|
|
35
|
+
body: JSON.stringify({ user: sobaUserId(session.user.username) }),
|
|
36
36
|
})
|
|
37
37
|
return Response.json(await res.json())
|
|
38
38
|
}
|
|
@@ -42,6 +42,12 @@ That is the whole integration. `user` is the same id you pass on a run, which is
|
|
|
42
42
|
what makes the person whose machine this is and the person a run is attributed to
|
|
43
43
|
the same row.
|
|
44
44
|
|
|
45
|
+
It also has to outlive your database. `session.user.id` is usually a key minted
|
|
46
|
+
at sign-up, so a reset or a move turns one person into two — holding the machine
|
|
47
|
+
under the old id, on metered compute under the new one. `sobaUserId`, from the
|
|
48
|
+
server-only subpath `@soba-so/sdk/identity`, derives a stable id from something
|
|
49
|
+
that survives: see [Identifying users](https://soba.so/docs/identity).
|
|
50
|
+
|
|
45
51
|
The session lives ten minutes. A settings page left open for an hour is the
|
|
46
52
|
ordinary case, so the provider refreshes it before it expires **and** heals a
|
|
47
53
|
request that meets a dead one: a 401 mints a new session and retries once, for
|
|
@@ -106,11 +112,12 @@ Point the provider at a route that does it, and the hook will read the stream:
|
|
|
106
112
|
```ts
|
|
107
113
|
// app/api/soba/run/route.ts
|
|
108
114
|
import { Soba } from "@soba-so/sdk"
|
|
115
|
+
import { sobaUserId } from "@soba-so/sdk/identity"
|
|
109
116
|
|
|
110
117
|
export async function POST(request: Request) {
|
|
111
118
|
const session = await auth()
|
|
112
119
|
const soba = new Soba({ apiKey: process.env.SOBA_KEY })
|
|
113
|
-
const run = await soba.run({ user: session.user.
|
|
120
|
+
const run = await soba.run({ user: sobaUserId(session.user.username), ...(await request.json()) })
|
|
114
121
|
return new Response(toSSE(run), { headers: { "content-type": "text/event-stream" } })
|
|
115
122
|
}
|
|
116
123
|
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE OTHER HALF OF A RUN THAT DID NOT WORK.
|
|
3
|
+
*
|
|
4
|
+
* `<RunLimit>` and `<ConnectNudge>` answer the two failures a person can do
|
|
5
|
+
* something about — `payment_required` and `no_compute` — both decided BEFORE a
|
|
6
|
+
* run starts, where "connect something" or "upgrade" is a real button. This
|
|
7
|
+
* answers everything else: a supplier that refused, a key with no credit left,
|
|
8
|
+
* a model that is gone, a machine that disconnected mid-answer.
|
|
9
|
+
*
|
|
10
|
+
* It exists because that half was shipped as homework. `useRunLimit` returns
|
|
11
|
+
* null for anything unanswerable, which is correct and is also an instruction
|
|
12
|
+
* to the app to go and render something itself — so every surface in this repo
|
|
13
|
+
* wrote its own, four times, each one slightly different, and the one with the
|
|
14
|
+
* most machinery dropped the message on the floor for a month without anybody
|
|
15
|
+
* noticing. A `<p className="error">` is not a hard component to write. It is a
|
|
16
|
+
* very easy one to forget, and forgetting it looks exactly like the product
|
|
17
|
+
* doing nothing at all.
|
|
18
|
+
*
|
|
19
|
+
* THE PAIR IS THE POINT. Drop both in and they never both speak:
|
|
20
|
+
*
|
|
21
|
+
* <RunLimit error={failure} />
|
|
22
|
+
* <RunError error={failure} />
|
|
23
|
+
*
|
|
24
|
+
* The first draws when the failure is answerable and the second when it is not,
|
|
25
|
+
* so between them every failed run says something. That is the whole
|
|
26
|
+
* integration, and neither one needs the app to classify anything.
|
|
27
|
+
*/
|
|
28
|
+
export interface RunErrorProps {
|
|
29
|
+
/**
|
|
30
|
+
* Whatever your catch block caught. `null` — the value every app holds before
|
|
31
|
+
* the first failure — renders nothing, as does any failure `<RunLimit>` is
|
|
32
|
+
* already answering.
|
|
33
|
+
*
|
|
34
|
+
* Required, unlike `RunLimit.error`, and deliberately: there is no state form
|
|
35
|
+
* of this. A limit can be foreseen from the plan before anybody presses
|
|
36
|
+
* anything; an error cannot be foreseen at all, and a component that drew one
|
|
37
|
+
* speculatively would be inventing a failure.
|
|
38
|
+
*/
|
|
39
|
+
error: unknown;
|
|
40
|
+
/**
|
|
41
|
+
* Run it again. Omitted draws no button, which is right for a surface where
|
|
42
|
+
* retrying is not one press — a chat has its own regenerate and should not
|
|
43
|
+
* grow a second one.
|
|
44
|
+
*/
|
|
45
|
+
onRetry?: () => void;
|
|
46
|
+
/** Dismissing. Without it the message stays until the next run replaces it. */
|
|
47
|
+
onDismiss?: () => void;
|
|
48
|
+
/**
|
|
49
|
+
* The line above the message. The default names no cause, because this
|
|
50
|
+
* component by definition does not know the cause — the one thing it is sure
|
|
51
|
+
* of is that the run did not finish.
|
|
52
|
+
*/
|
|
53
|
+
title?: string;
|
|
54
|
+
className?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* WHAT TO SAY ABOUT A FAILURE, or null when it is not this component's to say.
|
|
58
|
+
*
|
|
59
|
+
* Exported for the same reason `useRunLimit` is: an app whose errors have their
|
|
60
|
+
* own shape should ask this and draw its own. Null means either nothing failed
|
|
61
|
+
* or `<RunLimit>` has it, and in both cases the right thing to render is
|
|
62
|
+
* nothing — so `{runErrorMessage(e) && <MyError …/>}` is a complete integration
|
|
63
|
+
* without the app ever naming a status code.
|
|
64
|
+
*/
|
|
65
|
+
export declare function runErrorMessage(error: unknown): string | null;
|
|
66
|
+
export declare function RunError({ error, onRetry, onDismiss, title, className }: RunErrorProps): import("react").JSX.Element | null;
|
package/dist/SobaProvider.d.ts
CHANGED
|
@@ -15,14 +15,17 @@ export interface SobaProviderProps {
|
|
|
15
15
|
* const res = await fetch("https://www.soba.so/v1/end_users/session", {
|
|
16
16
|
* method: "POST",
|
|
17
17
|
* headers: { authorization: `Bearer ${process.env.SOBA_KEY}` },
|
|
18
|
-
* body: JSON.stringify({ user: session.user.
|
|
18
|
+
* body: JSON.stringify({ user: sobaUserId(session.user.username) }),
|
|
19
19
|
* })
|
|
20
20
|
* return Response.json(await res.json())
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
sessionEndpoint?: string;
|
|
24
24
|
/** The same id you pass as `user` on a run. It is what makes the person whose
|
|
25
|
-
* machine this is and the person a run is attributed to the same row
|
|
25
|
+
* machine this is and the person a run is attributed to the same row — and
|
|
26
|
+
* it has to outlive your database, or a reset splits that person in two.
|
|
27
|
+
* `sobaUserId` from `@soba-so/sdk/identity` derives one; the reasoning is at
|
|
28
|
+
* https://soba.so/docs/identity. */
|
|
26
29
|
user?: string;
|
|
27
30
|
/** Your route that starts a run with `@soba-so/sdk` and forwards the stream. */
|
|
28
31
|
runEndpoint?: string;
|
package/dist/context.d.ts
CHANGED
|
@@ -20,9 +20,16 @@ export interface ComputeState {
|
|
|
20
20
|
error: Error | null;
|
|
21
21
|
refresh(): Promise<void>;
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Why the flow was opened, carried into the panel's opening words. A person
|
|
25
|
+
* whose laptop went to sleep should not be sold the idea from scratch.
|
|
26
|
+
*
|
|
27
|
+
* `pair` is the one that is not about words: it says the caller is ALREADY
|
|
28
|
+
* showing the steady state — the panel under `<ConnectButton />` — so the
|
|
29
|
+
* sheet must open on the chooser rather than repeating it. The other three
|
|
30
|
+
* leave where it opens to the panel.
|
|
31
|
+
*/
|
|
32
|
+
export type ConnectReason = "payment_required" | "no_compute" | "manual" | "pair";
|
|
26
33
|
export interface ConnectState {
|
|
27
34
|
/** Your app's name, as the person connecting will see it. */
|
|
28
35
|
app: {
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { ConnectModal, type ConnectModalProps } from "./ConnectModal.js";
|
|
|
5
5
|
export { ConnectNudge, type ConnectNudgeProps } from "./ConnectNudge.js";
|
|
6
6
|
export { type ComputeState, type ConnectReason, type ConnectState, type PlanState, type RunRequest, type SobaContextValue, useSoba, } from "./context.js";
|
|
7
7
|
export { collectText, parseEvent, readEvents } from "./events.js";
|
|
8
|
+
export { RunError, type RunErrorProps, runErrorMessage } from "./RunError.js";
|
|
8
9
|
export { RunLimit, type RunLimitProps } from "./RunLimit.js";
|
|
9
10
|
export { SobaBilling, type SobaBillingProps } from "./SobaBilling.js";
|
|
10
11
|
export { SobaCheckout, type SobaCheckoutProps } from "./SobaCheckout.js";
|