@theoremjs/react 0.3.4-next.677.g14b70fe → 0.3.4-next.693.g4ab1526
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 +2 -0
- package/client/trace-view.js +6 -0
- package/components/live/use-live-runner-model.d.ts +1 -1
- package/components/live/use-live-runner-model.js +7 -1
- package/components/live/use-live-session-client.js +1 -0
- package/package.json +3 -3
- package/ui/LiveRunner.js +2 -2
- package/ui/TheoremChat.d.ts +4 -2
- package/ui/TheoremChat.js +12 -11
package/README.md
CHANGED
|
@@ -369,6 +369,8 @@ Three more props give the call values from the page. The profile declares each o
|
|
|
369
369
|
| `context` | `inputs.context` with `from: ['client']` | Any JSON the agent should know, such as the page the person is on. |
|
|
370
370
|
| `pageTools` | A function tool with `answeredBy: 'page'` | A function for each page tool, by name. It returns `{ output }`. |
|
|
371
371
|
|
|
372
|
+
`stated` is a fourth prop, for a page you trust as yourself. It says the `path` and `conditions` that choose the call's prompt lines, and the server reads it only with `pageStatesTurn` set. A change during a call takes the call up again, as a slot does. `TheoremChat` takes the same prop, on each turn.
|
|
373
|
+
|
|
372
374
|
```tsx
|
|
373
375
|
<LiveRunner
|
|
374
376
|
iface={iface}
|
package/client/trace-view.js
CHANGED
|
@@ -311,6 +311,9 @@ const STORED_FORMATS = new Set([
|
|
|
311
311
|
function isStringList(value) {
|
|
312
312
|
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
313
313
|
}
|
|
314
|
+
function isNumberList(value) {
|
|
315
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'number');
|
|
316
|
+
}
|
|
314
317
|
/** An object, or a list of objects, as items to read by a catalog entry's fields. */
|
|
315
318
|
function fieldItems(value) {
|
|
316
319
|
if (isRecord(value))
|
|
@@ -326,6 +329,9 @@ export function traceValueShape(meta, value) {
|
|
|
326
329
|
return { kind: 'scalar', value };
|
|
327
330
|
if (isStringList(value))
|
|
328
331
|
return { kind: 'list', value };
|
|
332
|
+
if (meta?.format === 'list' && isNumberList(value)) {
|
|
333
|
+
return { kind: 'list', value: value.map(String) };
|
|
334
|
+
}
|
|
329
335
|
const items = meta?.fields ? fieldItems(value) : undefined;
|
|
330
336
|
return meta?.fields && items
|
|
331
337
|
? { kind: 'fields', fields: meta.fields, items }
|
|
@@ -5,7 +5,7 @@ import { type LiveCaptionState, type LiveCaptionTurn } from '../../client/live/l
|
|
|
5
5
|
* Owns LiveRunner state, session client, and stage callbacks.
|
|
6
6
|
* `connection` resolves what the call opens: a host with a fixed profile returns
|
|
7
7
|
* its id; the studio returns its draft as the relay's open message. `options`
|
|
8
|
-
* are the host's part in the call: its slots, the page's context, and its page tools.
|
|
8
|
+
* are the host's part in the call: its slots, its stated turn, the page's context, and its page tools.
|
|
9
9
|
*/
|
|
10
10
|
export declare function useLiveRunnerModel(iface: LiveProfileInterface, connection: () => LiveConnection | Promise<LiveConnection>, options?: LiveCallOptions): {
|
|
11
11
|
handle: string;
|
|
@@ -53,7 +53,7 @@ function useLiveCallLifecycle(controls, captionsRef, sessionActive, videoAvailab
|
|
|
53
53
|
* Owns LiveRunner state, session client, and stage callbacks.
|
|
54
54
|
* `connection` resolves what the call opens: a host with a fixed profile returns
|
|
55
55
|
* its id; the studio returns its draft as the relay's open message. `options`
|
|
56
|
-
* are the host's part in the call: its slots, the page's context, and its page tools.
|
|
56
|
+
* are the host's part in the call: its slots, its stated turn, the page's context, and its page tools.
|
|
57
57
|
*/
|
|
58
58
|
export function useLiveRunnerModel(iface, connection, options = {}) {
|
|
59
59
|
const ui = useLiveRunnerUiState(iface.labels);
|
|
@@ -78,6 +78,7 @@ export function useLiveRunnerModel(iface, connection, options = {}) {
|
|
|
78
78
|
const traces = useMemo(createTraceFeed, []);
|
|
79
79
|
const { clientRef, ensureClient, clearClient } = useLiveSessionClient({
|
|
80
80
|
slots: options.slots,
|
|
81
|
+
stated: options.stated,
|
|
81
82
|
context: options.context,
|
|
82
83
|
pageTools: options.pageTools,
|
|
83
84
|
voiceAvailable,
|
|
@@ -148,6 +149,11 @@ export function useLiveRunnerModel(iface, connection, options = {}) {
|
|
|
148
149
|
useEffect(() => {
|
|
149
150
|
clientRef.current?.setSlots(slotsJson === undefined ? undefined : JSON.parse(slotsJson));
|
|
150
151
|
}, [clientRef, slotsJson]);
|
|
152
|
+
// why: As slots: a changed pick takes an open call up again with the lines it chooses.
|
|
153
|
+
const statedJson = JSON.stringify(options.stated);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
clientRef.current?.setStated(statedJson === undefined ? undefined : JSON.parse(statedJson));
|
|
156
|
+
}, [clientRef, statedJson]);
|
|
151
157
|
usePageToolWarnings(iface.tools.page, options.pageTools);
|
|
152
158
|
return {
|
|
153
159
|
handle: iface.identity.handle,
|
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.693.g4ab1526",
|
|
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",
|
|
@@ -60,8 +60,8 @@
|
|
|
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.693.g4ab1526",
|
|
64
|
+
"@theoremjs/web": "^0.3.4-next.693.g4ab1526"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@astryxdesign/core": {
|
package/ui/LiveRunner.js
CHANGED
|
@@ -38,9 +38,9 @@ const WAVE_HEIGHT = 320;
|
|
|
38
38
|
export function LiveRunner({ theme, mode, labels, ...props }) {
|
|
39
39
|
return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(LiveRunnerBody, { ...props }) }) }));
|
|
40
40
|
}
|
|
41
|
-
function LiveRunnerBody({ iface, connection, trace, slots, context, pageTools, }) {
|
|
41
|
+
function LiveRunnerBody({ iface, connection, trace, slots, stated, context, pageTools, }) {
|
|
42
42
|
const t = useLabels();
|
|
43
|
-
const model = useLiveRunnerModel(iface, connection, { slots, context, pageTools });
|
|
43
|
+
const model = useLiveRunnerModel(iface, connection, { slots, stated, context, pageTools });
|
|
44
44
|
// why: Both panels size against the whole live layout, so a third means the same for each.
|
|
45
45
|
const layoutRef = useRef(null);
|
|
46
46
|
const inspector = useTraceInspector(iface, model.traces, trace);
|
package/ui/TheoremChat.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DefinedTheme } from '@astryxdesign/core/theme';
|
|
2
2
|
import { type ComposerPendingMessage } from '@theoremjs/agents/interface';
|
|
3
|
-
import type { ChatController, PageTools } from '@theoremjs/web/client';
|
|
3
|
+
import type { ChatController, PageTools, StatedTurn } from '@theoremjs/web/client';
|
|
4
4
|
import { type ConversationView, type HttpTransportOptions, type TheoremTransport } from '@theoremjs/web/client';
|
|
5
5
|
import { type ReactNode, type Ref } from 'react';
|
|
6
6
|
import { type SendTextOptions, type SentTurn, type UseTheoremChatOptions } from '../hooks/use-theorem-chat.ts';
|
|
@@ -35,6 +35,8 @@ export type TheoremChatProps = {
|
|
|
35
35
|
slots?: Record<string, string>;
|
|
36
36
|
/** Told each time the visitor chooses a slot in the composer, with every slot's value. */
|
|
37
37
|
onSlotsChange?: (slots: Record<string, string>) => void;
|
|
38
|
+
/** Which prompt lines each turn gets, for a host that lets its page choose (`pageStatesTurn`). */
|
|
39
|
+
stated?: StatedTurn;
|
|
38
40
|
/**
|
|
39
41
|
* What the page wants the agent to know: any JSON. The latest value goes with every
|
|
40
42
|
* turn, and the profile's `inputs.context.from` must list `client`.
|
|
@@ -48,7 +50,7 @@ export type TheoremChatProps = {
|
|
|
48
50
|
/** Every event of every turn, as it arrives: also the ones the chat shows nothing for. */
|
|
49
51
|
onTurnEvent?: UseTheoremChatOptions['onTurnEvent'];
|
|
50
52
|
placeholder?: string;
|
|
51
|
-
/** Shown above the centred composer before the first message. Default: the agent's handle and a prompt. */
|
|
53
|
+
/** Shown above the centred composer before the first message. Default: the agent's handle and a prompt; `null` shows nothing. */
|
|
52
54
|
emptyState?: ReactNode;
|
|
53
55
|
density?: 'compact' | 'balanced' | 'spacious';
|
|
54
56
|
/**
|
package/ui/TheoremChat.js
CHANGED
|
@@ -51,7 +51,7 @@ function ChatComposerForChat({ chat, iface, placeholder, inputRef, }) {
|
|
|
51
51
|
void chat.handleSendNow(message);
|
|
52
52
|
} }));
|
|
53
53
|
}
|
|
54
|
-
function ChatBody({ transport, iface, placeholder, emptyState, density, maxWidth = DEFAULT_CHAT_MAX_WIDTH, scrollRef, trace, usage, className, style, conversation, conversationView, initialText, initialFiles, initialVoice, initialPending, onConversationChange, chatRef, composer: hasComposer = true, slots, onSlotsChange, context, pageTools, onTurnEvent, }) {
|
|
54
|
+
function ChatBody({ transport, iface, placeholder, emptyState, density, maxWidth = DEFAULT_CHAT_MAX_WIDTH, scrollRef, trace, usage, className, style, conversation, conversationView, initialText, initialFiles, initialVoice, initialPending, onConversationChange, chatRef, composer: hasComposer = true, slots, onSlotsChange, stated, context, pageTools, onTurnEvent, }) {
|
|
55
55
|
const t = useLabels();
|
|
56
56
|
const chat = useTheoremChat({
|
|
57
57
|
transport,
|
|
@@ -65,6 +65,7 @@ function ChatBody({ transport, iface, placeholder, emptyState, density, maxWidth
|
|
|
65
65
|
onConversationChange,
|
|
66
66
|
slots,
|
|
67
67
|
onSlotsChange,
|
|
68
|
+
stated,
|
|
68
69
|
context,
|
|
69
70
|
pageTools,
|
|
70
71
|
onTurnEvent,
|
|
@@ -97,7 +98,7 @@ const CHAT_SCROLL = { overscrollBehavior: 'none' };
|
|
|
97
98
|
*/
|
|
98
99
|
function ChatLanding({ composer, composerRef, maxWidth, handle, emptyState, }) {
|
|
99
100
|
const t = useLabels();
|
|
100
|
-
return (_jsxs(VStack, { minHeight: "100%", vAlign: "center", gap: 8, paddingInline: 3, children: [_jsx(ChatColumn, { maxWidth: maxWidth, children: emptyState
|
|
101
|
+
return (_jsxs(VStack, { minHeight: "100%", vAlign: "center", gap: 8, paddingInline: 3, children: [_jsx(ChatColumn, { maxWidth: maxWidth, children: emptyState === undefined ? (_jsxs(VStack, { gap: 1, children: [_jsx(Text, { type: "supporting", as: "h2", children: handle }), _jsx(Text, { type: "display-2", as: "h1", children: t('@theorem.chat.greeting') })] })) : (emptyState) }), composer && (_jsx(ChatColumn, { ref: composerRef, maxWidth: maxWidth, children: composer }))] }));
|
|
101
102
|
}
|
|
102
103
|
/**
|
|
103
104
|
* The chat once it has messages: the transcript, with the composer in ChatLayout's own dock.
|
|
@@ -105,15 +106,15 @@ function ChatLanding({ composer, composerRef, maxWidth, handle, emptyState, }) {
|
|
|
105
106
|
* bottom margin.
|
|
106
107
|
*/
|
|
107
108
|
function ChatThread({ composer, composerRef, maxWidth, chat, iface, blocks, handle, density, scrollRef, usage, }) {
|
|
108
|
-
return (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
return (_jsxs(VStack, { height: "100%", children: [_jsx("style", { href: "theorem-chat-thread", precedence: "default", children: '.theorem-chat-thread > :last-child > div:empty { display: none; }' }), _jsx(ChatLayout, { className: "theorem-chat-thread", density: density, scrollRef: scrollRef,
|
|
110
|
+
// why: Scrolling past either end does not bounce the transcript and its docked composer.
|
|
111
|
+
style: CHAT_SCROLL, composer: composer && (_jsx(VStack, { paddingBlockEnd: 3, children: _jsx(ChatColumn, { ref: composerRef, maxWidth: maxWidth, children: composer }) })), children: _jsx(ChatColumn, { maxWidth: maxWidth, children: _jsx(ChatTranscript, { blocks: blocks, handle: handle, streaming: chat.streaming, delivery: chat.delivery, answering: chat.answering, usage: usage, labels: iface.labels, imageOutput: iface.type === 'image'
|
|
112
|
+
? { ratio: parseAspectRatio(iface.image.aspectRatio) }
|
|
113
|
+
: undefined, onToolDecision: (index, action) => {
|
|
114
|
+
void chat.handleToolDecision(index, action);
|
|
115
|
+
}, onAuthenticated: (index, secret) => {
|
|
116
|
+
void chat.handleAuthenticated(index, secret);
|
|
117
|
+
} }) }) })] }));
|
|
117
118
|
}
|
|
118
119
|
function ChatForTransport(props) {
|
|
119
120
|
const t = useLabels();
|