@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.
Files changed (258) hide show
  1. package/README.md +728 -202
  2. package/client/composer-hints.js +1 -1
  3. package/client/decision-images.d.ts +76 -0
  4. package/client/decision-images.js +139 -0
  5. package/client/decision-transport.d.ts +26 -4
  6. package/client/decision-transport.js +79 -6
  7. package/client/embedding-input.d.ts +84 -0
  8. package/client/embedding-input.js +194 -0
  9. package/client/embedding-space.d.ts +82 -0
  10. package/client/embedding-space.js +389 -0
  11. package/client/embedding-transport.d.ts +40 -0
  12. package/client/embedding-transport.js +71 -0
  13. package/client/failure.d.ts +7 -10
  14. package/client/failure.js +9 -9
  15. package/client/host-call.d.ts +1 -1
  16. package/client/host-call.js +7 -2
  17. package/client/host-transport.d.ts +2 -0
  18. package/client/host-transport.js +3 -2
  19. package/client/index.d.ts +19 -11
  20. package/client/index.js +14 -10
  21. package/client/ink-waveform.d.ts +1 -1
  22. package/client/ink-waveform.js +1 -2
  23. package/client/live/apply-live-turn-tool-event.js +1 -1
  24. package/client/live/live-captions.d.ts +30 -0
  25. package/client/live/live-captions.js +105 -1
  26. package/client/live/live-mic-forward.d.ts +1 -0
  27. package/client/live/live-mic-forward.js +11 -2
  28. package/client/live/live-page-tool.d.ts +34 -0
  29. package/client/live/live-page-tool.js +11 -0
  30. package/client/live/live-state.d.ts +4 -2
  31. package/client/live/live-state.js +2 -0
  32. package/client/live/live-video.js +1 -1
  33. package/client/live/run-live-tool-call.d.ts +7 -2
  34. package/client/live/run-live-tool-call.js +48 -10
  35. package/client/live/use-live-caption-log.d.ts +17 -0
  36. package/client/live/use-live-caption-log.js +51 -0
  37. package/client/live-client.d.ts +52 -4
  38. package/client/live-client.js +250 -59
  39. package/client/live-messages.d.ts +51 -13
  40. package/client/live-messages.js +42 -9
  41. package/client/page-dispatch.d.ts +15 -0
  42. package/client/page-dispatch.js +63 -0
  43. package/client/run-commit.d.ts +16 -43
  44. package/client/run-commit.js +26 -101
  45. package/client/run-session.d.ts +40 -41
  46. package/client/run-session.js +104 -112
  47. package/client/schema-fields.js +27 -6
  48. package/client/shaped-data.js +37 -8
  49. package/client/source-citations.js +1 -1
  50. package/client/space-geometry.d.ts +39 -0
  51. package/client/space-geometry.js +183 -0
  52. package/client/state-fields.js +1 -1
  53. package/client/tool-result.js +30 -12
  54. package/client/tool-resume.d.ts +18 -1
  55. package/client/tool-resume.js +4 -1
  56. package/client/trace-feed.d.ts +1 -1
  57. package/client/trace-story.d.ts +2 -2
  58. package/client/trace-story.js +43 -19
  59. package/client/trace-view.d.ts +2 -2
  60. package/client/trace-view.js +56 -16
  61. package/client/transcript-groups.d.ts +28 -2
  62. package/client/transcript-groups.js +63 -13
  63. package/client/transcript-schema.d.ts +10 -0
  64. package/client/transcript-schema.js +120 -0
  65. package/client/transport.d.ts +114 -26
  66. package/client/transport.js +106 -32
  67. package/client/turn-client.d.ts +17 -8
  68. package/client/turn-client.js +26 -31
  69. package/client/voice-recorder.d.ts +1 -1
  70. package/client/voice-recorder.js +2 -5
  71. package/client/wire-line.js +5 -5
  72. package/client/worklets/mic-capture.js +9 -9
  73. package/components/InkWaveform.d.ts +12 -1
  74. package/components/InkWaveform.js +65 -42
  75. package/components/live/use-live-runner-controls.d.ts +3 -4
  76. package/components/live/use-live-runner-controls.js +6 -10
  77. package/components/live/use-live-runner-model.d.ts +10 -6
  78. package/components/live/use-live-runner-model.js +29 -14
  79. package/components/live/use-live-runner-ui.d.ts +10 -8
  80. package/components/live/use-live-runner-ui.js +19 -11
  81. package/components/live/use-live-session-client.d.ts +4 -4
  82. package/components/live/use-live-session-client.js +11 -8
  83. package/components/use-composer-voice.d.ts +4 -4
  84. package/components/use-composer-voice.js +8 -8
  85. package/components/use-editor-selection.js +4 -1
  86. package/components/use-voice-playback.d.ts +2 -1
  87. package/components/use-voice-playback.js +4 -4
  88. package/hooks/use-described.d.ts +5 -1
  89. package/hooks/use-described.js +11 -9
  90. package/hooks/use-embedding-index.d.ts +27 -0
  91. package/hooks/use-embedding-index.js +43 -0
  92. package/hooks/use-one-call.d.ts +31 -0
  93. package/hooks/use-one-call.js +76 -0
  94. package/hooks/use-page-tool-warnings.d.ts +6 -0
  95. package/hooks/use-page-tool-warnings.js +25 -0
  96. package/hooks/use-second-ticker.d.ts +2 -0
  97. package/hooks/use-second-ticker.js +13 -0
  98. package/hooks/use-theorem-chat-actions.d.ts +25 -21
  99. package/hooks/use-theorem-chat-actions.js +67 -45
  100. package/hooks/use-theorem-chat-state.d.ts +6 -6
  101. package/hooks/use-theorem-chat-state.js +13 -9
  102. package/hooks/use-theorem-chat.d.ts +48 -14
  103. package/hooks/use-theorem-chat.js +329 -141
  104. package/hooks/use-theorem-decision.d.ts +5 -10
  105. package/hooks/use-theorem-decision.js +4 -67
  106. package/hooks/use-theorem-embedding.d.ts +18 -0
  107. package/hooks/use-theorem-embedding.js +12 -0
  108. package/hooks/use-theorem-host.d.ts +5 -4
  109. package/hooks/use-theorem-host.js +26 -8
  110. package/hooks/use-theorem-interface.d.ts +6 -3
  111. package/hooks/use-theorem-interface.js +12 -22
  112. package/index.d.ts +6 -3
  113. package/index.js +5 -2
  114. package/live.d.ts +1 -0
  115. package/live.js +1 -1
  116. package/package.json +6 -5
  117. package/server/checkpoint-ownership.d.ts +6 -0
  118. package/server/checkpoint-ownership.js +42 -0
  119. package/server/conversation-store.d.ts +92 -0
  120. package/server/conversation-store.js +131 -0
  121. package/server/conversation.d.ts +193 -0
  122. package/server/conversation.js +552 -0
  123. package/server/decision-handler.d.ts +12 -4
  124. package/server/decision-handler.js +29 -38
  125. package/server/embedding-handler.d.ts +33 -0
  126. package/server/embedding-handler.js +45 -0
  127. package/server/handler.d.ts +58 -20
  128. package/server/handler.js +441 -238
  129. package/server/mod.d.ts +15 -6
  130. package/server/mod.js +13 -5
  131. package/server/page-dispatch.d.ts +22 -0
  132. package/server/page-dispatch.js +127 -0
  133. package/server/request-body.d.ts +2 -0
  134. package/server/request-body.js +42 -0
  135. package/server/request-check.d.ts +6 -1
  136. package/server/request-check.js +27 -12
  137. package/server/session-authority.d.ts +8 -0
  138. package/server/session-authority.js +78 -0
  139. package/server/session-boundary.d.ts +26 -0
  140. package/server/session-boundary.js +64 -0
  141. package/server/session-identity.d.ts +16 -0
  142. package/server/session-identity.js +41 -0
  143. package/server/session-limits.d.ts +12 -0
  144. package/server/session-limits.js +59 -0
  145. package/server/session-store.d.ts +69 -20
  146. package/server/session-store.js +220 -5
  147. package/server/single-action.d.ts +21 -0
  148. package/server/single-action.js +30 -0
  149. package/server/steer-inbox.js +1 -1
  150. package/server/tool-operations.d.ts +6 -0
  151. package/server/tool-operations.js +132 -0
  152. package/server/turn-input.d.ts +10 -0
  153. package/server/turn-input.js +29 -0
  154. package/styles/ink-controls.css +78 -78
  155. package/ui/CardWall.d.ts +10 -0
  156. package/ui/CardWall.js +61 -0
  157. package/ui/ChatComposerBar.d.ts +28 -5
  158. package/ui/ChatComposerBar.js +85 -30
  159. package/ui/ChatTranscript.d.ts +14 -1
  160. package/ui/ChatTranscript.js +110 -57
  161. package/ui/ConsoleFrame.d.ts +61 -4
  162. package/ui/ConsoleFrame.js +109 -9
  163. package/ui/DecisionAnswers.d.ts +40 -2
  164. package/ui/DecisionAnswers.js +166 -35
  165. package/ui/DecisionImages.d.ts +16 -0
  166. package/ui/DecisionImages.js +46 -0
  167. package/ui/EmbeddingDocuments.d.ts +78 -0
  168. package/ui/EmbeddingDocuments.js +278 -0
  169. package/ui/EmbeddingQuery.d.ts +27 -0
  170. package/ui/EmbeddingQuery.js +95 -0
  171. package/ui/EmbeddingResults.d.ts +41 -0
  172. package/ui/EmbeddingResults.js +165 -0
  173. package/ui/EmbeddingSpace.d.ts +26 -0
  174. package/ui/EmbeddingSpace.js +106 -0
  175. package/ui/LiveCaptionsPanel.d.ts +22 -0
  176. package/ui/LiveCaptionsPanel.js +27 -0
  177. package/ui/LiveRunner.d.ts +6 -4
  178. package/ui/LiveRunner.js +42 -55
  179. package/ui/PaneState.d.ts +33 -0
  180. package/ui/PaneState.js +50 -0
  181. package/ui/SchemaFields.d.ts +1 -1
  182. package/ui/SchemaFields.js +54 -26
  183. package/ui/ShapedData.js +73 -28
  184. package/ui/SidePanel.d.ts +74 -5
  185. package/ui/SidePanel.js +82 -9
  186. package/ui/StateFields.d.ts +10 -2
  187. package/ui/StateFields.js +56 -13
  188. package/ui/TheoremChat.d.ts +65 -14
  189. package/ui/TheoremChat.js +66 -78
  190. package/ui/TheoremDecision.d.ts +12 -20
  191. package/ui/TheoremDecision.js +78 -35
  192. package/ui/TheoremEmbedding.d.ts +27 -0
  193. package/ui/TheoremEmbedding.js +122 -0
  194. package/ui/TheoremHost.d.ts +22 -5
  195. package/ui/TheoremHost.js +127 -37
  196. package/ui/ToolGateCard.d.ts +5 -1
  197. package/ui/ToolGateCard.js +30 -23
  198. package/ui/ToolResult.js +12 -10
  199. package/ui/ToolResultCharts.js +29 -12
  200. package/ui/TraceCharts.d.ts +2 -2
  201. package/ui/TraceCharts.js +48 -17
  202. package/ui/TraceGuardrails.d.ts +3 -2
  203. package/ui/TraceGuardrails.js +24 -6
  204. package/ui/TraceInspector.d.ts +11 -1
  205. package/ui/TraceInspector.js +45 -28
  206. package/ui/TraceInspectorPanel.d.ts +31 -9
  207. package/ui/TraceInspectorPanel.js +77 -39
  208. package/ui/TraceOverview.d.ts +7 -3
  209. package/ui/TraceOverview.js +44 -14
  210. package/ui/TraceSpanDetail.js +24 -22
  211. package/ui/TraceStory.js +9 -8
  212. package/ui/TraceValues.js +19 -9
  213. package/ui/TraceWaterfall.js +12 -9
  214. package/ui/VoiceNote.js +4 -4
  215. package/ui/approval-heading.d.ts +4 -0
  216. package/ui/approval-heading.js +9 -0
  217. package/ui/arrive.d.ts +18 -0
  218. package/ui/arrive.js +46 -0
  219. package/ui/built/theme.css +86 -68
  220. package/ui/built/theme.css.d.ts +9 -0
  221. package/ui/built/theorem.d.ts +3 -3
  222. package/ui/built/theorem.js +34 -5
  223. package/ui/built/theorem.variants.d.ts +8 -2
  224. package/ui/code-language.d.ts +12 -0
  225. package/ui/code-language.js +50 -0
  226. package/ui/disclosure-motion.js +53 -9
  227. package/ui/embedding-space-gl.d.ts +37 -0
  228. package/ui/embedding-space-gl.js +663 -0
  229. package/ui/embedding-space-view.d.ts +8 -0
  230. package/ui/embedding-space-view.js +8 -0
  231. package/ui/glide.d.ts +7 -0
  232. package/ui/glide.js +44 -0
  233. package/ui/icons.js +2 -1
  234. package/ui/index.d.ts +20 -9
  235. package/ui/index.js +20 -10
  236. package/ui/labels-provider.js +5 -2
  237. package/ui/labels.d.ts +374 -85
  238. package/ui/labels.js +1028 -188
  239. package/ui/markdown-plugins.d.ts +9 -0
  240. package/ui/markdown-plugins.js +13 -0
  241. package/ui/mcp-icon.d.ts +6 -0
  242. package/ui/mcp-icon.js +8 -0
  243. package/ui/row-keys.d.ts +19 -0
  244. package/ui/row-keys.js +33 -0
  245. package/ui/theme.d.ts +1 -1
  246. package/ui/theme.js +2 -2
  247. package/ui/theorem-theme.js +32 -10
  248. package/ui/trace-palette.d.ts +2 -16
  249. package/ui/trace-palette.js +2 -16
  250. package/ui/trace-patterns.d.ts +11 -3
  251. package/ui/trace-patterns.js +16 -3
  252. package/ui/transcript-copy-text.js +1 -0
  253. package/ui/wall-columns.d.ts +8 -0
  254. package/ui/wall-columns.js +19 -0
  255. package/client/walk-away.d.ts +0 -34
  256. package/client/walk-away.js +0 -48
  257. package/server/walk-away.d.ts +0 -22
  258. package/server/walk-away.js +0 -41
