@optilogic/chat 1.0.0-beta.1 → 1.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +235 -0
  2. package/dist/index.cjs +1292 -43
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +524 -6
  5. package/dist/index.d.ts +524 -6
  6. package/dist/index.js +1267 -33
  7. package/dist/index.js.map +1 -1
  8. package/package.json +15 -9
  9. package/src/components/agent-response/AgentResponse.tsx +99 -10
  10. package/src/components/agent-response/components/ActivityIndicators.tsx +36 -4
  11. package/src/components/agent-response/components/HITLSection.tsx +95 -0
  12. package/src/components/agent-response/components/MetadataRow.tsx +21 -6
  13. package/src/components/agent-response/components/ThinkingSection.tsx +102 -10
  14. package/src/components/agent-response/components/TruncatedMessage.tsx +52 -0
  15. package/src/components/agent-response/components/index.ts +6 -0
  16. package/src/components/agent-response/hooks/useAgentResponseAccumulator.ts +79 -4
  17. package/src/components/agent-response/index.ts +23 -0
  18. package/src/components/agent-response/types.ts +96 -1
  19. package/src/components/agent-timeline/AgentTimeline.tsx +256 -0
  20. package/src/components/agent-timeline/TimelineAgentBlock.tsx +84 -0
  21. package/src/components/agent-timeline/TimelineItem.tsx +97 -0
  22. package/src/components/agent-timeline/index.ts +14 -0
  23. package/src/components/agent-timeline/types.ts +49 -0
  24. package/src/components/agent-timeline/utils.ts +167 -0
  25. package/src/components/hitl-interactions/HITLInteractionRecord.tsx +139 -0
  26. package/src/components/hitl-interactions/HITLQuestionPanel.tsx +270 -0
  27. package/src/components/hitl-interactions/index.ts +18 -0
  28. package/src/components/inline-actions/ActionMarkdownRenderer.tsx +60 -0
  29. package/src/components/inline-actions/index.ts +18 -0
  30. package/src/components/inline-actions/parseResponseSegments.ts +66 -0
  31. package/src/components/inline-actions/prompts.ts +41 -0
  32. package/src/components/inline-actions/types.ts +57 -0
  33. package/src/components/user-prompt/UserPrompt.tsx +60 -0
  34. package/src/components/user-prompt/index.ts +1 -0
  35. package/src/components/user-prompt-input/UserPromptInput.tsx +326 -0
  36. package/src/components/user-prompt-input/index.ts +2 -0
  37. package/src/components/user-prompt-input/types.ts +52 -0
  38. package/src/index.ts +54 -0
package/dist/index.js CHANGED
@@ -1,15 +1,36 @@
1
- import * as React from 'react';
2
- import { useState, useCallback, useMemo, useEffect } from 'react';
3
- import { cn, Popover, PopoverTrigger, PopoverContent, LoadingSpinner } from '@optilogic/core';
4
- import { Wrench, Book, HardDrive, Check, Copy, ThumbsUp, ThumbsDown, ChevronUp, ChevronDown } from 'lucide-react';
1
+ import * as React11 from 'react';
2
+ import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
3
+ import { cn, Popover, PopoverTrigger, PopoverContent, Button, Textarea, IconButton, LoadingSpinner } from '@optilogic/core';
4
+ import { Activity, Wrench, Book, HardDrive, Check, Copy, ThumbsUp, ThumbsDown, ChevronDown, ChevronRight, MessageCircleQuestion, Square, Loader2, Send, ChevronUp, Brain, BookOpen, MessageSquare, AlertCircle, ChevronsDownUp, ChevronsUpDown } from 'lucide-react';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+ import { SlateEditor, Text } from '@optilogic/editor';
6
7
 
7
8
  // src/components/agent-response/AgentResponse.tsx
