@theoremjs/react 0.3.0 → 0.3.1
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 +728 -202
- package/client/composer-hints.js +1 -1
- package/client/decision-images.d.ts +76 -0
- package/client/decision-images.js +139 -0
- package/client/decision-transport.d.ts +26 -4
- package/client/decision-transport.js +79 -6
- package/client/embedding-input.d.ts +84 -0
- package/client/embedding-input.js +194 -0
- package/client/embedding-space.d.ts +82 -0
- package/client/embedding-space.js +389 -0
- package/client/embedding-transport.d.ts +40 -0
- package/client/embedding-transport.js +71 -0
- package/client/failure.d.ts +7 -10
- package/client/failure.js +9 -9
- package/client/host-call.d.ts +1 -1
- package/client/host-call.js +7 -2
- package/client/host-transport.d.ts +2 -0
- package/client/host-transport.js +3 -2
- package/client/index.d.ts +19 -11
- package/client/index.js +14 -10
- package/client/ink-waveform.d.ts +1 -1
- package/client/ink-waveform.js +1 -2
- package/client/live/apply-live-turn-tool-event.js +1 -1
- package/client/live/live-captions.d.ts +30 -0
- package/client/live/live-captions.js +105 -1
- package/client/live/live-mic-forward.d.ts +1 -0
- package/client/live/live-mic-forward.js +11 -2
- package/client/live/live-page-tool.d.ts +34 -0
- package/client/live/live-page-tool.js +11 -0
- package/client/live/live-state.d.ts +4 -2
- package/client/live/live-state.js +2 -0
- package/client/live/live-video.js +1 -1
- package/client/live/run-live-tool-call.d.ts +7 -2
- package/client/live/run-live-tool-call.js +48 -10
- package/client/live/use-live-caption-log.d.ts +17 -0
- package/client/live/use-live-caption-log.js +51 -0
- package/client/live-client.d.ts +52 -4
- package/client/live-client.js +250 -59
- package/client/live-messages.d.ts +51 -13
- package/client/live-messages.js +42 -9
- package/client/page-dispatch.d.ts +15 -0
- package/client/page-dispatch.js +63 -0
- package/client/run-commit.d.ts +16 -43
- package/client/run-commit.js +26 -101
- package/client/run-session.d.ts +40 -41
- package/client/run-session.js +104 -112
- package/client/schema-fields.js +27 -6
- package/client/shaped-data.js +37 -8
- package/client/source-citations.js +1 -1
- package/client/space-geometry.d.ts +39 -0
- package/client/space-geometry.js +183 -0
- package/client/state-fields.js +1 -1
- package/client/tool-result.js +30 -12
- package/client/tool-resume.d.ts +18 -1
- package/client/tool-resume.js +4 -1
- package/client/trace-feed.d.ts +1 -1
- package/client/trace-story.d.ts +2 -2
- package/client/trace-story.js +43 -19
- package/client/trace-view.d.ts +2 -2
- package/client/trace-view.js +56 -16
- package/client/transcript-groups.d.ts +28 -2
- package/client/transcript-groups.js +63 -13
- package/client/transcript-schema.d.ts +10 -0
- package/client/transcript-schema.js +120 -0
- package/client/transport.d.ts +114 -26
- package/client/transport.js +106 -32
- package/client/turn-client.d.ts +17 -8
- package/client/turn-client.js +26 -31
- package/client/voice-recorder.d.ts +1 -1
- package/client/voice-recorder.js +2 -5
- package/client/wire-line.js +5 -5
- package/client/worklets/mic-capture.js +9 -9
- package/components/InkWaveform.d.ts +12 -1
- package/components/InkWaveform.js +65 -42
- package/components/live/use-live-runner-controls.d.ts +3 -4
- package/components/live/use-live-runner-controls.js +6 -10
- package/components/live/use-live-runner-model.d.ts +10 -6
- package/components/live/use-live-runner-model.js +29 -14
- package/components/live/use-live-runner-ui.d.ts +10 -8
- package/components/live/use-live-runner-ui.js +19 -11
- package/components/live/use-live-session-client.d.ts +4 -4
- package/components/live/use-live-session-client.js +11 -8
- package/components/use-composer-voice.d.ts +4 -4
- package/components/use-composer-voice.js +8 -8
- package/components/use-editor-selection.js +4 -1
- package/components/use-voice-playback.d.ts +2 -1
- package/components/use-voice-playback.js +4 -4
- package/hooks/use-described.d.ts +5 -1
- package/hooks/use-described.js +11 -9
- package/hooks/use-embedding-index.d.ts +27 -0
- package/hooks/use-embedding-index.js +43 -0
- package/hooks/use-one-call.d.ts +31 -0
- package/hooks/use-one-call.js +76 -0
- package/hooks/use-page-tool-warnings.d.ts +6 -0
- package/hooks/use-page-tool-warnings.js +25 -0
- package/hooks/use-second-ticker.d.ts +2 -0
- package/hooks/use-second-ticker.js +13 -0
- package/hooks/use-theorem-chat-actions.d.ts +25 -21
- package/hooks/use-theorem-chat-actions.js +67 -45
- package/hooks/use-theorem-chat-state.d.ts +6 -6
- package/hooks/use-theorem-chat-state.js +13 -9
- package/hooks/use-theorem-chat.d.ts +48 -14
- package/hooks/use-theorem-chat.js +329 -141
- package/hooks/use-theorem-decision.d.ts +5 -10
- package/hooks/use-theorem-decision.js +4 -67
- package/hooks/use-theorem-embedding.d.ts +18 -0
- package/hooks/use-theorem-embedding.js +12 -0
- package/hooks/use-theorem-host.d.ts +5 -4
- package/hooks/use-theorem-host.js +26 -8
- package/hooks/use-theorem-interface.d.ts +6 -3
- package/hooks/use-theorem-interface.js +12 -22
- package/index.d.ts +6 -3
- package/index.js +5 -2
- package/live.d.ts +1 -0
- package/live.js +1 -1
- package/package.json +6 -5
- package/server/checkpoint-ownership.d.ts +6 -0
- package/server/checkpoint-ownership.js +42 -0
- package/server/conversation-store.d.ts +92 -0
- package/server/conversation-store.js +131 -0
- package/server/conversation.d.ts +193 -0
- package/server/conversation.js +552 -0
- package/server/decision-handler.d.ts +12 -4
- package/server/decision-handler.js +29 -38
- package/server/embedding-handler.d.ts +33 -0
- package/server/embedding-handler.js +45 -0
- package/server/handler.d.ts +58 -20
- package/server/handler.js +441 -238
- package/server/mod.d.ts +15 -6
- package/server/mod.js +13 -5
- package/server/page-dispatch.d.ts +22 -0
- package/server/page-dispatch.js +127 -0
- package/server/request-body.d.ts +2 -0
- package/server/request-body.js +42 -0
- package/server/request-check.d.ts +6 -1
- package/server/request-check.js +27 -12
- package/server/session-authority.d.ts +8 -0
- package/server/session-authority.js +78 -0
- package/server/session-boundary.d.ts +26 -0
- package/server/session-boundary.js +64 -0
- package/server/session-identity.d.ts +16 -0
- package/server/session-identity.js +41 -0
- package/server/session-limits.d.ts +12 -0
- package/server/session-limits.js +59 -0
- package/server/session-store.d.ts +69 -20
- package/server/session-store.js +220 -5
- package/server/single-action.d.ts +21 -0
- package/server/single-action.js +30 -0
- package/server/steer-inbox.js +1 -1
- package/server/tool-operations.d.ts +6 -0
- package/server/tool-operations.js +132 -0
- package/server/turn-input.d.ts +10 -0
- package/server/turn-input.js +29 -0
- package/styles/ink-controls.css +78 -78
- package/ui/CardWall.d.ts +10 -0
- package/ui/CardWall.js +61 -0
- package/ui/ChatComposerBar.d.ts +28 -5
- package/ui/ChatComposerBar.js +85 -30
- package/ui/ChatTranscript.d.ts +14 -1
- package/ui/ChatTranscript.js +110 -57
- package/ui/ConsoleFrame.d.ts +61 -4
- package/ui/ConsoleFrame.js +109 -9
- package/ui/DecisionAnswers.d.ts +40 -2
- package/ui/DecisionAnswers.js +166 -35
- package/ui/DecisionImages.d.ts +16 -0
- package/ui/DecisionImages.js +46 -0
- package/ui/EmbeddingDocuments.d.ts +78 -0
- package/ui/EmbeddingDocuments.js +278 -0
- package/ui/EmbeddingQuery.d.ts +27 -0
- package/ui/EmbeddingQuery.js +95 -0
- package/ui/EmbeddingResults.d.ts +41 -0
- package/ui/EmbeddingResults.js +165 -0
- package/ui/EmbeddingSpace.d.ts +26 -0
- package/ui/EmbeddingSpace.js +106 -0
- package/ui/LiveCaptionsPanel.d.ts +22 -0
- package/ui/LiveCaptionsPanel.js +27 -0
- package/ui/LiveRunner.d.ts +6 -4
- package/ui/LiveRunner.js +42 -55
- package/ui/PaneState.d.ts +33 -0
- package/ui/PaneState.js +50 -0
- package/ui/SchemaFields.d.ts +1 -1
- package/ui/SchemaFields.js +54 -26
- package/ui/ShapedData.js +73 -28
- package/ui/SidePanel.d.ts +74 -5
- package/ui/SidePanel.js +82 -9
- package/ui/StateFields.d.ts +10 -2
- package/ui/StateFields.js +56 -13
- package/ui/TheoremChat.d.ts +65 -14
- package/ui/TheoremChat.js +66 -78
- package/ui/TheoremDecision.d.ts +12 -20
- package/ui/TheoremDecision.js +78 -35
- package/ui/TheoremEmbedding.d.ts +27 -0
- package/ui/TheoremEmbedding.js +122 -0
- package/ui/TheoremHost.d.ts +22 -5
- package/ui/TheoremHost.js +127 -37
- package/ui/ToolGateCard.d.ts +5 -1
- package/ui/ToolGateCard.js +30 -23
- package/ui/ToolResult.js +12 -10
- package/ui/ToolResultCharts.js +29 -12
- package/ui/TraceCharts.d.ts +2 -2
- package/ui/TraceCharts.js +48 -17
- package/ui/TraceGuardrails.d.ts +3 -2
- package/ui/TraceGuardrails.js +24 -6
- package/ui/TraceInspector.d.ts +11 -1
- package/ui/TraceInspector.js +45 -28
- package/ui/TraceInspectorPanel.d.ts +31 -9
- package/ui/TraceInspectorPanel.js +77 -39
- package/ui/TraceOverview.d.ts +7 -3
- package/ui/TraceOverview.js +44 -14
- package/ui/TraceSpanDetail.js +24 -22
- package/ui/TraceStory.js +9 -8
- package/ui/TraceValues.js +19 -9
- package/ui/TraceWaterfall.js +12 -9
- package/ui/VoiceNote.js +4 -4
- package/ui/approval-heading.d.ts +4 -0
- package/ui/approval-heading.js +9 -0
- package/ui/arrive.d.ts +18 -0
- package/ui/arrive.js +46 -0
- package/ui/built/theme.css +86 -68
- package/ui/built/theme.css.d.ts +9 -0
- package/ui/built/theorem.d.ts +3 -3
- package/ui/built/theorem.js +34 -5
- package/ui/built/theorem.variants.d.ts +8 -2
- package/ui/code-language.d.ts +12 -0
- package/ui/code-language.js +50 -0
- package/ui/disclosure-motion.js +53 -9
- package/ui/embedding-space-gl.d.ts +37 -0
- package/ui/embedding-space-gl.js +663 -0
- package/ui/embedding-space-view.d.ts +8 -0
- package/ui/embedding-space-view.js +8 -0
- package/ui/glide.d.ts +7 -0
- package/ui/glide.js +44 -0
- package/ui/icons.js +2 -1
- package/ui/index.d.ts +20 -9
- package/ui/index.js +20 -10
- package/ui/labels-provider.js +5 -2
- package/ui/labels.d.ts +374 -85
- package/ui/labels.js +1028 -188
- package/ui/markdown-plugins.d.ts +9 -0
- package/ui/markdown-plugins.js +13 -0
- package/ui/mcp-icon.d.ts +6 -0
- package/ui/mcp-icon.js +8 -0
- package/ui/row-keys.d.ts +19 -0
- package/ui/row-keys.js +33 -0
- package/ui/theme.d.ts +1 -1
- package/ui/theme.js +2 -2
- package/ui/theorem-theme.js +32 -10
- package/ui/trace-palette.d.ts +2 -16
- package/ui/trace-palette.js +2 -16
- package/ui/trace-patterns.d.ts +11 -3
- package/ui/trace-patterns.js +16 -3
- package/ui/transcript-copy-text.js +1 -0
- package/ui/wall-columns.d.ts +8 -0
- package/ui/wall-columns.js +19 -0
- package/client/walk-away.d.ts +0 -34
- package/client/walk-away.js +0 -48
- package/server/walk-away.d.ts +0 -22
- package/server/walk-away.js +0 -41
package/ui/TheoremDecision.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
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 {
|
|
4
|
-
import {
|
|
5
|
-
import { useMemo, useState } from 'react';
|
|
3
|
+
import { useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { droppedImages, namedImages, stateFields, } from "../client/decision-images.js";
|
|
6
5
|
import { createDecisionTransport, } from "../client/decision-transport.js";
|
|
7
6
|
import { useTheoremDecision } from "../hooks/use-theorem-decision.js";
|
|
7
|
+
import { ConsoleFrame, JsonRequest, RequestCard, ResponseColumn, RunBar, useBytes, } from "./ConsoleFrame.js";
|
|
8
8
|
import { TheoremDecisionAnswers } from "./DecisionAnswers.js";
|
|
9
|
-
import {
|
|
9
|
+
import { ImageDrop } from "./DecisionImages.js";
|
|
10
10
|
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { PaneFailure, PaneLoading } from "./PaneState.js";
|
|
12
|
+
import { StateFields, StateImages } from "./StateFields.js";
|
|
13
13
|
import { useTraceInspector } from "./TraceInspectorPanel.js";
|
|
14
|
+
import { TheoremThemeProvider } from "./theme.js";
|
|
14
15
|
/** The field's text as the state a decision sends, measured the way the kernel measures it. */
|
|
15
16
|
function checkState(text, maxBytes) {
|
|
16
17
|
let state;
|
|
@@ -27,48 +28,90 @@ function checkState(text, maxBytes) {
|
|
|
27
28
|
return { ok: false, reason: 'too_large', bytes, state };
|
|
28
29
|
return { ok: true, state, bytes };
|
|
29
30
|
}
|
|
30
|
-
function useBytes() {
|
|
31
|
-
const locale = useLocale();
|
|
32
|
-
return useMemo(() => {
|
|
33
|
-
const b = new Intl.NumberFormat(locale, { style: 'unit', unit: 'byte', unitDisplay: 'narrow' });
|
|
34
|
-
const kb = new Intl.NumberFormat(locale, { style: 'unit', unit: 'kilobyte', unitDisplay: 'narrow', maximumFractionDigits: 1 });
|
|
35
|
-
return (bytes) => (bytes < 1024 ? b.format(bytes) : kb.format(bytes / 1024));
|
|
36
|
-
}, [locale]);
|
|
37
|
-
}
|
|
38
31
|
function stateNote(t, check, maxBytes, bytes) {
|
|
39
32
|
if (!check.ok) {
|
|
40
33
|
if (check.reason === 'too_large')
|
|
41
34
|
return t('@theorem.decision.too_large', { limit: bytes(maxBytes ?? 0) });
|
|
42
|
-
return t(check.reason === 'invalid_json'
|
|
35
|
+
return t(check.reason === 'invalid_json'
|
|
36
|
+
? '@theorem.decision.invalid_json'
|
|
37
|
+
: '@theorem.decision.null_state');
|
|
43
38
|
}
|
|
44
|
-
return maxBytes === undefined
|
|
39
|
+
return maxBytes === undefined
|
|
40
|
+
? null
|
|
41
|
+
: t('@theorem.decision.state_size', { size: bytes(check.bytes), limit: bytes(maxBytes) });
|
|
45
42
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
/** Where a decision stands: nothing asked, asked with nothing back yet, or answered or failed. */
|
|
44
|
+
function decisionPhase(decision) {
|
|
45
|
+
if (decision.failure || decision.result)
|
|
46
|
+
return 'settled';
|
|
47
|
+
return decision.status === 'deciding' ? 'waiting' : 'idle';
|
|
48
|
+
}
|
|
49
|
+
/** The state a decision is asked about, held as text, and the view it is edited in. */
|
|
50
|
+
function useDecisionState(defaultState, maxStateBytes) {
|
|
49
51
|
const [text, setText] = useState(defaultState);
|
|
50
|
-
const check = checkState(text,
|
|
51
|
-
const
|
|
52
|
-
// Fields while the text parses; JSON to change the shape, or to fix it.
|
|
53
|
-
const [view, setView] = useState(() => (check.state === undefined ? 'json' : 'fields'));
|
|
52
|
+
const check = checkState(text, maxStateBytes);
|
|
53
|
+
const [view, setView] = useState(() => check.state === undefined ? 'json' : 'fields');
|
|
54
54
|
const shown = check.state === undefined ? 'json' : view;
|
|
55
|
-
|
|
55
|
+
return { text, setText, check, shown, setView };
|
|
56
|
+
}
|
|
57
|
+
/** The state, as fields or JSON, and Decide. */
|
|
58
|
+
function DecisionRequest({ form, iface, images, onImages, note, isRunning, onRun, onStop, }) {
|
|
59
|
+
const t = useLabels();
|
|
60
|
+
const { text, setText, check, shown, setView } = form;
|
|
61
|
+
const withImages = iface.questions.some((question) => question.type === 'box') || Object.keys(images).length > 0;
|
|
62
|
+
const fields = shown === 'fields' ? check.state : undefined;
|
|
63
|
+
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) => {
|
|
65
|
+
setText(JSON.stringify(next, null, 2));
|
|
66
|
+
} }) })) : (_jsx(JsonRequest, { label: t('@theorem.decision.state'), text: text, isInvalid: !check.ok, onChange: setText })), withImages && named ? (_jsx(ImageDrop, { onImages: (dropped) => {
|
|
67
|
+
const next = droppedImages(dropped, { images, host: iface.images, state: named });
|
|
68
|
+
onImages(next.images);
|
|
69
|
+
setText(JSON.stringify(next.state, null, 2));
|
|
70
|
+
} })) : null, _jsx(RunBar, { note: note, isReady: check.ok, isRunning: isRunning, runLabel: t('@theorem.decision.decide'), runningLabel: t('@theorem.decision.deciding'), stopLabel: t('@theorem.decision.stop'), onRun: onRun, onStop: onStop })] }));
|
|
71
|
+
}
|
|
72
|
+
/** What came back: the failure, the answers, or a spinner until the first of either. */
|
|
73
|
+
function DecisionAnswered({ iface, decision, asked, onQuestion, questionText, }) {
|
|
74
|
+
const t = useLabels();
|
|
56
75
|
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] }));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The frame's second child. A call, not a component: the frame counts its
|
|
80
|
+
* children to choose a layout, and no response yet has to count as none.
|
|
81
|
+
*/
|
|
82
|
+
function decisionResponse(columns, phase, labels, since, answers) {
|
|
83
|
+
if (phase === 'idle')
|
|
84
|
+
return null;
|
|
85
|
+
if (!columns)
|
|
86
|
+
return answers;
|
|
87
|
+
const waiting = phase === 'waiting';
|
|
88
|
+
return (_jsx(ResponseColumn, { label: labels.answers, fill: waiting, children: waiting ? _jsx(PaneLoading, { label: labels.deciding, since: since }) : answers }));
|
|
89
|
+
}
|
|
90
|
+
function DecisionBody({ transport, iface, decision, defaultState = '{}', defaultImages, onQuestion, questionText, maxWidth = '960px', trace, flush, columns = false, className, style, }) {
|
|
91
|
+
const t = useLabels();
|
|
92
|
+
const bytes = useBytes();
|
|
93
|
+
const form = useDecisionState(defaultState, iface.maxStateBytes);
|
|
94
|
+
const { check } = form;
|
|
95
|
+
const [images, setImages] = useState(defaultImages ?? {});
|
|
96
|
+
/** The images and state the answers were asked about, kept with them: later edits don't redraw them. */
|
|
97
|
+
const [asked, setAsked] = useState();
|
|
98
|
+
const inspector = useTraceInspector(iface, transport.traces, trace);
|
|
57
99
|
const decide = () => {
|
|
58
|
-
if (check.ok)
|
|
59
|
-
|
|
100
|
+
if (!check.ok)
|
|
101
|
+
return;
|
|
102
|
+
const sent = namedImages(images, check.state);
|
|
103
|
+
setAsked({ images: { ...iface.images, ...sent }, state: check.state });
|
|
104
|
+
void decision.decide(check.state, Object.keys(sent).length > 0 ? sent : undefined);
|
|
60
105
|
};
|
|
61
|
-
return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, className: className, style: style, children: [
|
|
62
|
-
setText(JSON.stringify(next, null, 2));
|
|
63
|
-
} })) : (_jsx(JsonRequest, { label: t('@theorem.decision.state'), text: text, isInvalid: !check.ok, onChange: setText })), _jsx(RunBar, { note: note, isReady: check.ok, isRunning: deciding, runLabel: t('@theorem.decision.decide'), runningLabel: t('@theorem.decision.deciding'), stopLabel: t('@theorem.decision.stop'), onRun: decide, onStop: decision.cancel })] }), decision.failure ? _jsx(Banner, { status: "error", title: decision.failure.error }) : null, decision.result ? (_jsx(TheoremDecisionAnswers, { iface: iface, result: decision.result, elapsedMs: decision.elapsedMs, isStale: deciding })) : null] }));
|
|
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: stateNote(t, check, iface.maxStateBytes, bytes), 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 }))] }));
|
|
64
107
|
}
|
|
65
108
|
function DecisionForTransport(props) {
|
|
66
109
|
const t = useLabels();
|
|
67
110
|
const decision = useTheoremDecision(props.transport);
|
|
68
111
|
if (decision.describeFailure)
|
|
69
|
-
return _jsx(
|
|
112
|
+
return _jsx(PaneFailure, { title: decision.describeFailure.error });
|
|
70
113
|
if (!decision.iface)
|
|
71
|
-
return _jsx(
|
|
114
|
+
return _jsx(PaneLoading, { label: t('@theorem.chat.loading') });
|
|
72
115
|
return _jsx(DecisionBody, { ...props, iface: decision.iface, decision: decision });
|
|
73
116
|
}
|
|
74
117
|
/**
|
|
@@ -80,8 +123,8 @@ function DecisionForTransport(props) {
|
|
|
80
123
|
* ```
|
|
81
124
|
*/
|
|
82
125
|
export function TheoremDecision({ endpoint, http, transport, theme, mode, labels, ...rest }) {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
[transport, endpoint]);
|
|
126
|
+
const httpRef = useRef(http);
|
|
127
|
+
httpRef.current = http;
|
|
128
|
+
const resolved = useMemo(() => transport ?? createDecisionTransport({ ...httpRef.current, endpoint }), [transport, endpoint]);
|
|
86
129
|
return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(DecisionForTransport, { ...rest, transport: resolved }) }) }));
|
|
87
130
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type EmbeddingTransport } from '../client/embedding-transport.ts';
|
|
2
|
+
import { type ConsolePageProps } from './ConsoleFrame.tsx';
|
|
3
|
+
import { type EmbeddingDocument } from './EmbeddingDocuments.tsx';
|
|
4
|
+
export type TheoremEmbeddingProps = ConsolePageProps & {
|
|
5
|
+
/** Where `createTheoremEmbeddingHandler` is mounted. Default `/api/embedding`. Ignored when `transport` is set. */
|
|
6
|
+
endpoint?: string;
|
|
7
|
+
/** Bring your own transport (tests, studios, non-HTTP hosts). */
|
|
8
|
+
transport?: EmbeddingTransport;
|
|
9
|
+
/** The text the documents card starts with, split into paragraphs. Default none. */
|
|
10
|
+
defaultText?: string;
|
|
11
|
+
/** Documents the card starts with beside the text, each with the file it came from. Default none. */
|
|
12
|
+
defaultDocuments?: readonly EmbeddingDocument[];
|
|
13
|
+
/** The query the search starts with. Default none. */
|
|
14
|
+
defaultQuery?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Drop-in search over an embedding profile served by `createTheoremEmbeddingHandler`:
|
|
18
|
+
* paste text and split it, or drop files, and Index embeds them as documents
|
|
19
|
+
* in as many requests as the profile's limits need. Then Search embeds a query
|
|
20
|
+
* and ranks the documents by cosine similarity. Every request is shown as
|
|
21
|
+
* JSON, and in the trace.
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <TheoremEmbedding endpoint="/api/embedding" />
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function TheoremEmbedding({ endpoint, http, transport, theme, mode, labels, ...rest }: TheoremEmbeddingProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
3
|
+
import { useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { rankMatches } from "../client/embedding-input.js";
|
|
5
|
+
import { createEmbeddingTransport, } from "../client/embedding-transport.js";
|
|
6
|
+
import { useEmbeddingIndex } from "../hooks/use-embedding-index.js";
|
|
7
|
+
import { useTheoremEmbedding } from "../hooks/use-theorem-embedding.js";
|
|
8
|
+
import { ConsoleFrame, ConsoleSection, ResponseColumn, } from "./ConsoleFrame.js";
|
|
9
|
+
import { EmbeddingDocuments, useEmbeddingDocuments, } from "./EmbeddingDocuments.js";
|
|
10
|
+
import { EmbeddingQuery, useEmbeddingQuery } from "./EmbeddingQuery.js";
|
|
11
|
+
import { EmbeddingResults, IndexCard } from "./EmbeddingResults.js";
|
|
12
|
+
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
13
|
+
import { PaneFailure, PaneLoading } from "./PaneState.js";
|
|
14
|
+
import { useTraceInspector } from "./TraceInspectorPanel.js";
|
|
15
|
+
import { TheoremThemeProvider } from "./theme.js";
|
|
16
|
+
/** The same documents: the same texts, and the very same files. */
|
|
17
|
+
function sameInputs(a, b) {
|
|
18
|
+
return a.length === b.length && a.every((input, at) => input === b[at]);
|
|
19
|
+
}
|
|
20
|
+
/** Where the documents stand for the last query: embedded, changed since, or embedded by another model. */
|
|
21
|
+
function indexStateOf(indexed, queried, inputs) {
|
|
22
|
+
if (!indexed)
|
|
23
|
+
return 'fresh';
|
|
24
|
+
// why: A query from another model than the index's is in another space; no score between them means anything.
|
|
25
|
+
if (queried && queried.model !== indexed.model)
|
|
26
|
+
return 'model';
|
|
27
|
+
return sameInputs(indexed.inputs, inputs) ? 'fresh' : 'stale';
|
|
28
|
+
}
|
|
29
|
+
function indexPhase(index) {
|
|
30
|
+
if (index.status === 'indexing')
|
|
31
|
+
return 'indexing';
|
|
32
|
+
return index.result !== null || index.failure !== null ? 'indexed' : 'idle';
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The frame's second child. A call, not a component: the frame counts its
|
|
36
|
+
* children to choose a layout, and nothing indexed yet has to count as none.
|
|
37
|
+
*/
|
|
38
|
+
function embeddingResponse(columns, label, phase, results) {
|
|
39
|
+
if (phase === 'idle')
|
|
40
|
+
return null;
|
|
41
|
+
return columns ? (_jsx(ResponseColumn, { label: label, fill: phase === 'indexing', children: results })) : (results);
|
|
42
|
+
}
|
|
43
|
+
/** The spinner while the documents embed, counting the requests once there is more than one. */
|
|
44
|
+
function IndexingPane({ index }) {
|
|
45
|
+
const t = useLabels();
|
|
46
|
+
const { progress } = index;
|
|
47
|
+
return (_jsx(PaneLoading, { label: progress
|
|
48
|
+
? t('@theorem.embedding.indexing_progress', {
|
|
49
|
+
done: Math.min(progress.done + 1, progress.total),
|
|
50
|
+
total: progress.total,
|
|
51
|
+
})
|
|
52
|
+
: t('@theorem.embedding.indexing'), since: index.startedAt }));
|
|
53
|
+
}
|
|
54
|
+
/** Where the last query stands against the index: whether it searched, and its vector while both share a model. */
|
|
55
|
+
function searchOf(index, search, inputs) {
|
|
56
|
+
const state = indexStateOf(index.result, search.result, inputs);
|
|
57
|
+
if (state === 'model')
|
|
58
|
+
return { state, searched: false, queried: null };
|
|
59
|
+
return {
|
|
60
|
+
state,
|
|
61
|
+
searched: search.status !== 'idle' || search.result !== null,
|
|
62
|
+
queried: search.result?.embeddings[0]?.values ?? null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** The query against the index, and once it ran, what it found. */
|
|
66
|
+
function IndexSearch({ iface, index, search, query, indexed, state, searched, matches, pointing, }) {
|
|
67
|
+
return (_jsxs(ConsoleSection, { children: [_jsx(EmbeddingQuery, { form: query, rules: iface.inputs, index: state, isRunning: search.status === 'embedding', onRun: (input) => {
|
|
68
|
+
void search.embed([input], { task: 'query' });
|
|
69
|
+
}, onStop: search.cancel }), searched ? (_jsx(VStack, { padding: 4, children: _jsx(EmbeddingResults, { index: index, search: search, documents: indexed, matches: matches, pointing: pointing }) })) : null] }));
|
|
70
|
+
}
|
|
71
|
+
/** The index, and once it holds vectors, the search over them. */
|
|
72
|
+
function EmbeddingIndexed({ inputs, ...props }) {
|
|
73
|
+
const { index, indexed } = props;
|
|
74
|
+
const { state, searched, queried } = searchOf(index, props.search, inputs);
|
|
75
|
+
const vectors = index.result?.vectors;
|
|
76
|
+
const matches = useMemo(() => (queried && vectors ? rankMatches(queried, vectors) : []), [queried, vectors]);
|
|
77
|
+
const [hovered, setHovered] = useState(null);
|
|
78
|
+
const pointing = useMemo(() => ({ hovered, onHover: setHovered }), [hovered]);
|
|
79
|
+
return (_jsxs(VStack, { gap: 3, children: [_jsx(IndexCard, { run: index, documents: indexed, query: queried, matches: matches, pointing: pointing }), index.result ? (_jsx(IndexSearch, { ...props, state: state, searched: searched, matches: matches, pointing: pointing })) : null] }));
|
|
80
|
+
}
|
|
81
|
+
function EmbeddingBody({ transport, iface, search, defaultText = '', defaultDocuments, defaultQuery = '', maxWidth = '960px', trace, flush, columns = false, className, style, }) {
|
|
82
|
+
const t = useLabels();
|
|
83
|
+
const documents = useEmbeddingDocuments(defaultText, iface.inputs, defaultDocuments);
|
|
84
|
+
const query = useEmbeddingQuery(defaultQuery, iface.inputs);
|
|
85
|
+
const index = useEmbeddingIndex(transport);
|
|
86
|
+
const [indexed, setIndexed] = useState([]);
|
|
87
|
+
const inspector = useTraceInspector(iface, transport.traces, trace);
|
|
88
|
+
const runIndex = () => {
|
|
89
|
+
if (documents.jsonInvalid || documents.issue || documents.inputs.length === 0)
|
|
90
|
+
return;
|
|
91
|
+
setIndexed(documents.documents);
|
|
92
|
+
void index.index(documents.inputs, iface.inputs);
|
|
93
|
+
};
|
|
94
|
+
const phase = indexPhase(index);
|
|
95
|
+
return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, flush: flush, columns: columns, requestLabel: t('@theorem.embedding.documents'), className: className, style: style, children: [_jsx(EmbeddingDocuments, { form: documents, rules: iface.inputs, isRunning: phase === 'indexing', onRun: runIndex, onStop: index.cancel }), embeddingResponse(columns, t('@theorem.embedding.results'), phase, phase === 'indexing' ? (_jsx(IndexingPane, { index: index })) : (_jsx(EmbeddingIndexed, { iface: iface, index: index, search: search, query: query, indexed: indexed, inputs: documents.inputs })))] }));
|
|
96
|
+
}
|
|
97
|
+
function EmbeddingForTransport(props) {
|
|
98
|
+
const t = useLabels();
|
|
99
|
+
const search = useTheoremEmbedding(props.transport);
|
|
100
|
+
if (search.describeFailure)
|
|
101
|
+
return _jsx(PaneFailure, { title: search.describeFailure.error });
|
|
102
|
+
if (!search.iface)
|
|
103
|
+
return _jsx(PaneLoading, { label: t('@theorem.chat.loading') });
|
|
104
|
+
return _jsx(EmbeddingBody, { ...props, iface: search.iface, search: search });
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Drop-in search over an embedding profile served by `createTheoremEmbeddingHandler`:
|
|
108
|
+
* paste text and split it, or drop files, and Index embeds them as documents
|
|
109
|
+
* in as many requests as the profile's limits need. Then Search embeds a query
|
|
110
|
+
* and ranks the documents by cosine similarity. Every request is shown as
|
|
111
|
+
* JSON, and in the trace.
|
|
112
|
+
*
|
|
113
|
+
* ```tsx
|
|
114
|
+
* <TheoremEmbedding endpoint="/api/embedding" />
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export function TheoremEmbedding({ endpoint, http, transport, theme, mode, labels, ...rest }) {
|
|
118
|
+
const httpRef = useRef(http);
|
|
119
|
+
httpRef.current = http;
|
|
120
|
+
const resolved = useMemo(() => transport ?? createEmbeddingTransport({ ...httpRef.current, endpoint }), [transport, endpoint]);
|
|
121
|
+
return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(EmbeddingForTransport, { ...rest, transport: resolved }) }) }));
|
|
122
|
+
}
|
package/ui/TheoremHost.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DefinedTheme } from '@astryxdesign/core/theme';
|
|
2
2
|
import { type CSSProperties } from 'react';
|
|
3
3
|
import { type HostTransport } from '../client/host-transport.ts';
|
|
4
|
+
import type { PageTools } from '../client/live/live-page-tool.ts';
|
|
4
5
|
import type { HttpOptions } from '../client/transport.ts';
|
|
5
6
|
import { type TheoremLabels } from './labels.ts';
|
|
6
7
|
export type TheoremHostProps = {
|
|
@@ -8,20 +9,36 @@ export type TheoremHostProps = {
|
|
|
8
9
|
endpoint?: string;
|
|
9
10
|
/** Extra fetch options for the default HTTP transport (auth headers, custom fetch). */
|
|
10
11
|
http?: HttpOptions;
|
|
11
|
-
/** Bring your own transport (tests,
|
|
12
|
+
/** Bring your own transport (tests, studios, non-HTTP hosts). */
|
|
12
13
|
transport?: HostTransport;
|
|
14
|
+
/** why: Page callbacks run only after the server saves a dispatch grant. */
|
|
15
|
+
pageTools?: PageTools;
|
|
13
16
|
theme?: DefinedTheme;
|
|
14
17
|
mode?: 'system' | 'light' | 'dark';
|
|
15
18
|
/** Replacement lines by locale, for any `@theorem.*` or `@astryx.*` key. */
|
|
16
19
|
labels?: TheoremLabels;
|
|
20
|
+
/**
|
|
21
|
+
* Colour code the reply left untagged: a fence with no language is read and, when it is plainly
|
|
22
|
+
* one of JavaScript, TypeScript, Python, shell, JSON, CSS, HTML, XML, YAML, or Markdown, shown as it.
|
|
23
|
+
* A fence with a language is never changed. Off by default.
|
|
24
|
+
*/
|
|
25
|
+
detectCodeLanguage?: boolean;
|
|
17
26
|
/** Widest the column may grow, as a CSS length. Default `960px`. */
|
|
18
27
|
maxWidth?: string;
|
|
19
28
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
29
|
+
* Drive the trace from the host's own control. The built-in toggle hides.
|
|
30
|
+
* The open trace takes the console's place, unless this page is inside
|
|
31
|
+
* `TracePlacement value="panel"`, where it docks. Omit to keep the toggle.
|
|
32
|
+
* Needs a profile that records traces.
|
|
23
33
|
*/
|
|
24
34
|
trace?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The page is already a raised panel. The console sits in it instead of
|
|
37
|
+
* painting the ground it uses on a flat page.
|
|
38
|
+
*/
|
|
39
|
+
flush?: boolean;
|
|
40
|
+
/** The request on the left and the response on the right. */
|
|
41
|
+
columns?: boolean;
|
|
25
42
|
className?: string;
|
|
26
43
|
style?: CSSProperties;
|
|
27
44
|
};
|
|
@@ -35,4 +52,4 @@ export type TheoremHostProps = {
|
|
|
35
52
|
* <TheoremHost endpoint="/api/host" />
|
|
36
53
|
* ```
|
|
37
54
|
*/
|
|
38
|
-
export declare function TheoremHost({ endpoint, http, transport, theme, mode, labels, ...rest }: TheoremHostProps): import("react").JSX.Element;
|
|
55
|
+
export declare function TheoremHost({ endpoint, http, transport, theme, mode, labels, detectCodeLanguage, ...rest }: TheoremHostProps): import("react").JSX.Element;
|
package/ui/TheoremHost.js
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Banner } from '@astryxdesign/core/Banner';
|
|
3
3
|
import { EmptyState } from '@astryxdesign/core/EmptyState';
|
|
4
4
|
import { HStack } from '@astryxdesign/core/HStack';
|
|
5
|
+
import { useLocale } from '@astryxdesign/core/i18n';
|
|
5
6
|
import { List, ListItem } from '@astryxdesign/core/List';
|
|
6
|
-
import { Selector } from '@astryxdesign/core/Selector';
|
|
7
|
-
import { Spinner } from '@astryxdesign/core/Spinner';
|
|
7
|
+
import { Selector, SelectorOption } from '@astryxdesign/core/Selector';
|
|
8
8
|
import { StatusDot } from '@astryxdesign/core/StatusDot';
|
|
9
9
|
import { Text } from '@astryxdesign/core/Text';
|
|
10
10
|
import { Token } from '@astryxdesign/core/Token';
|
|
11
11
|
import { VStack } from '@astryxdesign/core/VStack';
|
|
12
|
-
import {
|
|
13
|
-
import { useMemo, useState } from 'react';
|
|
14
|
-
import { hostCallFailure, hostCallStatus, hostConsoleView } from "../client/host-call.js";
|
|
15
|
-
import { createHostTransport } from "../client/host-transport.js";
|
|
12
|
+
import { IconMathFunction, IconWorld } from '@tabler/icons-react';
|
|
13
|
+
import { useMemo, useRef, useState } from 'react';
|
|
14
|
+
import { hostCallFailure, hostCallStatus, hostConsoleView, } from "../client/host-call.js";
|
|
15
|
+
import { createHostTransport, } from "../client/host-transport.js";
|
|
16
16
|
import { missingFields } from "../client/schema-fields.js";
|
|
17
17
|
import { isRow, json } from "../client/shaped-data.js";
|
|
18
|
+
import { toolCallLabel } from "../client/transcript-groups.js";
|
|
19
|
+
import { useSecondTicker } from "../hooks/use-second-ticker.js";
|
|
18
20
|
import { useTheoremHost } from "../hooks/use-theorem-host.js";
|
|
19
|
-
import {
|
|
20
|
-
import { ConsoleFrame, JsonRequest, RequestCard, RunBar } from "./ConsoleFrame.js";
|
|
21
|
+
import { Arrive, Stream } from "./arrive.js";
|
|
22
|
+
import { ConsoleFrame, JsonRequest, RequestCard, ResponseColumn, RunBar, } from "./ConsoleFrame.js";
|
|
23
|
+
import { liveDuration, workDuration } from "./labels.js";
|
|
21
24
|
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
25
|
+
import { MarkdownPluginsProvider } from "./markdown-plugins.js";
|
|
26
|
+
import { IconMcp } from "./mcp-icon.js";
|
|
27
|
+
import { PaneFailure, PaneLoading } from "./PaneState.js";
|
|
22
28
|
import { SchemaFields } from "./SchemaFields.js";
|
|
23
|
-
import { TheoremThemeProvider } from "./theme.js";
|
|
24
29
|
import { ApprovalCard, AuthChallengeCard } from "./ToolGateCard.js";
|
|
25
30
|
import { ToolResult } from "./ToolResult.js";
|
|
26
31
|
import { useTraceInspector } from "./TraceInspectorPanel.js";
|
|
32
|
+
import { TheoremThemeProvider } from "./theme.js";
|
|
27
33
|
/** Calls listed under the console before older ones drop off the page. */
|
|
28
34
|
const MAX_EARLIER = 8;
|
|
29
35
|
const STATUS_DOT = {
|
|
@@ -49,13 +55,39 @@ function checkRequest(t, tool, text) {
|
|
|
49
55
|
return { ok: false, note: t('@theorem.host.missing', { fields: missing.join(', ') }), input };
|
|
50
56
|
return { ok: true, input };
|
|
51
57
|
}
|
|
52
|
-
/**
|
|
58
|
+
/** The kind's icon. The same glyphs the studio uses for a tool's type. */
|
|
59
|
+
const KIND_ICON = {
|
|
60
|
+
function: IconMathFunction,
|
|
61
|
+
http: IconWorld,
|
|
62
|
+
mcp: IconMcp,
|
|
63
|
+
};
|
|
64
|
+
/** The most room a row of the tool menu takes, and the lines of description it shows. */
|
|
65
|
+
const TOOL_MENU_PX = 440;
|
|
66
|
+
const TOOL_MENU_LINES = 2;
|
|
67
|
+
/** Kind and description, shown on the dropdown row and nowhere else. */
|
|
68
|
+
function toolOptionDescription(t, tool) {
|
|
69
|
+
const kind = t(`@theorem.host.kind.${tool.kind}`);
|
|
70
|
+
return tool.description ? `${kind} · ${tool.description}` : kind;
|
|
71
|
+
}
|
|
72
|
+
/** A tool's access when it can change things. Its kind stays in the dropdown. */
|
|
53
73
|
function ToolTokens({ tool }) {
|
|
54
74
|
const t = useLabels();
|
|
55
|
-
|
|
75
|
+
if (tool.access === 'read-only')
|
|
76
|
+
return null;
|
|
77
|
+
return (_jsx(HStack, { gap: 1, wrap: "wrap", children: _jsx(Token, { label: t(`@theorem.tool.access.${tool.access}`), size: "sm", color: tool.access === 'destructive' ? 'red' : 'orange' }) }));
|
|
78
|
+
}
|
|
79
|
+
/** The tool's activity while it runs, and its activityPast once it has finished, with the clock beside it. */
|
|
80
|
+
function callActivity(t, call, now) {
|
|
81
|
+
const label = call.call ? toolCallLabel(call.call) : call.name;
|
|
82
|
+
const duration = call.isRunning
|
|
83
|
+
? liveDuration(t, now - call.startedAt)
|
|
84
|
+
: call.elapsedMs == null
|
|
85
|
+
? null
|
|
86
|
+
: workDuration(t, call.elapsedMs);
|
|
87
|
+
return { label, duration };
|
|
56
88
|
}
|
|
57
89
|
/** The gate a call paused on, answered in place. */
|
|
58
|
-
function CallGate({ call, onAnswer }) {
|
|
90
|
+
function CallGate({ call, onAnswer, }) {
|
|
59
91
|
const state = call.call?.state;
|
|
60
92
|
if (state?.phase !== 'gate')
|
|
61
93
|
return null;
|
|
@@ -64,16 +96,34 @@ function CallGate({ call, onAnswer }) {
|
|
|
64
96
|
}
|
|
65
97
|
return (_jsx(ApprovalCard, { gate: state.gate, toolName: call.name, input: call.input, decided: call.answering === 'auth' ? null : call.answering, onDecision: (action) => void onAnswer(call.id, { action }) }));
|
|
66
98
|
}
|
|
67
|
-
/**
|
|
68
|
-
function
|
|
69
|
-
const
|
|
99
|
+
/** A call is settled once it failed, completed, or stopped running. */
|
|
100
|
+
function callSettled(call) {
|
|
101
|
+
const state = call.call?.state;
|
|
102
|
+
return Boolean(hostCallFailure(call)) || state?.phase === 'complete' || !call.isRunning;
|
|
103
|
+
}
|
|
104
|
+
/** A running call with nothing else in the column fills the card, the way an empty trace does. */
|
|
105
|
+
function responseFills(call) {
|
|
106
|
+
const state = call.call?.state;
|
|
107
|
+
return !callSettled(call) && state?.phase !== 'gate';
|
|
108
|
+
}
|
|
109
|
+
/** A settled call: its failure or its output, then the activity line once it has stopped. */
|
|
110
|
+
function CallSettled({ call, activity }) {
|
|
70
111
|
const state = call.call?.state;
|
|
71
112
|
const failure = hostCallFailure(call);
|
|
72
|
-
|
|
73
|
-
|
|
113
|
+
return (_jsx(Stream, { children: _jsxs(VStack, { gap: 3, children: [failure ? (_jsx(Arrive, { children: _jsx(Banner, { status: "error", role: "alert", title: failure.title, description: failure.description }) })) : null, state?.phase === 'complete' ? (_jsx(ToolResult, { output: state.output, parts: state.parts })) : null, call.isRunning ? null : _jsx(Arrive, { children: activity })] }) }));
|
|
114
|
+
}
|
|
115
|
+
/** One call's outcome: its gate, its failure, or its output, then the tool's activity and the time. */
|
|
116
|
+
function CallOutcome({ call, onAnswer, }) {
|
|
117
|
+
const t = useLabels();
|
|
118
|
+
const now = useSecondTicker(call.isRunning);
|
|
119
|
+
const { label, duration } = callActivity(t, call, now);
|
|
120
|
+
if (responseFills(call))
|
|
121
|
+
return _jsx(PaneLoading, { label: label, since: call.startedAt });
|
|
122
|
+
const activity = (_jsx(Text, { size: "sm", color: "secondary", children: duration ? t('@theorem.host.finished', { activity: label, duration }) : label }));
|
|
123
|
+
return (_jsxs(VStack, { gap: 3, children: [_jsx(CallGate, { call: call, onAnswer: onAnswer }), callSettled(call) ? _jsx(CallSettled, { call: call, activity: activity }) : null] }));
|
|
74
124
|
}
|
|
75
125
|
/** This page's earlier calls, newest first; picking one shows it again with its request. */
|
|
76
|
-
function EarlierCalls({ calls, onPick }) {
|
|
126
|
+
function EarlierCalls({ calls, onPick, }) {
|
|
77
127
|
const t = useLabels();
|
|
78
128
|
const locale = useLocale();
|
|
79
129
|
const time = useMemo(() => new Intl.DateTimeFormat(locale, { timeStyle: 'medium' }), [locale]);
|
|
@@ -85,12 +135,17 @@ function EarlierCalls({ calls, onPick }) {
|
|
|
85
135
|
}) })] }));
|
|
86
136
|
}
|
|
87
137
|
/** The picked tool's request, as fields or JSON, and Run. */
|
|
88
|
-
function ToolRequest({ tools, tool, check, text, view, shown, onText, onPick, onView, onRun, onStop }) {
|
|
138
|
+
function ToolRequest({ tools, tool, check, text, view, shown, onText, onPick, onView, onRun, onStop, }) {
|
|
89
139
|
const t = useLabels();
|
|
90
140
|
const fieldsInput = 'input' in check ? check.input : undefined;
|
|
91
141
|
const showFields = view === 'fields' && fieldsInput !== undefined;
|
|
92
|
-
return (_jsxs(RequestCard, { title: t('@theorem.host.request'), view: showFields ? 'fields' : 'json', hasFields: fieldsInput !== undefined, onView: onView, onRun: onRun, children: [_jsxs(VStack, { gap: 2, children: [_jsx(Selector, { label: t('@theorem.host.tool'), size: "sm", options: tools.map((option) => ({
|
|
93
|
-
|
|
142
|
+
return (_jsxs(RequestCard, { title: t('@theorem.host.request'), view: showFields ? 'fields' : 'json', hasFields: fieldsInput !== undefined, onView: onView, onRun: onRun, children: [_jsxs(VStack, { gap: 2, children: [_jsx(Selector, { label: t('@theorem.host.tool'), size: "sm", options: tools.map((option) => ({
|
|
143
|
+
value: option.name,
|
|
144
|
+
label: option.name,
|
|
145
|
+
icon: KIND_ICON[option.kind],
|
|
146
|
+
description: toolOptionDescription(t, option),
|
|
147
|
+
})), value: tool.name, hasSearch: tools.length > 8, renderOption: (option) => (_jsx("div", { style: { width: TOOL_MENU_PX, maxWidth: '100%', minWidth: 0 }, children: _jsx(SelectorOption, { icon: option.icon, label: option.label, description: option.description && (_jsx(Text, { type: "supporting", color: "secondary", maxLines: TOOL_MENU_LINES, children: option.description })) }) })), onChange: onPick }), _jsx(ToolTokens, { tool: tool })] }), showFields ? (_jsx(SchemaFields
|
|
148
|
+
// why: A new tool starts a new form, so inputs don't carry state across schemas.
|
|
94
149
|
, { schema: tool.inputSchema, value: fieldsInput, onChange: (next) => {
|
|
95
150
|
onText(json(next));
|
|
96
151
|
} }, tool.name)) : (_jsx(JsonRequest, { label: t('@theorem.host.request'), text: text, isInvalid: fieldsInput === undefined, onChange: onText })), _jsx(RunBar, { note: check.ok ? t('@theorem.host.shortcut') : check.note, isReady: check.ok, isRunning: shown?.isRunning === true, runLabel: t('@theorem.host.run'), runningLabel: t('@theorem.host.running'), stopLabel: t('@theorem.host.stop'), onRun: onRun, onStop: () => {
|
|
@@ -98,38 +153,73 @@ function ToolRequest({ tools, tool, check, text, view, shown, onText, onPick, on
|
|
|
98
153
|
onStop(shown.id);
|
|
99
154
|
} })] }));
|
|
100
155
|
}
|
|
101
|
-
|
|
156
|
+
/** Starts the request as a call, and returns its id. Nothing starts while one is on screen running. */
|
|
157
|
+
function startCall(host, tool, check, shown) {
|
|
158
|
+
if (!tool || !check?.ok || shown?.isRunning)
|
|
159
|
+
return null;
|
|
160
|
+
return host.run(tool.name, check.input);
|
|
161
|
+
}
|
|
162
|
+
/** What the console is showing: the picked tool, its request, and the call on screen. */
|
|
163
|
+
function useHostConsole(iface, host) {
|
|
102
164
|
const t = useLabels();
|
|
103
165
|
const [picked, setPicked] = useState(iface.tools[0]?.name);
|
|
104
|
-
// Each tool keeps its own request while you move between them.
|
|
105
166
|
const [drafts, setDrafts] = useState({});
|
|
106
167
|
const [shownId, setShownId] = useState(null);
|
|
107
168
|
const [view, setView] = useState('fields');
|
|
108
|
-
const
|
|
109
|
-
|
|
169
|
+
const { tool, text, shown, earlier } = hostConsoleView({
|
|
170
|
+
tools: iface.tools,
|
|
171
|
+
picked,
|
|
172
|
+
drafts,
|
|
173
|
+
calls: host.calls,
|
|
174
|
+
shownId,
|
|
175
|
+
});
|
|
110
176
|
const check = tool ? checkRequest(t, tool, text) : null;
|
|
111
177
|
const setText = (next) => {
|
|
112
178
|
if (tool)
|
|
113
179
|
setDrafts((previous) => ({ ...previous, [tool.name]: next }));
|
|
114
180
|
};
|
|
115
181
|
const run = () => {
|
|
116
|
-
|
|
117
|
-
|
|
182
|
+
const id = startCall(host, tool, check, shown);
|
|
183
|
+
if (id !== null)
|
|
184
|
+
setShownId(id);
|
|
118
185
|
};
|
|
119
186
|
const pick = (call) => {
|
|
120
187
|
setPicked(call.name);
|
|
121
188
|
setDrafts((previous) => ({ ...previous, [call.name]: json(call.input) }));
|
|
122
189
|
setShownId(call.id);
|
|
123
190
|
};
|
|
124
|
-
return
|
|
191
|
+
return { tool, text, shown, earlier, check, view, setView, setPicked, setText, run, pick };
|
|
192
|
+
}
|
|
193
|
+
/** The call on screen, then this page's earlier ones. */
|
|
194
|
+
function HostCalls({ shown, earlier, onAnswer, onPick, }) {
|
|
195
|
+
return (_jsxs(_Fragment, { children: [shown ? _jsx(CallOutcome, { call: shown, onAnswer: onAnswer }) : null, _jsx(EarlierCalls, { calls: earlier, onPick: onPick })] }));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The frame's second child. A call, not a component: the frame counts its
|
|
199
|
+
* children to choose a layout, and no calls yet has to count as none.
|
|
200
|
+
*/
|
|
201
|
+
function hostResponse(columns, label, shown, earlier, calls) {
|
|
202
|
+
if (!columns)
|
|
203
|
+
return calls;
|
|
204
|
+
const alone = earlier.length === 0;
|
|
205
|
+
if (!shown && alone)
|
|
206
|
+
return null;
|
|
207
|
+
return (_jsx(ResponseColumn, { label: label, fill: shown != null && responseFills(shown) && alone, children: calls }));
|
|
208
|
+
}
|
|
209
|
+
function HostBody({ transport, iface, host, maxWidth = '960px', trace, flush, columns = false, className, style, }) {
|
|
210
|
+
const t = useLabels();
|
|
211
|
+
const inspector = useTraceInspector(iface, transport.traces, trace);
|
|
212
|
+
const desk = useHostConsole(iface, host);
|
|
213
|
+
const { tool, check, shown, earlier } = desk;
|
|
214
|
+
return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, flush: flush, columns: columns, requestLabel: t('@theorem.host.request'), className: className, style: style, children: [tool && check ? (_jsx(ToolRequest, { tools: iface.tools, tool: tool, check: check, text: desk.text, view: desk.view, shown: shown, onText: desk.setText, onPick: desk.setPicked, onView: desk.setView, onRun: desk.run, onStop: host.cancel })) : (_jsx(EmptyState, { title: t('@theorem.host.no_tools') })), hostResponse(columns, t('@theorem.host.response'), shown, earlier, _jsx(HostCalls, { shown: shown, earlier: earlier, onAnswer: host.answer, onPick: desk.pick }))] }));
|
|
125
215
|
}
|
|
126
216
|
function HostForTransport(props) {
|
|
127
217
|
const t = useLabels();
|
|
128
|
-
const host = useTheoremHost(props.transport);
|
|
218
|
+
const host = useTheoremHost(props.transport, props.pageTools);
|
|
129
219
|
if (host.describeFailure)
|
|
130
|
-
return _jsx(
|
|
220
|
+
return _jsx(PaneFailure, { title: host.describeFailure.error });
|
|
131
221
|
if (!host.iface)
|
|
132
|
-
return _jsx(
|
|
222
|
+
return _jsx(PaneLoading, { label: t('@theorem.chat.loading') });
|
|
133
223
|
return _jsx(HostBody, { ...props, iface: host.iface, host: host });
|
|
134
224
|
}
|
|
135
225
|
/**
|
|
@@ -142,9 +232,9 @@ function HostForTransport(props) {
|
|
|
142
232
|
* <TheoremHost endpoint="/api/host" />
|
|
143
233
|
* ```
|
|
144
234
|
*/
|
|
145
|
-
export function TheoremHost({ endpoint, http, transport, theme, mode, labels, ...rest }) {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
[transport, endpoint]);
|
|
149
|
-
return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(HostForTransport, { ...rest, transport: resolved }) }) }));
|
|
235
|
+
export function TheoremHost({ endpoint, http, transport, theme, mode, labels, detectCodeLanguage, ...rest }) {
|
|
236
|
+
const httpRef = useRef(http);
|
|
237
|
+
httpRef.current = http;
|
|
238
|
+
const resolved = useMemo(() => transport ?? createHostTransport({ ...httpRef.current, endpoint }), [transport, endpoint]);
|
|
239
|
+
return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(MarkdownPluginsProvider, { detectCodeLanguage: detectCodeLanguage, children: _jsx(HostForTransport, { ...rest, transport: resolved }) }) }) }));
|
|
150
240
|
}
|
package/ui/ToolGateCard.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { ToolGate } from '@theoremjs/agents/kernel';
|
|
2
2
|
import type { ToolDecisionAction } from '../client/tool-resume.ts';
|
|
3
3
|
export type ToolDecision = ToolDecisionAction;
|
|
4
|
+
/** The widest a gate card gets, in pixels. */
|
|
5
|
+
export declare const GATE_CARD_WIDTH = 400;
|
|
4
6
|
export type ApprovalCardProps = {
|
|
5
7
|
gate: ToolGate;
|
|
6
8
|
toolName: string;
|
|
9
|
+
/** The agent asking, as its name is shown (`@concierge`); the heading names it. */
|
|
10
|
+
agent?: string;
|
|
7
11
|
input?: unknown;
|
|
8
|
-
/** The decision on its way;
|
|
12
|
+
/** The decision on its way; its button shows loading and the other is disabled until the gate settles. */
|
|
9
13
|
decided?: ToolDecision | null;
|
|
10
14
|
onDecision?: (action: ToolDecision) => void;
|
|
11
15
|
};
|