@theoremjs/react 0.3.4-next.548.gbb156b2 → 0.3.4-next.554.g84e62b0
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 +24 -2
- package/hooks/use-theorem-decision.d.ts +6 -5
- package/hooks/use-theorem-decision.js +3 -2
- package/package.json +5 -5
- package/ui/TheoremDecision.js +19 -9
- package/ui/built/theme.css +2 -2
- package/ui/built/theme.css.d.ts +2 -2
- package/ui/built/theorem.d.ts +2 -2
- package/ui/built/theorem.js +2 -2
- package/ui/built/theorem.variants.d.ts +2 -2
package/README.md
CHANGED
|
@@ -275,7 +275,7 @@ import { TheoremDecision } from '@theoremjs/react/ui';
|
|
|
275
275
|
<TheoremDecision endpoint="/api/decision" />
|
|
276
276
|
```
|
|
277
277
|
|
|
278
|
-
- `questions` maps each question id to its definition.
|
|
278
|
+
- `questions` maps each question id to its definition. When the questions depend on the decision, such as one question for each passage in the state, pass a builder instead (below).
|
|
279
279
|
- `images` is optional. It holds pictures a decision can send, by reference, such as `<ghs02>` in "Which item matches <ghs02>?". The kernel sends only the ones that the state or a question references. The page sees them and refuses its own image under one of their names.
|
|
280
280
|
- `vault` holds the keys of the profile, by slot.
|
|
281
281
|
- `metadata` is optional. It adds data, such as the signed-in user, to the trace record of each decision.
|
|
@@ -292,7 +292,29 @@ Pass `onQuestion` to open a question where it is set up, such as in an editor. E
|
|
|
292
292
|
|
|
293
293
|
Pass `questionText`, each question's text by its id, to show it under its answer's title, cut to two lines with the rest on hover. The page is never sent a question's instructions, so it shows only the text you give it.
|
|
294
294
|
|
|
295
|
-
|
|
295
|
+
When the questions change from one decision to the next, `questions` can be a builder, `{ input?, build }`. `build({ state, input })` returns the questions of one decision. `input` is optional: a zod schema for what the page fills in beside the state. The handler checks the posted input against it before `build` runs, and refuses a decision whose input does not fit. `TheoremDecision` shows the input as a form above the state. Each answer shows the questions that the builder made for its decision:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
import { z } from '@theoremjs/agents';
|
|
299
|
+
import type { DecisionQuestionsBuilder } from '@theoremjs/web/server';
|
|
300
|
+
|
|
301
|
+
const perPassage: DecisionQuestionsBuilder<{ topic: string }> = {
|
|
302
|
+
input: z.object({ topic: z.string() }),
|
|
303
|
+
build: ({ state, input }) =>
|
|
304
|
+
Object.fromEntries(
|
|
305
|
+
Object.keys((state as { passages: Record<string, string> }).passages).map((id) => [
|
|
306
|
+
id,
|
|
307
|
+
{
|
|
308
|
+
type: 'choice',
|
|
309
|
+
instructions: `Does state.passages.${id} speak to ${input.topic}?`,
|
|
310
|
+
criteria: { yes: 'It does.', no: 'It does not.' },
|
|
311
|
+
},
|
|
312
|
+
]),
|
|
313
|
+
),
|
|
314
|
+
};
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
For your own UI, use `useTheoremDecision`. Its `decide(state, assets, input)` takes the images by reference, and the builder's input when the profile's interface has `build.input`. A result from a builder carries the `questions` it answers.
|
|
296
318
|
|
|
297
319
|
### Embedding
|
|
298
320
|
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import type { DecisionAsset, DecisionJson
|
|
2
|
-
import type { ClientFailure, DecisionInterface, DecisionTransport } from '@theoremjs/web/client';
|
|
1
|
+
import type { DecisionAsset, DecisionJson } from '@theoremjs/agents';
|
|
2
|
+
import type { ClientFailure, DecisionInterface, DecisionReplyResult, DecisionTransport } from '@theoremjs/web/client';
|
|
3
3
|
import { type OneCallState } from './use-one-call.ts';
|
|
4
4
|
export type TheoremDecisionState = {
|
|
5
5
|
/** The profile as the page sees it; null until the host describes it. */
|
|
6
6
|
iface: DecisionInterface | null;
|
|
7
7
|
/** Why the host could not describe the profile. */
|
|
8
8
|
describeFailure: ClientFailure | null;
|
|
9
|
-
} & OneCallState<'deciding',
|
|
9
|
+
} & OneCallState<'deciding', DecisionReplyResult>;
|
|
10
10
|
/**
|
|
11
11
|
* One decision profile: describe it once, then `decide(state)` as often as
|
|
12
|
-
* needed. A new decision aborts the one still running.
|
|
12
|
+
* needed. A new decision aborts the one still running. When the host builds its
|
|
13
|
+
* questions for each decision, `input` is what its builder takes (`iface.build.input`).
|
|
13
14
|
*/
|
|
14
15
|
export declare function useTheoremDecision(transport: DecisionTransport): TheoremDecisionState & {
|
|
15
|
-
decide: (state: Exclude<DecisionJson, null>, assets?: Record<string, DecisionAsset
|
|
16
|
+
decide: (state: Exclude<DecisionJson, null>, assets?: Record<string, DecisionAsset>, input?: unknown) => Promise<void>;
|
|
16
17
|
cancel: () => void;
|
|
17
18
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useDescribed } from "./use-described.js";
|
|
2
2
|
import { useOneCall } from "./use-one-call.js";
|
|
3
|
-
const decideWith = (transport, signal, state, assets) => transport.decide(state, signal, assets);
|
|
3
|
+
const decideWith = (transport, signal, state, assets, input) => transport.decide(state, signal, assets, input);
|
|
4
4
|
/**
|
|
5
5
|
* One decision profile: describe it once, then `decide(state)` as often as
|
|
6
|
-
* needed. A new decision aborts the one still running.
|
|
6
|
+
* needed. A new decision aborts the one still running. When the host builds its
|
|
7
|
+
* questions for each decision, `input` is what its builder takes (`iface.build.input`).
|
|
7
8
|
*/
|
|
8
9
|
export function useTheoremDecision(transport) {
|
|
9
10
|
const { iface, describeFailure } = useDescribed(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theoremjs/react",
|
|
3
|
-
"version": "0.3.4-next.
|
|
3
|
+
"version": "0.3.4-next.554.g84e62b0",
|
|
4
4
|
"description": "React bindings for Theorem: headless hooks + transport, an Astryx chat UI, and a Web-standard server handler.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
"recharts": "^3.10.1"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@astryxdesign/core": "0.7.
|
|
59
|
-
"@astryxdesign/theme-neutral": "0.7.
|
|
58
|
+
"@astryxdesign/core": "0.7.4",
|
|
59
|
+
"@astryxdesign/theme-neutral": "0.7.4",
|
|
60
60
|
"@stylexjs/stylex": "0.19.0",
|
|
61
61
|
"react": "^19.0.0",
|
|
62
62
|
"react-dom": "^19.0.0",
|
|
63
|
-
"@theoremjs/agents": "^0.3.4-next.
|
|
64
|
-
"@theoremjs/web": "^0.3.4-next.
|
|
63
|
+
"@theoremjs/agents": "^0.3.4-next.554.g84e62b0",
|
|
64
|
+
"@theoremjs/web": "^0.3.4-next.554.g84e62b0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@astryxdesign/core": {
|
package/ui/TheoremDecision.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Banner } from '@astryxdesign/core/Banner';
|
|
3
|
-
import { createDecisionTransport, } from '@theoremjs/web/client';
|
|
3
|
+
import { createDecisionTransport, missingFields, sampleFromSchema, } from '@theoremjs/web/client';
|
|
4
4
|
import { useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { droppedImages, namedImages, stateFields, } from "../client/decision-images.js";
|
|
6
6
|
import { useTheoremDecision } from "../hooks/use-theorem-decision.js";
|
|
@@ -9,6 +9,7 @@ import { TheoremDecisionAnswers } from "./DecisionAnswers.js";
|
|
|
9
9
|
import { ImageDrop } from "./DecisionImages.js";
|
|
10
10
|
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
11
11
|
import { PaneFailure, PaneLoading } from "./PaneState.js";
|
|
12
|
+
import { SchemaFields } from "./SchemaFields.js";
|
|
12
13
|
import { StateFields, StateImages } from "./StateFields.js";
|
|
13
14
|
import { useTraceInspector } from "./TraceInspectorPanel.js";
|
|
14
15
|
import { TheoremThemeProvider } from "./theme.js";
|
|
@@ -54,26 +55,32 @@ function useDecisionState(defaultState, maxStateBytes) {
|
|
|
54
55
|
const shown = check.state === undefined ? 'json' : view;
|
|
55
56
|
return { text, setText, check, shown, setView };
|
|
56
57
|
}
|
|
57
|
-
/**
|
|
58
|
-
function
|
|
58
|
+
/** What the host's question builder takes beside the state, and the fields it still needs. */
|
|
59
|
+
function useBuildInput(schema) {
|
|
60
|
+
const [input, setInput] = useState(() => schema ? sampleFromSchema(schema) : {});
|
|
61
|
+
const missing = schema ? missingFields(schema, input) : [];
|
|
62
|
+
return { schema, input, setInput, missing };
|
|
63
|
+
}
|
|
64
|
+
/** The builder's input, then the state, as fields or JSON, and Decide. */
|
|
65
|
+
function DecisionRequest({ form, built, iface, images, onImages, note, isReady, isRunning, onRun, onStop, }) {
|
|
59
66
|
const t = useLabels();
|
|
60
67
|
const { text, setText, check, shown, setView } = form;
|
|
61
68
|
const withImages = iface.questions.some((question) => question.type === 'box') || Object.keys(images).length > 0;
|
|
62
69
|
const fields = shown === 'fields' ? check.state : undefined;
|
|
63
70
|
const named = stateFields(fields);
|
|
64
|
-
return (_jsxs(RequestCard, { title: t('@theorem.decision.state'), view: shown, hasFields: check.state !== undefined, onView: setView, onRun: onRun, children: [fields !== undefined ? (_jsx(StateImages, { value: { images, onImages }, children: _jsx(StateFields, { label: t('@theorem.decision.state'), value: fields, onChange: (next) => {
|
|
71
|
+
return (_jsxs(RequestCard, { title: t('@theorem.decision.state'), view: shown, hasFields: check.state !== undefined, onView: setView, onRun: onRun, children: [built.schema ? (_jsx(SchemaFields, { schema: built.schema, value: built.input, onChange: built.setInput })) : null, fields !== undefined ? (_jsx(StateImages, { value: { images, onImages }, children: _jsx(StateFields, { label: t('@theorem.decision.state'), value: fields, onChange: (next) => {
|
|
65
72
|
setText(JSON.stringify(next, null, 2));
|
|
66
73
|
} }) })) : (_jsx(JsonRequest, { label: t('@theorem.decision.state'), text: text, isInvalid: !check.ok, onChange: setText })), withImages && named ? (_jsx(ImageDrop, { onImages: (dropped) => {
|
|
67
74
|
const next = droppedImages(dropped, { images, host: iface.images, state: named });
|
|
68
75
|
onImages(next.images);
|
|
69
76
|
setText(JSON.stringify(next.state, null, 2));
|
|
70
|
-
} })) : null, _jsx(RunBar, { note: note, isReady:
|
|
77
|
+
} })) : null, _jsx(RunBar, { note: note, isReady: isReady, isRunning: isRunning, runLabel: t('@theorem.decision.decide'), runningLabel: t('@theorem.decision.deciding'), stopLabel: t('@theorem.decision.stop'), onRun: onRun, onStop: onStop })] }));
|
|
71
78
|
}
|
|
72
79
|
/** What came back: the failure, the answers, or a spinner until the first of either. */
|
|
73
80
|
function DecisionAnswered({ iface, decision, asked, onQuestion, questionText, }) {
|
|
74
81
|
const t = useLabels();
|
|
75
82
|
const deciding = decision.status === 'deciding';
|
|
76
|
-
return (_jsxs(_Fragment, { children: [decision.failure ? (_jsx(Banner, { status: "error", role: "alert", title: decision.failure.error })) : null, deciding && !decision.result ? (_jsx(PaneLoading, { label: t('@theorem.decision.deciding'), since: decision.startedAt })) : null, decision.result ? (_jsx(TheoremDecisionAnswers, { iface: iface, result: decision.result, asked: asked, onQuestion: onQuestion, questionText: questionText, elapsedMs: decision.elapsedMs, isStale: deciding })) : null] }));
|
|
83
|
+
return (_jsxs(_Fragment, { children: [decision.failure ? (_jsx(Banner, { status: "error", role: "alert", title: decision.failure.error })) : null, deciding && !decision.result ? (_jsx(PaneLoading, { label: t('@theorem.decision.deciding'), since: decision.startedAt })) : null, decision.result ? (_jsx(TheoremDecisionAnswers, { iface: decision.result.questions ? { ...iface, questions: decision.result.questions } : iface, result: decision.result, asked: asked, onQuestion: onQuestion, questionText: questionText, elapsedMs: decision.elapsedMs, isStale: deciding })) : null] }));
|
|
77
84
|
}
|
|
78
85
|
/**
|
|
79
86
|
* The frame's second child. A call, not a component: the frame counts its
|
|
@@ -91,19 +98,22 @@ function DecisionBody({ transport, iface, decision, defaultState = '{}', default
|
|
|
91
98
|
const t = useLabels();
|
|
92
99
|
const bytes = useBytes();
|
|
93
100
|
const form = useDecisionState(defaultState, iface.maxStateBytes);
|
|
101
|
+
const built = useBuildInput(iface.build?.input);
|
|
94
102
|
const { check } = form;
|
|
95
103
|
const [images, setImages] = useState(defaultImages ?? {});
|
|
96
104
|
/** The images and state the answers were asked about, kept with them: later edits don't redraw them. */
|
|
97
105
|
const [asked, setAsked] = useState();
|
|
98
106
|
const inspector = useTraceInspector(iface, transport.traces, trace);
|
|
99
107
|
const decide = () => {
|
|
100
|
-
if (!check.ok)
|
|
108
|
+
if (!check.ok || built.missing.length > 0)
|
|
101
109
|
return;
|
|
102
110
|
const sent = namedImages(images, check.state);
|
|
103
111
|
setAsked({ images: { ...iface.images, ...sent }, state: check.state });
|
|
104
|
-
void decision.decide(check.state, Object.keys(sent).length > 0 ? sent : undefined);
|
|
112
|
+
void decision.decide(check.state, Object.keys(sent).length > 0 ? sent : undefined, built.schema ? built.input : undefined);
|
|
105
113
|
};
|
|
106
|
-
return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, flush: flush, columns: columns, requestLabel: t('@theorem.decision.state'), className: className, style: style, children: [_jsx(DecisionRequest, { form: form, iface: iface, images: images, onImages: setImages, note:
|
|
114
|
+
return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, flush: flush, columns: columns, requestLabel: t('@theorem.decision.state'), className: className, style: style, children: [_jsx(DecisionRequest, { form: form, built: built, iface: iface, images: images, onImages: setImages, note: built.missing.length > 0
|
|
115
|
+
? t('@theorem.host.missing', { fields: built.missing.join(', ') })
|
|
116
|
+
: stateNote(t, check, iface.maxStateBytes, bytes), isReady: check.ok && built.missing.length === 0, isRunning: decision.status === 'deciding', onRun: decide, onStop: decision.cancel }), decisionResponse(columns, decisionPhase(decision), { answers: t('@theorem.decision.answers'), deciding: t('@theorem.decision.deciding') }, decision.startedAt, _jsx(DecisionAnswered, { iface: iface, decision: decision, asked: asked, onQuestion: onQuestion, questionText: questionText }))] }));
|
|
107
117
|
}
|
|
108
118
|
function DecisionForTransport(props) {
|
|
109
119
|
const t = useLabels();
|
package/ui/built/theme.css
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @generated by `astryx theme build` — do not edit manually.
|
|
3
3
|
* Source: src/ui/theorem-theme.ts
|
|
4
4
|
* Command: astryx theme build src/ui/theorem-theme.ts --out src/ui/built/theme.css
|
|
5
|
-
* CLI: @astryxdesign/cli@0.7.
|
|
6
|
-
* Core: @astryxdesign/core@0.7.
|
|
5
|
+
* CLI: @astryxdesign/cli@0.7.4
|
|
6
|
+
* Core: @astryxdesign/core@0.7.4
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
@layer reset, astryx-base, astryx-theme;
|
package/ui/built/theme.css.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @generated by `astryx theme build` — do not edit manually.
|
|
3
3
|
* Source: src/ui/theorem-theme.ts
|
|
4
4
|
* Command: astryx theme build src/ui/theorem-theme.ts --out src/ui/built/theme.css
|
|
5
|
-
* CLI: @astryxdesign/cli@0.7.
|
|
6
|
-
* Core: @astryxdesign/core@0.7.
|
|
5
|
+
* CLI: @astryxdesign/cli@0.7.4
|
|
6
|
+
* Core: @astryxdesign/core@0.7.4
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export {};
|
package/ui/built/theorem.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @generated by `astryx theme build` — do not edit manually.
|
|
3
3
|
* Source: src/ui/theorem-theme.ts
|
|
4
4
|
* Command: astryx theme build src/ui/theorem-theme.ts --out src/ui/built/theme.css
|
|
5
|
-
* CLI: @astryxdesign/cli@0.7.
|
|
6
|
-
* Core: @astryxdesign/core@0.7.
|
|
5
|
+
* CLI: @astryxdesign/cli@0.7.4
|
|
6
|
+
* Core: @astryxdesign/core@0.7.4
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/// <reference path="./theorem.variants.d.ts" />
|
package/ui/built/theorem.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @generated by `astryx theme build` — do not edit manually.
|
|
3
3
|
* Source: src/ui/theorem-theme.ts
|
|
4
4
|
* Command: astryx theme build src/ui/theorem-theme.ts --out src/ui/built/theme.css
|
|
5
|
-
* CLI: @astryxdesign/cli@0.7.
|
|
6
|
-
* Core: @astryxdesign/core@0.7.
|
|
5
|
+
* CLI: @astryxdesign/cli@0.7.4
|
|
6
|
+
* Core: @astryxdesign/core@0.7.4
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { neutralTheme } from '@astryxdesign/theme-neutral/built';
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @generated by `astryx theme build` — do not edit manually.
|
|
3
3
|
* Source: src/ui/theorem-theme.ts
|
|
4
4
|
* Command: astryx theme build src/ui/theorem-theme.ts --out src/ui/built/theme.css
|
|
5
|
-
* CLI: @astryxdesign/cli@0.7.
|
|
6
|
-
* Core: @astryxdesign/core@0.7.
|
|
5
|
+
* CLI: @astryxdesign/cli@0.7.4
|
|
6
|
+
* Core: @astryxdesign/core@0.7.4
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
// Generated by astryx theme build
|