8
- var ActivityIndicators = React.forwardRef(
9
- ({ toolCalls, knowledge, memory, className, ...props }, ref) => {
10
- const hasAnyActivity = toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0;
9
+ var ActivityIndicators = React11.forwardRef(
10
+ ({ toolCalls, knowledge, memory, statusUpdates = [], className, ...props }, ref) => {
11
+ const hasAnyActivity = toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0 || statusUpdates.length > 0;
11
12
  if (!hasAnyActivity) return null;
12
13
  return /* @__PURE__ */ jsxs("div", { ref, className: cn("flex items-center gap-2", className), ...props, children: [
14
+ statusUpdates.length > 0 && /* @__PURE__ */ jsxs(Popover, { children: [
15
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
16
+ "button",
17
+ {
18
+ className: "flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors",
19
+ onClick: (e) => e.stopPropagation(),
20
+ children: [
21
+ /* @__PURE__ */ jsx(Activity, { className: "w-3.5 h-3.5" }),
22
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: statusUpdates.length })
23
+ ]
24
+ }
25
+ ) }),
26
+ /* @__PURE__ */ jsx(PopoverContent, { className: "w-80", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
27
+ /* @__PURE__ */ jsx("h4", { className: "font-medium text-sm", children: "Status Updates" }),
28
+ /* @__PURE__ */ jsx("div", { className: "space-y-2 max-h-60 overflow-auto", children: statusUpdates.map((item) => /* @__PURE__ */ jsxs("div", { className: "p-2 bg-muted rounded text-xs", children: [
29
+ item.agent && /* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground", children: item.agent }),
30
+ /* @__PURE__ */ jsx("div", { className: item.agent ? "mt-1" : "", children: item.message })
31
+ ] }, item.id)) })
32
+ ] }) })
33
+ ] }),
13
34
  toolCalls.length > 0 && /* @__PURE__ */ jsxs(Popover, { children: [
14
35
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
15
36
  "button",
@@ -104,7 +125,7 @@ function formatTotalTime(seconds) {
104
125
  const minutes = seconds / 60;
105
126
  return `${minutes.toFixed(1)}m`;
106
127
  }
107
- var MetadataRow = React.forwardRef(
128
+ var MetadataRow = React11.forwardRef(
108
129
  ({
109
130
  hasThinking,
110
131
  isExpanded,
@@ -112,6 +133,8 @@ var MetadataRow = React.forwardRef(
112
133
  toolCalls,
113
134
  knowledge,
114
135
  memory,
136
+ statusUpdates = [],
137
+ statusContent,
115
138
  status,
116
139
  elapsedTime,
117
140
  className,
@@ -119,7 +142,7 @@ var MetadataRow = React.forwardRef(
119
142
  }, ref) => {
120
143
  const isProcessing = status === "processing";
121
144
  const isComplete = status === "complete";
122
- const hasActivity = toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0;
145
+ const hasActivity = toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0 || statusUpdates.length > 0;
123
146
  const renderLeftContent = () => {
124
147
  if (hasThinking) {
125
148
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
@@ -136,7 +159,7 @@ var MetadataRow = React.forwardRef(
136
159
  return null;
137
160
  };
138
161
  const leftContent = renderLeftContent();
139
- if (!leftContent && !hasActivity) {
162
+ if (!leftContent && !hasActivity && !statusContent) {
140
163
  return null;
141
164
  }
142
165
  return /* @__PURE__ */ jsxs(
@@ -150,16 +173,18 @@ var MetadataRow = React.forwardRef(
150
173
  "button",
151
174
  {
152
175
  onClick: onToggle,
153
- className: "flex items-center gap-1.5 hover:bg-muted/50 -ml-1.5 pl-1.5 pr-2 py-0.5 rounded transition-colors",
176
+ className: "flex items-center gap-1.5 hover:bg-muted/50 -ml-1.5 pl-1.5 pr-2 py-0.5 rounded transition-colors shrink-0",
154
177
  children: leftContent
155
178
  }
156
- ) : /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: leftContent }),
179
+ ) : /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5 shrink-0", children: leftContent }),
180
+ statusContent && /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0 mx-2", children: statusContent }),
157
181
  /* @__PURE__ */ jsx(
158
182
  ActivityIndicators,
159
183
  {
160
184
  toolCalls,
161
185
  knowledge,
162
- memory
186
+ memory,
187
+ statusUpdates
163
188
  }
164
189
  )
165
190
  ]
@@ -168,24 +193,61 @@ var MetadataRow = React.forwardRef(
168
193
  }
169
194
  );
170
195
  MetadataRow.displayName = "MetadataRow";
171
- var ThinkingSection = React.forwardRef(
172
- ({ content, isExpanded, className, ...props }, ref) => {
173
- if (!isExpanded || !content) {
196
+ var ThinkingStepItem = ({ step, renderMarkdown }) => {
197
+ const [isCollapsed, setIsCollapsed] = useState(step.isCollapsed ?? false);
198
+ const toggleCollapse = useCallback(() => {
199
+ setIsCollapsed((prev) => !prev);
200
+ }, []);
201
+ const indentPadding = step.depth * 16;
202
+ return /* @__PURE__ */ jsxs("div", { className: "border-b border-border/50 last:border-b-0", children: [
203
+ /* @__PURE__ */ jsxs(
204
+ "button",
205
+ {
206
+ onClick: toggleCollapse,
207
+ className: "w-full flex items-center gap-1.5 py-1.5 px-2 hover:bg-muted/50 transition-colors text-left",
208
+ style: { paddingLeft: `${indentPadding + 8}px` },
209
+ children: [
210
+ isCollapsed ? /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3 text-muted-foreground flex-shrink-0" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "w-3 h-3 text-muted-foreground flex-shrink-0" }),
211
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground/80", children: step.label })
212
+ ]
213
+ }
214
+ ),
215
+ !isCollapsed && /* @__PURE__ */ jsx(
216
+ "div",
217
+ {
218
+ className: "pb-2 px-2",
219
+ style: { paddingLeft: `${indentPadding + 28}px` },
220
+ children: renderMarkdown ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: renderMarkdown(step.content) }) : /* @__PURE__ */ jsx("pre", { className: "text-xs text-muted-foreground whitespace-pre-wrap font-mono", children: step.content })
221
+ }
222
+ )
223
+ ] });
224
+ };
225
+ var ThinkingSection = React11.forwardRef(
226
+ ({ content, isExpanded, renderMarkdown, className, ...props }, ref) => {
227
+ if (!isExpanded || !content || Array.isArray(content) && content.length === 0) {
174
228
  return null;
175
229
  }
230
+ const isStructured = Array.isArray(content);
176
231
  return /* @__PURE__ */ jsx(
177
232
  "div",
178
233
  {
179
234
  ref,
180
235
  className: cn("px-3 pb-3 border-t border-border", className),
181
236
  ...props,
182
- children: /* @__PURE__ */ jsx("div", { className: "mt-2 max-h-[200px] overflow-y-auto", children: /* @__PURE__ */ jsx("pre", { className: "text-xs text-muted-foreground whitespace-pre-wrap font-mono", children: content }) })
237
+ children: /* @__PURE__ */ jsx("div", { className: "mt-2 max-h-[200px] overflow-y-auto scrollbar-thin", children: isStructured ? /* @__PURE__ */ jsx("div", { className: "space-y-0", children: content.map((step) => /* @__PURE__ */ jsx(
238
+ ThinkingStepItem,
239
+ {
240
+ step,
241
+ renderMarkdown
242
+ },
243
+ step.id
244
+ )) }) : renderMarkdown ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: renderMarkdown(content) }) : /* @__PURE__ */ jsx("pre", { className: "text-xs text-muted-foreground whitespace-pre-wrap font-mono", children: content }) })
183
245
  }
184
246
  );
185
247
  }
186
248
  );
187
249
  ThinkingSection.displayName = "ThinkingSection";
188
- var ActionBar = React.forwardRef(
250
+ var ActionBar = React11.forwardRef(
189
251
  ({
190
252
  response,
191
253
  isVisible,
@@ -274,6 +336,313 @@ var ActionBar = React.forwardRef(
274
336
  }
275
337
  );
276
338
  ActionBar.displayName = "ActionBar";
339
+ function buildResponseString(questions, selectedOptions, freeformText) {
340
+ const parts = [];
341
+ for (const q of questions) {
342
+ const answer = selectedOptions[q];
343
+ if (answer) {
344
+ parts.push(`Q: ${q}
345
+ A: ${answer}`);
346
+ }
347
+ }
348
+ const trimmed = freeformText.trim();
349
+ if (trimmed) {
350
+ parts.push(`Additional context: ${trimmed}`);
351
+ }
352
+ return parts.join("\n\n");
353
+ }
354
+ var HITLQuestionPanel = React11.forwardRef(({ question, onSubmit, onStop, className, ...props }, ref) => {
355
+ const [freeformText, setFreeformText] = useState("");
356
+ const [selectedOptions, setSelectedOptions] = useState({});
357
+ const hasTimeout = question.timeoutSeconds != null;
358
+ const [secondsLeft, setSecondsLeft] = useState(
359
+ () => hasTimeout ? Math.max(
360
+ 0,
361
+ Math.round(
362
+ question.timeoutSeconds - (Date.now() - question.receivedAt) / 1e3
363
+ )
364
+ ) : Infinity
365
+ );
366
+ const textareaRef = useRef(null);
367
+ useEffect(() => {
368
+ textareaRef.current?.focus();
369
+ }, []);
370
+ useEffect(() => {
371
+ if (!hasTimeout) return;
372
+ const interval = setInterval(() => {
373
+ const remaining = Math.max(
374
+ 0,
375
+ Math.round(
376
+ question.timeoutSeconds - (Date.now() - question.receivedAt) / 1e3
377
+ )
378
+ );
379
+ setSecondsLeft(remaining);
380
+ if (remaining <= 0) clearInterval(interval);
381
+ }, 1e3);
382
+ return () => clearInterval(interval);
383
+ }, [hasTimeout, question.timeoutSeconds, question.receivedAt]);
384
+ const timedOut = hasTimeout && secondsLeft <= 0;
385
+ const questionsWithOptions = useMemo(
386
+ () => question.questions.filter((q) => question.options?.[q]?.length),
387
+ [question.questions, question.options]
388
+ );
389
+ const allOptionsSelected = questionsWithOptions.length > 0 && questionsWithOptions.every((q) => selectedOptions[q]);
390
+ const hasFreeformText = freeformText.trim().length > 0;
391
+ const canSubmit = !timedOut && (allOptionsSelected || hasFreeformText);
392
+ const handleOptionClick = useCallback(
393
+ (questionText, option) => {
394
+ if (timedOut) return;
395
+ setSelectedOptions((prev) => {
396
+ if (prev[questionText] === option) {
397
+ const next = { ...prev };
398
+ delete next[questionText];
399
+ return next;
400
+ }
401
+ return { ...prev, [questionText]: option };
402
+ });
403
+ },
404
+ [timedOut]
405
+ );
406
+ const handleSubmit = useCallback(() => {
407
+ if (!canSubmit) return;
408
+ const combined = buildResponseString(
409
+ question.questions,
410
+ selectedOptions,
411
+ freeformText
412
+ );
413
+ onSubmit(combined, { selectedOptions, freeformText });
414
+ }, [canSubmit, question.questions, selectedOptions, freeformText, onSubmit]);
415
+ const handleKeyDown = useCallback(
416
+ (e) => {
417
+ if (e.key === "Enter" && !e.shiftKey) {
418
+ e.preventDefault();
419
+ handleSubmit();
420
+ }
421
+ },
422
+ [handleSubmit]
423
+ );
424
+ const formatTime2 = (s) => {
425
+ const m = Math.floor(s / 60);
426
+ const sec = s % 60;
427
+ return `${m}:${sec.toString().padStart(2, "0")}`;
428
+ };
429
+ return /* @__PURE__ */ jsxs(
430
+ "div",
431
+ {
432
+ ref,
433
+ className: cn(
434
+ "rounded-lg border border-border bg-muted p-4 space-y-3",
435
+ className
436
+ ),
437
+ ...props,
438
+ children: [
439
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
440
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: question.reason }) }),
441
+ hasTimeout && /* @__PURE__ */ jsx(
442
+ "span",
443
+ {
444
+ className: cn(
445
+ "text-xs font-mono whitespace-nowrap",
446
+ secondsLeft <= 30 ? "text-destructive" : "text-muted-foreground"
447
+ ),
448
+ children: timedOut ? "Timed out" : formatTime2(secondsLeft)
449
+ }
450
+ )
451
+ ] }),
452
+ question.context && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground bg-background rounded p-2 border border-border max-h-24 overflow-y-auto", children: /* @__PURE__ */ jsx("pre", { className: "whitespace-pre-wrap font-mono", children: question.context }) }),
453
+ /* @__PURE__ */ jsx("div", { className: "space-y-3", children: question.questions.map((q, i) => /* @__PURE__ */ jsxs("div", { children: [
454
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-foreground", children: q }),
455
+ question.options?.[q] && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mt-1.5", children: question.options[q].map((option, j) => {
456
+ const isSelected = selectedOptions[q] === option;
457
+ return /* @__PURE__ */ jsx(
458
+ Button,
459
+ {
460
+ variant: isSelected ? "primary" : "outline",
461
+ size: "sm",
462
+ onClick: () => handleOptionClick(q, option),
463
+ disabled: timedOut,
464
+ children: option
465
+ },
466
+ j
467
+ );
468
+ }) })
469
+ ] }, i)) }),
470
+ /* @__PURE__ */ jsx(
471
+ Textarea,
472
+ {
473
+ ref: textareaRef,
474
+ value: freeformText,
475
+ onChange: (e) => setFreeformText(e.target.value),
476
+ onKeyDown: handleKeyDown,
477
+ disabled: timedOut,
478
+ placeholder: "Add additional context or type a full response...",
479
+ rows: 2,
480
+ className: "resize-none"
481
+ }
482
+ ),
483
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
484
+ /* @__PURE__ */ jsx(
485
+ Button,
486
+ {
487
+ variant: "ghost",
488
+ size: "sm",
489
+ onClick: onStop,
490
+ className: "text-destructive hover:text-destructive hover:bg-destructive/10",
491
+ children: "Stop agent"
492
+ }
493
+ ),
494
+ /* @__PURE__ */ jsx(
495
+ Button,
496
+ {
497
+ variant: "primary",
498
+ size: "sm",
499
+ onClick: handleSubmit,
500
+ disabled: !canSubmit,
501
+ children: "Send response"
502
+ }
503
+ )
504
+ ] })
505
+ ]
506
+ }
507
+ );
508
+ });
509
+ HITLQuestionPanel.displayName = "HITLQuestionPanel";
510
+ function parseResponse(response) {
511
+ const answers = {};
512
+ let additionalContext = null;
513
+ const blocks = response.split("\n\n");
514
+ for (const block of blocks) {
515
+ const qaMatch = block.match(/^Q: (.+)\nA: (.+)$/s);
516
+ if (qaMatch) {
517
+ answers[qaMatch[1].trim()] = qaMatch[2].trim();
518
+ } else if (block.startsWith("Additional context: ")) {
519
+ additionalContext = block.slice("Additional context: ".length).trim();
520
+ }
521
+ }
522
+ return { answers, additionalContext };
523
+ }
524
+ var HITLInteractionRecord = React11.forwardRef(({ interaction, className, ...props }, ref) => {
525
+ const { question, response, respondedAt } = interaction;
526
+ const timestamp = new Date(respondedAt).toLocaleTimeString([], {
527
+ hour: "2-digit",
528
+ minute: "2-digit"
529
+ });
530
+ const { answers, additionalContext } = useMemo(
531
+ () => parseResponse(response),
532
+ [response]
533
+ );
534
+ const hasParsedAnswers = Object.keys(answers).length > 0;
535
+ return /* @__PURE__ */ jsxs(
536
+ "div",
537
+ {
538
+ ref,
539
+ className: cn(
540
+ "rounded-lg border border-border bg-muted p-3 space-y-2 text-sm",
541
+ className
542
+ ),
543
+ ...props,
544
+ children: [
545
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
546
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-muted-foreground", children: "Clarifying Question" }),
547
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: timestamp })
548
+ ] }),
549
+ /* @__PURE__ */ jsx("p", { className: "text-foreground font-medium", children: question.reason }),
550
+ question.context && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground bg-background rounded p-2 border border-border", children: /* @__PURE__ */ jsx("pre", { className: "whitespace-pre-wrap font-mono", children: question.context }) }),
551
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: question.questions.map((q, i) => /* @__PURE__ */ jsxs("div", { children: [
552
+ /* @__PURE__ */ jsx("p", { className: "text-foreground", children: q }),
553
+ question.options?.[q] && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground ml-2", children: [
554
+ "Options: ",
555
+ question.options[q].join(", ")
556
+ ] }),
557
+ hasParsedAnswers && answers[q] && /* @__PURE__ */ jsxs("p", { className: "text-xs text-foreground ml-2 mt-0.5 bg-background rounded px-2 py-1 border border-border", children: [
558
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Answer: " }),
559
+ answers[q]
560
+ ] })
561
+ ] }, i)) }),
562
+ hasParsedAnswers && additionalContext && /* @__PURE__ */ jsxs("div", { className: "border-t border-border pt-2", children: [
563
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: "Additional context:" }),
564
+ /* @__PURE__ */ jsx("p", { className: "text-foreground mt-0.5", children: additionalContext })
565
+ ] }),
566
+ !hasParsedAnswers && /* @__PURE__ */ jsxs("div", { className: "border-t border-border pt-2", children: [
567
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: "Response:" }),
568
+ /* @__PURE__ */ jsx("p", { className: "text-foreground mt-0.5", children: response })
569
+ ] })
570
+ ]
571
+ }
572
+ );
573
+ });
574
+ HITLInteractionRecord.displayName = "HITLInteractionRecord";
575
+ var HITLSection = React11.forwardRef(
576
+ ({
577
+ interactions,
578
+ defaultExpanded = false,
579
+ isExpanded: controlledExpanded,
580
+ onExpandedChange,
581
+ className,
582
+ ...props
583
+ }, ref) => {
584
+ const [uncontrolledExpanded, setUncontrolledExpanded] = useState(defaultExpanded);
585
+ const isControlled = controlledExpanded !== void 0;
586
+ const isExpanded = isControlled ? controlledExpanded : uncontrolledExpanded;
587
+ const toggleExpanded = useCallback(() => {
588
+ const newValue = !isExpanded;
589
+ if (isControlled) {
590
+ onExpandedChange?.(newValue);
591
+ } else {
592
+ setUncontrolledExpanded(newValue);
593
+ }
594
+ }, [isExpanded, isControlled, onExpandedChange]);
595
+ if (!interactions || interactions.length === 0) {
596
+ return null;
597
+ }
598
+ return /* @__PURE__ */ jsxs(
599
+ "div",
600
+ {
601
+ ref,
602
+ className: cn("border-t border-border", className),
603
+ ...props,
604
+ children: [
605
+ /* @__PURE__ */ jsxs(
606
+ "button",
607
+ {
608
+ onClick: toggleExpanded,
609
+ className: "w-full flex items-center gap-2 py-2 px-3 hover:bg-muted/50 transition-colors text-left",
610
+ children: [
611
+ isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "w-3.5 h-3.5 text-muted-foreground flex-shrink-0" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "w-3.5 h-3.5 text-muted-foreground flex-shrink-0" }),
612
+ /* @__PURE__ */ jsx(MessageCircleQuestion, { className: "w-3.5 h-3.5 text-muted-foreground flex-shrink-0" }),
613
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-foreground/80", children: [
614
+ "Clarifying Questions (",
615
+ interactions.length,
616
+ ")"
617
+ ] })
618
+ ]
619
+ }
620
+ ),
621
+ isExpanded && /* @__PURE__ */ jsx("div", { className: "px-3 pb-3 space-y-2", children: interactions.map((interaction, i) => /* @__PURE__ */ jsx(HITLInteractionRecord, { interaction }, i)) })
622
+ ]
623
+ }
624
+ );
625
+ }
626
+ );
627
+ HITLSection.displayName = "HITLSection";
628
+ var TruncatedMessage = React11.forwardRef(
629
+ ({ message, className, ...props }, ref) => {
630
+ return /* @__PURE__ */ jsx(
631
+ "div",
632
+ {
633
+ ref,
634
+ className: cn(
635
+ "text-xs text-muted-foreground truncate min-w-0",
636
+ className
637
+ ),
638
+ title: message,
639
+ ...props,
640
+ children: message
641
+ }
642
+ );
643
+ }
644
+ );
645
+ TruncatedMessage.displayName = "TruncatedMessage";
277
646
  function useThinkingTimer({
278
647
  startTime,
279
648
  endTime,
@@ -308,12 +677,133 @@ var initialAgentResponseState = {
308
677
  toolCalls: [],
309
678
  knowledge: [],
310
679
  memory: [],
680
+ statusUpdates: [],
311
681
  response: "",
312
682
  thinkingStartTime: null,
313
683
  responseCompleteTime: null,
314
684
  firstMessageTime: null
315
685
  };
316
686
 
687
+ // src/components/agent-timeline/utils.ts
688
+ function buildTimelineEntries(state) {
689
+ const entries = [];
690
+ if (state.thinkingSteps) {
691
+ let idx = 0;
692
+ for (const step of state.thinkingSteps) {
693
+ entries.push({
694
+ id: `tl-think-${idx++}`,
695
+ type: "thinking",
696
+ agentName: step.agentName ?? null,
697
+ parentAgent: step.parentAgent ?? null,
698
+ depth: step.depth ?? 0,
699
+ content: step.content,
700
+ title: step.label,
701
+ timestamp: step.timestamp ?? 0
702
+ });
703
+ }
704
+ } else if (state.thinking) {
705
+ entries.push({
706
+ id: "tl-think-0",
707
+ type: "thinking",
708
+ agentName: null,
709
+ parentAgent: null,
710
+ depth: 0,
711
+ content: state.thinking,
712
+ title: null,
713
+ timestamp: state.thinkingStartTime ?? 0
714
+ });
715
+ }
716
+ let toolIdx = 0;
717
+ for (const tool of state.toolCalls) {
718
+ entries.push({
719
+ id: `tl-tool-${toolIdx++}`,
720
+ type: "tool_call",
721
+ agentName: tool.agentName ?? null,
722
+ parentAgent: tool.parentAgent ?? null,
723
+ depth: tool.depth ?? 0,
724
+ content: tool.name,
725
+ title: null,
726
+ timestamp: tool.timestamp
727
+ });
728
+ }
729
+ let knowIdx = 0;
730
+ for (const item of state.knowledge) {
731
+ entries.push({
732
+ id: `tl-know-${knowIdx++}`,
733
+ type: "knowledge",
734
+ agentName: item.agentName ?? null,
735
+ parentAgent: item.parentAgent ?? null,
736
+ depth: item.depth ?? 0,
737
+ content: item.content,
738
+ title: item.source,
739
+ timestamp: item.timestamp
740
+ });
741
+ }
742
+ let memIdx = 0;
743
+ for (const item of state.memory) {
744
+ entries.push({
745
+ id: `tl-mem-${memIdx++}`,
746
+ type: "memory",
747
+ agentName: item.agentName ?? null,
748
+ parentAgent: item.parentAgent ?? null,
749
+ depth: item.depth ?? 0,
750
+ content: item.content,
751
+ title: item.type,
752
+ timestamp: item.timestamp
753
+ });
754
+ }
755
+ let statIdx = 0;
756
+ for (const item of state.statusUpdates) {
757
+ entries.push({
758
+ id: `tl-stat-${statIdx++}`,
759
+ type: "status_update",
760
+ agentName: item.agentName ?? item.agent ?? null,
761
+ parentAgent: item.parentAgent ?? null,
762
+ depth: item.depth ?? 0,
763
+ content: item.message,
764
+ title: null,
765
+ timestamp: item.timestamp
766
+ });
767
+ }
768
+ entries.sort((a, b) => a.timestamp - b.timestamp);
769
+ return entries;
770
+ }
771
+ function groupIntoAgentRuns(entries) {
772
+ const runs = [];
773
+ let currentRun = null;
774
+ for (const entry of entries) {
775
+ const name = entry.agentName || "Agent";
776
+ if (!currentRun || currentRun.agentName !== name) {
777
+ currentRun = {
778
+ agentName: name,
779
+ parentAgent: entry.parentAgent,
780
+ depth: entry.depth,
781
+ entries: []
782
+ };
783
+ runs.push(currentRun);
784
+ }
785
+ currentRun.entries.push({ entry, count: 1 });
786
+ }
787
+ for (const run of runs) {
788
+ run.entries = deduplicateEntries(run.entries);
789
+ }
790
+ return runs;
791
+ }
792
+ function deduplicateEntries(entries) {
793
+ if (entries.length === 0) return [];
794
+ const result = [entries[0]];
795
+ for (let i = 1; i < entries.length; i++) {
796
+ const prev = result[result.length - 1];
797
+ const curr = entries[i];
798
+ if (prev.entry.type === curr.entry.type && prev.entry.content === curr.entry.content) {
799
+ prev.count += curr.count;
800
+ } else {
801
+ result.push({ ...curr });
802
+ }
803
+ }
804
+ return result;
805
+ }
806
+
317
807
  // src/components/agent-response/hooks/useAgentResponseAccumulator.ts
318
808
  function useAgentResponseAccumulator(options) {
319
809
  const [state, setState] = useState(initialAgentResponseState);
@@ -342,16 +832,49 @@ function useAgentResponseAccumulator(options) {
342
832
  }
343
833
  return { ...prev, status: newStatus };
344
834
  case "thinking": {
835
+ if (payload.thinkingStep) {
836
+ const newStep = {
837
+ id: payload.thinkingStep.id || `step-${Date.now()}`,
838
+ label: payload.thinkingStep.label,
839
+ content: payload.thinkingStep.content,
840
+ depth: payload.thinkingStep.depth ?? payload.depth ?? 0,
841
+ isCollapsed: payload.thinkingStep.isCollapsed,
842
+ timestamp: Date.now(),
843
+ agentName: payload.agentName,
844
+ parentAgent: payload.parentAgent
845
+ };
846
+ const thinkingStartTime2 = prev.thinkingStartTime ?? Date.now();
847
+ const next2 = {
848
+ ...prev,
849
+ status: newStatus,
850
+ thinkingSteps: [...prev.thinkingSteps || [], newStep],
851
+ thinkingStartTime: thinkingStartTime2,
852
+ firstMessageTime
853
+ };
854
+ return { ...next2, timelineEntries: buildTimelineEntries(next2) };
855
+ }
345
856
  const newThinking = payload.message || payload.content || "";
346
857
  const separator = prev.thinking && newThinking ? "\n\n" : "";
347
858
  const thinkingStartTime = prev.thinkingStartTime ?? (newThinking ? Date.now() : null);
348
- return {
859
+ const prevSteps = prev.thinkingSteps || [];
860
+ const plainStep = {
861
+ id: `step-${prevSteps.length}`,
862
+ label: newThinking,
863
+ content: newThinking,
864
+ depth: payload.depth ?? 0,
865
+ timestamp: Date.now(),
866
+ agentName: payload.agentName,
867
+ parentAgent: payload.parentAgent
868
+ };
869
+ const next = {
349
870
  ...prev,
350
871
  status: newStatus,
351
872
  thinking: prev.thinking + separator + newThinking,
873
+ thinkingSteps: [...prevSteps, plainStep],
352
874
  thinkingStartTime,
353
875
  firstMessageTime
354
876
  };
877
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
355
878
  }
356
879
  case "tool_call": {
357
880
  const toolName = payload.message || payload.tool?.name;
@@ -360,14 +883,18 @@ function useAgentResponseAccumulator(options) {
360
883
  id: payload.tool?.id || `tool-${Date.now()}`,
361
884
  name: toolName,
362
885
  arguments: payload.tool?.arguments,
363
- timestamp: Date.now()
886
+ timestamp: Date.now(),
887
+ agentName: payload.agentName,
888
+ parentAgent: payload.parentAgent,
889
+ depth: payload.depth
364
890
  };
365
- return {
891
+ const next = {
366
892
  ...prev,
367
893
  status: newStatus,
368
894
  toolCalls: [...prev.toolCalls, newToolCall],
369
895
  firstMessageTime
370
896
  };
897
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
371
898
  }
372
899
  return { ...prev, status: newStatus, firstMessageTime };
373
900
  }
@@ -378,14 +905,18 @@ function useAgentResponseAccumulator(options) {
378
905
  id: payload.knowledge?.id || `knowledge-${Date.now()}`,
379
906
  source: payload.knowledge?.source || "unknown",
380
907
  content: knowledgeContent,
381
- timestamp: Date.now()
908
+ timestamp: Date.now(),
909
+ agentName: payload.agentName,
910
+ parentAgent: payload.parentAgent,
911
+ depth: payload.depth
382
912
  };
383
- return {
913
+ const next = {
384
914
  ...prev,
385
915
  status: newStatus,
386
916
  knowledge: [...prev.knowledge, newKnowledge],
387
917
  firstMessageTime
388
918
  };
919
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
389
920
  }
390
921
  return { ...prev, status: newStatus, firstMessageTime };
391
922
  }
@@ -396,14 +927,18 @@ function useAgentResponseAccumulator(options) {
396
927
  id: payload.memory?.id || `memory-${Date.now()}`,
397
928
  type: payload.memory?.type || "unknown",
398
929
  content: memoryContent,
399
- timestamp: Date.now()
930
+ timestamp: Date.now(),
931
+ agentName: payload.agentName,
932
+ parentAgent: payload.parentAgent,
933
+ depth: payload.depth
400
934
  };
401
- return {
935
+ const next = {
402
936
  ...prev,
403
937
  status: newStatus,
404
938
  memory: [...prev.memory, newMemory],
405
939
  firstMessageTime
406
940
  };
941
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
407
942
  }
408
943
  return { ...prev, status: newStatus, firstMessageTime };
409
944
  }
@@ -415,6 +950,28 @@ function useAgentResponseAccumulator(options) {
415
950
  responseCompleteTime: Date.now(),
416
951
  firstMessageTime: prev.firstMessageTime ?? Date.now()
417
952
  };
953
+ case "status_update": {
954
+ const statusMessage = payload.message || payload.statusUpdate?.message;
955
+ if (statusMessage) {
956
+ const newStatusItem = {
957
+ id: payload.statusUpdate?.id || `status-${Date.now()}`,
958
+ message: statusMessage,
959
+ agent: payload.statusUpdate?.agent,
960
+ timestamp: Date.now(),
961
+ agentName: payload.agentName,
962
+ parentAgent: payload.parentAgent,
963
+ depth: payload.depth
964
+ };
965
+ const next = {
966
+ ...prev,
967
+ status: newStatus,
968
+ statusUpdates: [...prev.statusUpdates, newStatusItem],
969
+ firstMessageTime
970
+ };
971
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
972
+ }
973
+ return { ...prev, status: newStatus, firstMessageTime };
974
+ }
418
975
  default:
419
976
  return { ...prev, status: newStatus, firstMessageTime };
420
977
  }
@@ -427,7 +984,288 @@ function useAgentResponseAccumulator(options) {
427
984
  }, []);