@@ -4,23 +4,20 @@ import { Button } from '@astryxdesign/core/Button';
4
4
  import { Card } from '@astryxdesign/core/Card';
5
5
  import { CodeBlock } from '@astryxdesign/core/CodeBlock';
6
6
  import { Collapsible } from '@astryxdesign/core/Collapsible';
7
- import { Heading } from '@astryxdesign/core/Heading';
8
7
  import { HStack } from '@astryxdesign/core/HStack';
8
+ import { Icon } from '@astryxdesign/core/Icon';
9
9
  import { Text } from '@astryxdesign/core/Text';
10
10
  import { TextInput } from '@astryxdesign/core/TextInput';
11
11
  import { Token } from '@astryxdesign/core/Token';
12
12
  import { VStack } from '@astryxdesign/core/VStack';
13
+ import { IconEye, IconFlame, IconPencil } from '@tabler/icons-react';
13
14
  import { useCallback, useEffect, useState } from 'react';
14
15
  import { isOAuthComplete } from "../client/oauth-popup.js";
16
+ import { humanize } from "../client/shaped-data.js";
17
+ import { approvalHeading } from "./approval-heading.js";
15
18
  import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
16
- /** What an approval means is the tool registrant's call: `session_consent` lasts the session. */
17
- function decisionLabel(t, decision, gate) {
18
- if (decision === 'deny')
19
- return t('@theorem.gate.approval.denied');
20
- return t(gate.permission === 'session_consent'
21
- ? '@theorem.gate.approval.approved_session'
22
- : '@theorem.gate.approval.approved_once');
23
- }
19
+ /** The widest a gate card gets, in pixels. */
20
+ export const GATE_CARD_WIDTH = 400;
24
21
  function formatInput(value) {
25
22
  if (typeof value === 'string')
26
23
  return value;
@@ -28,20 +25,30 @@ function formatInput(value) {
28
25
  return '';
29
26
  return JSON.stringify(value, null, 2);
30
27
  }
31
- function CardHeader(props) {
32
- return (_jsxs(HStack, { justify: "between", align: "center", gap: 2, children: [_jsxs(VStack, { gap: 1, children: [_jsx(Badge, { variant: "warning", label: props.badge }), _jsxs(Heading, { level: 4, children: [props.title, " ", _jsx("code", { children: props.toolName })] })] }), _jsx(Badge, { variant: "neutral", label: props.tag })] }));
28
+ /** The one frame of every gate card: what is asked, a tag beside it, and what answers it. */
29
+ function GateCard(props) {
30
+ return (_jsx(Card, { variant: "bubble", elevation: "med", maxWidth: GATE_CARD_WIDTH, children: _jsxs(VStack, { gap: 3, children: [_jsxs(HStack, { justify: "between", align: "start", gap: 3, children: [_jsx(Text, { type: "large", weight: "semibold", children: props.heading }), props.tag] }), props.children] }) }));
31
+ }
32
+ const ACCESS_ICON = {
33
+ 'read-only': IconEye,
34
+ 'read-write': IconPencil,
35
+ destructive: IconFlame,
36
+ };
37
+ function ToolAccessTag({ access }) {
38
+ const t = useLabels();
39
+ return (_jsxs(HStack, { gap: 1, align: "center", children: [_jsx(Icon, { icon: ACCESS_ICON[access], size: "sm", color: access === 'destructive' ? 'error' : 'secondary' }), _jsx(Text, { type: "supporting", textWrap: "nowrap", children: t(`@theorem.tool.access.${access}`) })] }));
33
40
  }
34
41
  export function ApprovalCard(props) {
35
42
  return (_jsx(TheoremLabelsProvider, { children: _jsx(ApprovalBody, { ...props }) }));
36
43
  }
37
- function ApprovalBody({ gate, toolName, input, decided = null, onDecision }) {
44
+ function ApprovalBody({ gate, toolName, agent, input, decided = null, onDecision, }) {
38
45
  const t = useLabels();
39
- const args = formatInput(input);
40
- return (_jsx(Card, { padding: 4, children: _jsxs(VStack, { gap: 3, children: [_jsx(CardHeader, { badge: t('@theorem.gate.approval.badge'), title: t('@theorem.gate.approval.title'), toolName: toolName, tag: t(`@theorem.gate.tag.${gate.permission ?? gate.kind}`) }), gate.summary ? _jsx(Text, { children: gate.summary }) : null, args ? (_jsx(Collapsible, { trigger: _jsx(Text, { size: "sm", children: t('@theorem.gate.approval.input') }), children: _jsx(CodeBlock, { code: args, language: "json", size: "sm" }) })) : null, decided === null ? (_jsxs(HStack, { gap: 2, justify: "end", children: [_jsx(Button, { label: t('@theorem.gate.approval.deny'), variant: "ghost", onClick: () => onDecision?.('deny') }), _jsx(Button, { label: t('@theorem.gate.approval.approve'), variant: "primary", onClick: () => onDecision?.('allow') })] })) : (_jsx(Badge, { variant: decided === 'deny' ? 'error' : 'success', label: decisionLabel(t, decided, gate) }))] }) }));
46
+ const args = formatInput(gate.preparedArguments ?? input);
47
+ return (_jsxs(GateCard, { heading: approvalHeading(t, gate, toolName, agent), tag: gate.access ? _jsx(ToolAccessTag, { access: gate.access }) : null, children: [gate.summary ? _jsx(Text, { color: "secondary", children: gate.summary }) : null, args ? (_jsx(Collapsible, { defaultIsOpen: false, trigger: _jsx(Text, { type: "supporting", children: t('@theorem.gate.approval.input') }), children: _jsx(CodeBlock, { code: args, language: "json", size: "sm" }) })) : null, _jsxs(HStack, { gap: 2, justify: "end", children: [_jsx(Button, { label: t('@theorem.gate.approval.deny'), variant: "ghost", isLoading: decided === 'deny', isDisabled: decided === 'allow', onClick: () => onDecision?.('deny') }), _jsx(Button, { label: t('@theorem.gate.approval.approve'), variant: "primary", isLoading: decided === 'allow', isDisabled: decided === 'deny', onClick: () => onDecision?.('allow') })] })] }));
41
48
  }
42
49
  function AuthChallengeDetails({ challenge }) {
43
50
  const t = useLabels();
44
- return (_jsxs(_Fragment, { children: [_jsx(Text, { children: challenge.message }), challenge.resource ? (_jsx(Text, { size: "sm", color: "secondary", children: t('@theorem.gate.auth.resource', { resource: challenge.resource }) })) : null, challenge.requiredScopes?.length ? (_jsx(HStack, { gap: 1, wrap: "wrap", children: challenge.requiredScopes.map((scope) => (_jsx(Token, { label: scope, size: "sm" }, scope))) })) : null] }));
51
+ return (_jsxs(_Fragment, { children: [_jsx(Text, { color: "secondary", children: challenge.message }), challenge.resource ? (_jsx(Text, { type: "supporting", color: "secondary", children: t('@theorem.gate.auth.resource', { resource: challenge.resource }) })) : null, challenge.requiredScopes?.length ? (_jsx(HStack, { gap: 1, wrap: "wrap", children: challenge.requiredScopes.map((scope) => (_jsx(Token, { label: scope, size: "sm" }, scope))) })) : null] }));
45
52
  }
46
53
  export function AuthChallengeCard(props) {
47
54
  return (_jsx(TheoremLabelsProvider, { children: _jsx(AuthChallengeBody, { ...props }) }));
@@ -63,7 +70,7 @@ function useOAuthPopup(slot, onComplete) {
63
70
  }, [popup, slot, onComplete]);
64
71
  return (url) => setPopup(globalThis.open(url, '_blank', 'width=600,height=700'));
65
72
  }
66
- function AuthChallengeBody({ gate, toolName, onAuthenticated, submitted = false }) {
73
+ function AuthChallengeBody({ gate, toolName, onAuthenticated, submitted = false, }) {
67
74
  const t = useLabels();
68
75
  const challenge = gate.authChallenge;
69
76
  const { authType, slot } = challenge;
@@ -74,25 +81,25 @@ function AuthChallengeBody({ gate, toolName, onAuthenticated, submitted = false
74
81
  const value = secret.trim();
75
82
  if (!value)
76
83
  return;
77
- // The key goes to the server once; the browser keeps no copy.
84
+ // why: The key goes to the server once; the browser keeps no copy.
78
85
  setSecret('');
79
86
  onAuthenticated?.(value);
80
87
  }
81
- return (_jsx(Card, { padding: 4, children: _jsxs(VStack, { gap: 3, children: [_jsx(CardHeader, { badge: t('@theorem.gate.auth.badge'), title: t('@theorem.gate.auth.title'), toolName: toolName, tag: t(`@theorem.gate.tag.${authType}`) }), _jsx(AuthChallengeDetails, { challenge: challenge }), _jsx(AuthAction, { authType: authType, authUrl: challenge.authorizationUrl, slot: slot, secret: secret, submitted: submitted, onSecretChange: setSecret, onSubmit: submit, onOpenSignIn: openSignIn })] }) }));
88
+ return (_jsxs(GateCard, { heading: t('@theorem.gate.auth.title', { tool: humanize(toolName).toLowerCase() }), tag: _jsx(Text, { type: "supporting", textWrap: "nowrap", children: t(`@theorem.gate.tag.${authType}`) }), children: [_jsx(AuthChallengeDetails, { challenge: challenge }), _jsx(AuthAction, { authType: authType, authUrl: challenge.authorizationUrl, slot: slot, secret: secret, submitted: submitted, onSecretChange: setSecret, onSubmit: submit, onOpenSignIn: openSignIn })] }));
82
89
  }
83
90
  function AuthAction(props) {
84
91
  const t = useLabels();
85
92
  if (props.submitted) {
86
- return _jsx(Badge, { variant: "success", label: t('@theorem.gate.auth.provided', { slot: props.slot }) });
93
+ return (_jsx(Badge, { variant: "success", label: t('@theorem.gate.auth.provided', { slot: props.slot }) }));
87
94
  }
88
95
  if (props.authType === 'oauth2') {
89
96
  if (!props.authUrl) {
90
97
  return _jsx(Text, { color: "secondary", children: t('@theorem.gate.auth.no_oauth') });
91
98
  }
92
- return (_jsx(Button, { label: t('@theorem.gate.auth.authorize'), variant: "primary", onClick: () => {
93
- if (props.authUrl)
94
- props.onOpenSignIn(props.authUrl);
95
- } }));
99
+ return (_jsx(HStack, { justify: "end", children: _jsx(Button, { label: t('@theorem.gate.auth.authorize'), variant: "primary", onClick: () => {
100
+ if (props.authUrl)
101
+ props.onOpenSignIn(props.authUrl);
102
+ } }) }));
96
103
  }
97
104
  const label = t(props.authType === 'api_key' ? '@theorem.gate.auth.api_key' : '@theorem.gate.auth.bearer');
98
105
  const note = t('@theorem.gate.auth.secret_note').trim();
package/ui/ToolResult.js CHANGED
@@ -2,17 +2,19 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { AspectRatio } from '@astryxdesign/core/AspectRatio';
3
3
  import { ClickableCard } from '@astryxdesign/core/ClickableCard';
4
4
  import { Grid } from '@astryxdesign/core/Grid';
5
+ import { useLocale } from '@astryxdesign/core/i18n';
5
6
  import { Lightbox } from '@astryxdesign/core/Lightbox';
6
7
  import { Skeleton } from '@astryxdesign/core/Skeleton';
7
8
  import { VStack } from '@astryxdesign/core/VStack';
8
- import { useLocale } from '@astryxdesign/core/i18n';
9
9
  import { lazy, Suspense, useMemo, useState } from 'react';
10
10
  import { withUnit } from "../client/shaped-data.js";
11
11
  import { hasLead, layoutResult, returnedMedia, } from "../client/tool-result.js";
12
+ import { Arrive } from "./arrive.js";
12
13
  import { useLabels } from "./labels-provider.js";
14
+ import { keyedByContent } from "./row-keys.js";
13
15
  import { ShapedData } from "./ShapedData.js";
14
- import { VoiceNote } from "./VoiceNote.js";
15
16
  import { ChartCard } from "./trace-patterns.js";
17
+ import { VoiceNote } from "./VoiceNote.js";
16
18
  const ToolResultCharts = lazy(() => import("./ToolResultCharts.js").then((module) => ({ default: module.ToolResultCharts })));
17
19
  /** Narrowest an image card gets. */
18
20
  const IMAGE_CARD_PX = 200;
@@ -20,7 +22,7 @@ const IMAGE_CARD_PX = 200;
20
22
  const CHART_CARD_PX = 236;
21
23
  function Images({ images }) {
22
24
  const [open, setOpen] = useState(null);
23
- return (_jsxs(_Fragment, { children: [_jsx(Grid, { columns: { minWidth: IMAGE_CARD_PX, repeat: 'fill' }, gap: 3, children: images.map((image, index) => (_jsx(ClickableCard, { label: image.alt || 'Open image', padding: 0, onClick: () => setOpen(index), children: _jsx(AspectRatio, { ratio: 4 / 3, fit: "cover", children: _jsx("img", { src: image.src, alt: image.alt, loading: "lazy" }) }) }, image.src))) }), _jsx(Lightbox, { isOpen: open !== null, onOpenChange: (isOpen) => {
25
+ return (_jsxs(_Fragment, { children: [_jsx(Grid, { columns: { minWidth: IMAGE_CARD_PX, repeat: 'fill' }, gap: 3, children: images.map((image, index) => (_jsx(Arrive, { children: _jsx(ClickableCard, { label: image.alt || 'Open image', padding: 0, onClick: () => setOpen(index), children: _jsx(AspectRatio, { ratio: 4 / 3, fit: "cover", children: _jsx("img", { src: image.src, alt: image.alt, loading: "lazy" }) }) }) }, image.src))) }), _jsx(Lightbox, { isOpen: open !== null, onOpenChange: (isOpen) => {
24
26
  if (!isOpen)
25
27
  setOpen(null);
26
28
  }, media: images.map((image) => ({ src: image.src, alt: image.alt })), index: open ?? 0, onIndexChange: setOpen })] }));
@@ -30,21 +32,21 @@ function Figures({ figures }) {
30
32
  const decimal = useMemo(() => new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }), [locale]);
31
33
  if (figures.length === 0)
32
34
  return null;
33
- return (_jsx(Grid, { columns: { minWidth: 160, repeat: 'fill' }, gap: 3, children: figures.map((figure) => (_jsx(ChartCard, { title: figure.label, value: withUnit(decimal.format(figure.value), figure.unit) }, figure.key))) }));
35
+ return (_jsx(Grid, { columns: { minWidth: 160, repeat: 'fill' }, gap: 3, children: figures.map((figure) => (_jsx(Arrive, { children: _jsx(ChartCard, { title: figure.label, value: withUnit(decimal.format(figure.value), figure.unit) }) }, figure.key))) }));
34
36
  }
35
37
  /** Charts load with their module; cards hold their place until then. */
36
38
  function Charts({ charts }) {
37
39
  if (charts.length === 0)
38
40
  return null;
39
- return (_jsx(Suspense, { fallback: _jsx(Grid, { columns: { minWidth: 260, repeat: 'fill' }, gap: 3, children: charts.map((chart) => (_jsx(Skeleton, { height: CHART_CARD_PX }, chart.key))) }), children: _jsx(ToolResultCharts, { charts: charts }) }));
41
+ return (_jsx(Suspense, { fallback: _jsx(Grid, { columns: { minWidth: 260, repeat: 'fill' }, gap: 3, children: charts.map((chart) => (_jsx(Arrive, { children: _jsx(Skeleton, { height: CHART_CARD_PX }) }, chart.key))) }), children: _jsx(ToolResultCharts, { charts: charts }) }));
40
42
  }
41
- /** What answers the call, laid out from the output: its figures as stat cards, its series as charts, the images and audio it returned. */
42
- function ResultLead({ layout, parts = [] }) {
43
+ /** What answers the call, laid out from the output: its figures as stat cards, its series as charts, the images, and audio it returned. */
44
+ function ResultLead({ layout, parts = [], }) {
43
45
  const t = useLabels();
44
46
  const media = useMemo(() => returnedMedia(layout, parts, t('@theorem.host.image')), [layout, parts, t]);
45
47
  if (!hasLead(layout, media))
46
48
  return null;
47
- return (_jsxs(VStack, { gap: 3, children: [_jsx(Figures, { figures: layout.figures }), _jsx(Charts, { charts: layout.charts }), media.images.length > 0 ? _jsx(Images, { images: media.images }) : null, media.audio.map((clip, index) => (_jsx(VoiceNote, { src: clip.src, mimeType: clip.mimeType }, index)))] }));
49
+ return (_jsxs(VStack, { gap: 3, children: [_jsx(Figures, { figures: layout.figures }), _jsx(Charts, { charts: layout.charts }), media.images.length > 0 ? _jsx(Images, { images: media.images }) : null, keyedByContent(media.audio, (clip) => clip.src).map(({ item: clip, key }) => (_jsx(Arrive, { children: _jsx(VoiceNote, { src: clip.src, mimeType: clip.mimeType }) }, key)))] }));
48
50
  }
49
51
  /**
50
52
  * A tool's output under one Response heading, led by what answers it (figures,
@@ -54,7 +56,7 @@ function ResultLead({ layout, parts = [] }) {
54
56
  export function ToolResult({ output, parts }) {
55
57
  const t = useLabels();
56
58
  const layout = useMemo(() => layoutResult(output), [output]);
57
- // A figure's card is its reading: the fields below don't repeat it.
59
+ // why: A figure's card is its reading: the fields below don't repeat it.
58
60
  const ledKeys = useMemo(() => layout.figures.map((figure) => figure.key), [layout]);
59
- return _jsx(ShapedData, { value: output, title: t('@theorem.host.response'), lead: _jsx(ResultLead, { layout: layout, parts: parts }), ledKeys: ledKeys });
61
+ return (_jsx(ShapedData, { value: output, title: t('@theorem.host.response'), lead: _jsx(ResultLead, { layout: layout, parts: parts }), ledKeys: ledKeys }));
60
62
  }
@@ -1,15 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Grid } from '@astryxdesign/core/Grid';
3
2
  import { HStack } from '@astryxdesign/core/HStack';
3
+ import { useLocale } from '@astryxdesign/core/i18n';
4
4
  import { Text } from '@astryxdesign/core/Text';
5
5
  import { VStack } from '@astryxdesign/core/VStack';
6
- import { useLocale } from '@astryxdesign/core/i18n';
7
6
  import { useMemo } from 'react';
8
- import { Bar, BarChart, Cell, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
7
+ import { Bar, BarChart, Cell, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
9
8
  import { withUnit } from "../client/shaped-data.js";
10
9
  import { timeFormats } from "../client/tool-result.js";
10
+ import { Arrive } from "./arrive.js";
11
+ import { CardWall } from "./CardWall.js";
11
12
  import { ChartFrame, ChartTip, FIRST_WIDTH_PX } from "./TraceOverview.js";
12
- import { ChartCard, TRACE_GAP, TRACE_RADIUS, TraceSwatch, traceFill } from "./trace-patterns.js";
13
+ import { ChartCard, TRACE_GAP, TRACE_RADIUS, TraceSwatch, traceFill, } from "./trace-patterns.js";
13
14
  /**
14
15
  * A tool result's charts, drawn as the trace draws its own: the same card,
15
16
  * fills, ticks, and hover card. Recharts loads with this module, only when a
@@ -22,7 +23,12 @@ const CHART_PX = 140;
22
23
  /** Height of one row of a ranking, in pixels. */
23
24
  const BAR_ROW_PX = 22;
24
25
  const TICK = { fill: 'var(--color-text-secondary)', fontSize: 11 };
25
- const BAR = { radius: TRACE_RADIUS, stroke: TRACE_GAP, strokeWidth: 2, isAnimationActive: false };
26
+ const BAR = {
27
+ radius: TRACE_RADIUS,
28
+ stroke: TRACE_GAP,
29
+ strokeWidth: 2,
30
+ isAnimationActive: false,
31
+ };
26
32
  /** Each overlaid series, in the trace's order. */
27
33
  const SERIES_FILL = ['clear', 'shade1', 'shade2'];
28
34
  function useFormats(chart) {
@@ -41,27 +47,33 @@ function useFormats(chart) {
41
47
  return { number, tick: read(tick), when: read(when) };
42
48
  }, [chart, locale]);
43
49
  }
44
- function TimeTip({ active, payload, label, chart, formats }) {
50
+ function TimeTip({ active, payload, label, chart, formats, }) {
45
51
  if (!active || !payload?.length)
46
52
  return null;
47
53
  return (_jsx(ChartTip, { children: _jsxs(VStack, { gap: 0, children: [_jsx(Text, { weight: "medium", children: formats.when(String(label)) }), chart.series.map((series, index) => {
48
54
  const value = payload.find((entry) => entry.dataKey === series.key)?.value;
49
- return typeof value === 'number' ? (_jsxs(HStack, { gap: 2, vAlign: "center", children: [chart.series.length > 1 ? _jsx(TraceSwatch, { fill: SERIES_FILL[index] ?? 'clear' }) : null, _jsxs(Text, { type: "supporting", hasTabularNumbers: true, children: [chart.series.length > 1 ? `${series.label} ` : '', formats.number(value, series.unit)] })] }, series.key)) : null;
55
+ return typeof value === 'number' ? (_jsxs(HStack, { gap: 2, vAlign: "center", children: [chart.series.length > 1 ? (_jsx(TraceSwatch, { fill: SERIES_FILL[index] ?? 'clear' })) : null, _jsxs(Text, { type: "supporting", hasTabularNumbers: true, children: [chart.series.length > 1 ? `${series.label} ` : '', formats.number(value, series.unit)] })] }, series.key)) : null;
50
56
  })] }) }));
51
57
  }
52
58
  /** One or more series over time: the first reading as the figure, the span of every reading as its note. */
53
59
  function TimeCard({ chart }) {
54
60
  const formats = useFormats(chart);
55
61
  const [lead] = chart.series;
56
- const values = chart.series.flatMap((series) => chart.rows.map((row) => row[series.key]).filter((value) => typeof value === 'number'));
57
- const first = lead ? chart.rows.map((row) => row[lead.key]).find((value) => typeof value === 'number') : undefined;
62
+ const values = chart.series.flatMap((series) => chart.rows
63
+ .map((row) => row[series.key])
64
+ .filter((value) => typeof value === 'number'));
65
+ const first = lead
66
+ ? chart.rows
67
+ .map((row) => row[lead.key])
68
+ .find((value) => typeof value === 'number')
69
+ : undefined;
58
70
  if (!lead || first === undefined)
59
71
  return null;
60
72
  const unit = lead.unit;
61
73
  const range = `${formats.number(Math.min(...values))}–${formats.number(Math.max(...values), unit)}`;
62
74
  return (_jsxs(ChartCard, { title: chart.title, value: formats.number(first, unit), note: range, children: [_jsx(ChartFrame, { children: _jsx(ResponsiveContainer, { width: "100%", height: CHART_PX, initialDimension: { width: FIRST_WIDTH_PX, height: CHART_PX }, children: _jsxs(LineChart, { data: chart.rows, margin: { top: 4, right: 4, bottom: 0, left: 0 }, accessibilityLayer: false, children: [_jsx(XAxis, { dataKey: chart.x, tick: TICK, tickLine: false, axisLine: false, tickFormatter: formats.tick, minTickGap: 16 }), _jsx(YAxis, { tick: TICK, tickLine: false, axisLine: false, width: 36, domain: ['auto', 'auto'], tickFormatter: (value) => formats.number(value) }), _jsx(Tooltip, { content: (props) => _jsx(TimeTip, { ...props, chart: chart, formats: formats }), cursor: { stroke: 'var(--color-border-emphasized)' }, isAnimationActive: false }), chart.series.map((series, index) => (_jsx(Line, { type: "monotone", dataKey: series.key, stroke: traceFill(SERIES_FILL[index] ?? 'clear'), strokeWidth: 2, dot: false, activeDot: { r: 3, strokeWidth: 0 }, connectNulls: true, isAnimationActive: false }, series.key)))] }) }) }), chart.series.length > 1 ? (_jsx(HStack, { gap: 3, wrap: "wrap", children: chart.series.map((series, index) => (_jsxs(HStack, { gap: 2, vAlign: "center", children: [_jsx(TraceSwatch, { fill: SERIES_FILL[index] ?? 'clear' }), _jsx(Text, { type: "supporting", color: "secondary", children: series.label })] }, series.key))) })) : null] }));
63
75
  }
64
- function BarTip({ active, payload, chart, formats }) {
76
+ function BarTip({ active, payload, chart, formats, }) {
65
77
  const row = payload?.[0]?.payload;
66
78
  if (!active || !row)
67
79
  return null;
@@ -78,9 +90,14 @@ function CategoryCard({ chart }) {
78
90
  return (_jsx(ChartCard, { title: chart.title, value: formats.number(top.value, chart.series.unit), note: top.label, children: _jsx(ChartFrame, { children: _jsx(ResponsiveContainer, { width: "100%", height: height, initialDimension: { width: FIRST_WIDTH_PX, height }, children: _jsxs(BarChart, { data: rows, layout: "vertical", margin: { top: 0, right: 56, bottom: 0, left: 0 }, accessibilityLayer: false, barCategoryGap: 5, children: [_jsx(XAxis, { type: "number", hide: true }), _jsx(YAxis, { type: "category", dataKey: "id", width: 104, tick: TICK, tickLine: false, axisLine: false, interval: 0, tickFormatter: (id) => {
79
91
  const label = rows[Number(id)]?.label ?? '';
80
92
  return label.length > 15 ? `${label.slice(0, 14)}…` : label;
81
- } }), _jsx(Tooltip, { content: (props) => _jsx(BarTip, { ...props, chart: chart, formats: formats }), cursor: { fill: 'var(--color-overlay-hover)', radius: TRACE_RADIUS }, isAnimationActive: false }), _jsx(Bar, { dataKey: "value", ...BAR, label: { position: 'right', fill: 'var(--color-text-secondary)', fontSize: 11, formatter: (value) => formats.number(Number(value), chart.series.unit) }, children: rows.map((row, index) => (_jsx(Cell, { fill: traceFill(index === 0 ? 'clear' : 'shade2') }, row.id))) })] }) }) }) }));
93
+ } }), _jsx(Tooltip, { content: (props) => _jsx(BarTip, { ...props, chart: chart, formats: formats }), cursor: { fill: 'var(--color-overlay-hover)', radius: TRACE_RADIUS }, isAnimationActive: false }), _jsx(Bar, { dataKey: "value", ...BAR, label: {
94
+ position: 'right',
95
+ fill: 'var(--color-text-secondary)',
96
+ fontSize: 11,
97
+ formatter: (value) => formats.number(Number(value), chart.series.unit),
98
+ }, children: rows.map((row, index) => (_jsx(Cell, { fill: traceFill(index === 0 ? 'clear' : 'shade2') }, row.id))) })] }) }) }) }));
82
99
  }
83
100
  /** Each chart in its card, side by side where the column is wide enough. */
84
101
  export function ToolResultCharts({ charts }) {
85
- return (_jsx(Grid, { columns: { minWidth: 260, repeat: 'fill' }, gap: 3, children: charts.map((chart) => (chart.kind === 'time' ? _jsx(TimeCard, { chart: chart }, chart.key) : _jsx(CategoryCard, { chart: chart }, chart.key))) }));
102
+ return (_jsx(CardWall, { minWidth: 260, children: charts.map((chart) => (_jsx(Arrive, { children: chart.kind === 'time' ? _jsx(TimeCard, { chart: chart }) : _jsx(CategoryCard, { chart: chart }) }, chart.key))) }));
86
103
  }
@@ -1,13 +1,13 @@
1
1
  import { type TraceTimeSplit, type TraceTurn } from '../client/trace-story.ts';
2
2
  import { type TraceNode } from '../client/trace-view.ts';
3
- /** Where a turn's time, tokens and slowest steps went. A bar opens its span. */
3
+ /** Where a turn's time, tokens, and slowest steps went. A bar opens its span. */
4
4
  export declare function TraceCharts({ root, split, onSelect, }: {
5
5
  root: TraceNode;
6
6
  split: TraceTimeSplit;
7
7
  onSelect: (node: TraceNode) => void;
8
8
  }): import("react").JSX.Element;
9
9
  /** The whole conversation: each turn's time and tokens. A column opens its turn. */
10
- export declare function ConversationCharts({ turns, onPick }: {
10
+ export declare function ConversationCharts({ turns, onPick, }: {
11
11
  turns: readonly TraceTurn[];
12
12
  onPick: (index: number) => void;
13
13
  }): import("react").JSX.Element;
package/ui/TraceCharts.js CHANGED
@@ -4,12 +4,12 @@ import { HStack } from '@astryxdesign/core/HStack';
4
4
  import { Text } from '@astryxdesign/core/Text';
5
5
  import { VStack } from '@astryxdesign/core/VStack';
6
6
  import { useMemo } from 'react';
7
- import { Bar, BarChart, Cell, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
8
- import { isCallTrace, traceActor } from "../client/trace-story.js";
7
+ import { Bar, BarChart, Cell, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
8
+ import { isCallTrace, traceActor, } from "../client/trace-story.js";
9
9
  import { traceSpans } from "../client/trace-view.js";
10
10
  import { ChartFrame, ChartTip, FIRST_WIDTH_PX } from "./TraceOverview.js";
11
- import { ACTOR_FILL, ChartCard, TRACE_GAP, TRACE_RADIUS, TraceSwatch, traceFill } from "./trace-patterns.js";
12
11
  import { useTraceFormat } from "./TraceValues.js";
12
+ import { ACTOR_FILL, ChartCard, TRACE_GAP, TRACE_RADIUS, TraceSwatch, traceFill, } from "./trace-patterns.js";
13
13
  /** Height of each column chart's plot, in pixels. */
14
14
  const CHART_PX = 120;
15
15
  /** Height of a share bar, in pixels. */
@@ -20,7 +20,12 @@ const SLOW_ROW_PX = 22;
20
20
  const SLOWEST = 5;
21
21
  const TICK = { fill: 'var(--color-text-secondary)', fontSize: 11 };
22
22
  /** Every bar: rounded, and parted from its neighbours by the card's background. */
23
- const BAR = { radius: TRACE_RADIUS, stroke: TRACE_GAP, strokeWidth: 2, isAnimationActive: false };
23
+ const BAR = {
24
+ radius: TRACE_RADIUS,
25
+ stroke: TRACE_GAP,
26
+ strokeWidth: 2,
27
+ isAnimationActive: false,
28
+ };
24
29
  const MODEL_CALLS = new Set(['call', 'response']);
25
30
  const SPLIT_KEYS = ['model', 'tools', 'guardrails', 'hooks', 'other'];
26
31
  const SPLIT_FILL = {
@@ -31,7 +36,11 @@ const SPLIT_FILL = {
31
36
  other: 'shade2',
32
37
  };
33
38
  const TOKEN_KEYS = ['fresh', 'cached', 'output'];
34
- const TOKEN_FILL = { fresh: 'clear', cached: 'hatch', output: 'shade1' };
39
+ const TOKEN_FILL = {
40
+ fresh: 'clear',
41
+ cached: 'hatch',
42
+ output: 'shade1',
43
+ };
35
44
  const compact = new Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1 });
36
45
  const percent = new Intl.NumberFormat(undefined, { style: 'percent' });
37
46
  function numberAt(node, key) {
@@ -45,11 +54,11 @@ function modelCalls(root) {
45
54
  .sort((a, b) => a.startMs - b.startMs);
46
55
  }
47
56
  /** A chart's key: each series' swatch and name, with its total at the end. */
48
- function Legend({ items }) {
57
+ function Legend({ items, }) {
49
58
  return (_jsx(VStack, { gap: 1, children: items.map((item) => (_jsxs(HStack, { gap: 2, align: "center", justify: "between", children: [_jsxs(HStack, { gap: 2, align: "center", children: [_jsx(TraceSwatch, { fill: item.fill }), _jsx(Text, { type: "supporting", color: "secondary", children: item.label })] }), _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: item.value })] }, item.label))) }));
50
59
  }
51
60
  /** One bar split into shares, each rounded and parted from the next. */
52
- function ShareBar({ parts }) {
61
+ function ShareBar({ parts, }) {
53
62
  const total = parts.reduce((sum, part) => sum + part.value, 0);
54
63
  return (_jsx(ChartFrame, { children: _jsx(ResponsiveContainer, { width: "100%", height: SHARE_PX, initialDimension: { width: FIRST_WIDTH_PX, height: SHARE_PX }, children: _jsxs(BarChart, { data: [Object.fromEntries(parts.map((part) => [part.key, part.value]))], layout: "vertical", margin: { top: 0, right: 0, bottom: 0, left: 0 }, barCategoryGap: 0, accessibilityLayer: false, children: [_jsx(XAxis, { type: "number", domain: [0, total], hide: true }), _jsx(YAxis, { type: "category", hide: true }), parts.map((part) => (_jsx(Bar, { dataKey: part.key, stackId: "share", fill: traceFill(part.fill), ...BAR }, part.key)))] }) }) }));
55
64
  }
@@ -77,9 +86,13 @@ function TimeCard({ split }) {
77
86
  const total = sumOf(split);
78
87
  if (total <= 0)
79
88
  return null;
80
- // A share under half a percent is too thin to draw; the legend still lists it.
89
+ // why: A share under half a percent is too thin to draw; the legend still lists it.
81
90
  const keys = SPLIT_KEYS.filter((key) => split[key] / total >= 0.005);
82
- return (_jsxs(ChartCard, { title: t('@theorem.panel.trace.time'), value: format.duration(total), note: split.model > 0 ? t('@theorem.panel.trace.charts.modelShare', { share: percent.format(split.model / total) }) : undefined, children: [_jsx(ShareBar, { parts: keys.map((key) => ({ key, fill: SPLIT_FILL[key], value: split[key] })) }), _jsx(SplitLegend, { split: split, total: total })] }));
91
+ return (_jsxs(ChartCard, { title: t('@theorem.panel.trace.time'), value: format.duration(total), note: split.model > 0
92
+ ? t('@theorem.panel.trace.charts.modelShare', {
93
+ share: percent.format(split.model / total),
94
+ })
95
+ : undefined, children: [_jsx(ShareBar, { parts: keys.map((key) => ({ key, fill: SPLIT_FILL[key], value: split[key] })) }), _jsx(SplitLegend, { split: split, total: total })] }));
83
96
  }
84
97
  /** A bar's tokens: read from cache, read fresh, written, and its cost when known. */
85
98
  function tokenSplit(usage) {
@@ -97,7 +110,10 @@ function TokenTip({ active, payload }) {
97
110
  const row = payload?.[0]?.payload;
98
111
  if (!active || !row)
99
112
  return null;
100
- return (_jsx(ChartTip, { children: _jsxs(VStack, { gap: 0, children: [_jsx(Text, { weight: "medium", children: row.title }), _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: [format.duration(row.ms), ...(row.cost === undefined ? [] : [format.usd(row.cost)])].join(t('@theorem.panel.trace.separator')) }), _jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: t('@theorem.panel.trace.charts.tokens', {
113
+ return (_jsx(ChartTip, { children: _jsxs(VStack, { gap: 0, children: [_jsx(Text, { weight: "medium", children: row.title }), _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: [
114
+ format.duration(row.ms),
115
+ ...(row.cost === undefined ? [] : [format.usd(row.cost)]),
116
+ ].join(t('@theorem.panel.trace.separator')) }), _jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: t('@theorem.panel.trace.charts.tokens', {
101
117
  cached: format.number(row.cached),
102
118
  fresh: format.number(row.fresh),
103
119
  output: format.number(row.output),
@@ -107,7 +123,7 @@ function TokenTip({ active, payload }) {
107
123
  function Columns({ rows, keys, fills, tip, }) {
108
124
  return (_jsx(ChartFrame, { children: _jsx(ResponsiveContainer, { width: "100%", height: CHART_PX, initialDimension: { width: FIRST_WIDTH_PX, height: CHART_PX }, children: _jsxs(BarChart, { data: rows, margin: { top: 0, right: 0, bottom: 0, left: 0 }, accessibilityLayer: false, barCategoryGap: "24%", maxBarSize: 24, onClick: (state) => rows[Number(state?.activeIndex)]?.open(), style: { cursor: 'pointer' }, children: [_jsx(XAxis, { dataKey: "index", tick: TICK, tickLine: false, axisLine: false, height: 18 }), _jsx(YAxis, { hide: true }), _jsx(Tooltip, { content: tip, cursor: { fill: 'var(--color-overlay-hover)', radius: TRACE_RADIUS }, isAnimationActive: false }), keys.map((key) => (_jsx(Bar, { dataKey: key, stackId: "stack", fill: traceFill(fills[key] ?? 'clear'), ...BAR }, key)))] }) }) }));
109
125
  }
110
- /** Tokens stacked new, cached and written per row: the total, the columns, and each series' sum. */
126
+ /** Tokens stacked new, cached, and written per row: the total, the columns, and each series' sum. */
111
127
  function TokensCard({ title, rows }) {
112
128
  const format = useTraceFormat();
113
129
  const { t } = format;
@@ -120,7 +136,11 @@ function TokensCard({ title, rows }) {
120
136
  return null;
121
137
  const input = sums.fresh + sums.cached;
122
138
  const labels = { fresh: 'input', cached: 'cached', output: 'output' };
123
- return (_jsxs(ChartCard, { title: title, value: format.number(total), ...(sums.cached > 0 && { note: t('@theorem.panel.trace.charts.cachedShare', { share: percent.format(sums.cached / input) }) }), children: [_jsx(Columns, { rows: rows, keys: TOKEN_KEYS.filter((key) => sums[key] > 0), fills: TOKEN_FILL, tip: (props) => _jsx(TokenTip, { ...props }) }), _jsx(Legend, { items: TOKEN_KEYS.filter((key) => sums[key] > 0).map((key) => ({
139
+ return (_jsxs(ChartCard, { title: title, value: format.number(total), ...(sums.cached > 0 && {
140
+ note: t('@theorem.panel.trace.charts.cachedShare', {
141
+ share: percent.format(sums.cached / input),
142
+ }),
143
+ }), children: [_jsx(Columns, { rows: rows, keys: TOKEN_KEYS.filter((key) => sums[key] > 0), fills: TOKEN_FILL, tip: (props) => _jsx(TokenTip, { ...props }) }), _jsx(Legend, { items: TOKEN_KEYS.filter((key) => sums[key] > 0).map((key) => ({
124
144
  fill: TOKEN_FILL[key],
125
145
  label: t(`@theorem.panel.trace.charts.${labels[key]}`),
126
146
  value: compact.format(sums[key]),
@@ -179,9 +199,14 @@ function SlowestCard({ root, onSelect }) {
179
199
  }, style: { cursor: 'pointer' }, children: [_jsx(XAxis, { type: "number", hide: true }), _jsx(YAxis, { type: "category", dataKey: "id", width: 104, tick: TICK, tickLine: false, axisLine: false, interval: 0, tickFormatter: (id) => {
180
200
  const name = rows.find((row) => row.id === id)?.name ?? '';
181
201
  return name.length > 15 ? `${name.slice(0, 14)}…` : name;
182
- } }), _jsx(Tooltip, { content: (props) => _jsx(SlowTip, { ...props }), cursor: false, isAnimationActive: false }), _jsx(Bar, { dataKey: "ms", ...BAR, label: { position: 'right', fill: 'var(--color-text-secondary)', fontSize: 11, formatter: (value) => format.duration(Number(value)) }, children: rows.map((row) => (_jsx(Cell, { fill: traceFill(ACTOR_FILL[traceActor(row.node)]) }, row.id))) })] }) }) }) }));
202
+ } }), _jsx(Tooltip, { content: (props) => _jsx(SlowTip, { ...props }), cursor: false, isAnimationActive: false }), _jsx(Bar, { dataKey: "ms", ...BAR, label: {
203
+ position: 'right',
204
+ fill: 'var(--color-text-secondary)',
205
+ fontSize: 11,
206
+ formatter: (value) => format.duration(Number(value)),
207
+ }, children: rows.map((row) => (_jsx(Cell, { fill: traceFill(ACTOR_FILL[traceActor(row.node)]) }, row.id))) })] }) }) }) }));
183
208
  }
184
- /** Where a turn's time, tokens and slowest steps went. A bar opens its span. */
209
+ /** Where a turn's time, tokens, and slowest steps went. A bar opens its span. */
185
210
  export function TraceCharts({ root, split, onSelect, }) {
186
211
  const { t } = useTraceFormat();
187
212
  const callRows = useCallRows(root, onSelect);
@@ -206,13 +231,19 @@ function TurnTimeCard({ rows, isCalls }) {
206
231
  const total = sumOf(sums);
207
232
  if (total <= 0)
208
233
  return null;
209
- return (_jsxs(ChartCard, { title: t(isCalls ? '@theorem.panel.trace.charts.callTime' : '@theorem.panel.trace.charts.turnTime'), value: format.duration(total), note: sums.model > 0 ? t('@theorem.panel.trace.charts.modelShare', { share: percent.format(sums.model / total) }) : undefined, children: [_jsx(Columns, { rows: rows, keys: SPLIT_KEYS, fills: SPLIT_FILL, tip: (props) => _jsx(TurnTimeTip, { ...props }) }), _jsx(SplitLegend, { split: sums, total: total })] }));
234
+ return (_jsxs(ChartCard, { title: t(isCalls ? '@theorem.panel.trace.charts.callTime' : '@theorem.panel.trace.charts.turnTime'), value: format.duration(total), note: sums.model > 0
235
+ ? t('@theorem.panel.trace.charts.modelShare', {
236
+ share: percent.format(sums.model / total),
237
+ })
238
+ : undefined, children: [_jsx(Columns, { rows: rows, keys: SPLIT_KEYS, fills: SPLIT_FILL, tip: (props) => _jsx(TurnTimeTip, { ...props }) }), _jsx(SplitLegend, { split: sums, total: total })] }));
210
239
  }
211
240
  /** The whole conversation: each turn's time and tokens. A column opens its turn. */
212
- export function ConversationCharts({ turns, onPick }) {
241
+ export function ConversationCharts({ turns, onPick, }) {
213
242
  const { t } = useTraceFormat();
214
243
  const isCalls = isCallTrace(turns);
215
- const column = isCalls ? '@theorem.panel.trace.charts.hostCall' : '@theorem.panel.trace.charts.turn';
244
+ const column = isCalls
245
+ ? '@theorem.panel.trace.charts.hostCall'
246
+ : '@theorem.panel.trace.charts.turn';
216
247
  const time = useMemo(() => turns.map((turn, position) => ({
217
248
  index: position + 1,
218
249
  title: t(column, { index: position + 1 }),
@@ -4,7 +4,8 @@ import type { TraceNode } from '../client/trace-view.ts';
4
4
  * Every guardrail check of the turn. The ones that acted lead, each with the
5
5
  * rules it matched and why; the passes follow on one quiet line.
6
6
  */
7
- export declare function TraceGuardrails({ checks, onSelect }: {
7
+ export declare function TraceGuardrails({ checks, onSelect, }: {
8
8
  checks: readonly TraceGuardrailCheck[];
9
- onSelect: (node: TraceNode) => void;
9
+ /** Opens a check's step; without it a check is not a button. */
10
+ onSelect?: (node: TraceNode) => void;
10
11
  }): import("react").JSX.Element;
@@ -1,11 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Badge } from '@astryxdesign/core/Badge';
3
2
  import { Icon } from '@astryxdesign/core/Icon';
4
3
  import { Item } from '@astryxdesign/core/Item';
5
4
  import { Text } from '@astryxdesign/core/Text';
5
+ import { Token } from '@astryxdesign/core/Token';
6
6
  import { Tooltip as HoverTip } from '@astryxdesign/core/Tooltip';
7
7
  import { VStack } from '@astryxdesign/core/VStack';
8
- import { IconShield } from '@tabler/icons-react';
8
+ import { IconBiohazard, IconEyeOff, IconHandStop, IconInfoCircle, IconShield, } from '@tabler/icons-react';
9
+ import { TOOL_RULES } from '@theoremjs/agents/guardrails';
10
+ import { Arrive, Stream } from "./arrive.js";
9
11
  import { useTraceFormat } from "./TraceValues.js";
10
12
  /** What a check is called: the timed check's own name, or else the text it looked at. */
11
13
  function checkName(check) {
@@ -26,8 +28,24 @@ function CheckWhy({ check, format }) {
26
28
  .filter(Boolean)
27
29
  .join(separator) }), check.hits.map((hit) => (_jsx(HitWhy, { hit: hit, separator: separator }, `${hit.rule}:${hit.match ?? ''}`))), check.reason ? (_jsx(Text, { type: "supporting", color: "secondary", maxLines: 3, children: check.reason })) : null] }));
28
30
  }
29
- function ActedCheck({ check, format, onSelect }) {
30
- return (_jsx(Item, { startContent: _jsx(Icon, { icon: IconShield, size: "sm", color: "secondary" }), label: _jsx(HoverTip, { content: check.actionDoc ?? check.actionLabel, children: _jsx(Badge, { variant: check.action === 'block' ? 'error' : 'warning', label: check.actionLabel }) }), description: _jsx(CheckWhy, { check: check, format: format }), endContent: _jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: format.t('@theorem.panel.trace.offset', { duration: format.duration(check.offsetMs) }) }), align: "start", density: "compact", onClick: () => onSelect(check.node) }));
31
+ /** What a check did, as a token. A report of a call made after a remote read says so in place of "flagged". */
32
+ function ActionToken({ check, format }) {
33
+ if (check.action === 'block') {
34
+ return (_jsx(Token, { label: check.actionLabel, color: "red", icon: _jsx(Icon, { icon: IconHandStop, size: "sm" }) }));
35
+ }
36
+ if (check.action === 'redact') {
37
+ return (_jsx(Token, { label: check.actionLabel, color: "orange", icon: _jsx(Icon, { icon: IconEyeOff, size: "sm" }) }));
38
+ }
39
+ const rules = new Set(check.hits.map((hit) => hit.rule));
40
+ if (rules.has(TOOL_RULES.steeredTurn) || rules.has(TOOL_RULES.taintedTurn)) {
41
+ return (_jsx(Token, { label: format.t(rules.has(TOOL_RULES.steeredTurn)
42
+ ? '@theorem.panel.trace.guardrails.steered'
43
+ : '@theorem.panel.trace.guardrails.tainted'), color: "yellow", icon: _jsx(Icon, { icon: IconBiohazard, size: "sm" }) }));
44
+ }
45
+ return (_jsx(Token, { label: check.actionLabel, color: "blue", icon: _jsx(Icon, { icon: IconInfoCircle, size: "sm" }) }));
46
+ }
47
+ function ActedCheck({ check, format, onSelect, }) {
48
+ return (_jsx(Item, { startContent: _jsx(Icon, { icon: IconShield, size: "sm", color: "secondary" }), label: _jsx(HoverTip, { content: check.actionDoc ?? check.actionLabel, children: _jsx(ActionToken, { check: check, format: format }) }), description: _jsx(CheckWhy, { check: check, format: format }), endContent: _jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: format.t('@theorem.panel.trace.offset', { duration: format.duration(check.offsetMs) }) }), align: "start", density: "compact", ...(onSelect ? { onClick: () => onSelect(check.node) } : {}) }));
31
49
  }
32
50
  /** "Input check 0.8ms, Result check ×3 0.4ms": the passes, one entry per kind of check with its count and total time. */
33
51
  function passSummary(passes, format) {
@@ -48,9 +66,9 @@ function passSummary(passes, format) {
48
66
  * Every guardrail check of the turn. The ones that acted lead, each with the
49
67
  * rules it matched and why; the passes follow on one quiet line.
50
68
  */
51
- export function TraceGuardrails({ checks, onSelect }) {
69
+ export function TraceGuardrails({ checks, onSelect, }) {
52
70
  const format = useTraceFormat();
53
71
  const acted = checks.filter((check) => check.action !== 'allow');
54
72
  const passed = passSummary(checks.filter((check) => check.action === 'allow'), format);
55
- return (_jsxs(VStack, { gap: 1, children: [acted.map((check) => (_jsx(ActedCheck, { check: check, format: format, onSelect: onSelect }, check.id))), passed ? (_jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: format.t('@theorem.panel.trace.guardrails.passed', { checks: passed }) })) : null] }));
73
+ return (_jsx(Stream, { children: _jsxs(VStack, { gap: 1, children: [acted.map((check) => (_jsx(Arrive, { children: _jsx(ActedCheck, { check: check, format: format, onSelect: onSelect }) }, check.id))), passed ? (_jsx(Arrive, { children: _jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: format.t('@theorem.panel.trace.guardrails.passed', { checks: passed }) }) })) : null] }) }));
56
74
  }
@@ -1,6 +1,16 @@
1
+ import type { TraceRecord } from '@theoremjs/agents';
2
+ import { type ReactNode } from 'react';
1
3
  import type { TraceFeed } from '../client/trace-feed.ts';
2
4
  /** The inspector over the records a feed has delivered; the panel loads this module when it first opens. */
3
- export declare function TraceFeedBody({ traces, isWide }: {
5
+ export declare function TraceFeedBody({ traces, isWide, }: {
4
6
  traces: TraceFeed | undefined;
5
7
  isWide: boolean;
6
8
  }): import("react").JSX.Element;
9
+ /**
10
+ * Only what the records say of guardrails: how long the checks took, how many
11
+ * ran and acted, then each check. `head` leads it.
12
+ */
13
+ export declare function TraceGuardrailsBody({ records, head, }: {
14
+ records: readonly TraceRecord[];
15
+ head?: ReactNode;
16
+ }): import("react").JSX.Element;