@invana/assistant 0.0.32
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 +103 -0
- package/dist/index.cjs +2007 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1468 -0
- package/dist/index.d.ts +1468 -0
- package/dist/index.js +1892 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2007 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React6 = require('react');
|
|
4
|
+
var blocks = require('@invana/blocks');
|
|
5
|
+
var ui = require('@invana/ui');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
var forms = require('@invana/forms');
|
|
8
|
+
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n.default = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var React6__namespace = /*#__PURE__*/_interopNamespace(React6);
|
|
28
|
+
|
|
29
|
+
// src/styles/chat-session.tsx
|
|
30
|
+
function answerToPage(turn) {
|
|
31
|
+
const blocks$1 = turn.blocks.filter(
|
|
32
|
+
(b) => blocks.BLOCK_RENDERERS[b.kind] != null && b.status !== "loading" && b.status !== "empty"
|
|
33
|
+
);
|
|
34
|
+
return { title: turn.title, sections: [{ blocks: blocks$1 }] };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/answers/index.ts
|
|
38
|
+
var EmissionCard = ui.EmissionCard;
|
|
39
|
+
var EmissionBody = ui.EmissionBody;
|
|
40
|
+
var EmissionHeader = ui.EmissionHeader;
|
|
41
|
+
var CitationMarker = ui.CitationMarker;
|
|
42
|
+
var TemplatePicker = ui.TemplatePicker;
|
|
43
|
+
var ChatSessionFrame = ui.ChatSession;
|
|
44
|
+
var ChatSessionMessage = ui.ChatSessionMessage;
|
|
45
|
+
var ChatSessionMessageOptions = ui.ChatSessionMessageOptions;
|
|
46
|
+
var ChatSessionComposer = ui.ChatSessionComposer;
|
|
47
|
+
var ChatSessionActivityRow = ui.ChatSessionActivityRow;
|
|
48
|
+
var ChatSessionActivitySubLine = ui.ChatSessionActivitySubLine;
|
|
49
|
+
var chatSessionGutterClass = ui.chatSessionGutterClass;
|
|
50
|
+
var ChatSessionPromptRow = ui.ChatSessionPromptRow;
|
|
51
|
+
var ChatSessionProgressLine = ui.ChatSessionProgressLine;
|
|
52
|
+
var ChatSessionCaret = ui.ChatSessionCaret;
|
|
53
|
+
var ChatSessionDisclosure = ui.ChatSessionDisclosure;
|
|
54
|
+
var ChatSessionDisclosureCode = ui.ChatSessionDisclosureCode;
|
|
55
|
+
var ChatSessionDisclosureSteps = ui.ChatSessionDisclosureSteps;
|
|
56
|
+
var ChatSessionTaskRow = ui.ChatSessionTaskRow;
|
|
57
|
+
var ChatSessionTaskGroup = ui.ChatSessionTaskGroup;
|
|
58
|
+
var ChatSessionStatusBar = ui.ChatSessionStatusBar;
|
|
59
|
+
var ChatSessionContextChip = ui.ChatSessionContextChip;
|
|
60
|
+
var focus = /* @__PURE__ */ new Map();
|
|
61
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
62
|
+
function set(turn, n) {
|
|
63
|
+
if (focus.get(turn) === n) return;
|
|
64
|
+
focus.set(turn, n);
|
|
65
|
+
listeners.forEach((l) => l());
|
|
66
|
+
}
|
|
67
|
+
function subscribe(listener) {
|
|
68
|
+
listeners.add(listener);
|
|
69
|
+
return () => listeners.delete(listener);
|
|
70
|
+
}
|
|
71
|
+
function useCiteFocus(turn, initial) {
|
|
72
|
+
const pointed = React6__namespace.useSyncExternalStore(
|
|
73
|
+
subscribe,
|
|
74
|
+
() => focus.get(turn),
|
|
75
|
+
() => void 0
|
|
76
|
+
);
|
|
77
|
+
return [pointed ?? initial, (n) => set(turn, n)];
|
|
78
|
+
}
|
|
79
|
+
function toEvent(turn, action, value, block) {
|
|
80
|
+
switch (action) {
|
|
81
|
+
case "reply":
|
|
82
|
+
return { type: "reply", turn, value };
|
|
83
|
+
case "change":
|
|
84
|
+
return { type: "change", turn, value };
|
|
85
|
+
case "skip":
|
|
86
|
+
return { type: "skip", turn };
|
|
87
|
+
case "prompt":
|
|
88
|
+
return { type: "prompt", text: String(value) };
|
|
89
|
+
case "scope": {
|
|
90
|
+
const edit = value;
|
|
91
|
+
return typeof edit?.part === "number" ? { type: "scope", turn, part: edit.part, value: edit.value } : void 0;
|
|
92
|
+
}
|
|
93
|
+
case "open":
|
|
94
|
+
return block >= 0 ? { type: "open", turn, block } : void 0;
|
|
95
|
+
case "action":
|
|
96
|
+
return { type: "action", turn, action: String(value) };
|
|
97
|
+
default:
|
|
98
|
+
return value === void 0 ? { type: "action", turn, action } : { type: "action", turn, action, value };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function answerBlock(kind) {
|
|
102
|
+
const Renderer = blocks.BLOCK_RENDERERS[kind];
|
|
103
|
+
function AnswerBlock({ turn, block, onEvent }) {
|
|
104
|
+
const index = turn.blocks.indexOf(block);
|
|
105
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
106
|
+
Renderer,
|
|
107
|
+
{
|
|
108
|
+
spec: block,
|
|
109
|
+
onAction: (action, value) => {
|
|
110
|
+
const event = toEvent(turn.id, action, value, index);
|
|
111
|
+
if (event) onEvent(event);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
AnswerBlock.displayName = `Answer(${kind})`;
|
|
117
|
+
return AnswerBlock;
|
|
118
|
+
}
|
|
119
|
+
function askBlock(kind) {
|
|
120
|
+
const Renderer = blocks.BLOCK_RENDERERS[kind];
|
|
121
|
+
function AskBlock({ turn, options, onEvent }) {
|
|
122
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
123
|
+
Renderer,
|
|
124
|
+
{
|
|
125
|
+
spec: options,
|
|
126
|
+
state: turn.state,
|
|
127
|
+
value: turn.value,
|
|
128
|
+
id: turn.id,
|
|
129
|
+
onAction: (action, value) => {
|
|
130
|
+
const event = toEvent(turn.id, action, value, -1);
|
|
131
|
+
if (event) onEvent(event);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
AskBlock.displayName = `Ask(${kind})`;
|
|
137
|
+
return AskBlock;
|
|
138
|
+
}
|
|
139
|
+
function NarrativeAnswer({ block, turn }) {
|
|
140
|
+
const [active, setActive] = useCiteFocus(turn.id, block.active);
|
|
141
|
+
const writing = turn.state === "running" && turn.blocks[turn.blocks.length - 1] === block;
|
|
142
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
143
|
+
blocks.NarrativeBlock,
|
|
144
|
+
{
|
|
145
|
+
spec: block,
|
|
146
|
+
active,
|
|
147
|
+
onActiveChange: setActive,
|
|
148
|
+
trailing: writing ? /* @__PURE__ */ jsxRuntime.jsx(ChatSessionCaret, {}) : null
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
function CitationsAnswer({ block, turn }) {
|
|
153
|
+
const [active] = useCiteFocus(turn.id, block.active);
|
|
154
|
+
return /* @__PURE__ */ jsxRuntime.jsx(blocks.CitationsBlock, { spec: block, active });
|
|
155
|
+
}
|
|
156
|
+
function ScopeAnswer({ block, turn, onEvent }) {
|
|
157
|
+
const fromEnvelope = !turn.blocks.includes(block);
|
|
158
|
+
const fixed = fromEnvelope && turn.envelope?.freshness ? [block.parts.length - 1] : void 0;
|
|
159
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
160
|
+
blocks.ScopeBlock,
|
|
161
|
+
{
|
|
162
|
+
spec: block,
|
|
163
|
+
fixed,
|
|
164
|
+
onAction: (action, value) => {
|
|
165
|
+
const event = toEvent(turn.id, action, value, -1);
|
|
166
|
+
if (event) onEvent(event);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
var ProposalBody = answerBlock("proposal");
|
|
172
|
+
function ProposalAnswer(props) {
|
|
173
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EmissionCard, { kind: "proposal", title: props.block.title, citation: props.block.done?.at, children: /* @__PURE__ */ jsxRuntime.jsx(EmissionBody, { children: /* @__PURE__ */ jsxRuntime.jsx(ProposalBody, { ...props }) }) });
|
|
174
|
+
}
|
|
175
|
+
var ANSWER_WRAPPERS = {
|
|
176
|
+
narrative: NarrativeAnswer,
|
|
177
|
+
citations: CitationsAnswer,
|
|
178
|
+
scope: ScopeAnswer,
|
|
179
|
+
proposal: ProposalAnswer
|
|
180
|
+
};
|
|
181
|
+
var ANSWER_RENDERERS = Object.fromEntries(
|
|
182
|
+
blocks.ANSWER_KINDS.map((kind) => [kind, blocks.BLOCK_RENDERERS[kind] ? ANSWER_WRAPPERS[kind] ?? answerBlock(kind) : null])
|
|
183
|
+
);
|
|
184
|
+
var ASK_RENDERERS = Object.fromEntries(
|
|
185
|
+
blocks.ASK_KINDS.map((kind) => [kind, blocks.BLOCK_RENDERERS[kind] ? askBlock(kind) : null])
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// src/conversations/registry.ts
|
|
189
|
+
var BUILT_IN_ASKS = ASK_RENDERERS;
|
|
190
|
+
var BUILT_IN_BLOCKS = ANSWER_RENDERERS;
|
|
191
|
+
var BUILT_IN_ASK_TRAITS = {
|
|
192
|
+
suggestions: { frame: "none" },
|
|
193
|
+
confirm: { kind: "confirm" },
|
|
194
|
+
approval: { kind: "proposal" }
|
|
195
|
+
};
|
|
196
|
+
var BUILT_IN_BLOCK_TRAITS = {
|
|
197
|
+
proposal: { placement: "own" }
|
|
198
|
+
};
|
|
199
|
+
function resolveRegistry(extra) {
|
|
200
|
+
const askTraits = { ...BUILT_IN_ASK_TRAITS, ...extra?.askTraits };
|
|
201
|
+
const blockTraits = { ...BUILT_IN_BLOCK_TRAITS, ...extra?.blockTraits };
|
|
202
|
+
return {
|
|
203
|
+
asks: { ...BUILT_IN_ASKS, ...extra?.asks },
|
|
204
|
+
blocks: { ...BUILT_IN_BLOCKS, ...extra?.blocks },
|
|
205
|
+
askTraits: (kind) => ({ frame: "card", ...askTraits[kind] }),
|
|
206
|
+
blockTraits: (kind) => ({ placement: "card", ...blockTraits[kind] })
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
var PatchError = class extends Error {
|
|
210
|
+
};
|
|
211
|
+
function onTurn(spec, id, fn) {
|
|
212
|
+
const index = spec.turns.findIndex((t) => t.id === id);
|
|
213
|
+
if (index < 0) throw new PatchError(`No turn "${id}" in conversation "${spec.id}".`);
|
|
214
|
+
const turns = spec.turns.slice();
|
|
215
|
+
turns[index] = fn(turns[index]);
|
|
216
|
+
return { ...spec, turns };
|
|
217
|
+
}
|
|
218
|
+
function asAnswer(turn, op) {
|
|
219
|
+
if (turn.role !== "assistant" || turn.kind !== "answer") {
|
|
220
|
+
throw new PatchError(`"${op}" applies to an answer; turn "${turn.id}" is not one.`);
|
|
221
|
+
}
|
|
222
|
+
return turn;
|
|
223
|
+
}
|
|
224
|
+
function onStep(answer, id, op, fn) {
|
|
225
|
+
const trace = answer.trace ?? [];
|
|
226
|
+
const at = trace.findIndex((s) => s.id === id);
|
|
227
|
+
if (at < 0) throw new PatchError(`"${op}": no step "${id}" on turn "${answer.id}".`);
|
|
228
|
+
return { ...answer, trace: trace.map((s, i) => i === at ? fn(s) : s) };
|
|
229
|
+
}
|
|
230
|
+
function patchBlock(answer, op, index, patch) {
|
|
231
|
+
const at = index ?? answer.blocks.length - 1;
|
|
232
|
+
const block = answer.blocks[at];
|
|
233
|
+
if (!block) throw new PatchError(`"${op}": turn "${answer.id}" has no block ${at}.`);
|
|
234
|
+
const blocks$1 = answer.blocks.slice();
|
|
235
|
+
try {
|
|
236
|
+
blocks$1[at] = blocks.applyBlockPatch(block, patch);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (error instanceof blocks.BlockPatchError) {
|
|
239
|
+
throw new PatchError(`"${op}": block ${at} on turn "${answer.id}": ${error.message}`);
|
|
240
|
+
}
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
return { ...answer, blocks: blocks$1 };
|
|
244
|
+
}
|
|
245
|
+
function applyPatch(spec, patch) {
|
|
246
|
+
switch (patch.op) {
|
|
247
|
+
case "add-turn":
|
|
248
|
+
if (spec.turns.some((t) => t.id === patch.turn.id)) {
|
|
249
|
+
throw new PatchError(`Turn "${patch.turn.id}" already exists.`);
|
|
250
|
+
}
|
|
251
|
+
return { ...spec, turns: [...spec.turns, patch.turn] };
|
|
252
|
+
case "set-state":
|
|
253
|
+
return onTurn(spec, patch.turn, (turn) => {
|
|
254
|
+
if (turn.role !== "assistant") throw new PatchError(`Turn "${turn.id}" has no state.`);
|
|
255
|
+
if (turn.kind === "ask") {
|
|
256
|
+
return {
|
|
257
|
+
...turn,
|
|
258
|
+
state: patch.state,
|
|
259
|
+
...patch.value !== void 0 ? { value: patch.value } : {}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return { ...turn, state: patch.state };
|
|
263
|
+
});
|
|
264
|
+
case "add-block":
|
|
265
|
+
return onTurn(spec, patch.turn, (turn) => {
|
|
266
|
+
const answer = asAnswer(turn, patch.op);
|
|
267
|
+
return { ...answer, blocks: [...answer.blocks, patch.block] };
|
|
268
|
+
});
|
|
269
|
+
case "patch-block":
|
|
270
|
+
return onTurn(spec, patch.turn, (turn) => patchBlock(asAnswer(turn, patch.op), patch.op, patch.block, patch.patch));
|
|
271
|
+
case "update-block":
|
|
272
|
+
return onTurn(
|
|
273
|
+
spec,
|
|
274
|
+
patch.turn,
|
|
275
|
+
(turn) => patchBlock(asAnswer(turn, patch.op), patch.op, patch.block, { op: "set", fields: patch.fields })
|
|
276
|
+
);
|
|
277
|
+
case "append-text":
|
|
278
|
+
return onTurn(
|
|
279
|
+
spec,
|
|
280
|
+
patch.turn,
|
|
281
|
+
(turn) => patchBlock(asAnswer(turn, patch.op), patch.op, patch.block, { op: "append", at: [patch.field ?? "text"], text: patch.text })
|
|
282
|
+
);
|
|
283
|
+
case "add-trace-step":
|
|
284
|
+
return onTurn(spec, patch.turn, (turn) => {
|
|
285
|
+
const answer = asAnswer(turn, patch.op);
|
|
286
|
+
const trace = answer.trace ?? [];
|
|
287
|
+
const at = patch.step.id ? trace.findIndex((s) => s.id === patch.step.id) : -1;
|
|
288
|
+
const next = at >= 0 ? trace.map((s, i) => i === at ? patch.step : s) : [...trace, patch.step];
|
|
289
|
+
return { ...answer, trace: next };
|
|
290
|
+
});
|
|
291
|
+
case "update-trace-step":
|
|
292
|
+
return onTurn(
|
|
293
|
+
spec,
|
|
294
|
+
patch.turn,
|
|
295
|
+
(turn) => onStep(asAnswer(turn, patch.op), patch.step, patch.op, (s) => ({ ...s, ...patch.fields, id: s.id }))
|
|
296
|
+
);
|
|
297
|
+
case "append-thinking":
|
|
298
|
+
return onTurn(
|
|
299
|
+
spec,
|
|
300
|
+
patch.turn,
|
|
301
|
+
(turn) => onStep(asAnswer(turn, patch.op), patch.step, patch.op, (s) => ({
|
|
302
|
+
...s,
|
|
303
|
+
thinking: (s.thinking ?? "") + patch.text
|
|
304
|
+
}))
|
|
305
|
+
);
|
|
306
|
+
case "update-turn":
|
|
307
|
+
return onTurn(spec, patch.turn, (turn) => ({ ...turn, ...patch.fields, id: turn.id }));
|
|
308
|
+
case "set-records": {
|
|
309
|
+
const models = spec.access?.models ?? [];
|
|
310
|
+
if (!spec.access || !models.some((m) => m.id === patch.model)) {
|
|
311
|
+
throw new PatchError(`"set-records": no model "${patch.model}" in conversation "${spec.id}".`);
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
...spec,
|
|
315
|
+
access: {
|
|
316
|
+
...spec.access,
|
|
317
|
+
models: models.map((m) => m.id === patch.model ? { ...m, records: patch.records } : m)
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
case "update-spec":
|
|
322
|
+
return { ...spec, ...patch.fields, id: spec.id, turns: spec.turns };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function applyPatches(spec, patches) {
|
|
326
|
+
return patches.reduce(applyPatch, spec);
|
|
327
|
+
}
|
|
328
|
+
var patchesOf = (source) => blocks.patchesOf(source);
|
|
329
|
+
var fromNdjson = (input) => blocks.fromNdjson(input);
|
|
330
|
+
var fromEventSource = (source, options) => blocks.fromEventSource(source, options);
|
|
331
|
+
var playScript = (script, options) => blocks.playScript(script, options);
|
|
332
|
+
var offsetScript = (script, ms) => blocks.offsetScript(script, ms);
|
|
333
|
+
function deltas(text, by) {
|
|
334
|
+
if (by === "char") return text.match(/[\s\S]{1,3}/g) ?? [];
|
|
335
|
+
return text.match(/\S+\s*|\s+/g) ?? [];
|
|
336
|
+
}
|
|
337
|
+
function textDeltas(turn, text, { from = 0, every = 40, by = "word", block, field } = {}) {
|
|
338
|
+
return deltas(text, by).map((piece, i) => ({
|
|
339
|
+
at: from + i * every,
|
|
340
|
+
patch: {
|
|
341
|
+
op: "append-text",
|
|
342
|
+
turn,
|
|
343
|
+
text: piece,
|
|
344
|
+
...block === void 0 ? {} : { block },
|
|
345
|
+
...field === void 0 ? {} : { field }
|
|
346
|
+
}
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
function thinkingDeltas(turn, step, text, { from = 0, every = 40, by = "word" } = {}) {
|
|
350
|
+
return deltas(text, by).map((piece, i) => ({
|
|
351
|
+
at: from + i * every,
|
|
352
|
+
patch: { op: "append-thinking", turn, step, text: piece }
|
|
353
|
+
}));
|
|
354
|
+
}
|
|
355
|
+
var LIVE_STEP = /* @__PURE__ */ new Set(["running", "retrying"]);
|
|
356
|
+
var isLive = (turn) => turn.state === "running";
|
|
357
|
+
function isRunning(spec) {
|
|
358
|
+
return spec.turns.some((t) => t.role === "assistant" && t.kind === "answer" && isLive(t));
|
|
359
|
+
}
|
|
360
|
+
function stopPatches(spec, now = Date.now()) {
|
|
361
|
+
const patches = [];
|
|
362
|
+
for (const turn of spec.turns) {
|
|
363
|
+
if (turn.role !== "assistant" || turn.kind !== "answer" || !isLive(turn)) continue;
|
|
364
|
+
for (const step of turn.trace ?? []) {
|
|
365
|
+
if (!step.id || !LIVE_STEP.has(step.state)) continue;
|
|
366
|
+
const started2 = step.startedAt ? Date.parse(step.startedAt) : NaN;
|
|
367
|
+
patches.push({
|
|
368
|
+
op: "update-trace-step",
|
|
369
|
+
turn: turn.id,
|
|
370
|
+
step: step.id,
|
|
371
|
+
fields: {
|
|
372
|
+
state: "stopped",
|
|
373
|
+
thinking: void 0,
|
|
374
|
+
...Number.isNaN(started2) ? {} : { duration: now - started2 }
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
const started = turn.startedAt ? Date.parse(turn.startedAt) : NaN;
|
|
379
|
+
patches.push({
|
|
380
|
+
op: "update-turn",
|
|
381
|
+
turn: turn.id,
|
|
382
|
+
fields: {
|
|
383
|
+
at: new Date(now).toISOString(),
|
|
384
|
+
...Number.isNaN(started) ? {} : { duration: now - started }
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
patches.push({ op: "set-state", turn: turn.id, state: "stopped" });
|
|
388
|
+
}
|
|
389
|
+
return patches;
|
|
390
|
+
}
|
|
391
|
+
var ChatSessionContext = React6__namespace.createContext(null);
|
|
392
|
+
function useChatSessionContext() {
|
|
393
|
+
const value = React6__namespace.useContext(ChatSessionContext);
|
|
394
|
+
if (!value) throw new Error("A ChatSession part was rendered outside <ChatSession>.");
|
|
395
|
+
return value;
|
|
396
|
+
}
|
|
397
|
+
var DEFAULT_ICONS = {
|
|
398
|
+
send: "\u2191",
|
|
399
|
+
stop: "\u25A0",
|
|
400
|
+
attach: "+",
|
|
401
|
+
close: "\xD7",
|
|
402
|
+
retry: "\u21BB",
|
|
403
|
+
copy: "\u29C9",
|
|
404
|
+
steps: "\u2261",
|
|
405
|
+
rateUp: "+1",
|
|
406
|
+
rateDown: "\u22121"
|
|
407
|
+
};
|
|
408
|
+
function useViewState() {
|
|
409
|
+
const [steps, setSteps] = React6__namespace.useState({});
|
|
410
|
+
const [records, setRecords] = React6__namespace.useState({});
|
|
411
|
+
const [ratings, setRatings] = React6__namespace.useState({});
|
|
412
|
+
const stepsOpen = React6__namespace.useCallback((turnId, live) => steps[turnId] ?? live, [steps]);
|
|
413
|
+
const toggleSteps = React6__namespace.useCallback(
|
|
414
|
+
(turnId, open) => setSteps((prev) => ({ ...prev, [turnId]: open ?? !(prev[turnId] ?? false) })),
|
|
415
|
+
[]
|
|
416
|
+
);
|
|
417
|
+
const recordOpen = React6__namespace.useCallback(
|
|
418
|
+
(turnId, stepId) => !!records[`${turnId}/${stepId}`],
|
|
419
|
+
[records]
|
|
420
|
+
);
|
|
421
|
+
const toggleRecord = React6__namespace.useCallback((turnId, stepId, open) => {
|
|
422
|
+
const key = `${turnId}/${stepId}`;
|
|
423
|
+
setRecords((prev) => ({ ...prev, [key]: open ?? !prev[key] }));
|
|
424
|
+
}, []);
|
|
425
|
+
const rating = React6__namespace.useCallback((turnId) => ratings[turnId], [ratings]);
|
|
426
|
+
const setRating = React6__namespace.useCallback(
|
|
427
|
+
(turnId, value) => setRatings((prev) => ({ ...prev, [turnId]: value })),
|
|
428
|
+
[]
|
|
429
|
+
);
|
|
430
|
+
return { stepsOpen, toggleSteps, recordOpen, toggleRecord, rating, setRating };
|
|
431
|
+
}
|
|
432
|
+
function useClock(fixed, live, every = 250) {
|
|
433
|
+
const [now, setNow] = React6__namespace.useState(() => fixed ?? Date.now());
|
|
434
|
+
React6__namespace.useEffect(() => {
|
|
435
|
+
if (fixed !== void 0 || !live) return;
|
|
436
|
+
const timer = setInterval(() => setNow(Date.now()), every);
|
|
437
|
+
return () => clearInterval(timer);
|
|
438
|
+
}, [fixed, live, every]);
|
|
439
|
+
return fixed ?? now;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/styles/base/format.ts
|
|
443
|
+
function formatDuration(ms, { spaced = false } = {}) {
|
|
444
|
+
const gap = spaced ? " " : "";
|
|
445
|
+
if (!Number.isFinite(ms) || ms < 0) return `0${gap}ms`;
|
|
446
|
+
if (ms < 1e3) return `${Math.round(ms)}${gap}ms`;
|
|
447
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}${gap}s`;
|
|
448
|
+
const pair = (big, bigUnit, small, smallUnit) => small ? `${big}${gap}${bigUnit} ${small}${gap}${smallUnit}` : `${big}${gap}${bigUnit}`;
|
|
449
|
+
if (ms < 36e5) return pair(Math.floor(ms / 6e4), "m", Math.round(ms % 6e4 / 1e3), "s");
|
|
450
|
+
return pair(Math.floor(ms / 36e5), "h", Math.round(ms % 36e5 / 6e4), "m");
|
|
451
|
+
}
|
|
452
|
+
function elapsedSince(iso, now) {
|
|
453
|
+
if (!iso) return void 0;
|
|
454
|
+
const then = Date.parse(iso);
|
|
455
|
+
return Number.isNaN(then) ? void 0 : Math.max(0, now - then);
|
|
456
|
+
}
|
|
457
|
+
function clockTime(iso) {
|
|
458
|
+
if (!iso) return void 0;
|
|
459
|
+
const date = new Date(iso);
|
|
460
|
+
if (Number.isNaN(date.getTime())) return void 0;
|
|
461
|
+
return date.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
462
|
+
}
|
|
463
|
+
var dayKey = (d) => `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
|
|
464
|
+
function sameDay(a, b) {
|
|
465
|
+
if (!a || !b) return true;
|
|
466
|
+
return dayKey(new Date(a)) === dayKey(new Date(b));
|
|
467
|
+
}
|
|
468
|
+
function dayLabel(iso, now) {
|
|
469
|
+
const date = new Date(iso);
|
|
470
|
+
const written = date.toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short" });
|
|
471
|
+
const today = new Date(now);
|
|
472
|
+
if (dayKey(date) === dayKey(today)) return `Today \xB7 ${written}`;
|
|
473
|
+
const yesterday = new Date(now - 864e5);
|
|
474
|
+
if (dayKey(date) === dayKey(yesterday)) return `Yesterday \xB7 ${written}`;
|
|
475
|
+
return written;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/styles/base/model.ts
|
|
479
|
+
function exchangesOf(spec) {
|
|
480
|
+
const out = [];
|
|
481
|
+
for (const turn of spec.turns) {
|
|
482
|
+
if (turn.role === "analyst") {
|
|
483
|
+
out.push({ id: turn.id, prompt: turn, replies: [] });
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const last = out[out.length - 1];
|
|
487
|
+
if (last) last.replies.push(turn);
|
|
488
|
+
else out.push({ id: turn.id, replies: [turn] });
|
|
489
|
+
}
|
|
490
|
+
return out;
|
|
491
|
+
}
|
|
492
|
+
var isAnswer = (turn) => turn.role === "assistant" && turn.kind === "answer";
|
|
493
|
+
var isAsk = (turn) => turn.role === "assistant" && turn.kind === "ask";
|
|
494
|
+
var ACTIVE = /* @__PURE__ */ new Set(["running", "retrying", "waiting"]);
|
|
495
|
+
function currentStep(answer) {
|
|
496
|
+
return answer.trace?.find((s) => ACTIVE.has(s.state));
|
|
497
|
+
}
|
|
498
|
+
function stepTime(step, now) {
|
|
499
|
+
if (step.state === "running" || step.state === "retrying") return elapsedSince(step.startedAt, now);
|
|
500
|
+
return step.duration;
|
|
501
|
+
}
|
|
502
|
+
function answerTime(answer, now) {
|
|
503
|
+
if (isLive(answer)) return elapsedSince(answer.startedAt, now);
|
|
504
|
+
if (answer.duration !== void 0) return answer.duration;
|
|
505
|
+
if (answer.startedAt && answer.at) {
|
|
506
|
+
const span = Date.parse(answer.at) - Date.parse(answer.startedAt);
|
|
507
|
+
if (!Number.isNaN(span)) return span;
|
|
508
|
+
}
|
|
509
|
+
const steps = answer.trace ?? [];
|
|
510
|
+
return steps.some((s) => s.duration !== void 0) ? steps.reduce((sum, s) => sum + (s.duration ?? 0), 0) : void 0;
|
|
511
|
+
}
|
|
512
|
+
function runOutcome(answer) {
|
|
513
|
+
if (answer.state === "error") return "failed";
|
|
514
|
+
if (answer.state === "stopped") return "stopped";
|
|
515
|
+
if (isLive(answer)) return answer.trace?.some((s) => s.state === "waiting") ? "waiting" : "live";
|
|
516
|
+
return "done";
|
|
517
|
+
}
|
|
518
|
+
function stepCount(answer) {
|
|
519
|
+
const steps = answer.trace ?? [];
|
|
520
|
+
return { done: steps.filter((s) => s.state === "done").length, total: steps.length };
|
|
521
|
+
}
|
|
522
|
+
function exchangeState(exchange, blocks = () => true) {
|
|
523
|
+
let waiting = false;
|
|
524
|
+
for (const reply of exchange.replies) {
|
|
525
|
+
if (isAsk(reply) && reply.state === "pending" && blocks(reply)) waiting = true;
|
|
526
|
+
if (isAnswer(reply)) {
|
|
527
|
+
const outcome = runOutcome(reply);
|
|
528
|
+
if (outcome === "live") return "running";
|
|
529
|
+
if (outcome === "waiting") waiting = true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return waiting ? "waiting" : "settled";
|
|
533
|
+
}
|
|
534
|
+
function threadCounts(spec, blocks) {
|
|
535
|
+
let running = 0;
|
|
536
|
+
let waiting = 0;
|
|
537
|
+
let steps = 0;
|
|
538
|
+
for (const exchange of exchangesOf(spec)) {
|
|
539
|
+
const state = exchangeState(exchange, blocks);
|
|
540
|
+
if (state === "running") running++;
|
|
541
|
+
if (state === "waiting") waiting++;
|
|
542
|
+
for (const reply of exchange.replies) if (isAnswer(reply)) steps += reply.trace?.length ?? 0;
|
|
543
|
+
}
|
|
544
|
+
return { running, waiting, steps };
|
|
545
|
+
}
|
|
546
|
+
function plainText(answer) {
|
|
547
|
+
return answer.blocks.map((b) => b.text).filter((t) => typeof t === "string" && t.length > 0).join("\n\n").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\[(\d+)\]/g, "");
|
|
548
|
+
}
|
|
549
|
+
var turnDomId = (spec, turnId) => `chat-${spec.id}-${turnId}`;
|
|
550
|
+
function SessionHeader({ onClose, className }) {
|
|
551
|
+
const ctx = useChatSessionContext();
|
|
552
|
+
const prompts = ctx.spec.turns.filter((t) => t.role === "analyst").length;
|
|
553
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("flex h-[34px] shrink-0 items-center gap-2 border-b border-border px-3", className), children: [
|
|
554
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate font-medium", children: ctx.spec.title }),
|
|
555
|
+
prompts ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "shrink-0 text-muted-foreground tabular-nums", children: [
|
|
556
|
+
"\xB7 ",
|
|
557
|
+
prompts,
|
|
558
|
+
" ",
|
|
559
|
+
prompts === 1 ? "question" : "questions"
|
|
560
|
+
] }) : null,
|
|
561
|
+
onClose ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
562
|
+
"button",
|
|
563
|
+
{
|
|
564
|
+
type: "button",
|
|
565
|
+
onClick: onClose,
|
|
566
|
+
"aria-label": "Close the session",
|
|
567
|
+
title: "Close",
|
|
568
|
+
className: "ml-auto inline-flex size-[22px] shrink-0 items-center justify-center rounded-control text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
569
|
+
children: ctx.icons.close
|
|
570
|
+
}
|
|
571
|
+
) : null
|
|
572
|
+
] });
|
|
573
|
+
}
|
|
574
|
+
var SETTLED_RATEABLE = /* @__PURE__ */ new Set(["complete", "partial"]);
|
|
575
|
+
function AnswerActions({ turn }) {
|
|
576
|
+
const ctx = useChatSessionContext();
|
|
577
|
+
const running = ctx.spec.turns.some((t) => isAnswer(t) && runOutcome(t) === "live");
|
|
578
|
+
const rating = ctx.rating(turn.id) ?? turn.rating;
|
|
579
|
+
const text = plainText(turn);
|
|
580
|
+
const rate = (value) => {
|
|
581
|
+
const next = rating === value ? 0 : value;
|
|
582
|
+
ctx.rate(turn.id, next);
|
|
583
|
+
ctx.emit({ type: "rate", turn: turn.id, value: next });
|
|
584
|
+
};
|
|
585
|
+
const built = {
|
|
586
|
+
retry: () => [
|
|
587
|
+
{
|
|
588
|
+
icon: ctx.icons.retry,
|
|
589
|
+
label: "Run again",
|
|
590
|
+
disabled: running,
|
|
591
|
+
onClick: () => ctx.emit({ type: "retry", turn: turn.id })
|
|
592
|
+
}
|
|
593
|
+
],
|
|
594
|
+
copy: () => text ? [
|
|
595
|
+
{
|
|
596
|
+
icon: ctx.icons.copy,
|
|
597
|
+
label: "Copy",
|
|
598
|
+
onClick: () => {
|
|
599
|
+
void navigator.clipboard?.writeText(text);
|
|
600
|
+
ctx.emit({ type: "copy", turn: turn.id, text });
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
] : [],
|
|
604
|
+
steps: () => {
|
|
605
|
+
if (!turn.trace?.length) return [];
|
|
606
|
+
const open = ctx.stepsOpen(turn.id, false);
|
|
607
|
+
return [{ icon: ctx.icons.steps, label: "Steps", active: open, onClick: () => ctx.setSteps(turn.id, !open) }];
|
|
608
|
+
},
|
|
609
|
+
rate: () => SETTLED_RATEABLE.has(turn.state) ? [
|
|
610
|
+
{
|
|
611
|
+
icon: ctx.icons.rateUp,
|
|
612
|
+
label: "Good answer",
|
|
613
|
+
active: rating === 1,
|
|
614
|
+
activeClassName: "text-success hover:text-success",
|
|
615
|
+
onClick: () => rate(1)
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
icon: ctx.icons.rateDown,
|
|
619
|
+
label: "Not what I wanted",
|
|
620
|
+
active: rating === -1,
|
|
621
|
+
activeClassName: "text-destructive hover:text-destructive",
|
|
622
|
+
onClick: () => rate(-1)
|
|
623
|
+
}
|
|
624
|
+
] : []
|
|
625
|
+
};
|
|
626
|
+
const actions = ctx.actions.flatMap(
|
|
627
|
+
(a) => typeof a === "string" ? built[a]() : !a.states || a.states.includes(turn.state) ? [{ icon: a.icon, label: a.label, onClick: () => ctx.emit({ type: "action", turn: turn.id, action: a.id }) }] : []
|
|
628
|
+
);
|
|
629
|
+
if (!actions.length) return null;
|
|
630
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
631
|
+
ChatSessionMessageOptions,
|
|
632
|
+
{
|
|
633
|
+
actions: actions.map((a) => ({
|
|
634
|
+
...a,
|
|
635
|
+
align: "end",
|
|
636
|
+
className: ui.cn(
|
|
637
|
+
"size-5 hover:bg-transparent [&_svg]:!size-[11px]",
|
|
638
|
+
!a.active && "text-muted-foreground/60 hover:text-foreground"
|
|
639
|
+
)
|
|
640
|
+
}))
|
|
641
|
+
}
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
function AnswerLine({ turn, children }) {
|
|
645
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
646
|
+
children ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 shrink truncate", children }) : null,
|
|
647
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(AnswerActions, { turn }) })
|
|
648
|
+
] });
|
|
649
|
+
}
|
|
650
|
+
function KeyHints({ running }) {
|
|
651
|
+
return running ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: "esc stop" }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
652
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u21B5 send" }),
|
|
653
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u21E7\u21B5 newline" })
|
|
654
|
+
] });
|
|
655
|
+
}
|
|
656
|
+
function useStopKey(running, stop) {
|
|
657
|
+
React6__namespace.useEffect(() => {
|
|
658
|
+
if (!running) return;
|
|
659
|
+
const onKey = (e) => {
|
|
660
|
+
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
661
|
+
e.preventDefault();
|
|
662
|
+
stop();
|
|
663
|
+
};
|
|
664
|
+
document.addEventListener("keydown", onKey);
|
|
665
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
666
|
+
}, [running, stop]);
|
|
667
|
+
}
|
|
668
|
+
var ClarifyCard = ui.ClarifyCard;
|
|
669
|
+
var ClarifyFootnote = ui.ClarifyFootnote;
|
|
670
|
+
var ClarifyActions = ui.ClarifyActions;
|
|
671
|
+
|
|
672
|
+
// src/conversations/relative-time.ts
|
|
673
|
+
function relativeTime(iso, now) {
|
|
674
|
+
const then = Date.parse(iso);
|
|
675
|
+
if (Number.isNaN(then)) return void 0;
|
|
676
|
+
const minutes = Math.floor((now - then) / 6e4);
|
|
677
|
+
if (minutes < 1) return "just now";
|
|
678
|
+
if (minutes < 60) return `${minutes} min ago`;
|
|
679
|
+
const hours = Math.floor(minutes / 60);
|
|
680
|
+
if (hours < 24) return `${hours} h ago`;
|
|
681
|
+
return new Date(then).toLocaleDateString("en-GB", { day: "numeric", month: "short" });
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/grammar/stages.ts
|
|
685
|
+
var STAGES = [
|
|
686
|
+
{ id: "frame", name: "Frame" },
|
|
687
|
+
{ id: "scope", name: "Scope" },
|
|
688
|
+
{ id: "check", name: "Check data" },
|
|
689
|
+
{ id: "analyse", name: "Analyse" },
|
|
690
|
+
{ id: "explain", name: "Explain" },
|
|
691
|
+
{ id: "act", name: "Act" },
|
|
692
|
+
{ id: "monitor", name: "Monitor" }
|
|
693
|
+
];
|
|
694
|
+
|
|
695
|
+
// src/grammar/patterns.ts
|
|
696
|
+
var PATTERNS = [
|
|
697
|
+
{ id: "snapshot", name: "KPI snapshot", blocks: ["grid", "timeseries", "narrative", "scope"] },
|
|
698
|
+
{ id: "bridge", name: "Change explanation", blocks: ["narrative", "waterfall", "ranked", "citations"] },
|
|
699
|
+
{ id: "comparison", name: "Comparison", blocks: ["bars", "table", "caveat"] },
|
|
700
|
+
{ id: "ranking", name: "Ranked list", blocks: ["ranked", "table"] },
|
|
701
|
+
{ id: "profile", name: "Segment profile", blocks: ["table", "bars", "narrative"] },
|
|
702
|
+
{ id: "cohort", name: "Cohort matrix", blocks: ["matrix", "narrative"] },
|
|
703
|
+
{ id: "forecast", name: "Forecast", blocks: ["timeseries", "record", "caveat"] },
|
|
704
|
+
{ id: "anomaly", name: "Anomaly report", blocks: ["timeseries", "timeline", "ranked"] },
|
|
705
|
+
{ id: "drivers", name: "Driver ranking", blocks: ["ranked", "narrative", "caveat"] },
|
|
706
|
+
{ id: "readout", name: "Experiment readout", blocks: ["metric", "record", "caveat", "proposal"] },
|
|
707
|
+
{ id: "scenariot", name: "Scenario table", blocks: ["record", "table", "ranked"] },
|
|
708
|
+
{ id: "funnelv", name: "Funnel", blocks: ["funnel", "table"] },
|
|
709
|
+
{ id: "spread", name: "Distribution", blocks: ["histogram", "table"] },
|
|
710
|
+
{ id: "quality", name: "Quality report", blocks: ["checks", "table", "files"] },
|
|
711
|
+
{ id: "e360", name: "Entity 360", blocks: ["record", "timeline", "table"] },
|
|
712
|
+
{ id: "relmap", name: "Relationship map", blocks: ["subgraph", "ranked"] },
|
|
713
|
+
{ id: "memo", name: "Findings memo", blocks: ["narrative", "citations", "caveat"] },
|
|
714
|
+
{ id: "recommend", name: "Recommendation", blocks: ["table", "ranked", "proposal"] },
|
|
715
|
+
{ id: "delivery", name: "Scheduled delivery", blocks: ["files", "proposal", "record"] },
|
|
716
|
+
{ id: "shortlist", name: "Candidate shortlist", blocks: ["attr", "histogram", "caveat", "proposal"] },
|
|
717
|
+
{ id: "significance", name: "Significance test", blocks: ["test", "box", "evidence", "caveat"] },
|
|
718
|
+
{ id: "effects", name: "Effect estimates", blocks: ["coef", "forest", "method", "caveat"] },
|
|
719
|
+
{ id: "association", name: "Association", blocks: ["correlation", "scatter", "caveat"] },
|
|
720
|
+
{ id: "spc", name: "Control report", blocks: ["control", "pareto", "decomposition"] },
|
|
721
|
+
{ id: "timeto", name: "Time to event", blocks: ["survival", "quantiles", "dumbbell", "caveat"] },
|
|
722
|
+
{ id: "modelcheck", name: "Model check", blocks: ["modeleval", "profile", "pivot", "tornado"] }
|
|
723
|
+
];
|
|
724
|
+
|
|
725
|
+
// src/grammar/asks.ts
|
|
726
|
+
var ASK_INTENTS = [
|
|
727
|
+
{ id: "clarify", name: "Clarify what was meant", stage: "frame", kind: "single" },
|
|
728
|
+
{ id: "measure", name: "Choose the measure", stage: "frame", kind: "single" },
|
|
729
|
+
{ id: "definition", name: "Confirm a definition", stage: "frame", kind: "confirm" },
|
|
730
|
+
{ id: "hunch", name: "Share a hunch", stage: "frame", kind: "long" },
|
|
731
|
+
{ id: "window", name: "Time window", stage: "scope", kind: "period" },
|
|
732
|
+
{ id: "baseline", name: "Compare against", stage: "scope", kind: "single" },
|
|
733
|
+
{ id: "filter", name: "Filter and segment", stage: "scope", kind: "multi" },
|
|
734
|
+
{ id: "granularity", name: "Granularity", stage: "scope", kind: "quick" },
|
|
735
|
+
{ id: "entities", name: "Pick entities", stage: "scope", kind: "entity" },
|
|
736
|
+
{ id: "threshold", name: "Set a threshold", stage: "scope", kind: "number" },
|
|
737
|
+
{ id: "ambiguity", name: "Resolve ambiguity", stage: "check", kind: "single" },
|
|
738
|
+
{ id: "dataissue", name: "Handle a data issue", stage: "check", kind: "single" },
|
|
739
|
+
{ id: "cost", name: "Approve an expensive run", stage: "check", kind: "confirm" },
|
|
740
|
+
{ id: "method", name: "Choose a method", stage: "analyse", kind: "single" },
|
|
741
|
+
{ id: "assumptions", name: "Confirm assumptions", stage: "analyse", kind: "multi" },
|
|
742
|
+
{ id: "confidence", name: "Confidence level", stage: "analyse", kind: "quick" },
|
|
743
|
+
{ id: "objectives", name: "Weigh objectives", stage: "analyse", kind: "weights" },
|
|
744
|
+
{ id: "scenario", name: "Scenario inputs", stage: "analyse", kind: "form" },
|
|
745
|
+
{ id: "format", name: "Choose the output", stage: "explain", kind: "quick" },
|
|
746
|
+
{ id: "rate", name: "Rate the answer", stage: "explain", kind: "scale" },
|
|
747
|
+
{ id: "next", name: "Suggest what's next", stage: "explain", kind: "suggestions" },
|
|
748
|
+
{ id: "approve", name: "Approve an action", stage: "act", kind: "approval" },
|
|
749
|
+
{ id: "schedule", name: "Schedule and share", stage: "monitor", kind: "multistep" },
|
|
750
|
+
{ id: "reading", name: "Check my reading", stage: "frame", kind: "interpretation" },
|
|
751
|
+
{ id: "fork", name: "Pick a reading", stage: "frame", kind: "fork" },
|
|
752
|
+
{ id: "range", name: "Set a range", stage: "scope", kind: "range" },
|
|
753
|
+
{ id: "model", name: "Specify the model", stage: "analyse", kind: "modelspec" },
|
|
754
|
+
{ id: "plan", name: "Review the plan", stage: "analyse", kind: "plan" },
|
|
755
|
+
{ id: "hypothesis", name: "State the hypothesis", stage: "analyse", kind: "hypothesis" }
|
|
756
|
+
];
|
|
757
|
+
|
|
758
|
+
// src/grammar/answers.ts
|
|
759
|
+
var ANSWER_INTENTS = [
|
|
760
|
+
{ id: "say", name: "Say it in words", kinds: ["narrative"] },
|
|
761
|
+
{ id: "figure", name: "One number, or a few", kinds: ["metric", "grid"] },
|
|
762
|
+
{ id: "trend", name: "Change over time", kinds: ["timeseries", "control"] },
|
|
763
|
+
{ id: "compare", name: "Groups side by side", kinds: ["bars", "dumbbell", "matrix", "funnel"] },
|
|
764
|
+
{ id: "rank", name: "Leaders and laggards", kinds: ["ranked", "pareto", "tornado"] },
|
|
765
|
+
{ id: "explain-change", name: "What moved the total", kinds: ["waterfall", "decomposition"] },
|
|
766
|
+
{ id: "records", name: "Rows, or one entity", kinds: ["table", "record", "attr", "pivot", "profile"] },
|
|
767
|
+
{ id: "sequence", name: "What happened when", kinds: ["timeline", "heatstrip"] },
|
|
768
|
+
{ id: "spread", name: "The shape of the values", kinds: ["histogram", "box", "quantiles", "survival"] },
|
|
769
|
+
{ id: "relate", name: "How things connect", kinds: ["scatter", "correlation", "subgraph"] },
|
|
770
|
+
{ id: "stats", name: "Test and model results", kinds: ["test", "coef", "forest", "evidence", "modeleval"] },
|
|
771
|
+
{ id: "trust", name: "Method, sources, scope and limits", kinds: ["method", "citations", "scope", "caveat", "cannot", "checks"] },
|
|
772
|
+
{ id: "act", name: "A proposed action, or a file", kinds: ["proposal", "files"] },
|
|
773
|
+
{ id: "run.steps", name: "What the run did, in order", kinds: ["trace", "gantt"] },
|
|
774
|
+
{ id: "run.activity", name: "Which layers were busy", kinds: ["activity"] }
|
|
775
|
+
];
|
|
776
|
+
|
|
777
|
+
// src/grammar/flows.ts
|
|
778
|
+
var FLOWS = [
|
|
779
|
+
{ id: "kpi", name: "KPI check", template: "How is {measure} tracking against {baseline}?", stages: ["frame", "scope", "analyse", "explain"], asks: ["measure", "window", "baseline"], pattern: "snapshot" },
|
|
780
|
+
{ id: "diagnose", name: "Root cause", template: "Why did {measure} change in {period}?", stages: ["frame", "scope", "check", "analyse", "explain", "act"], asks: ["measure", "window", "baseline", "hunch"], pattern: "bridge" },
|
|
781
|
+
{ id: "compare", name: "Comparison & benchmark", template: "How does {A} compare with {B} on {measures}?", stages: ["frame", "scope", "analyse", "explain"], asks: ["entities", "measure", "confidence"], pattern: "comparison" },
|
|
782
|
+
{ id: "rank", name: "Top-N ranking", template: "Which {entities} lead or lag on {measure}?", stages: ["frame", "scope", "analyse", "explain", "act"], asks: ["measure", "filter", "threshold"], pattern: "ranking" },
|
|
783
|
+
{ id: "segment", name: "Segmentation", template: "What groups exist within {population}, and how do they differ?", stages: ["frame", "scope", "analyse", "explain"], asks: ["filter", "method", "assumptions"], pattern: "profile" },
|
|
784
|
+
{ id: "cohort", name: "Cohort & retention", template: "How do {cohorts} behave over time?", stages: ["frame", "scope", "analyse", "explain"], asks: ["definition", "window", "granularity"], pattern: "cohort" },
|
|
785
|
+
{ id: "forecast", name: "Trend & forecast", template: "Where is {measure} heading?", stages: ["frame", "scope", "analyse", "explain", "monitor"], asks: ["window", "granularity", "method", "assumptions"], pattern: "forecast" },
|
|
786
|
+
{ id: "anomaly", name: "Anomaly & monitoring", template: "Tell me when {measure} behaves unusually.", stages: ["scope", "analyse", "act", "monitor"], asks: ["measure", "threshold", "approve", "schedule"], pattern: "anomaly" },
|
|
787
|
+
{ id: "drivers", name: "Driver analysis", template: "What moves {measure}?", stages: ["frame", "analyse", "explain"], asks: ["measure", "method", "assumptions"], pattern: "drivers" },
|
|
788
|
+
{ id: "experiment", name: "Experiment readout", template: "Did {change} work?", stages: ["frame", "check", "analyse", "explain", "act"], asks: ["entities", "definition", "confidence"], pattern: "readout" },
|
|
789
|
+
{ id: "scenario", name: "Scenario & what-if", template: "What happens to {outcome} if {input} changes?", stages: ["frame", "scope", "analyse", "explain", "act"], asks: ["scenario", "assumptions"], pattern: "scenariot" },
|
|
790
|
+
{ id: "funnel", name: "Funnel & journey", template: "Where do {entities} drop out of {process}?", stages: ["frame", "scope", "analyse", "explain"], asks: ["definition", "window", "filter"], pattern: "funnelv" },
|
|
791
|
+
{ id: "distribution", name: "Distribution & outliers", template: "What does the spread of {measure} look like, and what sits outside it?", stages: ["scope", "analyse", "explain"], asks: ["measure", "filter", "threshold"], pattern: "spread" },
|
|
792
|
+
{ id: "audit", name: "Data audit & reconciliation", template: "Can I trust {dataset}? Do {A} and {B} agree?", stages: ["check", "analyse", "explain", "act"], asks: ["definition", "dataissue", "cost"], pattern: "quality" },
|
|
793
|
+
{ id: "entity", name: "Entity lookup (360)", template: "Tell me everything about {entity}.", stages: ["frame", "check", "explain"], asks: ["ambiguity", "entities"], pattern: "e360" },
|
|
794
|
+
{ id: "network", name: "Relationships & networks", template: "How are {entities} connected?", stages: ["frame", "scope", "check", "analyse", "explain"], asks: ["entities", "threshold", "cost"], pattern: "relmap" },
|
|
795
|
+
{ id: "qual", name: "Qualitative synthesis", template: "What are people saying about {topic}?", stages: ["frame", "scope", "analyse", "explain"], asks: ["filter", "window", "hunch"], pattern: "memo" },
|
|
796
|
+
{ id: "recommend", name: "Recommendation & priority", template: "What should we do about {problem}, and in what order?", stages: ["analyse", "explain", "act"], asks: ["assumptions", "method", "approve"], pattern: "recommend" },
|
|
797
|
+
{ id: "report", name: "Report & schedule", template: "Send me {analysis} every {cadence}.", stages: ["explain", "act", "monitor"], asks: ["format", "schedule", "approve"], pattern: "delivery" },
|
|
798
|
+
{ id: "combine", name: "Combination design", template: "Which combination of {candidates} best meets {objectives}?", stages: ["frame", "scope", "analyse", "explain", "act"], asks: ["entities", "objectives", "threshold", "assumptions"], pattern: "shortlist" },
|
|
799
|
+
{ id: "significance", name: "Significance test", template: "Is the difference in {measure} between {A} and {B} real?", stages: ["frame", "scope", "analyse", "explain"], asks: ["hypothesis", "confidence", "assumptions"], pattern: "significance" },
|
|
800
|
+
{ id: "effects", name: "Effect estimation", template: "How much does each {factor} change {outcome}, holding the others fixed?", stages: ["frame", "scope", "analyse", "explain"], asks: ["model", "plan", "assumptions"], pattern: "effects" },
|
|
801
|
+
{ id: "association", name: "Correlation & association", template: "Which {measures} move together, and how strongly?", stages: ["frame", "scope", "check", "analyse", "explain"], asks: ["reading", "filter", "range"], pattern: "association" },
|
|
802
|
+
{ id: "spc", name: "Process control", template: "Is {process} in control, and what causes most of the defects?", stages: ["scope", "analyse", "explain", "monitor"], asks: ["window", "granularity", "range"], pattern: "spc" },
|
|
803
|
+
{ id: "timeto", name: "Time to event", template: "How long until {event}, and does it differ by {group}?", stages: ["frame", "scope", "analyse", "explain"], asks: ["definition", "fork", "window"], pattern: "timeto" },
|
|
804
|
+
{ id: "modelcheck", name: "Model check", template: "How well does {model} predict {outcome}, and where does it fail?", stages: ["check", "analyse", "explain", "act"], asks: ["model", "range", "plan"], pattern: "modelcheck" }
|
|
805
|
+
];
|
|
806
|
+
var STAGE_NAME = new Map(STAGES.map((s) => [s.id, s.name]));
|
|
807
|
+
var stageName = (stage) => (STAGE_NAME.get(stage) ?? stage).toLowerCase();
|
|
808
|
+
function AskView({ turn, registry, onEvent, now }) {
|
|
809
|
+
const Renderer = registry.asks[turn.ask.kind];
|
|
810
|
+
const traits = registry.askTraits(turn.ask.kind);
|
|
811
|
+
if (Renderer && traits.frame === "none") {
|
|
812
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { turn, options: turn.ask, onEvent });
|
|
813
|
+
}
|
|
814
|
+
const time = turn.state === "answered" && turn.answeredAt ? relativeTime(turn.answeredAt, now) : void 0;
|
|
815
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
816
|
+
ClarifyCard,
|
|
817
|
+
{
|
|
818
|
+
state: turn.state,
|
|
819
|
+
kind: traits.kind,
|
|
820
|
+
step: stageName(turn.stage),
|
|
821
|
+
waiting: turn.state === "pending" ? turn.waiting : void 0,
|
|
822
|
+
time,
|
|
823
|
+
children: Renderer ? /* @__PURE__ */ jsxRuntime.jsx(Renderer, { turn, options: turn.ask, onEvent }) : /* @__PURE__ */ jsxRuntime.jsx(blocks.Placeholder, { kind: turn.ask.kind, options: turn.ask })
|
|
824
|
+
}
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
var LINES = [[0.6], [0.75], [0.5]];
|
|
828
|
+
var CHART = [[1, 110], [0.55, 7]];
|
|
829
|
+
var ROWS = [[0.92], [0.74], [0.55], [0.38], [0.4, 7]];
|
|
830
|
+
var SHAPE = {
|
|
831
|
+
metric: [[0.45], [0.3, 22], [0.55]],
|
|
832
|
+
grid: [[1, 56]],
|
|
833
|
+
record: [[0.6], [0.75], [0.5], [0.68]],
|
|
834
|
+
table: [[1, 7], [1], [1], [1]],
|
|
835
|
+
attr: [[1, 7], [1], [1], [1]],
|
|
836
|
+
pivot: [[1, 7], [1], [1], [1]],
|
|
837
|
+
coef: [[1, 7], [1], [1], [1]],
|
|
838
|
+
files: [[0.7], [0.55], [0.62]],
|
|
839
|
+
ranked: ROWS,
|
|
840
|
+
timeline: ROWS,
|
|
841
|
+
funnel: ROWS,
|
|
842
|
+
pareto: ROWS,
|
|
843
|
+
tornado: ROWS,
|
|
844
|
+
forest: ROWS,
|
|
845
|
+
dumbbell: ROWS,
|
|
846
|
+
timeseries: CHART,
|
|
847
|
+
bars: CHART,
|
|
848
|
+
waterfall: CHART,
|
|
849
|
+
histogram: CHART,
|
|
850
|
+
matrix: CHART,
|
|
851
|
+
correlation: CHART,
|
|
852
|
+
scatter: CHART,
|
|
853
|
+
box: CHART,
|
|
854
|
+
control: CHART,
|
|
855
|
+
survival: CHART,
|
|
856
|
+
decomposition: CHART,
|
|
857
|
+
modeleval: CHART,
|
|
858
|
+
quantiles: CHART,
|
|
859
|
+
subgraph: CHART
|
|
860
|
+
};
|
|
861
|
+
function BlockSkeleton({ kind }) {
|
|
862
|
+
const bars = SHAPE[kind] ?? LINES;
|
|
863
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-label": "Loading", className: "flex flex-col gap-1.5 py-0.5", children: bars.map(([width, height], i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
864
|
+
ui.Skeleton,
|
|
865
|
+
{
|
|
866
|
+
style: { width: `${width * 100}%`, height: height ?? 9 }
|
|
867
|
+
},
|
|
868
|
+
i
|
|
869
|
+
)) });
|
|
870
|
+
}
|
|
871
|
+
function BlockEmpty({
|
|
872
|
+
text,
|
|
873
|
+
suggestions,
|
|
874
|
+
onSuggest
|
|
875
|
+
}) {
|
|
876
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
877
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 rounded-md border border-dashed border-border px-2 py-3.5 text-center text-sm text-muted-foreground", children: text }),
|
|
878
|
+
suggestions?.length ? /* @__PURE__ */ jsxRuntime.jsx(blocks.SuggestionChips, { items: suggestions, onSelect: onSuggest }) : null
|
|
879
|
+
] });
|
|
880
|
+
}
|
|
881
|
+
function BlockCaption({ text, tone }) {
|
|
882
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
883
|
+
"p",
|
|
884
|
+
{
|
|
885
|
+
className: ui.cn(
|
|
886
|
+
"m-0 text-xs",
|
|
887
|
+
tone === "warn" ? "text-warning" : tone === "bad" ? "text-destructive" : "text-muted-foreground"
|
|
888
|
+
),
|
|
889
|
+
children: text
|
|
890
|
+
}
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
function BlockView({ block, turn, registry, onEvent }) {
|
|
894
|
+
const Renderer = registry.blocks[block.kind];
|
|
895
|
+
const body = block.status === "loading" ? /* @__PURE__ */ jsxRuntime.jsx(BlockSkeleton, { kind: block.kind }) : block.status === "empty" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
896
|
+
BlockEmpty,
|
|
897
|
+
{
|
|
898
|
+
text: block.emptyText ?? "Nothing to show.",
|
|
899
|
+
suggestions: block.emptySuggestions,
|
|
900
|
+
onSuggest: (text) => onEvent({ type: "prompt", text })
|
|
901
|
+
}
|
|
902
|
+
) : !Renderer ? /* @__PURE__ */ jsxRuntime.jsx(blocks.Placeholder, { kind: block.kind, options: block }) : /* @__PURE__ */ jsxRuntime.jsx(Renderer, { block, turn, onEvent });
|
|
903
|
+
if (!block.caption || block.status) return body;
|
|
904
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-1.5", children: [
|
|
905
|
+
body,
|
|
906
|
+
/* @__PURE__ */ jsxRuntime.jsx(BlockCaption, { text: block.caption, tone: block.captionTone })
|
|
907
|
+
] });
|
|
908
|
+
}
|
|
909
|
+
function envelopeBlocks(turn) {
|
|
910
|
+
const env = turn.envelope;
|
|
911
|
+
if (!env) return { top: [], bottom: [] };
|
|
912
|
+
const parts = [...env.scope ?? [], ...env.freshness ? [`as of ${env.freshness}`] : []];
|
|
913
|
+
const top = parts.length ? [{ kind: "scope", parts }] : [];
|
|
914
|
+
const bottom = [
|
|
915
|
+
...env.method ? [{ kind: "method", label: "method", code: env.method }] : [],
|
|
916
|
+
...(env.caveats ?? []).map((c) => ({ kind: "caveat", label: c.label, text: c.text }))
|
|
917
|
+
];
|
|
918
|
+
return { top, bottom };
|
|
919
|
+
}
|
|
920
|
+
function splitBlocks(turn, registry) {
|
|
921
|
+
const { top, bottom } = envelopeBlocks(turn);
|
|
922
|
+
const own = turn.blocks.filter((b) => registry.blockTraits(b.kind).placement === "own");
|
|
923
|
+
const inCard = turn.blocks.filter((b) => registry.blockTraits(b.kind).placement !== "own");
|
|
924
|
+
return { inCard: [...top, ...inCard, ...bottom], own, hasEvidence: inCard.length > 0 };
|
|
925
|
+
}
|
|
926
|
+
function citationOf(turn) {
|
|
927
|
+
const g = turn.envelope?.grounding;
|
|
928
|
+
return g ? `cite \xB7 ${g.records.toLocaleString("en-GB")}${g.noun ? ` ${g.noun}` : ""}` : void 0;
|
|
929
|
+
}
|
|
930
|
+
function OutcomeView({
|
|
931
|
+
outcome,
|
|
932
|
+
turn,
|
|
933
|
+
onEvent,
|
|
934
|
+
tone,
|
|
935
|
+
className
|
|
936
|
+
}) {
|
|
937
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
938
|
+
"div",
|
|
939
|
+
{
|
|
940
|
+
className: ui.cn(
|
|
941
|
+
"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1.5 rounded-md border border-border px-2 py-1.5",
|
|
942
|
+
tone === "bad" && "border-destructive/50",
|
|
943
|
+
className
|
|
944
|
+
),
|
|
945
|
+
children: [
|
|
946
|
+
outcome.text ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 text-muted-foreground", children: outcome.text }) : null,
|
|
947
|
+
outcome.actions?.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("min-w-0", outcome.text ? "shrink-0" : "flex-1"), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
948
|
+
blocks.ActionRow,
|
|
949
|
+
{
|
|
950
|
+
actions: outcome.actions,
|
|
951
|
+
onAction: (action) => onEvent({ type: "action", turn: turn.id, action })
|
|
952
|
+
}
|
|
953
|
+
) }) : null
|
|
954
|
+
]
|
|
955
|
+
}
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
function BlockStack({ children, className }) {
|
|
959
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("flex min-w-0 flex-col gap-3", className), children });
|
|
960
|
+
}
|
|
961
|
+
function PathIcon({ path, className }) {
|
|
962
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
963
|
+
"svg",
|
|
964
|
+
{
|
|
965
|
+
viewBox: "0 0 16 16",
|
|
966
|
+
fill: "none",
|
|
967
|
+
stroke: "currentColor",
|
|
968
|
+
strokeWidth: 1.3,
|
|
969
|
+
strokeLinecap: "round",
|
|
970
|
+
strokeLinejoin: "round",
|
|
971
|
+
"aria-hidden": true,
|
|
972
|
+
className: ui.cn("size-3.5 shrink-0", className),
|
|
973
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: path })
|
|
974
|
+
}
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
function ComposerSelect({ control, value, onValueChange, grow }) {
|
|
978
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(forms.Select, { value, onValueChange, children: [
|
|
979
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
980
|
+
forms.SelectTrigger,
|
|
981
|
+
{
|
|
982
|
+
"aria-label": control.label,
|
|
983
|
+
title: control.label,
|
|
984
|
+
className: ui.cn(
|
|
985
|
+
"h-control-sm gap-1 border-0 bg-transparent px-2 shadow-none hover:bg-accent",
|
|
986
|
+
grow ? "w-full min-w-0" : "w-auto shrink-0",
|
|
987
|
+
control.quiet && "text-muted-foreground"
|
|
988
|
+
),
|
|
989
|
+
children: [
|
|
990
|
+
control.icon ? /* @__PURE__ */ jsxRuntime.jsx(PathIcon, { path: control.icon }) : null,
|
|
991
|
+
/* @__PURE__ */ jsxRuntime.jsx(forms.SelectValue, {})
|
|
992
|
+
]
|
|
993
|
+
}
|
|
994
|
+
),
|
|
995
|
+
/* @__PURE__ */ jsxRuntime.jsx(forms.SelectContent, { children: control.options.map((o) => /* @__PURE__ */ jsxRuntime.jsx(forms.SelectItem, { value: o.value, children: o.label }, o.value)) })
|
|
996
|
+
] });
|
|
997
|
+
}
|
|
998
|
+
function AttachmentChip({ name, onRemove }) {
|
|
999
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex max-w-40 items-center gap-1 rounded-control border border-border bg-muted/40 px-2 py-1 text-sm", children: [
|
|
1000
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: name }),
|
|
1001
|
+
onRemove ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1002
|
+
"button",
|
|
1003
|
+
{
|
|
1004
|
+
type: "button",
|
|
1005
|
+
onClick: onRemove,
|
|
1006
|
+
"aria-label": `Remove ${name}`,
|
|
1007
|
+
className: "shrink-0 rounded-sm px-0.5 text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
1008
|
+
children: "\xD7"
|
|
1009
|
+
}
|
|
1010
|
+
) : null
|
|
1011
|
+
] });
|
|
1012
|
+
}
|
|
1013
|
+
function initialSettings(composer) {
|
|
1014
|
+
const out = {};
|
|
1015
|
+
for (const c of composer?.controls ?? []) out[c.id] = c.default ?? c.options[0]?.value ?? "";
|
|
1016
|
+
return out;
|
|
1017
|
+
}
|
|
1018
|
+
function ComposerBar({ isRunning: isRunning2, onStop }) {
|
|
1019
|
+
const { spec, emit, icons, setComposer } = useChatSessionContext();
|
|
1020
|
+
const composer = spec.composer;
|
|
1021
|
+
const controls = composer?.controls ?? [];
|
|
1022
|
+
const [draft, setDraft] = React6__namespace.useState("");
|
|
1023
|
+
const [files, setFiles] = React6__namespace.useState([]);
|
|
1024
|
+
const [picks, setPicks] = React6__namespace.useState({});
|
|
1025
|
+
const settings = initialSettings(composer);
|
|
1026
|
+
for (const c of controls) {
|
|
1027
|
+
const pick = picks[c.id];
|
|
1028
|
+
if (pick !== void 0 && c.options.some((o) => o.value === pick)) settings[c.id] = pick;
|
|
1029
|
+
}
|
|
1030
|
+
const fileInput = React6__namespace.useRef(null);
|
|
1031
|
+
const picked = controls.map((c) => c.options.find((o) => o.value === settings[c.id])).filter(Boolean);
|
|
1032
|
+
const placeholder = picked.find((o) => o?.placeholder)?.placeholder ?? composer?.placeholder;
|
|
1033
|
+
const mono = picked.some((o) => o?.mono);
|
|
1034
|
+
const setting = (id, value) => {
|
|
1035
|
+
setPicks((prev) => ({ ...prev, [id]: value }));
|
|
1036
|
+
emit({ type: "setting", id, value });
|
|
1037
|
+
};
|
|
1038
|
+
const send = () => {
|
|
1039
|
+
const text = draft.trim();
|
|
1040
|
+
if (!text && !files.length) return;
|
|
1041
|
+
emit({
|
|
1042
|
+
type: "prompt",
|
|
1043
|
+
text,
|
|
1044
|
+
...controls.length ? { settings } : {},
|
|
1045
|
+
...files.length ? { files } : {}
|
|
1046
|
+
});
|
|
1047
|
+
setDraft("");
|
|
1048
|
+
setFiles([]);
|
|
1049
|
+
};
|
|
1050
|
+
const attach = composer?.attach;
|
|
1051
|
+
const attachOptions = typeof attach === "object" ? attach : {};
|
|
1052
|
+
const select = (c, grow) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1053
|
+
ComposerSelect,
|
|
1054
|
+
{
|
|
1055
|
+
control: c,
|
|
1056
|
+
value: settings[c.id] ?? "",
|
|
1057
|
+
onValueChange: (v) => setting(c.id, v),
|
|
1058
|
+
grow
|
|
1059
|
+
},
|
|
1060
|
+
c.id
|
|
1061
|
+
);
|
|
1062
|
+
const start = controls.filter((c) => (c.align ?? "start") === "start");
|
|
1063
|
+
const end = controls.filter((c) => c.align === "end");
|
|
1064
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: setComposer, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1065
|
+
ChatSessionComposer,
|
|
1066
|
+
{
|
|
1067
|
+
value: draft,
|
|
1068
|
+
onChange: setDraft,
|
|
1069
|
+
onSend: send,
|
|
1070
|
+
onStop,
|
|
1071
|
+
isRunning: isRunning2,
|
|
1072
|
+
placeholder,
|
|
1073
|
+
textareaClassName: ui.cn("h-[72px] min-h-14", mono && "font-mono"),
|
|
1074
|
+
sendIcon: icons.send,
|
|
1075
|
+
stopIcon: icons.stop,
|
|
1076
|
+
sendDisabled: isRunning2 || !draft.trim() && !files.length,
|
|
1077
|
+
attachments: files.length ? files.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1078
|
+
AttachmentChip,
|
|
1079
|
+
{
|
|
1080
|
+
name: f.name,
|
|
1081
|
+
onRemove: () => setFiles((prev) => prev.filter((_, j) => j !== i))
|
|
1082
|
+
},
|
|
1083
|
+
`${f.name}-${i}`
|
|
1084
|
+
)) : void 0,
|
|
1085
|
+
toolbarStart: start.length ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: start.map((c, i) => select(c, i === start.length - 1)) }) : void 0,
|
|
1086
|
+
toolbarEnd: end.length || attach ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1087
|
+
end.map((c) => select(c)),
|
|
1088
|
+
attach ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1089
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1090
|
+
"input",
|
|
1091
|
+
{
|
|
1092
|
+
ref: fileInput,
|
|
1093
|
+
type: "file",
|
|
1094
|
+
hidden: true,
|
|
1095
|
+
accept: attachOptions.accept,
|
|
1096
|
+
multiple: attachOptions.multiple ?? true,
|
|
1097
|
+
onChange: (e) => {
|
|
1098
|
+
const list = e.target.files;
|
|
1099
|
+
if (list?.length) setFiles((prev) => [...prev, ...Array.from(list)]);
|
|
1100
|
+
e.target.value = "";
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
),
|
|
1104
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1105
|
+
"button",
|
|
1106
|
+
{
|
|
1107
|
+
type: "button",
|
|
1108
|
+
onClick: () => fileInput.current?.click(),
|
|
1109
|
+
title: "Attach files",
|
|
1110
|
+
"aria-label": "Attach files",
|
|
1111
|
+
className: "inline-flex size-7 shrink-0 items-center justify-center rounded-control text-muted-foreground hover:bg-accent",
|
|
1112
|
+
children: icons.attach
|
|
1113
|
+
}
|
|
1114
|
+
)
|
|
1115
|
+
] }) : null
|
|
1116
|
+
] }) : void 0
|
|
1117
|
+
}
|
|
1118
|
+
) });
|
|
1119
|
+
}
|
|
1120
|
+
var ROW_STATUS = {
|
|
1121
|
+
pending: "queued",
|
|
1122
|
+
running: "running",
|
|
1123
|
+
done: "success",
|
|
1124
|
+
failed: "error",
|
|
1125
|
+
waiting: "needs-input",
|
|
1126
|
+
retrying: "needs-input",
|
|
1127
|
+
stopped: "needs-input"
|
|
1128
|
+
};
|
|
1129
|
+
var TONE = {
|
|
1130
|
+
failed: "text-destructive",
|
|
1131
|
+
waiting: "text-warning",
|
|
1132
|
+
retrying: "text-warning",
|
|
1133
|
+
stopped: "text-warning"
|
|
1134
|
+
};
|
|
1135
|
+
function stepDescription(step) {
|
|
1136
|
+
const detail = step.state === "failed" ? step.error ?? step.detail : step.detail;
|
|
1137
|
+
switch (step.state) {
|
|
1138
|
+
case "retrying":
|
|
1139
|
+
return [`retrying ${step.attempt ?? 2}${step.attempts ? `/${step.attempts}` : ""}`, detail].filter(Boolean).join(" \xB7 ");
|
|
1140
|
+
case "waiting":
|
|
1141
|
+
return ["needs input", detail].filter(Boolean).join(" \xB7 ");
|
|
1142
|
+
case "stopped":
|
|
1143
|
+
return "stopped";
|
|
1144
|
+
default:
|
|
1145
|
+
return detail;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
function IoRows({ label, rows }) {
|
|
1149
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [
|
|
1150
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Eyebrow, { children: label }),
|
|
1151
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.PropertyList, { labelWidth: "auto", variant: "summary", children: rows.map((row, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyRow, { label: row.label, mono: row.code, children: row.code ? /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "m-0 whitespace-pre-wrap break-words", children: row.value }) : row.value }, `${row.label}-${i}`)) })
|
|
1152
|
+
] });
|
|
1153
|
+
}
|
|
1154
|
+
function StepRecord({ step }) {
|
|
1155
|
+
const input = step.io?.input ?? [];
|
|
1156
|
+
const output = step.io?.output ?? [];
|
|
1157
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [
|
|
1158
|
+
input.length ? /* @__PURE__ */ jsxRuntime.jsx(IoRows, { label: "Input", rows: input }) : null,
|
|
1159
|
+
output.length ? /* @__PURE__ */ jsxRuntime.jsx(IoRows, { label: "Output", rows: output }) : null,
|
|
1160
|
+
step.error ? /* @__PURE__ */ jsxRuntime.jsx(IoRows, { label: "Error", rows: [{ label: "cause", value: step.error }] }) : null
|
|
1161
|
+
] });
|
|
1162
|
+
}
|
|
1163
|
+
var hasRecord = (step) => !!(step.io?.input?.length || step.io?.output?.length || step.error);
|
|
1164
|
+
function StepRow({ turn, step, detail = true, onSelect, suffix }) {
|
|
1165
|
+
const ctx = useChatSessionContext();
|
|
1166
|
+
const id = step.id ?? step.label;
|
|
1167
|
+
const time = stepTime(step, ctx.now);
|
|
1168
|
+
const description = [stepDescription(step), suffix].filter(Boolean).join(" \xB7 ");
|
|
1169
|
+
const open = detail && ctx.recordOpen(turn.id, id);
|
|
1170
|
+
const select = onSelect ?? (hasRecord(step) ? () => ctx.toggleRecord(turn.id, id) : ctx.opensRuns ? () => ctx.emit({ type: "open-run", turn: turn.id, step: step.id }) : void 0);
|
|
1171
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col", children: [
|
|
1172
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1173
|
+
ChatSessionTaskRow,
|
|
1174
|
+
{
|
|
1175
|
+
status: ROW_STATUS[step.state],
|
|
1176
|
+
name: step.label,
|
|
1177
|
+
description: description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: TONE[step.state], children: description }) : void 0,
|
|
1178
|
+
meta: time !== void 0 ? formatDuration(time) : void 0,
|
|
1179
|
+
onClick: select
|
|
1180
|
+
}
|
|
1181
|
+
),
|
|
1182
|
+
detail && step.thinking && (step.state === "running" || step.state === "retrying") ? /* @__PURE__ */ jsxRuntime.jsx(ChatSessionActivitySubLine, { className: "pl-[22px] italic", children: step.thinking }) : null,
|
|
1183
|
+
open && hasRecord(step) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1184
|
+
ChatSessionDisclosure,
|
|
1185
|
+
{
|
|
1186
|
+
className: "my-0.5 ml-[22px]",
|
|
1187
|
+
label: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono", children: step.key ?? step.label }),
|
|
1188
|
+
meta: [
|
|
1189
|
+
step.attempt ? `attempt ${step.attempt}` : void 0,
|
|
1190
|
+
step.duration !== void 0 ? formatDuration(step.duration) : void 0
|
|
1191
|
+
].filter(Boolean).join(" \xB7 "),
|
|
1192
|
+
open: true,
|
|
1193
|
+
onOpenChange: () => ctx.toggleRecord(turn.id, id, false),
|
|
1194
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(StepRecord, { step })
|
|
1195
|
+
}
|
|
1196
|
+
) : null
|
|
1197
|
+
] });
|
|
1198
|
+
}
|
|
1199
|
+
function StepList({ turn, className }) {
|
|
1200
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("flex min-w-0 flex-col gap-px", className), children: (turn.trace ?? []).map((step, i) => /* @__PURE__ */ jsxRuntime.jsx(StepRow, { turn, step }, step.id ?? i)) });
|
|
1201
|
+
}
|
|
1202
|
+
var VERB = { done: "Thought for", failed: "Failed after", stopped: "Stopped after" };
|
|
1203
|
+
function RunSummaryLine({ turn }) {
|
|
1204
|
+
const ctx = useChatSessionContext();
|
|
1205
|
+
const outcome = runOutcome(turn);
|
|
1206
|
+
const verb = VERB[outcome === "failed" || outcome === "stopped" ? outcome : "done"];
|
|
1207
|
+
const time = answerTime(turn, ctx.now);
|
|
1208
|
+
const { done, total } = stepCount(turn);
|
|
1209
|
+
const open = ctx.stepsOpen(turn.id, false);
|
|
1210
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2 text-muted-foreground", children: [
|
|
1211
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "select-none text-border", "aria-hidden": true, children: "\u273B" }),
|
|
1212
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate", children: [
|
|
1213
|
+
verb,
|
|
1214
|
+
" ",
|
|
1215
|
+
time !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1216
|
+
"button",
|
|
1217
|
+
{
|
|
1218
|
+
type: "button",
|
|
1219
|
+
className: "tabular-nums underline-offset-2 hover:text-foreground hover:underline",
|
|
1220
|
+
onClick: () => ctx.opensRuns ? ctx.emit({ type: "open-run", turn: turn.id }) : ctx.setSteps(turn.id, !open),
|
|
1221
|
+
children: formatDuration(time)
|
|
1222
|
+
}
|
|
1223
|
+
) : "\u2014",
|
|
1224
|
+
total ? ` \xB7 ${done} of ${total} steps` : null
|
|
1225
|
+
] }),
|
|
1226
|
+
total ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1227
|
+
"button",
|
|
1228
|
+
{
|
|
1229
|
+
type: "button",
|
|
1230
|
+
"aria-expanded": open,
|
|
1231
|
+
"aria-label": open ? "Fold the steps" : "Show the steps",
|
|
1232
|
+
className: "hover:text-foreground",
|
|
1233
|
+
onClick: () => ctx.setSteps(turn.id, !open),
|
|
1234
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: ui.cn("inline-block transition-transform", open && "rotate-90"), children: "\u25B8" })
|
|
1235
|
+
}
|
|
1236
|
+
) : null
|
|
1237
|
+
] });
|
|
1238
|
+
}
|
|
1239
|
+
var ANSWER_DOT = {
|
|
1240
|
+
live: "pending",
|
|
1241
|
+
waiting: "info",
|
|
1242
|
+
done: "success",
|
|
1243
|
+
failed: "error",
|
|
1244
|
+
stopped: "warning"
|
|
1245
|
+
};
|
|
1246
|
+
var ASK_DOT = {
|
|
1247
|
+
pending: "info",
|
|
1248
|
+
answered: "success",
|
|
1249
|
+
skipped: "default",
|
|
1250
|
+
superseded: "default",
|
|
1251
|
+
expired: "default"
|
|
1252
|
+
};
|
|
1253
|
+
function useJump() {
|
|
1254
|
+
const ctx = useChatSessionContext();
|
|
1255
|
+
return (turnId) => {
|
|
1256
|
+
ctx.setView("chat");
|
|
1257
|
+
ctx.toggleSteps(turnId, true);
|
|
1258
|
+
ctx.scrollToTurn(turnId);
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
function CliAnswer({ turn }) {
|
|
1262
|
+
const ctx = useChatSessionContext();
|
|
1263
|
+
const outcome = runOutcome(turn);
|
|
1264
|
+
const live = outcome === "live" || outcome === "waiting";
|
|
1265
|
+
const { inCard, own, hasEvidence } = splitBlocks(turn, ctx.registry);
|
|
1266
|
+
const blocks = [...inCard, ...own];
|
|
1267
|
+
const step = currentStep(turn);
|
|
1268
|
+
const time = answerTime(turn, ctx.now);
|
|
1269
|
+
const showSteps = !!turn.trace?.length && (live || ctx.stepsOpen(turn.id, false));
|
|
1270
|
+
const body = live && !hasEvidence ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1271
|
+
ChatSessionProgressLine,
|
|
1272
|
+
{
|
|
1273
|
+
className: "-ml-[22px]",
|
|
1274
|
+
elapsed: time !== void 0 ? formatDuration(time) : void 0,
|
|
1275
|
+
spinner: outcome === "waiting" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1.5 rounded-full bg-info", "aria-hidden": true }) : void 0,
|
|
1276
|
+
children: outcome === "waiting" ? "Waiting on your answer" : step ? `${step.label}\u2026` : "Planning\u2026"
|
|
1277
|
+
}
|
|
1278
|
+
) : blocks.length ? /* @__PURE__ */ jsxRuntime.jsx(BlockStack, { className: "whitespace-normal", children: blocks.map((b, i) => /* @__PURE__ */ jsxRuntime.jsx(BlockView, { block: b, turn, registry: ctx.registry, onEvent: ctx.emit }, `${b.kind}-${i}`)) }) : outcome === "failed" ? "The run failed." : outcome === "stopped" ? "Stopped by you." : null;
|
|
1279
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1280
|
+
ChatSessionActivityRow,
|
|
1281
|
+
{
|
|
1282
|
+
status: ANSWER_DOT[outcome],
|
|
1283
|
+
meta: !live && turn.meta ? turn.meta : void 0,
|
|
1284
|
+
footer: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1285
|
+
live && turn.trace?.length ? /* @__PURE__ */ jsxRuntime.jsx(StepList, { turn, className: "pt-0.5" }) : null,
|
|
1286
|
+
!live ? (
|
|
1287
|
+
// The run in one line, and what can be done with the answer at its right.
|
|
1288
|
+
/* @__PURE__ */ jsxRuntime.jsx(AnswerLine, { turn, children: turn.trace?.length ? /* @__PURE__ */ jsxRuntime.jsx(RunSummaryLine, { turn }) : null })
|
|
1289
|
+
) : null,
|
|
1290
|
+
!live && showSteps ? /* @__PURE__ */ jsxRuntime.jsx(StepList, { turn, className: "pt-0.5" }) : null,
|
|
1291
|
+
turn.outcome && !live ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1292
|
+
OutcomeView,
|
|
1293
|
+
{
|
|
1294
|
+
className: "mt-0.5",
|
|
1295
|
+
outcome: turn.outcome,
|
|
1296
|
+
turn,
|
|
1297
|
+
onEvent: ctx.emit,
|
|
1298
|
+
tone: outcome === "failed" ? "bad" : void 0
|
|
1299
|
+
}
|
|
1300
|
+
) : null,
|
|
1301
|
+
outcome === "stopped" ? /* @__PURE__ */ jsxRuntime.jsx(ChatSessionActivitySubLine, { children: "Interrupted \xB7 ask again, or narrow the question" }) : null
|
|
1302
|
+
] }),
|
|
1303
|
+
children: body
|
|
1304
|
+
}
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
function CliAsk({ turn }) {
|
|
1308
|
+
const ctx = useChatSessionContext();
|
|
1309
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ChatSessionActivityRow, { status: ASK_DOT[turn.state] ?? "default", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "whitespace-normal", children: /* @__PURE__ */ jsxRuntime.jsx(AskView, { turn, registry: ctx.registry, onEvent: ctx.emit, now: ctx.now }) }) });
|
|
1310
|
+
}
|
|
1311
|
+
function CliExchange({ exchange }) {
|
|
1312
|
+
const ctx = useChatSessionContext();
|
|
1313
|
+
const prompt = exchange.prompt;
|
|
1314
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "contents", children: [
|
|
1315
|
+
prompt ? /* @__PURE__ */ jsxRuntime.jsx("div", { id: turnDomId(ctx.spec, prompt.id), className: "scroll-mt-3", children: /* @__PURE__ */ jsxRuntime.jsx(ChatSessionPromptRow, { meta: prompt.at ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: clockTime(prompt.at) }) : void 0, children: prompt.text }) }) : null,
|
|
1316
|
+
exchange.replies.map((reply) => /* @__PURE__ */ jsxRuntime.jsx("div", { id: turnDomId(ctx.spec, reply.id), className: "min-w-0 scroll-mt-3", children: isAnswer(reply) ? /* @__PURE__ */ jsxRuntime.jsx(CliAnswer, { turn: reply }) : /* @__PURE__ */ jsxRuntime.jsx(CliAsk, { turn: reply }) }, reply.id))
|
|
1317
|
+
] });
|
|
1318
|
+
}
|
|
1319
|
+
var WEIGHT = { running: 0, waiting: 1, settled: 2 };
|
|
1320
|
+
function CliTasks() {
|
|
1321
|
+
const ctx = useChatSessionContext();
|
|
1322
|
+
const jump = useJump();
|
|
1323
|
+
const blocks = (ask) => ctx.registry.askTraits(ask.ask.kind).frame === "card";
|
|
1324
|
+
const groups = exchangesOf(ctx.spec).filter((e) => e.replies.some((r) => isAnswer(r) && r.trace?.length)).reverse().sort((a, b) => WEIGHT[exchangeState(a, blocks)] - WEIGHT[exchangeState(b, blocks)]);
|
|
1325
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: groups.map((exchange) => {
|
|
1326
|
+
const state = exchangeState(exchange, blocks);
|
|
1327
|
+
const answers = exchange.replies.filter(isAnswer);
|
|
1328
|
+
const total = answers.reduce((sum, a) => sum + (answerTime(a, ctx.now) ?? 0), 0);
|
|
1329
|
+
const when = clockTime(exchange.prompt?.at);
|
|
1330
|
+
const said = state === "running" ? "running" : state === "waiting" ? "waiting on you" : formatDuration(total);
|
|
1331
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1332
|
+
ChatSessionTaskGroup,
|
|
1333
|
+
{
|
|
1334
|
+
heading: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between gap-2", children: [
|
|
1335
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", title: exchange.prompt?.text, children: exchange.prompt?.text ?? ctx.spec.title }),
|
|
1336
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 font-normal text-muted-foreground tabular-nums", children: [when, said].filter(Boolean).join(" \xB7 ") })
|
|
1337
|
+
] }),
|
|
1338
|
+
children: answers.flatMap(
|
|
1339
|
+
(a) => (a.trace ?? []).map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(StepRow, { turn: a, step: s, detail: false, onSelect: () => jump(a.id) }, `${a.id}-${s.id ?? i}`))
|
|
1340
|
+
)
|
|
1341
|
+
},
|
|
1342
|
+
exchange.id
|
|
1343
|
+
);
|
|
1344
|
+
}) });
|
|
1345
|
+
}
|
|
1346
|
+
function PinnedSteps() {
|
|
1347
|
+
const ctx = useChatSessionContext();
|
|
1348
|
+
const jump = useJump();
|
|
1349
|
+
const live = ctx.spec.turns.filter(
|
|
1350
|
+
(t) => isAnswer(t) && (runOutcome(t) === "live" || runOutcome(t) === "waiting")
|
|
1351
|
+
);
|
|
1352
|
+
if (!live.length) return null;
|
|
1353
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-border px-3 py-1.5", children: live.map((a) => {
|
|
1354
|
+
const step = currentStep(a) ?? a.trace?.[0];
|
|
1355
|
+
return step ? /* @__PURE__ */ jsxRuntime.jsx(StepRow, { turn: a, step, detail: false, onSelect: () => jump(a.id) }, a.id) : null;
|
|
1356
|
+
}) });
|
|
1357
|
+
}
|
|
1358
|
+
function CliSession({ running, onStop, header, onClose, emptyState, className }) {
|
|
1359
|
+
const ctx = useChatSessionContext();
|
|
1360
|
+
const counts = threadCounts(ctx.spec, (ask) => ctx.registry.askTraits(ask.ask.kind).frame === "card");
|
|
1361
|
+
const tasks = ctx.view === "tasks";
|
|
1362
|
+
const viewButton = (target, label) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1363
|
+
"button",
|
|
1364
|
+
{
|
|
1365
|
+
type: "button",
|
|
1366
|
+
"aria-pressed": ctx.view === target,
|
|
1367
|
+
onClick: () => ctx.setView(target),
|
|
1368
|
+
className: ctx.view === target ? "font-medium text-foreground" : "hover:text-foreground",
|
|
1369
|
+
children: label
|
|
1370
|
+
}
|
|
1371
|
+
);
|
|
1372
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("flex h-full min-h-0 flex-col bg-background", className), children: [
|
|
1373
|
+
header === true ? /* @__PURE__ */ jsxRuntime.jsx(SessionHeader, { onClose }) : header || null,
|
|
1374
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1375
|
+
ChatSessionFrame,
|
|
1376
|
+
{
|
|
1377
|
+
className: "min-h-0 flex-1",
|
|
1378
|
+
autoScrollKey: `${ctx.view}-${ctx.spec.turns.length}`,
|
|
1379
|
+
emptyState,
|
|
1380
|
+
bodyClassName: tasks ? "gap-[18px]" : void 0,
|
|
1381
|
+
footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-border", children: [
|
|
1382
|
+
!tasks ? /* @__PURE__ */ jsxRuntime.jsx(PinnedSteps, {}) : null,
|
|
1383
|
+
/* @__PURE__ */ jsxRuntime.jsx(ComposerBar, { isRunning: running, onStop }),
|
|
1384
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1385
|
+
ChatSessionStatusBar,
|
|
1386
|
+
{
|
|
1387
|
+
className: "pt-0",
|
|
1388
|
+
start: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1389
|
+
viewButton("chat", "Chat"),
|
|
1390
|
+
viewButton("tasks", `Tasks (${counts.steps})`),
|
|
1391
|
+
counts.running ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-primary", children: [
|
|
1392
|
+
counts.running,
|
|
1393
|
+
" running"
|
|
1394
|
+
] }) : counts.waiting ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-warning", children: [
|
|
1395
|
+
counts.waiting,
|
|
1396
|
+
" needs input"
|
|
1397
|
+
] }) : null
|
|
1398
|
+
] }),
|
|
1399
|
+
end: ctx.spec.composer?.hints === false ? void 0 : /* @__PURE__ */ jsxRuntime.jsx(KeyHints, { running })
|
|
1400
|
+
}
|
|
1401
|
+
)
|
|
1402
|
+
] }),
|
|
1403
|
+
children: tasks ? /* @__PURE__ */ jsxRuntime.jsx(CliTasks, {}) : exchangesOf(ctx.spec).map((e) => /* @__PURE__ */ jsxRuntime.jsx(CliExchange, { exchange: e }, e.id))
|
|
1404
|
+
}
|
|
1405
|
+
)
|
|
1406
|
+
] });
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// src/styles/types.ts
|
|
1410
|
+
var DEFAULT_ANSWER_ACTIONS = ["retry", "copy", "steps", "rate"];
|
|
1411
|
+
var TurnLabel = React6__namespace.forwardRef(
|
|
1412
|
+
({ align = "start", className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1413
|
+
"div",
|
|
1414
|
+
{
|
|
1415
|
+
ref,
|
|
1416
|
+
className: ui.cn(
|
|
1417
|
+
"font-mono text-xs tracking-wider text-muted-foreground uppercase",
|
|
1418
|
+
align === "end" && "text-right",
|
|
1419
|
+
className
|
|
1420
|
+
),
|
|
1421
|
+
...props,
|
|
1422
|
+
children
|
|
1423
|
+
}
|
|
1424
|
+
)
|
|
1425
|
+
);
|
|
1426
|
+
TurnLabel.displayName = "TurnLabel";
|
|
1427
|
+
var RUN_WORDS = { done: "Answered in", failed: "Failed after", stopped: "Stopped after" };
|
|
1428
|
+
function RunLine({ turn }) {
|
|
1429
|
+
const ctx = useChatSessionContext();
|
|
1430
|
+
const outcome = runOutcome(turn);
|
|
1431
|
+
const time = answerTime(turn, ctx.now);
|
|
1432
|
+
const at = clockTime(turn.at);
|
|
1433
|
+
const words = RUN_WORDS[outcome === "failed" || outcome === "stopped" ? outcome : "done"];
|
|
1434
|
+
const open = ctx.stepsOpen(turn.id, false);
|
|
1435
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-1.5 text-sm text-muted-foreground", children: [
|
|
1436
|
+
at ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: at }) : null,
|
|
1437
|
+
at && time !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, children: "\xB7" }) : null,
|
|
1438
|
+
time !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1439
|
+
"button",
|
|
1440
|
+
{
|
|
1441
|
+
type: "button",
|
|
1442
|
+
"aria-haspopup": ctx.opensRuns ? "dialog" : void 0,
|
|
1443
|
+
"aria-expanded": ctx.opensRuns ? void 0 : open,
|
|
1444
|
+
className: "underline decoration-dotted underline-offset-2 hover:text-foreground",
|
|
1445
|
+
onClick: () => ctx.opensRuns ? ctx.emit({ type: "open-run", turn: turn.id }) : ctx.setSteps(turn.id, !open),
|
|
1446
|
+
children: [
|
|
1447
|
+
words,
|
|
1448
|
+
" ",
|
|
1449
|
+
formatDuration(time, { spaced: true })
|
|
1450
|
+
]
|
|
1451
|
+
}
|
|
1452
|
+
) : null
|
|
1453
|
+
] });
|
|
1454
|
+
}
|
|
1455
|
+
function WebAnswer({ turn, chrome = true }) {
|
|
1456
|
+
const ctx = useChatSessionContext();
|
|
1457
|
+
const outcome = runOutcome(turn);
|
|
1458
|
+
const live = outcome === "live" || outcome === "waiting";
|
|
1459
|
+
const { inCard, own, hasEvidence } = splitBlocks(turn, ctx.registry);
|
|
1460
|
+
const block = (b, i) => /* @__PURE__ */ jsxRuntime.jsx(BlockView, { block: b, turn, registry: ctx.registry, onEvent: ctx.emit }, `${b.kind}-${i}`);
|
|
1461
|
+
const steps = !!turn.trace?.length && (live || ctx.stepsOpen(turn.id, outcome === "failed"));
|
|
1462
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [
|
|
1463
|
+
live && !turn.trace?.length && !hasEvidence ? /* @__PURE__ */ jsxRuntime.jsx(ChatSessionMessage, { role: "assistant", status: "running", children: "Working" }) : null,
|
|
1464
|
+
steps ? /* @__PURE__ */ jsxRuntime.jsx(EmissionCard, { kind: live ? outcome === "waiting" ? "waiting" : "running" : "steps", children: /* @__PURE__ */ jsxRuntime.jsx(EmissionBody, { children: /* @__PURE__ */ jsxRuntime.jsx(StepList, { turn }) }) }) : null,
|
|
1465
|
+
hasEvidence ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1466
|
+
EmissionCard,
|
|
1467
|
+
{
|
|
1468
|
+
kind: turn.label ?? turn.pattern ?? "answer",
|
|
1469
|
+
title: turn.title,
|
|
1470
|
+
citation: citationOf(turn) ?? turn.aside ?? (live && !inCard.some((b) => b.status === "loading") ? "writing\u2026" : void 0),
|
|
1471
|
+
note: turn.state === "partial" ? turn.state : void 0,
|
|
1472
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(EmissionBody, { children: inCard.map(block) })
|
|
1473
|
+
}
|
|
1474
|
+
) : null,
|
|
1475
|
+
own.map(block),
|
|
1476
|
+
!hasEvidence && !own.length && (outcome === "failed" || outcome === "stopped") ? /* @__PURE__ */ jsxRuntime.jsx(ChatSessionMessage, { role: "assistant", status: outcome === "failed" ? "error" : "stopped", children: outcome === "failed" ? "The run failed." : "Stopped by you." }) : null,
|
|
1477
|
+
turn.outcome && !live ? /* @__PURE__ */ jsxRuntime.jsx(OutcomeView, { outcome: turn.outcome, turn, onEvent: ctx.emit, tone: outcome === "failed" ? "bad" : void 0 }) : null,
|
|
1478
|
+
chrome && !live ? /* @__PURE__ */ jsxRuntime.jsx(AnswerLine, { turn, children: /* @__PURE__ */ jsxRuntime.jsx(RunLine, { turn }) }) : null
|
|
1479
|
+
] });
|
|
1480
|
+
}
|
|
1481
|
+
function WebExchange({ exchange, dayBreak }) {
|
|
1482
|
+
const ctx = useChatSessionContext();
|
|
1483
|
+
const you = ctx.spec.analyst ?? "You";
|
|
1484
|
+
const assistant = ctx.spec.assistant ?? "Assistant";
|
|
1485
|
+
const prompt = exchange.prompt;
|
|
1486
|
+
const sentAt = clockTime(prompt?.at);
|
|
1487
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1488
|
+
dayBreak ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", role: "separator", children: [
|
|
1489
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-px flex-1 bg-border" }),
|
|
1490
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: dayBreak }),
|
|
1491
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-px flex-1 bg-border" })
|
|
1492
|
+
] }) : null,
|
|
1493
|
+
prompt ? /* @__PURE__ */ jsxRuntime.jsxs("div", { id: turnDomId(ctx.spec, prompt.id), className: "flex scroll-mt-3 flex-col gap-1", children: [
|
|
1494
|
+
/* @__PURE__ */ jsxRuntime.jsx(TurnLabel, { align: "end", children: you }),
|
|
1495
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChatSessionMessage, { role: "user", children: prompt.text }),
|
|
1496
|
+
sentAt ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "self-end text-sm text-muted-foreground tabular-nums", children: sentAt }) : null
|
|
1497
|
+
] }) : null,
|
|
1498
|
+
exchange.replies.length ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [
|
|
1499
|
+
/* @__PURE__ */ jsxRuntime.jsx(TurnLabel, { children: assistant }),
|
|
1500
|
+
exchange.replies.map((reply) => /* @__PURE__ */ jsxRuntime.jsx("div", { id: turnDomId(ctx.spec, reply.id), className: "min-w-0 scroll-mt-3", children: isAnswer(reply) ? /* @__PURE__ */ jsxRuntime.jsx(WebAnswer, { turn: reply }) : /* @__PURE__ */ jsxRuntime.jsx(AskView, { turn: reply, registry: ctx.registry, onEvent: ctx.emit, now: ctx.now }) }, reply.id))
|
|
1501
|
+
] }) : null
|
|
1502
|
+
] });
|
|
1503
|
+
}
|
|
1504
|
+
function dayBreaks(exchanges, now) {
|
|
1505
|
+
let last;
|
|
1506
|
+
return exchanges.map((exchange) => {
|
|
1507
|
+
const at = exchange.prompt?.at;
|
|
1508
|
+
if (!at) return void 0;
|
|
1509
|
+
const label = last === void 0 || !sameDay(last, at) ? dayLabel(at, now) : void 0;
|
|
1510
|
+
last = at;
|
|
1511
|
+
return label;
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
function WebSession({ running, onStop, header, onClose, emptyState, className }) {
|
|
1515
|
+
const ctx = useChatSessionContext();
|
|
1516
|
+
const exchanges = exchangesOf(ctx.spec);
|
|
1517
|
+
const breaks = dayBreaks(exchanges, ctx.now);
|
|
1518
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("flex h-full min-h-0 flex-col bg-background", className), children: [
|
|
1519
|
+
header === true ? /* @__PURE__ */ jsxRuntime.jsx(SessionHeader, { onClose }) : header || null,
|
|
1520
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1521
|
+
ChatSessionFrame,
|
|
1522
|
+
{
|
|
1523
|
+
className: "min-h-0 flex-1",
|
|
1524
|
+
autoScrollKey: ctx.spec.turns.length,
|
|
1525
|
+
emptyState,
|
|
1526
|
+
bodyClassName: "mx-auto w-full max-w-[52rem] gap-5",
|
|
1527
|
+
footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-[52rem]", children: [
|
|
1528
|
+
/* @__PURE__ */ jsxRuntime.jsx(ComposerBar, { isRunning: running, onStop }),
|
|
1529
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1530
|
+
ChatSessionStatusBar,
|
|
1531
|
+
{
|
|
1532
|
+
className: "pt-0",
|
|
1533
|
+
start: ctx.spec.assistant ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: ctx.spec.assistant }) : void 0,
|
|
1534
|
+
end: ctx.spec.composer?.hints === false ? void 0 : /* @__PURE__ */ jsxRuntime.jsx(KeyHints, { running })
|
|
1535
|
+
}
|
|
1536
|
+
)
|
|
1537
|
+
] }),
|
|
1538
|
+
children: exchanges.map((exchange, i) => /* @__PURE__ */ jsxRuntime.jsx(WebExchange, { exchange, dayBreak: breaks[i] }, exchange.id))
|
|
1539
|
+
}
|
|
1540
|
+
)
|
|
1541
|
+
] });
|
|
1542
|
+
}
|
|
1543
|
+
var handlerName = (type) => `on${type.replace(/(^|-)([a-z])/g, (_, __, c) => c.toUpperCase())}`;
|
|
1544
|
+
var ChatSession = React6__namespace.forwardRef(function ChatSession2(props, ref) {
|
|
1545
|
+
const {
|
|
1546
|
+
spec,
|
|
1547
|
+
variant = "web",
|
|
1548
|
+
stream,
|
|
1549
|
+
onStreamEnd,
|
|
1550
|
+
onStreamError,
|
|
1551
|
+
registry,
|
|
1552
|
+
icons,
|
|
1553
|
+
now: fixedNow,
|
|
1554
|
+
onClose,
|
|
1555
|
+
header,
|
|
1556
|
+
emptyState,
|
|
1557
|
+
view: controlledView,
|
|
1558
|
+
defaultView = "chat",
|
|
1559
|
+
actions = DEFAULT_ANSWER_ACTIONS,
|
|
1560
|
+
className
|
|
1561
|
+
} = props;
|
|
1562
|
+
const { live, stop: stopStream } = blocks.useStreamedSpec(spec, stream, applyPatches, {
|
|
1563
|
+
onEnd: onStreamEnd,
|
|
1564
|
+
onError: onStreamError,
|
|
1565
|
+
stopPatches
|
|
1566
|
+
});
|
|
1567
|
+
const running = live.turns.some((t) => isAnswer(t) && runOutcome(t) === "live");
|
|
1568
|
+
const now = useClock(fixedNow, running);
|
|
1569
|
+
const resolved = React6__namespace.useMemo(() => resolveRegistry(registry), [registry]);
|
|
1570
|
+
const resolvedIcons = React6__namespace.useMemo(() => ({ ...DEFAULT_ICONS, ...icons }), [icons]);
|
|
1571
|
+
const viewState = useViewState();
|
|
1572
|
+
const [uncontrolledView, setUncontrolledView] = React6__namespace.useState(defaultView);
|
|
1573
|
+
const view = controlledView ?? uncontrolledView;
|
|
1574
|
+
const root = React6__namespace.useRef(null);
|
|
1575
|
+
const composer = React6__namespace.useRef(null);
|
|
1576
|
+
const setComposer = React6__namespace.useCallback((element) => {
|
|
1577
|
+
composer.current = element;
|
|
1578
|
+
}, []);
|
|
1579
|
+
const latest = React6__namespace.useRef(props);
|
|
1580
|
+
const liveRef = React6__namespace.useRef(live);
|
|
1581
|
+
React6__namespace.useLayoutEffect(() => {
|
|
1582
|
+
latest.current = props;
|
|
1583
|
+
liveRef.current = live;
|
|
1584
|
+
});
|
|
1585
|
+
const emit = React6__namespace.useCallback((event) => {
|
|
1586
|
+
const handler = latest.current[handlerName(event.type)];
|
|
1587
|
+
if (typeof handler === "function") handler(event);
|
|
1588
|
+
latest.current.onEvent?.(event);
|
|
1589
|
+
}, []);
|
|
1590
|
+
const stop = React6__namespace.useCallback(() => {
|
|
1591
|
+
stopStream();
|
|
1592
|
+
emit({ type: "stop" });
|
|
1593
|
+
}, [stopStream, emit]);
|
|
1594
|
+
useStopKey(running, stop);
|
|
1595
|
+
const setView = React6__namespace.useCallback(
|
|
1596
|
+
(next) => {
|
|
1597
|
+
if (latest.current.view === void 0) setUncontrolledView(next);
|
|
1598
|
+
latest.current.onViewChange?.(next);
|
|
1599
|
+
},
|
|
1600
|
+
[]
|
|
1601
|
+
);
|
|
1602
|
+
const scrollToTurn = React6__namespace.useCallback((turnId) => {
|
|
1603
|
+
setTimeout(() => {
|
|
1604
|
+
const id = turnDomId(liveRef.current, turnId);
|
|
1605
|
+
root.current?.querySelector(`[id="${CSS.escape(id)}"]`)?.scrollIntoView({ block: "start", behavior: "smooth" });
|
|
1606
|
+
}, 80);
|
|
1607
|
+
}, []);
|
|
1608
|
+
const focusComposer = React6__namespace.useCallback(() => {
|
|
1609
|
+
composer.current?.querySelector("textarea")?.focus();
|
|
1610
|
+
}, []);
|
|
1611
|
+
React6__namespace.useImperativeHandle(
|
|
1612
|
+
ref,
|
|
1613
|
+
() => ({
|
|
1614
|
+
scrollToTurn,
|
|
1615
|
+
showSteps: (turnId, open = true) => viewState.toggleSteps(turnId, open),
|
|
1616
|
+
openStep: (turnId, stepId) => {
|
|
1617
|
+
viewState.toggleSteps(turnId, true);
|
|
1618
|
+
viewState.toggleRecord(turnId, stepId, true);
|
|
1619
|
+
scrollToTurn(turnId);
|
|
1620
|
+
},
|
|
1621
|
+
setView,
|
|
1622
|
+
focusComposer
|
|
1623
|
+
}),
|
|
1624
|
+
[scrollToTurn, setView, focusComposer, viewState]
|
|
1625
|
+
);
|
|
1626
|
+
const context = {
|
|
1627
|
+
spec: live,
|
|
1628
|
+
registry: resolved,
|
|
1629
|
+
emit,
|
|
1630
|
+
icons: resolvedIcons,
|
|
1631
|
+
now,
|
|
1632
|
+
opensRuns: typeof props.onOpenRun === "function",
|
|
1633
|
+
stepsOpen: viewState.stepsOpen,
|
|
1634
|
+
toggleSteps: viewState.toggleSteps,
|
|
1635
|
+
setSteps: (turnId, open) => {
|
|
1636
|
+
viewState.toggleSteps(turnId, open);
|
|
1637
|
+
emit({ type: "toggle-steps", turn: turnId, open });
|
|
1638
|
+
},
|
|
1639
|
+
actions,
|
|
1640
|
+
recordOpen: viewState.recordOpen,
|
|
1641
|
+
toggleRecord: viewState.toggleRecord,
|
|
1642
|
+
rating: viewState.rating,
|
|
1643
|
+
rate: viewState.setRating,
|
|
1644
|
+
view,
|
|
1645
|
+
setView,
|
|
1646
|
+
scrollToTurn,
|
|
1647
|
+
focusComposer,
|
|
1648
|
+
setComposer
|
|
1649
|
+
};
|
|
1650
|
+
const shared = {
|
|
1651
|
+
running,
|
|
1652
|
+
onStop: stop,
|
|
1653
|
+
header: header === void 0 ? !!live.title : header,
|
|
1654
|
+
onClose,
|
|
1655
|
+
emptyState,
|
|
1656
|
+
className
|
|
1657
|
+
};
|
|
1658
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ChatSessionContext.Provider, { value: context, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: root, "data-variant": variant, className: "contents", children: variant === "cli" ? /* @__PURE__ */ jsxRuntime.jsx(CliSession, { ...shared }) : /* @__PURE__ */ jsxRuntime.jsx(WebSession, { ...shared }) }) });
|
|
1659
|
+
});
|
|
1660
|
+
function useChatSession(initial) {
|
|
1661
|
+
const [spec, setSpec] = React6__namespace.useState(initial);
|
|
1662
|
+
const current = React6__namespace.useRef(spec);
|
|
1663
|
+
const controllers = React6__namespace.useRef(/* @__PURE__ */ new Set());
|
|
1664
|
+
const [streams, setStreams] = React6__namespace.useState(0);
|
|
1665
|
+
const set2 = React6__namespace.useCallback((next) => {
|
|
1666
|
+
current.current = next;
|
|
1667
|
+
setSpec(next);
|
|
1668
|
+
}, []);
|
|
1669
|
+
const apply = React6__namespace.useCallback(
|
|
1670
|
+
(patch) => set2(applyPatches(current.current, Array.isArray(patch) ? patch : [patch])),
|
|
1671
|
+
[set2]
|
|
1672
|
+
);
|
|
1673
|
+
const run = React6__namespace.useCallback(
|
|
1674
|
+
async (open) => {
|
|
1675
|
+
const controller = new AbortController();
|
|
1676
|
+
controllers.current.add(controller);
|
|
1677
|
+
setStreams((n) => n + 1);
|
|
1678
|
+
try {
|
|
1679
|
+
for await (const batch of patchesOf(open(controller.signal))) {
|
|
1680
|
+
if (controller.signal.aborted) break;
|
|
1681
|
+
set2(applyPatches(current.current, batch));
|
|
1682
|
+
}
|
|
1683
|
+
} finally {
|
|
1684
|
+
controllers.current.delete(controller);
|
|
1685
|
+
setStreams((n) => n - 1);
|
|
1686
|
+
}
|
|
1687
|
+
return current.current;
|
|
1688
|
+
},
|
|
1689
|
+
[set2]
|
|
1690
|
+
);
|
|
1691
|
+
const stream = React6__namespace.useCallback((source) => run(() => source), [run]);
|
|
1692
|
+
const play = React6__namespace.useCallback(
|
|
1693
|
+
(script, options) => run((signal) => playScript(script, { ...options, signal })),
|
|
1694
|
+
[run]
|
|
1695
|
+
);
|
|
1696
|
+
const abortAll = () => {
|
|
1697
|
+
for (const c of controllers.current) c.abort();
|
|
1698
|
+
controllers.current.clear();
|
|
1699
|
+
};
|
|
1700
|
+
const stop = React6__namespace.useCallback(() => {
|
|
1701
|
+
abortAll();
|
|
1702
|
+
set2(applyPatches(current.current, stopPatches(current.current)));
|
|
1703
|
+
}, [set2]);
|
|
1704
|
+
const reset = React6__namespace.useCallback(
|
|
1705
|
+
(next) => {
|
|
1706
|
+
abortAll();
|
|
1707
|
+
set2(next);
|
|
1708
|
+
},
|
|
1709
|
+
[set2]
|
|
1710
|
+
);
|
|
1711
|
+
React6__namespace.useEffect(() => () => abortAll(), []);
|
|
1712
|
+
return { spec, apply, stream, play, stop, reset, streaming: streams > 0, running: isRunning(spec) };
|
|
1713
|
+
}
|
|
1714
|
+
function ChatSessionTurn({ turn, onEvent, registry, now }) {
|
|
1715
|
+
const resolved = React6__namespace.useMemo(() => resolveRegistry(registry), [registry]);
|
|
1716
|
+
const view = useViewState();
|
|
1717
|
+
const [clock] = React6__namespace.useState(() => now ?? Date.now());
|
|
1718
|
+
const emit = React6__namespace.useCallback((event) => onEvent?.(event), [onEvent]);
|
|
1719
|
+
const context = {
|
|
1720
|
+
spec: { id: `turn-${turn.id}`, turns: [turn] },
|
|
1721
|
+
registry: resolved,
|
|
1722
|
+
emit,
|
|
1723
|
+
icons: DEFAULT_ICONS,
|
|
1724
|
+
now: now ?? clock,
|
|
1725
|
+
opensRuns: false,
|
|
1726
|
+
stepsOpen: view.stepsOpen,
|
|
1727
|
+
toggleSteps: view.toggleSteps,
|
|
1728
|
+
setSteps: (turnId, open) => {
|
|
1729
|
+
view.toggleSteps(turnId, open);
|
|
1730
|
+
emit({ type: "toggle-steps", turn: turnId, open });
|
|
1731
|
+
},
|
|
1732
|
+
actions: [],
|
|
1733
|
+
recordOpen: view.recordOpen,
|
|
1734
|
+
toggleRecord: view.toggleRecord,
|
|
1735
|
+
rating: view.rating,
|
|
1736
|
+
rate: view.setRating,
|
|
1737
|
+
view: "chat",
|
|
1738
|
+
setView: () => {
|
|
1739
|
+
},
|
|
1740
|
+
scrollToTurn: () => {
|
|
1741
|
+
},
|
|
1742
|
+
focusComposer: () => {
|
|
1743
|
+
},
|
|
1744
|
+
setComposer: () => {
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ChatSessionContext.Provider, { value: context, children: turn.role === "analyst" ? /* @__PURE__ */ jsxRuntime.jsx(ChatSessionMessage, { role: "user", children: turn.text }) : turn.kind === "ask" ? /* @__PURE__ */ jsxRuntime.jsx(AskView, { turn, registry: resolved, onEvent: emit, now: context.now }) : /* @__PURE__ */ jsxRuntime.jsx(WebAnswer, { turn, chrome: false }) });
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// src/protocol/events.ts
|
|
1751
|
+
var EVENT_TYPES = [
|
|
1752
|
+
"prompt",
|
|
1753
|
+
"reply",
|
|
1754
|
+
"skip",
|
|
1755
|
+
"change",
|
|
1756
|
+
"action",
|
|
1757
|
+
"scope",
|
|
1758
|
+
"open",
|
|
1759
|
+
"template",
|
|
1760
|
+
"refine",
|
|
1761
|
+
"rate",
|
|
1762
|
+
"stop",
|
|
1763
|
+
"retry",
|
|
1764
|
+
"open-run",
|
|
1765
|
+
"setting",
|
|
1766
|
+
"copy",
|
|
1767
|
+
"toggle-steps"
|
|
1768
|
+
];
|
|
1769
|
+
|
|
1770
|
+
// src/protocol/validate.ts
|
|
1771
|
+
var ASK_IDS = new Set(blocks.ASK_KINDS);
|
|
1772
|
+
var BLOCK_IDS = new Set(blocks.ANSWER_KINDS);
|
|
1773
|
+
var STAGE_IDS = new Set(STAGES.map((s) => s.id));
|
|
1774
|
+
var FLOW_IDS = new Set(FLOWS.map((f) => f.id));
|
|
1775
|
+
var PATTERN_BY = new Map(PATTERNS.map((p) => [p.id, p.blocks]));
|
|
1776
|
+
var ASK_STATES = /* @__PURE__ */ new Set(["pending", "answered", "skipped", "superseded", "expired"]);
|
|
1777
|
+
var ANSWER_STATES = /* @__PURE__ */ new Set(["running", "partial", "complete", "cannot", "error", "stopped"]);
|
|
1778
|
+
var STEP_STATES = /* @__PURE__ */ new Set(["done", "running", "pending", "failed", "waiting", "retrying", "stopped"]);
|
|
1779
|
+
var notIso = (value) => value !== void 0 && (typeof value !== "string" || Number.isNaN(Date.parse(value)));
|
|
1780
|
+
var CARRIED = {
|
|
1781
|
+
scope: (a) => !!a.envelope?.scope?.length,
|
|
1782
|
+
method: (a) => !!a.envelope?.method,
|
|
1783
|
+
caveat: (a) => !!a.envelope?.caveats?.length,
|
|
1784
|
+
trace: (a) => !!a.trace?.length
|
|
1785
|
+
};
|
|
1786
|
+
var ANYWHERE = /* @__PURE__ */ new Set(["narrative", "citations"]);
|
|
1787
|
+
function checkPattern(turn, issues) {
|
|
1788
|
+
if (!turn.pattern) return;
|
|
1789
|
+
const expected = PATTERN_BY.get(turn.pattern);
|
|
1790
|
+
if (!expected) return;
|
|
1791
|
+
const present = new Set(turn.blocks.map((b) => b.kind));
|
|
1792
|
+
for (const kind of expected) {
|
|
1793
|
+
const carried = CARRIED[kind];
|
|
1794
|
+
const has = carried ? carried(turn) : present.has(kind);
|
|
1795
|
+
if (!has) {
|
|
1796
|
+
issues.push({
|
|
1797
|
+
level: "warning",
|
|
1798
|
+
turn: turn.id,
|
|
1799
|
+
message: `Pattern "${turn.pattern}" expects "${kind}", which this answer does not have.`
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
for (const kind of present) {
|
|
1804
|
+
if (!expected.includes(kind) && !ANYWHERE.has(kind)) {
|
|
1805
|
+
issues.push({
|
|
1806
|
+
level: "warning",
|
|
1807
|
+
turn: turn.id,
|
|
1808
|
+
message: `Block "${kind}" is not part of pattern "${turn.pattern}".`
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
function validate(spec, options = {}) {
|
|
1814
|
+
const issues = [];
|
|
1815
|
+
const error = (message, turn) => issues.push({ level: "error", turn, message });
|
|
1816
|
+
const asks = /* @__PURE__ */ new Set([...ASK_IDS, ...options.extraAsks ?? []]);
|
|
1817
|
+
const blocks = /* @__PURE__ */ new Set([...BLOCK_IDS, ...options.extraBlocks ?? []]);
|
|
1818
|
+
if (typeof spec?.id !== "string" || !spec.id) error("The conversation has no id.");
|
|
1819
|
+
if (spec?.analyst !== void 0 && (typeof spec.analyst !== "string" || !spec.analyst)) {
|
|
1820
|
+
error("The analyst's name is not a non-empty string.");
|
|
1821
|
+
}
|
|
1822
|
+
const controls = spec?.composer?.controls ?? [];
|
|
1823
|
+
const controlIds = /* @__PURE__ */ new Set();
|
|
1824
|
+
for (const control of controls) {
|
|
1825
|
+
if (!control?.id) error("A composer control has no id.");
|
|
1826
|
+
else if (controlIds.has(control.id)) error(`Composer control id "${control.id}" is used twice.`);
|
|
1827
|
+
else controlIds.add(control.id);
|
|
1828
|
+
if (!control?.options?.length) error(`Composer control "${control?.id}" has no options.`);
|
|
1829
|
+
else if (control.default !== void 0 && !control.options.some((o) => o.value === control.default)) {
|
|
1830
|
+
error(`Composer control "${control.id}" defaults to "${control.default}", which is not one of its options.`);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
if (!Array.isArray(spec?.turns)) {
|
|
1834
|
+
error("The conversation has no turns array.");
|
|
1835
|
+
return issues;
|
|
1836
|
+
}
|
|
1837
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1838
|
+
for (const turn of spec.turns) {
|
|
1839
|
+
const id = typeof turn?.id === "string" ? turn.id : void 0;
|
|
1840
|
+
if (!id) {
|
|
1841
|
+
error("A turn has no id.");
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
if (seen.has(id)) error(`Turn id "${id}" is used twice.`, id);
|
|
1845
|
+
seen.add(id);
|
|
1846
|
+
if (turn.role === "analyst") {
|
|
1847
|
+
if (typeof turn.text !== "string" || !turn.text) error("An analyst turn has no text.", id);
|
|
1848
|
+
if (notIso(turn.at)) error(`at "${turn.at}" is not an ISO time.`, id);
|
|
1849
|
+
continue;
|
|
1850
|
+
}
|
|
1851
|
+
if (turn.role !== "assistant") {
|
|
1852
|
+
error(`Unknown role "${turn.role}".`, id);
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
if (turn.kind === "ask") {
|
|
1856
|
+
if (!STAGE_IDS.has(turn.stage)) error(`Unknown stage "${turn.stage}".`, id);
|
|
1857
|
+
if (!ASK_STATES.has(turn.state)) error(`Unknown ask state "${turn.state}".`, id);
|
|
1858
|
+
if (!asks.has(turn.ask?.kind)) error(`Unknown ask block "${turn.ask?.kind}".`, id);
|
|
1859
|
+
if (turn.answeredAt !== void 0 && Number.isNaN(Date.parse(turn.answeredAt))) {
|
|
1860
|
+
error(`answeredAt "${turn.answeredAt}" is not an ISO time.`, id);
|
|
1861
|
+
}
|
|
1862
|
+
if (turn.ask?.kind === "multistep") {
|
|
1863
|
+
for (const step of turn.ask.steps ?? []) {
|
|
1864
|
+
if (!asks.has(step.kind)) error(`Unknown ask block "${step.kind}" in step "${step.id}".`, id);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
if (turn.kind === "answer") {
|
|
1870
|
+
if (!ANSWER_STATES.has(turn.state)) error(`Unknown answer state "${turn.state}".`, id);
|
|
1871
|
+
if (turn.flow && !FLOW_IDS.has(turn.flow)) error(`Unknown flow "${turn.flow}".`, id);
|
|
1872
|
+
if (turn.pattern && !PATTERN_BY.has(turn.pattern)) error(`Unknown pattern "${turn.pattern}".`, id);
|
|
1873
|
+
if (notIso(turn.at)) error(`at "${turn.at}" is not an ISO time.`, id);
|
|
1874
|
+
if (notIso(turn.startedAt)) error(`startedAt "${turn.startedAt}" is not an ISO time.`, id);
|
|
1875
|
+
const stepIds = /* @__PURE__ */ new Set();
|
|
1876
|
+
for (const step of turn.trace ?? []) {
|
|
1877
|
+
if (!STEP_STATES.has(step?.state)) error(`Unknown step state "${step?.state}" on "${step?.label}".`, id);
|
|
1878
|
+
if (notIso(step?.startedAt)) error(`Step "${step.label}" startedAt "${step.startedAt}" is not an ISO time.`, id);
|
|
1879
|
+
if (step?.id) {
|
|
1880
|
+
if (stepIds.has(step.id)) error(`Step id "${step.id}" is used twice.`, id);
|
|
1881
|
+
stepIds.add(step.id);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
for (const action of turn.outcome?.actions ?? []) {
|
|
1885
|
+
if (!action?.id || !action.label) error("An outcome action has no id or label.", id);
|
|
1886
|
+
}
|
|
1887
|
+
if (!Array.isArray(turn.blocks)) {
|
|
1888
|
+
error("An answer has no blocks array.", id);
|
|
1889
|
+
continue;
|
|
1890
|
+
}
|
|
1891
|
+
for (const block of turn.blocks) {
|
|
1892
|
+
if (!blocks.has(block?.kind)) error(`Unknown block "${block?.kind}".`, id);
|
|
1893
|
+
else if (block.kind in CARRIED) {
|
|
1894
|
+
error(`"${block.kind}" is part of the answer, not a block: send it in the ${block.kind === "trace" ? `answer's "trace"` : "envelope"}.`, id);
|
|
1895
|
+
}
|
|
1896
|
+
if (block?.status !== void 0 && block.status !== "loading" && block.status !== "empty") {
|
|
1897
|
+
error(`Unknown block status "${block.status}".`, id);
|
|
1898
|
+
}
|
|
1899
|
+
if (block?.status === "empty" && !block.emptyText) {
|
|
1900
|
+
error(`An empty "${block.kind}" block does not say what is missing in emptyText.`, id);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
checkPattern(turn, issues);
|
|
1904
|
+
continue;
|
|
1905
|
+
}
|
|
1906
|
+
error(`Unknown assistant turn kind "${turn.kind}".`, id);
|
|
1907
|
+
}
|
|
1908
|
+
return issues;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
Object.defineProperty(exports, "ANSWER_KINDS", {
|
|
1912
|
+
enumerable: true,
|
|
1913
|
+
get: function () { return blocks.ANSWER_KINDS; }
|
|
1914
|
+
});
|
|
1915
|
+
Object.defineProperty(exports, "ASK_KINDS", {
|
|
1916
|
+
enumerable: true,
|
|
1917
|
+
get: function () { return blocks.ASK_KINDS; }
|
|
1918
|
+
});
|
|
1919
|
+
Object.defineProperty(exports, "BLOCKS", {
|
|
1920
|
+
enumerable: true,
|
|
1921
|
+
get: function () { return blocks.BLOCKS; }
|
|
1922
|
+
});
|
|
1923
|
+
Object.defineProperty(exports, "ConfirmCard", {
|
|
1924
|
+
enumerable: true,
|
|
1925
|
+
get: function () { return blocks.ConfirmCard; }
|
|
1926
|
+
});
|
|
1927
|
+
Object.defineProperty(exports, "Placeholder", {
|
|
1928
|
+
enumerable: true,
|
|
1929
|
+
get: function () { return blocks.Placeholder; }
|
|
1930
|
+
});
|
|
1931
|
+
Object.defineProperty(exports, "SuggestionChips", {
|
|
1932
|
+
enumerable: true,
|
|
1933
|
+
get: function () { return blocks.SuggestionChips; }
|
|
1934
|
+
});
|
|
1935
|
+
Object.defineProperty(exports, "scriptLength", {
|
|
1936
|
+
enumerable: true,
|
|
1937
|
+
get: function () { return blocks.scriptLength; }
|
|
1938
|
+
});
|
|
1939
|
+
exports.ANSWER_INTENTS = ANSWER_INTENTS;
|
|
1940
|
+
exports.ASK_INTENTS = ASK_INTENTS;
|
|
1941
|
+
exports.BUILT_IN_ASKS = BUILT_IN_ASKS;
|
|
1942
|
+
exports.BUILT_IN_ASK_TRAITS = BUILT_IN_ASK_TRAITS;
|
|
1943
|
+
exports.BUILT_IN_BLOCKS = BUILT_IN_BLOCKS;
|
|
1944
|
+
exports.BUILT_IN_BLOCK_TRAITS = BUILT_IN_BLOCK_TRAITS;
|
|
1945
|
+
exports.ChatSession = ChatSession;
|
|
1946
|
+
exports.ChatSessionActivityRow = ChatSessionActivityRow;
|
|
1947
|
+
exports.ChatSessionActivitySubLine = ChatSessionActivitySubLine;
|
|
1948
|
+
exports.ChatSessionCaret = ChatSessionCaret;
|
|
1949
|
+
exports.ChatSessionComposer = ChatSessionComposer;
|
|
1950
|
+
exports.ChatSessionContextChip = ChatSessionContextChip;
|
|
1951
|
+
exports.ChatSessionDisclosure = ChatSessionDisclosure;
|
|
1952
|
+
exports.ChatSessionDisclosureCode = ChatSessionDisclosureCode;
|
|
1953
|
+
exports.ChatSessionDisclosureSteps = ChatSessionDisclosureSteps;
|
|
1954
|
+
exports.ChatSessionFrame = ChatSessionFrame;
|
|
1955
|
+
exports.ChatSessionMessage = ChatSessionMessage;
|
|
1956
|
+
exports.ChatSessionMessageOptions = ChatSessionMessageOptions;
|
|
1957
|
+
exports.ChatSessionProgressLine = ChatSessionProgressLine;
|
|
1958
|
+
exports.ChatSessionPromptRow = ChatSessionPromptRow;
|
|
1959
|
+
exports.ChatSessionStatusBar = ChatSessionStatusBar;
|
|
1960
|
+
exports.ChatSessionTaskGroup = ChatSessionTaskGroup;
|
|
1961
|
+
exports.ChatSessionTaskRow = ChatSessionTaskRow;
|
|
1962
|
+
exports.ChatSessionTurn = ChatSessionTurn;
|
|
1963
|
+
exports.CitationMarker = CitationMarker;
|
|
1964
|
+
exports.ClarifyActions = ClarifyActions;
|
|
1965
|
+
exports.ClarifyCard = ClarifyCard;
|
|
1966
|
+
exports.ClarifyFootnote = ClarifyFootnote;
|
|
1967
|
+
exports.DEFAULT_ANSWER_ACTIONS = DEFAULT_ANSWER_ACTIONS;
|
|
1968
|
+
exports.EVENT_TYPES = EVENT_TYPES;
|
|
1969
|
+
exports.EmissionBody = EmissionBody;
|
|
1970
|
+
exports.EmissionCard = EmissionCard;
|
|
1971
|
+
exports.EmissionHeader = EmissionHeader;
|
|
1972
|
+
exports.FLOWS = FLOWS;
|
|
1973
|
+
exports.PATTERNS = PATTERNS;
|
|
1974
|
+
exports.PatchError = PatchError;
|
|
1975
|
+
exports.STAGES = STAGES;
|
|
1976
|
+
exports.TemplatePicker = TemplatePicker;
|
|
1977
|
+
exports.TurnLabel = TurnLabel;
|
|
1978
|
+
exports.answerTime = answerTime;
|
|
1979
|
+
exports.answerToPage = answerToPage;
|
|
1980
|
+
exports.applyPatch = applyPatch;
|
|
1981
|
+
exports.applyPatches = applyPatches;
|
|
1982
|
+
exports.chatSessionGutterClass = chatSessionGutterClass;
|
|
1983
|
+
exports.clockTime = clockTime;
|
|
1984
|
+
exports.currentStep = currentStep;
|
|
1985
|
+
exports.dayLabel = dayLabel;
|
|
1986
|
+
exports.elapsedSince = elapsedSince;
|
|
1987
|
+
exports.exchangesOf = exchangesOf;
|
|
1988
|
+
exports.formatDuration = formatDuration;
|
|
1989
|
+
exports.fromEventSource = fromEventSource;
|
|
1990
|
+
exports.fromNdjson = fromNdjson;
|
|
1991
|
+
exports.isLive = isLive;
|
|
1992
|
+
exports.isRunning = isRunning;
|
|
1993
|
+
exports.offsetScript = offsetScript;
|
|
1994
|
+
exports.patchesOf = patchesOf;
|
|
1995
|
+
exports.plainText = plainText;
|
|
1996
|
+
exports.playScript = playScript;
|
|
1997
|
+
exports.resolveRegistry = resolveRegistry;
|
|
1998
|
+
exports.runOutcome = runOutcome;
|
|
1999
|
+
exports.stepTime = stepTime;
|
|
2000
|
+
exports.stopPatches = stopPatches;
|
|
2001
|
+
exports.textDeltas = textDeltas;
|
|
2002
|
+
exports.thinkingDeltas = thinkingDeltas;
|
|
2003
|
+
exports.threadCounts = threadCounts;
|
|
2004
|
+
exports.useChatSession = useChatSession;
|
|
2005
|
+
exports.validate = validate;
|
|
2006
|
+
//# sourceMappingURL=index.cjs.map
|
|
2007
|
+
//# sourceMappingURL=index.cjs.map
|