428
985
  return { state, handleMessage, reset };
429
986
  }
430
- var AgentResponse = React.forwardRef(
987
+ var ICON_MAP = {
988
+ thinking: Brain,
989
+ tool_call: Wrench,
990
+ knowledge: BookOpen,
991
+ memory: HardDrive,
992
+ status_update: Activity,
993
+ ai_response: MessageSquare,
994
+ error: AlertCircle
995
+ };
996
+ function TimelineItem({
997
+ displayEntry,
998
+ renderMarkdown,
999
+ isExpanded,
1000
+ onToggleExpanded
1001
+ }) {
1002
+ const { entry, count } = displayEntry;
1003
+ const Icon = ICON_MAP[entry.type] ?? Activity;
1004
+ const isLong = entry.content.length > 200 || entry.content.split("\n").length > 3;
1005
+ const canExpand = isLong || entry.type === "ai_response";
1006
+ return /* @__PURE__ */ jsxs("div", { className: "py-1 flex items-start gap-2 group", children: [
1007
+ /* @__PURE__ */ jsx(Icon, { className: "w-3.5 h-3.5 text-muted-foreground flex-shrink-0 mt-0.5" }),
1008
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: isExpanded && entry.type === "ai_response" && renderMarkdown ? (
1009
+ // Expanded AI response: rendered markdown
1010
+ /* @__PURE__ */ jsxs("div", { children: [
1011
+ renderMarkdown(entry.content),
1012
+ /* @__PURE__ */ jsx(
1013
+ "button",
1014
+ {
1015
+ onClick: onToggleExpanded,
1016
+ className: "text-[10px] text-muted-foreground/70 hover:text-muted-foreground mt-1",
1017
+ children: "Show less"
1018
+ }
1019
+ )
1020
+ ] })
1021
+ ) : isExpanded ? (
1022
+ // Expanded non-AI: plain text
1023
+ /* @__PURE__ */ jsxs("div", { children: [
1024
+ /* @__PURE__ */ jsx("pre", { className: "text-xs text-muted-foreground whitespace-pre-wrap font-mono", children: entry.content }),
1025
+ /* @__PURE__ */ jsx(
1026
+ "button",
1027
+ {
1028
+ onClick: onToggleExpanded,
1029
+ className: "text-[10px] text-muted-foreground/70 hover:text-muted-foreground mt-1",
1030
+ children: "Show less"
1031
+ }
1032
+ )
1033
+ ] })
1034
+ ) : (
1035
+ // Collapsed: truncated with optional expand
1036
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5 min-w-0", children: [
1037
+ /* @__PURE__ */ jsx(
1038
+ "div",
1039
+ {
1040
+ className: `text-xs text-muted-foreground min-w-0 ${canExpand ? "line-clamp-2 cursor-pointer hover:text-foreground/80" : ""}`,
1041
+ onClick: canExpand ? onToggleExpanded : void 0,
1042
+ children: entry.content
1043
+ }
1044
+ ),
1045
+ count > 1 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground/60 whitespace-nowrap flex-shrink-0", children: [
1046
+ "(x",
1047
+ count,
1048
+ ")"
1049
+ ] })
1050
+ ] })
1051
+ ) })
1052
+ ] });
1053
+ }
1054
+ function TimelineAgentBlock({
1055
+ block,
1056
+ renderMarkdown,
1057
+ isSingleAgent,
1058
+ isCollapsed,
1059
+ onToggleCollapsed,
1060
+ expandedItems,
1061
+ onToggleItemExpanded
1062
+ }) {
1063
+ const indentPx = block.depth * 16;
1064
+ if (isSingleAgent && block.depth === 0) {
1065
+ return /* @__PURE__ */ jsx("div", { style: { paddingLeft: `${indentPx}px` }, children: block.entries.map((displayEntry, i) => /* @__PURE__ */ jsx(
1066
+ TimelineItem,
1067
+ {
1068
+ displayEntry,
1069
+ renderMarkdown,
1070
+ isExpanded: expandedItems.has(displayEntry.entry.id),
1071
+ onToggleExpanded: () => onToggleItemExpanded(displayEntry.entry.id)
1072
+ },
1073
+ displayEntry.entry.id + "-" + i
1074
+ )) });
1075
+ }
1076
+ return /* @__PURE__ */ jsxs("div", { style: { paddingLeft: `${indentPx}px` }, children: [
1077
+ /* @__PURE__ */ jsxs(
1078
+ "button",
1079
+ {
1080
+ onClick: onToggleCollapsed,
1081
+ className: "w-full flex items-center gap-1.5 py-1 hover:bg-muted/50 -ml-1 pl-1 pr-2 rounded transition-colors text-left",
1082
+ children: [
1083
+ isCollapsed ? /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3 text-muted-foreground flex-shrink-0" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "w-3 h-3 text-muted-foreground flex-shrink-0" }),
1084
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground/80", children: block.agentName }),
1085
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground/60", children: [
1086
+ "(",
1087
+ block.entries.reduce((sum, e) => sum + e.count, 0),
1088
+ ")"
1089
+ ] })
1090
+ ]
1091
+ }
1092
+ ),
1093
+ !isCollapsed && /* @__PURE__ */ jsx("div", { className: "ml-4", children: block.entries.map((displayEntry, i) => /* @__PURE__ */ jsx(
1094
+ TimelineItem,
1095
+ {
1096
+ displayEntry,
1097
+ renderMarkdown,
1098
+ isExpanded: expandedItems.has(displayEntry.entry.id),
1099
+ onToggleExpanded: () => onToggleItemExpanded(displayEntry.entry.id)
1100
+ },
1101
+ displayEntry.entry.id + "-" + i
1102
+ )) })
1103
+ ] });
1104
+ }
1105
+ function createTimelineUIState() {
1106
+ return {
1107
+ expandedItems: /* @__PURE__ */ new Set(),
1108
+ collapsedRuns: /* @__PURE__ */ new Set(),
1109
+ activeFilters: /* @__PURE__ */ new Set()
1110
+ };
1111
+ }
1112
+ var TYPE_CONFIG = [
1113
+ { type: "status_update", icon: Activity, label: "Status" },
1114
+ { type: "thinking", icon: Brain, label: "Thinking" },
1115
+ { type: "tool_call", icon: Wrench, label: "Tools" },
1116
+ { type: "knowledge", icon: BookOpen, label: "Knowledge" },
1117
+ { type: "memory", icon: HardDrive, label: "Memory" },
1118
+ { type: "ai_response", icon: MessageSquare, label: "AI" },
1119
+ { type: "error", icon: AlertCircle, label: "Errors" }
1120
+ ];
1121
+ function AgentTimeline({ entries, renderMarkdown, uiState, maxHeight = "300px" }) {
1122
+ const containerRef = useRef(null);
1123
+ const [renderTick, setRenderTick] = useState(0);
1124
+ const forceRender = useCallback(() => setRenderTick((t) => t + 1), []);
1125
+ const [internalExpandedItems] = useState(() => /* @__PURE__ */ new Set());
1126
+ const [internalCollapsedRuns] = useState(() => /* @__PURE__ */ new Set());
1127
+ const [internalActiveFilters] = useState(() => /* @__PURE__ */ new Set());
1128
+ const expandedItems = uiState?.expandedItems ?? internalExpandedItems;
1129
+ const collapsedRuns = uiState?.collapsedRuns ?? internalCollapsedRuns;
1130
+ const activeFilters = uiState?.activeFilters ?? internalActiveFilters;
1131
+ const availableTypes = useMemo(() => {
1132
+ const types = /* @__PURE__ */ new Set();
1133
+ for (const entry of entries) {
1134
+ types.add(entry.type);
1135
+ }
1136
+ return types;
1137
+ }, [entries]);
1138
+ const filteredEntries = useMemo(
1139
+ () => activeFilters.size === 0 ? entries : entries.filter((e) => activeFilters.has(e.type)),
1140
+ [entries, activeFilters, renderTick]
1141
+ );
1142
+ const agentRuns = useMemo(() => groupIntoAgentRuns(filteredEntries), [filteredEntries, renderTick]);
1143
+ const toggleFilter = useCallback((type) => {
1144
+ if (activeFilters.has(type)) {
1145
+ activeFilters.delete(type);
1146
+ } else {
1147
+ activeFilters.add(type);
1148
+ }
1149
+ forceRender();
1150
+ }, [activeFilters, forceRender]);
1151
+ const clearFilters = useCallback(() => {
1152
+ activeFilters.clear();
1153
+ forceRender();
1154
+ }, [activeFilters, forceRender]);
1155
+ const toggleItemExpanded = useCallback((entryId) => {
1156
+ if (expandedItems.has(entryId)) {
1157
+ expandedItems.delete(entryId);
1158
+ } else {
1159
+ expandedItems.add(entryId);
1160
+ }
1161
+ forceRender();
1162
+ }, [expandedItems, forceRender]);
1163
+ const collapseAll = useCallback(() => {
1164
+ collapsedRuns.clear();
1165
+ agentRuns.forEach((run, i) => {
1166
+ collapsedRuns.add(`${run.agentName}-${i}`);
1167
+ });
1168
+ expandedItems.clear();
1169
+ forceRender();
1170
+ }, [agentRuns, collapsedRuns, expandedItems, forceRender]);
1171
+ const expandAll = useCallback(() => {
1172
+ collapsedRuns.clear();
1173
+ agentRuns.forEach((run, i) => {
1174
+ collapsedRuns.add(`${run.agentName}-${i}:expanded`);
1175
+ });
1176
+ forceRender();
1177
+ }, [agentRuns, collapsedRuns, forceRender]);
1178
+ if (entries.length === 0) return null;
1179
+ const isSingle = agentRuns.length === 1;
1180
+ const hasActiveFilter = activeFilters.size > 0;
1181
+ const scrollStyle = maxHeight !== "none" ? { maxHeight } : void 0;
1182
+ return /* @__PURE__ */ jsxs(
1183
+ "div",
1184
+ {
1185
+ ref: containerRef,
1186
+ className: `-mt-1 ${maxHeight !== "none" ? "overflow-y-auto scrollbar-thin" : ""}`,
1187
+ style: scrollStyle,
1188
+ children: [
1189
+ /* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-10 bg-background flex items-center gap-1 py-1.5 mb-1 border-b border-border/50 flex-wrap", children: [
1190
+ TYPE_CONFIG.filter((tc) => availableTypes.has(tc.type)).map((tc) => {
1191
+ const isActive = activeFilters.has(tc.type);
1192
+ const count = entries.filter((e) => e.type === tc.type).length;
1193
+ return /* @__PURE__ */ jsxs(
1194
+ "button",
1195
+ {
1196
+ onClick: () => toggleFilter(tc.type),
1197
+ className: `inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] transition-colors ${isActive ? "bg-accent text-accent-foreground ring-1 ring-accent-foreground/20" : "text-muted-foreground/60 hover:text-muted-foreground hover:bg-muted/50"}`,
1198
+ title: `${isActive ? "Hide" : "Show only"} ${tc.label}`,
1199
+ children: [
1200
+ /* @__PURE__ */ jsx(tc.icon, { className: "w-3 h-3" }),
1201
+ /* @__PURE__ */ jsx("span", { children: count })
1202
+ ]
1203
+ },
1204
+ tc.type
1205
+ );
1206
+ }),
1207
+ hasActiveFilter && /* @__PURE__ */ jsx(
1208
+ "button",
1209
+ {
1210
+ onClick: clearFilters,
1211
+ className: "text-[10px] text-muted-foreground/60 hover:text-muted-foreground px-1",
1212
+ children: "Clear"
1213
+ }
1214
+ ),
1215
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
1216
+ !isSingle && /* @__PURE__ */ jsxs(Fragment, { children: [
1217
+ /* @__PURE__ */ jsx(
1218
+ "button",
1219
+ {
1220
+ onClick: collapseAll,
1221
+ className: "inline-flex items-center gap-0.5 text-[10px] text-muted-foreground/60 hover:text-muted-foreground px-1 py-0.5 rounded hover:bg-muted/50 transition-colors",
1222
+ title: "Collapse all",
1223
+ children: /* @__PURE__ */ jsx(ChevronsDownUp, { className: "w-3 h-3" })
1224
+ }
1225
+ ),
1226
+ /* @__PURE__ */ jsx(
1227
+ "button",
1228
+ {
1229
+ onClick: expandAll,
1230
+ className: "inline-flex items-center gap-0.5 text-[10px] text-muted-foreground/60 hover:text-muted-foreground px-1 py-0.5 rounded hover:bg-muted/50 transition-colors",
1231
+ title: "Expand all",
1232
+ children: /* @__PURE__ */ jsx(ChevronsUpDown, { className: "w-3 h-3" })
1233
+ }
1234
+ )
1235
+ ] })
1236
+ ] }),
1237
+ filteredEntries.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-[10px] text-muted-foreground/50 py-2 text-center", children: "No entries match the selected filters" }) : /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: agentRuns.map((run, i) => {
1238
+ const runKey = `${run.agentName}-${i}`;
1239
+ const defaultCollapsed = run.depth > 0;
1240
+ const isCollapsed = collapsedRuns.has(runKey) ? true : collapsedRuns.has(`${runKey}:expanded`) ? false : defaultCollapsed;
1241
+ return /* @__PURE__ */ jsx(
1242
+ TimelineAgentBlock,
1243
+ {
1244
+ block: run,
1245
+ renderMarkdown,
1246
+ isSingleAgent: isSingle,
1247
+ isCollapsed,
1248
+ onToggleCollapsed: () => {
1249
+ if (isCollapsed) {
1250
+ collapsedRuns.delete(runKey);
1251
+ collapsedRuns.add(`${runKey}:expanded`);
1252
+ } else {
1253
+ collapsedRuns.delete(`${runKey}:expanded`);
1254
+ collapsedRuns.add(runKey);
1255
+ }
1256
+ forceRender();
1257
+ },
1258
+ expandedItems,
1259
+ onToggleItemExpanded: toggleItemExpanded
1260
+ },
1261
+ runKey
1262
+ );
1263
+ }) })
1264
+ ]
1265
+ }
1266
+ );
1267
+ }
1268
+ var AgentResponse = React11.forwardRef(
431
1269
  ({
432
1270
  state,
433
1271
  id,
@@ -439,10 +1277,15 @@ var AgentResponse = React.forwardRef(
439
1277
  thinkingExpanded: controlledThinkingExpanded,
440
1278
  onThinkingExpandedChange,
441
1279
  actionsVisible = "hover",
1280
+ hitlInteractions,
1281
+ defaultHITLExpanded = false,
1282
+ statusContent,
442
1283
  renderMarkdown,
1284
+ renderThinkingMarkdown,
443
1285
  className,
444
1286
  ...props
445
1287
  }, ref) => {
1288
+ const timelineUIStateRef = useRef(createTimelineUIState());
446
1289
  const [uncontrolledExpanded, setUncontrolledExpanded] = useState(defaultThinkingExpanded);
447
1290
  const isThinkingControlled = controlledThinkingExpanded !== void 0;
448
1291
  const thinkingExpanded = isThinkingControlled ? controlledThinkingExpanded : uncontrolledExpanded;
@@ -464,8 +1307,11 @@ var AgentResponse = React.forwardRef(
464
1307
  if (!state.firstMessageTime || !state.responseCompleteTime) return 0;
465
1308
  return (state.responseCompleteTime - state.firstMessageTime) / 1e3;
466
1309
  }, [state.firstMessageTime, state.responseCompleteTime]);
467
- const hasAnyContent = state.thinking || state.toolCalls.length > 0 || state.knowledge.length > 0 || state.memory.length > 0 || state.response;
468
- const showMetadataRow = state.thinking || state.toolCalls.length > 0 || state.knowledge.length > 0 || state.memory.length > 0 || state.status === "processing";
1310
+ const hasTimelineEntries = !!(state.timelineEntries && state.timelineEntries.length > 0);
1311
+ const hasThinkingContent = !!state.thinking || state.thinkingSteps && state.thinkingSteps.length > 0 || hasTimelineEntries || false;
1312
+ const hasHITLInteractions = hitlInteractions && hitlInteractions.length > 0;
1313
+ const hasAnyContent = hasThinkingContent || state.toolCalls.length > 0 || state.knowledge.length > 0 || state.memory.length > 0 || state.statusUpdates.length > 0 || hasHITLInteractions || state.response;
1314
+ const showMetadataRow = hasThinkingContent || state.toolCalls.length > 0 || state.knowledge.length > 0 || state.memory.length > 0 || state.statusUpdates.length > 0 || state.status === "processing";
469
1315
  const showActionBar = state.status === "complete" && state.response;
470
1316
  const isActionBarVisible = actionsVisible === true || actionsVisible === "hover" && isHovered;
471
1317
  if (!hasAnyContent) {
@@ -485,24 +1331,41 @@ var AgentResponse = React.forwardRef(
485
1331
  /* @__PURE__ */ jsx(
486
1332
  MetadataRow,
487
1333
  {
488
- hasThinking: !!state.thinking,
1334
+ hasThinking: hasThinkingContent,
489
1335
  isExpanded: thinkingExpanded,
490
1336
  onToggle: toggleThinking,
491
1337
  toolCalls: state.toolCalls,
492
1338
  knowledge: state.knowledge,
493
1339
  memory: state.memory,
1340
+ statusUpdates: state.statusUpdates,
1341
+ statusContent,
494
1342
  status: state.status,
495
1343
  elapsedTime
496
1344
  }
497
1345
  ),
498
- /* @__PURE__ */ jsx(
1346
+ hasTimelineEntries ? thinkingExpanded && /* @__PURE__ */ jsx("div", { className: "px-3 pb-3 border-t border-border mt-2", children: /* @__PURE__ */ jsx(
1347
+ AgentTimeline,
1348
+ {
1349
+ entries: state.timelineEntries,
1350
+ renderMarkdown: renderThinkingMarkdown,
1351
+ uiState: timelineUIStateRef.current
1352
+ }
1353
+ ) }) : /* @__PURE__ */ jsx(
499
1354
  ThinkingSection,
500
1355
  {
501
- content: state.thinking,
502
- isExpanded: thinkingExpanded
1356
+ content: state.thinkingSteps && state.thinkingSteps.length > 0 ? state.thinkingSteps : state.thinking,
1357
+ isExpanded: thinkingExpanded,
1358
+ renderMarkdown: renderThinkingMarkdown
503
1359
  }
504
1360
  )
505
1361
  ] }),
1362
+ hasHITLInteractions && /* @__PURE__ */ jsx(
1363
+ HITLSection,
1364
+ {
1365
+ interactions: hitlInteractions,
1366
+ defaultExpanded: defaultHITLExpanded
1367
+ }
1368
+ ),
506
1369
  state.response && /* @__PURE__ */ jsx(
507
1370
  "div",
508
1371
  {
@@ -531,7 +1394,378 @@ var AgentResponse = React.forwardRef(
531
1394
  }
532
1395
  );
533
1396
  AgentResponse.displayName = "AgentResponse";
1397
+ var UserPrompt = React11.forwardRef(
1398
+ ({ content, timestamp, className, ...props }, ref) => {
1399
+ return /* @__PURE__ */ jsxs(
1400
+ "div",
1401
+ {
1402
+ ref,
1403
+ className: cn(
1404
+ "w-fit max-w-[80%] rounded-lg px-4 pt-3.5 pb-3",
1405
+ "bg-secondary text-secondary-foreground",
1406
+ className
1407
+ ),
1408
+ ...props,
1409
+ children: [
1410
+ /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: content }),
1411
+ timestamp && /* @__PURE__ */ jsx("p", { className: "text-xs text-secondary-foreground/70 mt-1", children: timestamp.toLocaleTimeString([], {
1412
+ hour: "2-digit",
1413
+ minute: "2-digit"
1414
+ }) })
1415
+ ]
1416
+ }
1417
+ );
1418
+ }
1419
+ );
1420
+ UserPrompt.displayName = "UserPrompt";
1421
+ function createCodeBlockDecorate(entry) {
1422
+ const [node, path] = entry;
1423
+ const ranges = [];
1424
+ if (!Text.isText(node)) {
1425
+ return ranges;
1426
+ }
1427
+ const { text } = node;
1428
+ const backtickPositions = [];
1429
+ let searchStart = 0;
1430
+ while (true) {
1431
+ const pos = text.indexOf("```", searchStart);
1432
+ if (pos === -1) break;
1433
+ backtickPositions.push(pos);
1434
+ searchStart = pos + 3;
1435
+ }
1436
+ let i = 0;
1437
+ while (i < backtickPositions.length) {
1438
+ const openPos = backtickPositions[i];
1439
+ const closePos = backtickPositions[i + 1];
1440
+ ranges.push({
1441
+ anchor: { path, offset: openPos },
1442
+ focus: { path, offset: openPos + 3 },
1443
+ codeDelimiter: true
1444
+ });
1445
+ if (closePos !== void 0) {
1446
+ if (closePos > openPos + 3) {
1447
+ ranges.push({
1448
+ anchor: { path, offset: openPos + 3 },
1449
+ focus: { path, offset: closePos },
1450
+ codeBlock: true
1451
+ });
1452
+ }
1453
+ ranges.push({
1454
+ anchor: { path, offset: closePos },
1455
+ focus: { path, offset: closePos + 3 },
1456
+ codeDelimiter: true
1457
+ });
1458
+ i += 2;
1459
+ } else {
1460
+ if (text.length > openPos + 3) {
1461
+ ranges.push({
1462
+ anchor: { path, offset: openPos + 3 },
1463
+ focus: { path, offset: text.length },
1464
+ codeBlock: true
1465
+ });
1466
+ }
1467
+ i += 1;
1468
+ }
1469
+ }
1470
+ return ranges;
1471
+ }
1472
+ function CodeBlockLeaf({ attributes, children, leaf }) {
1473
+ const leafAny = leaf;
1474
+ if (leafAny.codeBlock) {
1475
+ return /* @__PURE__ */ jsx(
1476
+ "span",
1477
+ {
1478
+ ...attributes,
1479
+ className: "bg-muted/50 text-muted-foreground font-mono text-sm rounded px-1",
1480
+ children
1481
+ }
1482
+ );
1483
+ }
1484
+ if (leafAny.codeDelimiter) {
1485
+ return /* @__PURE__ */ jsx(
1486
+ "span",
1487
+ {
1488
+ ...attributes,
1489
+ className: "text-muted-foreground/50 font-mono text-sm",
1490
+ children
1491
+ }
1492
+ );
1493
+ }
1494
+ return /* @__PURE__ */ jsx("span", { ...attributes, children });
1495
+ }
1496
+ var UserPromptInput = React11.forwardRef(
1497
+ ({
1498
+ value = "",
1499
+ onChange,
1500
+ onSubmit,
1501
+ clearOnSubmit = true,
1502
+ placeholder = "Type your message...",
1503
+ disabled = false,
1504
+ isSubmitting = false,
1505
+ onStop,
1506
+ disableWhileSubmitting = true,
1507
+ autoFocus = false,
1508
+ refocusAfterSubmit = false,
1509
+ onReady,
1510
+ minRows = 1,
1511
+ maxRows = 6,
1512
+ renderActions,
1513
+ enableTags = false,
1514
+ onTagCreate,
1515
+ onTagDelete,
1516
+ className,
1517
+ ...props
1518
+ }, ref) => {
1519
+ const editorRef = React11.useRef(null);
1520
+ const [internalValue, setInternalValue] = React11.useState(value);
1521
+ const prevIsSubmitting = React11.useRef(isSubmitting);
1522
+ const hasEmittedReady = React11.useRef(false);
1523
+ React11.useEffect(() => {
1524
+ setInternalValue(value);
1525
+ }, [value]);
1526
+ React11.useEffect(() => {
1527
+ if (autoFocus) {
1528
+ requestAnimationFrame(() => {
1529
+ requestAnimationFrame(() => {
1530
+ editorRef.current?.focus();
1531
+ });
1532
+ });
1533
+ }
1534
+ }, [autoFocus]);
1535
+ React11.useEffect(() => {
1536
+ if (!hasEmittedReady.current && onReady) {
1537
+ requestAnimationFrame(() => {
1538
+ requestAnimationFrame(() => {
1539
+ hasEmittedReady.current = true;
1540
+ onReady();
1541
+ });
1542
+ });
1543
+ }
1544
+ }, [onReady]);
1545
+ React11.useEffect(() => {
1546
+ if (refocusAfterSubmit && prevIsSubmitting.current && !isSubmitting) {
1547
+ requestAnimationFrame(() => {
1548
+ editorRef.current?.focus();
1549
+ });
1550
+ }
1551
+ prevIsSubmitting.current = isSubmitting;
1552
+ }, [isSubmitting, refocusAfterSubmit]);
1553
+ React11.useImperativeHandle(
1554
+ ref,
1555
+ () => ({
1556
+ focus: () => {
1557
+ try {
1558
+ editorRef.current?.focus();
1559
+ } catch {
1560
+ requestAnimationFrame(() => {
1561
+ requestAnimationFrame(() => {
1562
+ editorRef.current?.focus();
1563
+ });
1564
+ });
1565
+ }
1566
+ },
1567
+ clear: () => {
1568
+ editorRef.current?.clear();
1569
+ setInternalValue("");
1570
+ },
1571
+ getText: () => editorRef.current?.getText() ?? "",
1572
+ insertText: (text) => editorRef.current?.insertText(text)
1573
+ }),
1574
+ []
1575
+ );
1576
+ const handleChange = React11.useCallback(
1577
+ (newValue) => {
1578
+ setInternalValue(newValue);
1579
+ onChange?.(newValue);
1580
+ },
1581
+ [onChange]
1582
+ );
1583
+ const handleSubmit = React11.useCallback(
1584
+ (text) => {
1585
+ if (disabled || isSubmitting) return;
1586
+ if (!text.trim()) return;
1587
+ onSubmit?.(text.trim());
1588
+ if (clearOnSubmit) {
1589
+ editorRef.current?.clear();
1590
+ setInternalValue("");
1591
+ }
1592
+ },
1593
+ [disabled, isSubmitting, onSubmit, clearOnSubmit]
1594
+ );
1595
+ const handleSendClick = React11.useCallback(() => {
1596
+ const text = editorRef.current?.getText() ?? "";
1597
+ handleSubmit(text);
1598
+ }, [handleSubmit]);
1599
+ const hasContent = internalValue.trim().length > 0;
1600
+ const canSubmit = hasContent && !disabled && !isSubmitting;
1601
+ return /* @__PURE__ */ jsxs(
1602
+ "div",
1603
+ {
1604
+ className: cn(
1605
+ "rounded-lg border border-input bg-background",
1606
+ disabled && "opacity-50 cursor-not-allowed",
1607
+ className
1608
+ ),
1609
+ ...props,
1610
+ children: [
1611
+ /* @__PURE__ */ jsx("div", { className: "pl-2 pr-0 pt-1 pb-1", children: /* @__PURE__ */ jsx(
1612
+ SlateEditor,
1613
+ {
1614
+ ref: editorRef,
1615
+ value: internalValue,
1616
+ onChange: handleChange,
1617
+ onSubmit: handleSubmit,
1618
+ clearOnSubmit: false,
1619
+ placeholder,
1620
+ disabled: disabled || disableWhileSubmitting && isSubmitting,
1621
+ enableTags,
1622
+ onTagCreate,
1623
+ onTagDelete,
1624
+ minRows,
1625
+ maxRows,
1626
+ decorate: createCodeBlockDecorate,
1627
+ renderLeaf: CodeBlockLeaf
1628
+ }
1629
+ ) }),
1630
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pl-2 pr-1 pb-1 pt-1", children: [
1631
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: renderActions?.() }),
1632
+ isSubmitting && onStop ? /* @__PURE__ */ jsx(
1633
+ IconButton,
1634
+ {
1635
+ icon: /* @__PURE__ */ jsx(Square, {}),
1636
+ variant: "filled",
1637
+ size: "sm",
1638
+ "aria-label": "Stop",
1639
+ onClick: onStop
1640
+ }
1641
+ ) : /* @__PURE__ */ jsx(
1642
+ IconButton,
1643
+ {
1644
+ icon: isSubmitting ? /* @__PURE__ */ jsx(Loader2, { className: "animate-spin" }) : /* @__PURE__ */ jsx(Send, {}),
1645
+ variant: "filled",
1646
+ size: "sm",
1647
+ "aria-label": isSubmitting ? "Sending..." : "Send message",
1648
+ disabled: !canSubmit,
1649
+ onClick: handleSendClick
1650
+ }
1651
+ )
1652
+ ] })
1653
+ ]
1654
+ }
1655
+ );
1656
+ }
1657
+ );
1658
+ UserPromptInput.displayName = "UserPromptInput";
1659
+
1660
+ // src/components/inline-actions/parseResponseSegments.ts
1661
+ var ACTION_BLOCK_REGEX = /```json:action\s*\n([\s\S]*?)```/g;
1662
+ function parseResponseSegments(text) {
1663
+ if (!text) return [];
1664
+ const segments = [];
1665
+ let lastIndex = 0;
1666
+ ACTION_BLOCK_REGEX.lastIndex = 0;
1667
+ let match;
1668
+ while ((match = ACTION_BLOCK_REGEX.exec(text)) !== null) {
1669
+ const before = text.slice(lastIndex, match.index);
1670
+ if (before.trim()) {
1671
+ segments.push({ kind: "markdown", content: before });
1672
+ }
1673
+ const jsonContent = match[1].trim();
1674
+ let parsed = null;
1675
+ try {
1676
+ parsed = JSON.parse(jsonContent);
1677
+ } catch {
1678
+ }
1679
+ if (parsed && typeof parsed === "object" && typeof parsed.type === "string") {
1680
+ segments.push({
1681
+ kind: "action",
1682
+ actionType: parsed.type,
1683
+ payload: parsed
1684
+ });
1685
+ } else {
1686
+ const rawBlock = match[0];
1687
+ segments.push({ kind: "markdown", content: rawBlock });
1688
+ }
1689
+ lastIndex = match.index + match[0].length;
1690
+ }
1691
+ const trailing = text.slice(lastIndex);
1692
+ if (trailing.trim()) {
1693
+ segments.push({ kind: "markdown", content: trailing });
1694
+ }
1695
+ return segments;
1696
+ }
1697
+ function ActionMarkdownRenderer({
1698
+ content,
1699
+ registry,
1700
+ renderMarkdown,
1701
+ onAction,
1702
+ isLatest
1703
+ }) {
1704
+ const segments = useMemo(() => parseResponseSegments(content), [content]);
1705
+ if (segments.length === 1 && segments[0].kind === "markdown") {
1706
+ return /* @__PURE__ */ jsx(Fragment, { children: renderMarkdown(segments[0].content) });
1707
+ }
1708
+ return /* @__PURE__ */ jsx(Fragment, { children: segments.map((segment, index) => {
1709
+ if (segment.kind === "markdown") {
1710
+ return /* @__PURE__ */ jsx("div", { children: renderMarkdown(segment.content) }, `md-${index}`);
1711
+ }
1712
+ const Component = registry[segment.actionType];
1713
+ if (!Component) {
1714
+ return /* @__PURE__ */ jsx(
1715
+ "pre",
1716
+ {
1717
+ className: "my-4 p-4 rounded-lg border border-border bg-muted text-sm font-mono overflow-x-auto",
1718
+ children: /* @__PURE__ */ jsx("code", { children: JSON.stringify(segment.payload, null, 2) })
1719
+ },
1720
+ `action-fallback-${index}`
1721
+ );
1722
+ }
1723
+ return /* @__PURE__ */ jsx(
1724
+ Component,
1725
+ {
1726
+ payload: segment.payload,
1727
+ onAction,
1728
+ isLatest
1729
+ },
1730
+ `action-${segment.actionType}-${index}`
1731
+ );
1732
+ }) });
1733
+ }
1734
+
1735
+ // src/components/inline-actions/prompts.ts
1736
+ var INLINE_ACTION_PROMPT = `
1737
+ <inline_actions>
1738
+ When your response should include interactive components (like query viewers,
1739
+ data tables, or executable actions), embed them as fenced code blocks using
1740
+ the \`json:action\` language tag:
1741
+
1742
+ \`\`\`json:action
1743
+ {
1744
+ "type": "action-type-here",
1745
+ ...action-specific fields
1746
+ }
1747
+ \`\`\`
1748
+
1749
+ Rules:
1750
+ - Each block must contain valid JSON with a "type" field.
1751
+ - The "type" must match a registered action component on the frontend.
1752
+ - Multiple action blocks per response are allowed.
1753
+ - Surround action blocks with normal markdown text for user context.
1754
+ - The action block is rendered as an interactive component in the chat UI.
1755
+ - SQL strings inside JSON must be properly escaped (newlines as \\n, quotes as \\").
1756
+
1757
+ Available action types:
1758
+
1759
+ - "optimap-query": Displays SQL queries with a button to execute them and
1760
+ update the 3D globe map.
1761
+ Required fields:
1762
+ - type: "optimap-query"
1763
+ - locations_sql: string (the validated locations SQL query)
1764
+ - routes_sql: string (the validated routes SQL query)
1765
+ - database_name: string (the target database name)
1766
+ </inline_actions>
1767
+ `;
534
1768
 
535
- export { ActionBar, ActivityIndicators, AgentResponse, MetadataRow, ThinkingSection, formatTime, formatTotalTime, initialAgentResponseState, useAgentResponseAccumulator, useThinkingTimer };
1769
+ export { ActionBar, ActionMarkdownRenderer, ActivityIndicators, AgentResponse, AgentTimeline, HITLInteractionRecord, HITLQuestionPanel, HITLSection, INLINE_ACTION_PROMPT, MetadataRow, ThinkingSection, TruncatedMessage, UserPrompt, UserPromptInput, buildResponseString, buildTimelineEntries, createTimelineUIState, deduplicateEntries, formatTime, formatTotalTime, groupIntoAgentRuns, initialAgentResponseState, parseResponseSegments, useAgentResponseAccumulator, useThinkingTimer };
536
1770
  //# sourceMappingURL=index.js.map
537
1771
  //# sourceMappingURL=index.js.map