@theoremjs/react 0.0.0-stage → 0.3.0
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/LICENSE +21 -0
- package/README.md +246 -2
- package/client/attachment-hover-preview.d.ts +11 -0
- package/client/attachment-hover-preview.js +36 -0
- package/client/audio-level.d.ts +5 -0
- package/client/audio-level.js +24 -0
- package/client/composer-attachments.d.ts +30 -0
- package/client/composer-attachments.js +33 -0
- package/client/composer-drawer.d.ts +19 -0
- package/client/composer-drawer.js +15 -0
- package/client/composer-hints.d.ts +25 -0
- package/client/composer-hints.js +20 -0
- package/client/composer-primary.d.ts +16 -0
- package/client/composer-primary.js +25 -0
- package/client/decision-transport.d.ts +48 -0
- package/client/decision-transport.js +102 -0
- package/client/decode-composer-draft.d.ts +11 -0
- package/client/decode-composer-draft.js +12 -0
- package/client/encode-composer-draft.d.ts +9 -0
- package/client/encode-composer-draft.js +34 -0
- package/client/encode-files.d.ts +18 -0
- package/client/encode-files.js +54 -0
- package/client/failure.d.ts +32 -0
- package/client/failure.js +34 -0
- package/client/generation-selection.d.ts +15 -0
- package/client/generation-selection.js +24 -0
- package/client/host-call.d.ts +51 -0
- package/client/host-call.js +42 -0
- package/client/host-transport.d.ts +68 -0
- package/client/host-transport.js +74 -0
- package/client/image-output.d.ts +6 -0
- package/client/image-output.js +13 -0
- package/client/index.d.ts +20 -0
- package/client/index.js +18 -0
- package/client/ink-waveform.d.ts +17 -0
- package/client/ink-waveform.js +69 -0
- package/client/live/apply-live-turn-tool-event.d.ts +14 -0
- package/client/live/apply-live-turn-tool-event.js +39 -0
- package/client/live/live-captions.d.ts +23 -0
- package/client/live/live-captions.js +70 -0
- package/client/live/live-mic-forward.d.ts +29 -0
- package/client/live/live-mic-forward.js +37 -0
- package/client/live/live-state.d.ts +11 -0
- package/client/live/live-state.js +20 -0
- package/client/live/live-tool.d.ts +11 -0
- package/client/live/live-tool.js +1 -0
- package/client/live/live-video.d.ts +10 -0
- package/client/live/live-video.js +71 -0
- package/client/live/run-live-tool-call.d.ts +17 -0
- package/client/live/run-live-tool-call.js +32 -0
- package/client/live-client.d.ts +141 -0
- package/client/live-client.js +638 -0
- package/client/live-errors.d.ts +2 -0
- package/client/live-errors.js +9 -0
- package/client/live-messages.d.ts +110 -0
- package/client/live-messages.js +67 -0
- package/client/mic-capture.d.ts +4 -0
- package/client/mic-capture.js +6 -0
- package/client/oauth-popup.d.ts +26 -0
- package/client/oauth-popup.js +30 -0
- package/client/pcm-downsample.d.ts +4 -0
- package/client/pcm-downsample.js +45 -0
- package/client/run-commit.d.ts +61 -0
- package/client/run-commit.js +124 -0
- package/client/run-session.d.ts +80 -0
- package/client/run-session.js +215 -0
- package/client/schema-fields.d.ts +63 -0
- package/client/schema-fields.js +178 -0
- package/client/shaped-data.d.ts +118 -0
- package/client/shaped-data.js +314 -0
- package/client/source-citations.d.ts +13 -0
- package/client/source-citations.js +56 -0
- package/client/state-fields.d.ts +16 -0
- package/client/state-fields.js +44 -0
- package/client/tool-result.d.ts +78 -0
- package/client/tool-result.js +275 -0
- package/client/tool-resume.d.ts +33 -0
- package/client/tool-resume.js +11 -0
- package/client/trace-feed.d.ts +13 -0
- package/client/trace-feed.js +18 -0
- package/client/trace-story.d.ts +163 -0
- package/client/trace-story.js +416 -0
- package/client/trace-view.d.ts +130 -0
- package/client/trace-view.js +293 -0
- package/client/transcript-groups.d.ts +114 -0
- package/client/transcript-groups.js +167 -0
- package/client/transcript-scroll.d.ts +7 -0
- package/client/transcript-scroll.js +6 -0
- package/client/transport.d.ts +176 -0
- package/client/transport.js +256 -0
- package/client/turn-client.d.ts +35 -0
- package/client/turn-client.js +93 -0
- package/client/voice-label.d.ts +2 -0
- package/client/voice-label.js +15 -0
- package/client/voice-recorder.d.ts +27 -0
- package/client/voice-recorder.js +165 -0
- package/client/walk-away.d.ts +34 -0
- package/client/walk-away.js +48 -0
- package/client/wire-line.d.ts +56 -0
- package/client/wire-line.js +90 -0
- package/client/worklets/mic-capture.js +16 -0
- package/components/InkWaveform.d.ts +10 -0
- package/components/InkWaveform.js +108 -0
- package/components/live/use-live-runner-controls.d.ts +48 -0
- package/components/live/use-live-runner-controls.js +100 -0
- package/components/live/use-live-runner-model.d.ts +45 -0
- package/components/live/use-live-runner-model.js +175 -0
- package/components/live/use-live-runner-ui.d.ts +65 -0
- package/components/live/use-live-runner-ui.js +159 -0
- package/components/live/use-live-session-client.d.ts +41 -0
- package/components/live/use-live-session-client.js +123 -0
- package/components/use-composer-voice.d.ts +28 -0
- package/components/use-composer-voice.js +90 -0
- package/components/use-editor-selection.d.ts +3 -0
- package/components/use-editor-selection.js +16 -0
- package/components/use-voice-playback.d.ts +16 -0
- package/components/use-voice-playback.js +68 -0
- package/hooks/use-described.d.ts +8 -0
- package/hooks/use-described.js +23 -0
- package/hooks/use-theorem-chat-actions.d.ts +75 -0
- package/hooks/use-theorem-chat-actions.js +265 -0
- package/hooks/use-theorem-chat-state.d.ts +66 -0
- package/hooks/use-theorem-chat-state.js +118 -0
- package/hooks/use-theorem-chat.d.ts +63 -0
- package/hooks/use-theorem-chat.js +340 -0
- package/hooks/use-theorem-decision.d.ts +23 -0
- package/hooks/use-theorem-decision.js +75 -0
- package/hooks/use-theorem-host.d.ts +44 -0
- package/hooks/use-theorem-host.js +117 -0
- package/hooks/use-theorem-interface.d.ts +19 -0
- package/hooks/use-theorem-interface.js +25 -0
- package/hooks/use-trace-records.d.ts +4 -0
- package/hooks/use-trace-records.js +7 -0
- package/index.d.ts +14 -0
- package/index.js +14 -0
- package/live.d.ts +8 -0
- package/live.js +10 -0
- package/package.json +73 -4
- package/server/credential-store.d.ts +24 -0
- package/server/credential-store.js +14 -0
- package/server/decision-handler.d.ts +34 -0
- package/server/decision-handler.js +55 -0
- package/server/handler.d.ts +114 -0
- package/server/handler.js +568 -0
- package/server/json-response.d.ts +2 -0
- package/server/json-response.js +7 -0
- package/server/mod.d.ts +16 -0
- package/server/mod.js +15 -0
- package/server/request-check.d.ts +16 -0
- package/server/request-check.js +34 -0
- package/server/session-store.d.ts +74 -0
- package/server/session-store.js +49 -0
- package/server/steer-inbox.d.ts +35 -0
- package/server/steer-inbox.js +52 -0
- package/server/walk-away.d.ts +22 -0
- package/server/walk-away.js +41 -0
- package/styles/ink-controls.css +134 -0
- package/ui/ChatComposerBar.d.ts +39 -0
- package/ui/ChatComposerBar.js +251 -0
- package/ui/ChatTranscript.d.ts +30 -0
- package/ui/ChatTranscript.js +474 -0
- package/ui/ConsoleFrame.d.ts +47 -0
- package/ui/ConsoleFrame.js +46 -0
- package/ui/DecisionAnswers.d.ts +15 -0
- package/ui/DecisionAnswers.js +102 -0
- package/ui/LiveRunner.d.ts +28 -0
- package/ui/LiveRunner.js +193 -0
- package/ui/SchemaFields.d.ts +10 -0
- package/ui/SchemaFields.js +122 -0
- package/ui/ShapedData.d.ts +11 -0
- package/ui/ShapedData.js +151 -0
- package/ui/SidePanel.d.ts +57 -0
- package/ui/SidePanel.js +50 -0
- package/ui/StateFields.d.ts +36 -0
- package/ui/StateFields.js +96 -0
- package/ui/TheoremChat.d.ts +71 -0
- package/ui/TheoremChat.js +146 -0
- package/ui/TheoremDecision.d.ts +38 -0
- package/ui/TheoremDecision.js +87 -0
- package/ui/TheoremHost.d.ts +38 -0
- package/ui/TheoremHost.js +150 -0
- package/ui/ToolGateCard.d.ts +28 -0
- package/ui/ToolGateCard.js +100 -0
- package/ui/ToolResult.d.ts +10 -0
- package/ui/ToolResult.js +60 -0
- package/ui/ToolResultCharts.d.ts +5 -0
- package/ui/ToolResultCharts.js +86 -0
- package/ui/TraceCharts.d.ts +13 -0
- package/ui/TraceCharts.js +235 -0
- package/ui/TraceGuardrails.d.ts +10 -0
- package/ui/TraceGuardrails.js +56 -0
- package/ui/TraceInspector.d.ts +6 -0
- package/ui/TraceInspector.js +189 -0
- package/ui/TraceInspectorPanel.d.ts +24 -0
- package/ui/TraceInspectorPanel.js +57 -0
- package/ui/TraceOverview.d.ts +34 -0
- package/ui/TraceOverview.js +96 -0
- package/ui/TraceSpanDetail.d.ts +10 -0
- package/ui/TraceSpanDetail.js +188 -0
- package/ui/TraceStory.d.ts +20 -0
- package/ui/TraceStory.js +107 -0
- package/ui/TraceValues.d.ts +36 -0
- package/ui/TraceValues.js +113 -0
- package/ui/TraceWaterfall.d.ts +13 -0
- package/ui/TraceWaterfall.js +132 -0
- package/ui/VoiceNote.d.ts +12 -0
- package/ui/VoiceNote.js +28 -0
- package/ui/built/theme.css +774 -0
- package/ui/built/theorem.d.ts +13 -0
- package/ui/built/theorem.js +631 -0
- package/ui/built/theorem.variants.d.ts +16 -0
- package/ui/disclosure-motion.d.ts +6 -0
- package/ui/disclosure-motion.js +163 -0
- package/ui/icons.d.ts +4 -0
- package/ui/icons.js +37 -0
- package/ui/index.d.ts +25 -0
- package/ui/index.js +29 -0
- package/ui/labels-provider.d.ts +16 -0
- package/ui/labels-provider.js +61 -0
- package/ui/labels.d.ts +1153 -0
- package/ui/labels.js +603 -0
- package/ui/theme.d.ts +10 -0
- package/ui/theme.js +13 -0
- package/ui/theorem-theme.d.ts +10 -0
- package/ui/theorem-theme.js +83 -0
- package/ui/trace-palette.d.ts +24 -0
- package/ui/trace-palette.js +24 -0
- package/ui/trace-patterns.d.ts +32 -0
- package/ui/trace-patterns.js +44 -0
- package/ui/transcript-copy-text.d.ts +4 -0
- package/ui/transcript-copy-text.js +39 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules `ShapedData` draws a payload by, as plain decisions: which shape a
|
|
3
|
+
* value takes, what a row is called, what a field's label and unit are. The
|
|
4
|
+
* component only maps each decision to its Astryx part.
|
|
5
|
+
*/
|
|
6
|
+
/** Fields that name a row, in the order they're tried. */
|
|
7
|
+
const TITLE_KEYS = ['name', 'title', 'label', 'headline', 'subject', 'id'];
|
|
8
|
+
/** Fields that place or sort a row, shown as tokens beside its title. */
|
|
9
|
+
const TAG_KEYS = ['country', 'region', 'state', 'admin1', 'city', 'category', 'type', 'kind', 'status'];
|
|
10
|
+
const MAX_TAGS = 2;
|
|
11
|
+
const MAX_TAG_LENGTH = 24;
|
|
12
|
+
/** Fields that say, in a line, what a row is. */
|
|
13
|
+
const DESCRIPTION_KEYS = ['description', 'summary', 'snippet', 'extract', 'subtitle', 'address', 'display_name', 'text'];
|
|
14
|
+
/** Fields that link a row to its page. */
|
|
15
|
+
const LINK_KEYS = ['url', 'link', 'href'];
|
|
16
|
+
/** Fields a list row shows beyond its name, line, link and tags, at its end. */
|
|
17
|
+
const MAX_LIST_EXTRAS = 2;
|
|
18
|
+
/** A list of flat objects with at most this many fields reads as a table. */
|
|
19
|
+
const MAX_TABLE_COLUMNS = 6;
|
|
20
|
+
/** Rows shown before the rest are left to the JSON view. */
|
|
21
|
+
export const MAX_ROWS = 50;
|
|
22
|
+
/** A list of plain values this short sits inline as tokens beside the other fields. */
|
|
23
|
+
const MAX_INLINE_TOKENS = 8;
|
|
24
|
+
/** Levels drawn before a subtree shows as JSON. */
|
|
25
|
+
const MAX_DEPTH = 8;
|
|
26
|
+
export const ISO_DATE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
27
|
+
export function isRow(value) {
|
|
28
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
export function isPlain(value) {
|
|
31
|
+
return value === null || typeof value !== 'object';
|
|
32
|
+
}
|
|
33
|
+
const UNREADABLE = '[Unreadable]';
|
|
34
|
+
const NO_TO_JSON = Symbol('no toJSON');
|
|
35
|
+
/** A read that throws reads as a marker. */
|
|
36
|
+
function readSafe(read) {
|
|
37
|
+
try {
|
|
38
|
+
return read();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return UNREADABLE;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** What the value's own toJSON gives, made safe; NO_TO_JSON when it has none. */
|
|
45
|
+
function viaToJSON(value, inside) {
|
|
46
|
+
const own = value.toJSON;
|
|
47
|
+
return typeof own === 'function' ? jsonSafe(own.call(value), inside) : NO_TO_JSON;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* What JSON.stringify can't take, made plain: a cycle, a BigInt, a property
|
|
51
|
+
* that throws when read. Each becomes a marker in place, so the rest still shows.
|
|
52
|
+
*/
|
|
53
|
+
function jsonSafe(value, ancestors = []) {
|
|
54
|
+
if (typeof value === 'bigint')
|
|
55
|
+
return String(value);
|
|
56
|
+
if (value === null || typeof value !== 'object')
|
|
57
|
+
return value;
|
|
58
|
+
if (ancestors.includes(value))
|
|
59
|
+
return '[Circular]';
|
|
60
|
+
const inside = [...ancestors, value];
|
|
61
|
+
const converted = readSafe(() => viaToJSON(value, inside));
|
|
62
|
+
if (converted !== NO_TO_JSON)
|
|
63
|
+
return converted;
|
|
64
|
+
if (Array.isArray(value))
|
|
65
|
+
return value.map((item) => jsonSafe(item, inside));
|
|
66
|
+
return Object.fromEntries(Object.keys(value).map((key) => [key, readSafe(() => jsonSafe(value[key], inside))]));
|
|
67
|
+
}
|
|
68
|
+
/** The value as indented JSON, whatever it holds. */
|
|
69
|
+
export function json(value) {
|
|
70
|
+
try {
|
|
71
|
+
return JSON.stringify(value, null, 2) ?? String(value);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return JSON.stringify(jsonSafe(value), null, 2) ?? String(value);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** A string that holds a JSON object or list, parsed; anything else as it came. */
|
|
78
|
+
export function unpacked(value) {
|
|
79
|
+
if (typeof value !== 'string')
|
|
80
|
+
return value;
|
|
81
|
+
const text = value.trim();
|
|
82
|
+
if (!/^[[{]/.test(text))
|
|
83
|
+
return value;
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(text);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** `country_code`, `countryCode` → "Country code". */
|
|
92
|
+
export function humanize(key) {
|
|
93
|
+
const words = key
|
|
94
|
+
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
95
|
+
.replace(/([a-zA-Z])(\d)/g, '$1 $2')
|
|
96
|
+
.replace(/[_-]+/g, ' ')
|
|
97
|
+
.trim()
|
|
98
|
+
.toLowerCase();
|
|
99
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
100
|
+
}
|
|
101
|
+
/** A unit worth printing: a symbol or abbreviation, not a format name like `iso8601` or `wmo code`. */
|
|
102
|
+
export function printableUnit(unit) {
|
|
103
|
+
if (typeof unit !== 'string' || unit === '' || /\s/.test(unit) || unit === 'iso8601')
|
|
104
|
+
return undefined;
|
|
105
|
+
return unit;
|
|
106
|
+
}
|
|
107
|
+
/** Unit suffixes a key can end in (`distance_m`, `duration_ms`), and the unit each prints as. */
|
|
108
|
+
const SUFFIX_UNITS = {
|
|
109
|
+
m: 'm',
|
|
110
|
+
km: 'km',
|
|
111
|
+
mi: 'mi',
|
|
112
|
+
ms: 'ms',
|
|
113
|
+
s: 's',
|
|
114
|
+
sec: 's',
|
|
115
|
+
min: 'min',
|
|
116
|
+
h: 'h',
|
|
117
|
+
kg: 'kg',
|
|
118
|
+
g: 'g',
|
|
119
|
+
pct: '%',
|
|
120
|
+
percent: '%',
|
|
121
|
+
c: '°C',
|
|
122
|
+
f: '°F',
|
|
123
|
+
usd: 'USD',
|
|
124
|
+
eur: 'EUR',
|
|
125
|
+
gbp: 'GBP',
|
|
126
|
+
jpy: 'JPY',
|
|
127
|
+
};
|
|
128
|
+
/** The unit a key's suffix names, and the key without it: `distance_m` → Distance, m. */
|
|
129
|
+
function suffixUnit(key) {
|
|
130
|
+
const match = /^(.+?)[_-]([a-z]+)$/i.exec(key);
|
|
131
|
+
const unit = match ? SUFFIX_UNITS[(match[2] ?? '').toLowerCase()] : undefined;
|
|
132
|
+
return match?.[1] && unit ? { label: humanize(match[1]), unit } : undefined;
|
|
133
|
+
}
|
|
134
|
+
/** A column's label and unit: from its `<key>_units` entry, else its key's suffix. */
|
|
135
|
+
export function fieldLabel(key, units) {
|
|
136
|
+
const listed = printableUnit(units?.[key]);
|
|
137
|
+
if (listed)
|
|
138
|
+
return { label: humanize(key), unit: listed };
|
|
139
|
+
return suffixUnit(key) ?? { label: humanize(key) };
|
|
140
|
+
}
|
|
141
|
+
/** A field's label and the unit its value reads with. A unit only reads onto a number: `country_code` stays "Country code". */
|
|
142
|
+
export function fieldReading(key, value, units) {
|
|
143
|
+
if (typeof value !== 'number')
|
|
144
|
+
return { label: humanize(key) };
|
|
145
|
+
return fieldLabel(key, units);
|
|
146
|
+
}
|
|
147
|
+
/** The `<key>_units` object beside `key`, if there is one. */
|
|
148
|
+
export function unitsOf(row, key) {
|
|
149
|
+
const units = row[`${key}_units`];
|
|
150
|
+
return isRow(units) ? units : undefined;
|
|
151
|
+
}
|
|
152
|
+
/** A row's fields, less the units objects its other fields read. */
|
|
153
|
+
export function shownEntries(row) {
|
|
154
|
+
return Object.entries(row).filter(([key, value]) => !(key.endsWith('_units') && isRow(value) && key.slice(0, -'_units'.length) in row));
|
|
155
|
+
}
|
|
156
|
+
/** A string that's a date: a bare day or a moment. */
|
|
157
|
+
function dateReading(text) {
|
|
158
|
+
if (!ISO_DATE.test(text))
|
|
159
|
+
return undefined;
|
|
160
|
+
const date = new Date(text);
|
|
161
|
+
if (Number.isNaN(date.getTime()))
|
|
162
|
+
return undefined;
|
|
163
|
+
return text.includes('T') ? { kind: 'date-time', date } : { kind: 'date', date };
|
|
164
|
+
}
|
|
165
|
+
export function plainReading(value, unit) {
|
|
166
|
+
if (value === null || value === undefined)
|
|
167
|
+
return { kind: 'none' };
|
|
168
|
+
if (typeof value === 'boolean')
|
|
169
|
+
return { kind: 'boolean', value };
|
|
170
|
+
if (typeof value === 'number')
|
|
171
|
+
return unit ? { kind: 'number', value, unit } : { kind: 'number', value };
|
|
172
|
+
const text = String(value);
|
|
173
|
+
if (/^https?:\/\/\S+$/.test(text))
|
|
174
|
+
return { kind: 'link', href: text };
|
|
175
|
+
return dateReading(text) ?? { kind: 'text', text };
|
|
176
|
+
}
|
|
177
|
+
/** A formatted number with its unit: symbols sit close (21°C, 35%), words stand apart (320 m). */
|
|
178
|
+
export function withUnit(number, unit) {
|
|
179
|
+
if (!unit)
|
|
180
|
+
return number;
|
|
181
|
+
return /^[°%]/.test(unit) ? `${number}${unit}` : `${number} ${unit}`;
|
|
182
|
+
}
|
|
183
|
+
/** The field that names a row, if it has one. */
|
|
184
|
+
export function titleKey(row) {
|
|
185
|
+
return TITLE_KEYS.find((key) => typeof row[key] === 'string' || typeof row[key] === 'number');
|
|
186
|
+
}
|
|
187
|
+
/** Up to two short strings that place or sort a row. */
|
|
188
|
+
function tags(row, skip) {
|
|
189
|
+
return TAG_KEYS.filter((key) => {
|
|
190
|
+
const value = row[key];
|
|
191
|
+
return key !== skip && typeof value === 'string' && value.length > 0 && value.length <= MAX_TAG_LENGTH;
|
|
192
|
+
})
|
|
193
|
+
.slice(0, MAX_TAGS)
|
|
194
|
+
.map((key) => row[key]);
|
|
195
|
+
}
|
|
196
|
+
/** A flat row's parts, if a name and a line say what it is. */
|
|
197
|
+
function listRow(item) {
|
|
198
|
+
if (!isRow(item) || !Object.values(item).every(isPlain))
|
|
199
|
+
return undefined;
|
|
200
|
+
const title = titleKey(item);
|
|
201
|
+
const description = DESCRIPTION_KEYS.find((key) => typeof item[key] === 'string' && item[key] !== '');
|
|
202
|
+
if (!title || !description)
|
|
203
|
+
return undefined;
|
|
204
|
+
const link = LINK_KEYS.find((key) => typeof item[key] === 'string' && /^https?:\/\//.test(item[key]));
|
|
205
|
+
const rowTags = tags(item, title);
|
|
206
|
+
const shown = new Set([title, description, link]);
|
|
207
|
+
const extras = Object.entries(item).filter(([key, value]) => !shown.has(key) && !(TAG_KEYS.includes(key) && rowTags.includes(value)));
|
|
208
|
+
if (extras.length > MAX_LIST_EXTRAS)
|
|
209
|
+
return undefined;
|
|
210
|
+
return {
|
|
211
|
+
title: String(item[title]),
|
|
212
|
+
description: item[description],
|
|
213
|
+
href: link ? item[link] : undefined,
|
|
214
|
+
tags: rowTags,
|
|
215
|
+
extras,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/** A collapsible row's heading and what it opens to: a titled row loses its title field; else it's named by its place. */
|
|
219
|
+
export function rowHeading(item) {
|
|
220
|
+
if (!isRow(item))
|
|
221
|
+
return { tags: [], rest: item };
|
|
222
|
+
const key = titleKey(item);
|
|
223
|
+
if (!key)
|
|
224
|
+
return { tags: tags(item, undefined), rest: item };
|
|
225
|
+
return {
|
|
226
|
+
title: String(item[key]),
|
|
227
|
+
tags: tags(item, key),
|
|
228
|
+
rest: Object.fromEntries(Object.entries(item).filter(([k]) => k !== key)),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/** Small flat objects sharing their fields: a table's rows. */
|
|
232
|
+
function tableColumns(items) {
|
|
233
|
+
if (items.length < 2 || !items.every((item) => isRow(item) && Object.values(item).every(isPlain)))
|
|
234
|
+
return undefined;
|
|
235
|
+
const columns = [...new Set(items.flatMap((item) => Object.keys(item)))];
|
|
236
|
+
return columns.length <= MAX_TABLE_COLUMNS ? columns : undefined;
|
|
237
|
+
}
|
|
238
|
+
/** An object of equal-length lists of plain values (a column store): a table's columns. */
|
|
239
|
+
export function columnStore(row) {
|
|
240
|
+
const entries = shownEntries(row);
|
|
241
|
+
if (entries.length < 2)
|
|
242
|
+
return undefined;
|
|
243
|
+
const lengths = new Set(entries.map(([, value]) => (Array.isArray(value) && value.every(isPlain) ? value.length : -1)));
|
|
244
|
+
const [length] = lengths;
|
|
245
|
+
return lengths.size === 1 && length !== undefined && length >= 2 ? entries.map(([key]) => key) : undefined;
|
|
246
|
+
}
|
|
247
|
+
/** A column store's lists, turned into rows. */
|
|
248
|
+
export function storeRows(row, columns) {
|
|
249
|
+
const lists = columns.map((key) => row[key]);
|
|
250
|
+
return (lists[0] ?? []).map((_, index) => Object.fromEntries(columns.map((key, k) => [key, lists[k]?.[index]])));
|
|
251
|
+
}
|
|
252
|
+
function listShape(items) {
|
|
253
|
+
if (items.length === 0)
|
|
254
|
+
return { kind: 'none' };
|
|
255
|
+
if (items.every(isPlain))
|
|
256
|
+
return { kind: 'tokens', items };
|
|
257
|
+
const listed = items.map(listRow);
|
|
258
|
+
if (listed.every((row) => row !== undefined))
|
|
259
|
+
return { kind: 'list', rows: listed };
|
|
260
|
+
const columns = tableColumns(items);
|
|
261
|
+
return columns ? { kind: 'table', columns, rows: items } : { kind: 'rows', items };
|
|
262
|
+
}
|
|
263
|
+
function rowShape(row) {
|
|
264
|
+
if (Object.keys(row).length === 0)
|
|
265
|
+
return { kind: 'none' };
|
|
266
|
+
const store = columnStore(row);
|
|
267
|
+
return store ? { kind: 'table', columns: store, rows: storeRows(row, store) } : { kind: 'fields', row };
|
|
268
|
+
}
|
|
269
|
+
/** How a value (already unpacked) at `depth` is drawn. */
|
|
270
|
+
export function shapeOf(value, depth) {
|
|
271
|
+
if (depth > MAX_DEPTH)
|
|
272
|
+
return { kind: 'json' };
|
|
273
|
+
if (Array.isArray(value))
|
|
274
|
+
return listShape(value);
|
|
275
|
+
return isRow(value) ? rowShape(value) : { kind: 'plain' };
|
|
276
|
+
}
|
|
277
|
+
/** Text this long, or on several lines, reads as prose: full width, as Markdown. */
|
|
278
|
+
const PROSE_CHARS = 160;
|
|
279
|
+
export function isProse(value) {
|
|
280
|
+
return typeof value === 'string' && (value.length > PROSE_CHARS || value.includes('\n'));
|
|
281
|
+
}
|
|
282
|
+
/** A field that sits in the field list: a plain value (prose aside), or a short list of them. */
|
|
283
|
+
function isInline(value) {
|
|
284
|
+
const inner = unpacked(value);
|
|
285
|
+
if (isProse(inner))
|
|
286
|
+
return false;
|
|
287
|
+
if (isPlain(inner))
|
|
288
|
+
return true;
|
|
289
|
+
return Array.isArray(inner) && inner.length > 0 && inner.length <= MAX_INLINE_TOKENS && inner.every(isPlain);
|
|
290
|
+
}
|
|
291
|
+
function section(row, key) {
|
|
292
|
+
const path = [key];
|
|
293
|
+
let inner = unpacked(row[key]);
|
|
294
|
+
let units = unitsOf(row, key);
|
|
295
|
+
while (isRow(inner)) {
|
|
296
|
+
// A units object beside the one field doesn't count: `{ hourly, hourly_units }` is still a wrapper.
|
|
297
|
+
const entries = shownEntries(inner);
|
|
298
|
+
const only = entries.length === 1 ? entries[0] : undefined;
|
|
299
|
+
if (!only || isPlain(unpacked(only[1])))
|
|
300
|
+
break;
|
|
301
|
+
path.push(only[0]);
|
|
302
|
+
units = unitsOf(inner, only[0]);
|
|
303
|
+
inner = unpacked(only[1]);
|
|
304
|
+
}
|
|
305
|
+
return { key, title: path.map(humanize).join(' › '), value: inner, units };
|
|
306
|
+
}
|
|
307
|
+
/** An object's fields split: plain ones for the field list, nested ones as sections. */
|
|
308
|
+
export function splitFields(row) {
|
|
309
|
+
const entries = shownEntries(row);
|
|
310
|
+
return {
|
|
311
|
+
plain: entries.filter(([, value]) => isInline(value)),
|
|
312
|
+
sections: entries.filter(([, value]) => !isInline(value)).map(([key]) => section(row, key)),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TranscriptBlock } from '@theoremjs/agents/interface';
|
|
2
|
+
/** One source as a citation: its title, and when it's on the web, its link and favicon. */
|
|
3
|
+
export type SourceCitation = {
|
|
4
|
+
key: string;
|
|
5
|
+
title: string;
|
|
6
|
+
href?: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
};
|
|
9
|
+
export type SourceCitationBlock = Extract<TranscriptBlock, {
|
|
10
|
+
kind: 'citation';
|
|
11
|
+
}>;
|
|
12
|
+
/** A citation's sources, in the order they were cited. */
|
|
13
|
+
export declare function citationsFromBlock(block: SourceCitationBlock): SourceCitation[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini grounding links through this host rather than to the page, and names
|
|
3
|
+
* the page's own site in the source title instead.
|
|
4
|
+
*/
|
|
5
|
+
const GROUNDING_REDIRECT_HOST = 'vertexaisearch.cloud.google.com';
|
|
6
|
+
/** A citation's sources, in the order they were cited. */
|
|
7
|
+
export function citationsFromBlock(block) {
|
|
8
|
+
return block.sources.map((source, i) => citationFromSource(source, `c-${String(i)}`));
|
|
9
|
+
}
|
|
10
|
+
function citationFromSource(source, key) {
|
|
11
|
+
const url = webUrl(source.uri);
|
|
12
|
+
const site = url && siteOf(url, source.title);
|
|
13
|
+
return {
|
|
14
|
+
key,
|
|
15
|
+
title: source.title.trim() || hostLabel(source.uri) || source.type,
|
|
16
|
+
...(url ? { href: url.href } : {}),
|
|
17
|
+
...(site ? { icon: faviconUrl(site) } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A source is linked only at an http(s) URL. Sources come from the model and
|
|
22
|
+
* from tool output, so a `javascript:`, `data:` or other scheme never becomes
|
|
23
|
+
* a clickable link.
|
|
24
|
+
*/
|
|
25
|
+
function webUrl(uri) {
|
|
26
|
+
if (!URL.canParse(uri))
|
|
27
|
+
return undefined;
|
|
28
|
+
const url = new URL(uri);
|
|
29
|
+
return url.protocol === 'https:' || url.protocol === 'http:' ? url : undefined;
|
|
30
|
+
}
|
|
31
|
+
/** The site a source is on: the link's host, or for a grounding redirect, the site its title names. */
|
|
32
|
+
function siteOf(url, title) {
|
|
33
|
+
if (url.hostname !== GROUNDING_REDIRECT_HOST)
|
|
34
|
+
return url.hostname;
|
|
35
|
+
const named = title.trim().toLowerCase();
|
|
36
|
+
if (!named.includes('.') || !URL.canParse(`https://${named}`))
|
|
37
|
+
return undefined;
|
|
38
|
+
return new URL(`https://${named}`).hostname === named ? named : undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Google's favicon service; the visitor's browser sends it the site's host, never the page. */
|
|
41
|
+
function faviconUrl(site) {
|
|
42
|
+
return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(site)}&sz=32`;
|
|
43
|
+
}
|
|
44
|
+
function hostLabel(uri) {
|
|
45
|
+
try {
|
|
46
|
+
return new URL(uri).hostname.replace(/^www\./, '');
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return truncate(uri, 40);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function truncate(value, max) {
|
|
53
|
+
if (value.length <= max)
|
|
54
|
+
return value;
|
|
55
|
+
return `${value.slice(0, max - 1)}…`;
|
|
56
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules behind editing a decision's state as fields: which lists read as
|
|
3
|
+
* words, and what a new row in a list starts as.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import type { DecisionJson } from '@theoremjs/agents';
|
|
8
|
+
/** A list edited as tokens: empty, or every entry a string. */
|
|
9
|
+
export declare function isWordList(value: DecisionJson[]): value is string[];
|
|
10
|
+
/** A new row shaped like `like`: its fields kept, its values emptied. */
|
|
11
|
+
export declare function blankLike(like: DecisionJson | undefined): DecisionJson;
|
|
12
|
+
/** Columns a list of rows edits as a table in, or `null` when its rows are not all small, flat, and alike. */
|
|
13
|
+
export declare function tableColumns(value: DecisionJson[]): {
|
|
14
|
+
key: string;
|
|
15
|
+
weight: number;
|
|
16
|
+
}[] | null;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules behind editing a decision's state as fields: which lists read as
|
|
3
|
+
* words, and what a new row in a list starts as.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
/** A list edited as tokens: empty, or every entry a string. */
|
|
8
|
+
export function isWordList(value) {
|
|
9
|
+
return value.every((entry) => typeof entry === 'string');
|
|
10
|
+
}
|
|
11
|
+
/** A new row shaped like `like`: its fields kept, its values emptied. */
|
|
12
|
+
export function blankLike(like) {
|
|
13
|
+
if (like === undefined || like === null || typeof like === 'string')
|
|
14
|
+
return '';
|
|
15
|
+
if (typeof like === 'number')
|
|
16
|
+
return 0;
|
|
17
|
+
if (typeof like === 'boolean')
|
|
18
|
+
return false;
|
|
19
|
+
if (Array.isArray(like))
|
|
20
|
+
return [];
|
|
21
|
+
return Object.fromEntries(Object.entries(like).map(([key, value]) => [key, blankLike(value)]));
|
|
22
|
+
}
|
|
23
|
+
/** Columns a list of rows edits as a table in, or `null` when its rows are not all small, flat, and alike. */
|
|
24
|
+
export function tableColumns(value) {
|
|
25
|
+
const first = value[0];
|
|
26
|
+
if (first === undefined || first === null || typeof first !== 'object' || Array.isArray(first))
|
|
27
|
+
return null;
|
|
28
|
+
const keys = Object.keys(first);
|
|
29
|
+
if (keys.length === 0 || keys.length > 4)
|
|
30
|
+
return null;
|
|
31
|
+
const alike = value.every((row) => row !== null &&
|
|
32
|
+
typeof row === 'object' &&
|
|
33
|
+
!Array.isArray(row) &&
|
|
34
|
+
Object.keys(row).join() === keys.join() &&
|
|
35
|
+
Object.values(row).every((cell) => cell === null || typeof cell !== 'object'));
|
|
36
|
+
if (!alike)
|
|
37
|
+
return null;
|
|
38
|
+
// Wider columns for longer values: a role stays narrow beside its message.
|
|
39
|
+
return keys.map((key) => {
|
|
40
|
+
const lengths = value.map((row) => String(row[key] ?? '').length);
|
|
41
|
+
const longest = Math.max(key.length, ...lengths);
|
|
42
|
+
return { key, weight: Math.min(4, Math.max(1, Math.round(longest / 10))) };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a tool's result leads with, above its data: the figures that answer it,
|
|
3
|
+
* the series worth a chart, and the images it links. Plain decisions over the
|
|
4
|
+
* value alone, so any tool's output gets them without a schema saying so; the
|
|
5
|
+
* data itself still reads in full below, drawn by `ShapedData`.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { type Row } from './shaped-data.ts';
|
|
10
|
+
/** One number that answers the call, as a stat card shows it. */
|
|
11
|
+
export type ResultFigure = {
|
|
12
|
+
key: string;
|
|
13
|
+
label: string;
|
|
14
|
+
value: number;
|
|
15
|
+
unit?: string;
|
|
16
|
+
};
|
|
17
|
+
/** One numeric field drawn across a chart's rows. */
|
|
18
|
+
export type ResultSeries = {
|
|
19
|
+
key: string;
|
|
20
|
+
label: string;
|
|
21
|
+
unit?: string;
|
|
22
|
+
};
|
|
23
|
+
/** A time chart draws its series over dates; a category chart ranks rows by one series. */
|
|
24
|
+
export type ResultChart = {
|
|
25
|
+
kind: 'time';
|
|
26
|
+
key: string;
|
|
27
|
+
title: string;
|
|
28
|
+
x: string;
|
|
29
|
+
series: ResultSeries[];
|
|
30
|
+
rows: Row[];
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'category';
|
|
33
|
+
key: string;
|
|
34
|
+
title: string;
|
|
35
|
+
series: ResultSeries;
|
|
36
|
+
rows: {
|
|
37
|
+
label: string;
|
|
38
|
+
value: number;
|
|
39
|
+
}[];
|
|
40
|
+
};
|
|
41
|
+
export type ResultImage = {
|
|
42
|
+
src: string;
|
|
43
|
+
alt: string;
|
|
44
|
+
};
|
|
45
|
+
export type ResultLayout = {
|
|
46
|
+
figures: ResultFigure[];
|
|
47
|
+
charts: ResultChart[];
|
|
48
|
+
images: ResultImage[];
|
|
49
|
+
};
|
|
50
|
+
/** Media a tool returned beside its value, as the complete event carries it. */
|
|
51
|
+
export type ResultMedia = {
|
|
52
|
+
type: string;
|
|
53
|
+
mimeType?: string;
|
|
54
|
+
data?: string;
|
|
55
|
+
};
|
|
56
|
+
/** What a call returned to see or hear: its image parts, then the images its value links; its audio parts. */
|
|
57
|
+
export type ReturnedMedia = {
|
|
58
|
+
images: ResultImage[];
|
|
59
|
+
audio: {
|
|
60
|
+
src: string;
|
|
61
|
+
mimeType?: string;
|
|
62
|
+
}[];
|
|
63
|
+
};
|
|
64
|
+
/** What a tool's output leads with. Empty when nothing stands out: the data alone reads best. */
|
|
65
|
+
export declare function layoutResult(output: unknown): ResultLayout;
|
|
66
|
+
/** The images and audio a call returned, each with its bytes; parts without them are left out. */
|
|
67
|
+
export declare function returnedMedia(layout: ResultLayout, parts: readonly ResultMedia[], imageAlt: string): ReturnedMedia;
|
|
68
|
+
/** Whether the result has anything to lead with above its data. */
|
|
69
|
+
export declare function hasLead(layout: ResultLayout, media: ReturnedMedia): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* How a time chart's dates read, on its axis and in its tooltip: hours within
|
|
72
|
+
* a day, else dates. A bare date is a calendar day, parsed at UTC midnight, so
|
|
73
|
+
* it reads in UTC and doesn't slip a day.
|
|
74
|
+
*/
|
|
75
|
+
export declare function timeFormats(dates: readonly string[]): {
|
|
76
|
+
tick: Intl.DateTimeFormatOptions;
|
|
77
|
+
when: Intl.DateTimeFormatOptions;
|
|
78
|
+
};
|