@optilogic/chat 1.0.0-beta.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +136 -0
  2. package/dist/index.cjs +989 -58
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +361 -2
  5. package/dist/index.d.ts +361 -2
  6. package/dist/index.js +964 -46
  7. package/dist/index.js.map +1 -1
  8. package/package.json +3 -3
  9. package/src/components/agent-response/AgentResponse.tsx +86 -14
  10. package/src/components/agent-response/components/HITLSection.tsx +95 -0
  11. package/src/components/agent-response/components/MetadataRow.tsx +15 -4
  12. package/src/components/agent-response/components/TruncatedMessage.tsx +52 -0
  13. package/src/components/agent-response/components/index.ts +6 -0
  14. package/src/components/agent-response/hooks/useAgentResponseAccumulator.ts +65 -8
  15. package/src/components/agent-response/index.ts +21 -0
  16. package/src/components/agent-response/types.ts +61 -1
  17. package/src/components/agent-timeline/AgentTimeline.tsx +256 -0
  18. package/src/components/agent-timeline/TimelineAgentBlock.tsx +84 -0
  19. package/src/components/agent-timeline/TimelineItem.tsx +97 -0
  20. package/src/components/agent-timeline/index.ts +14 -0
  21. package/src/components/agent-timeline/types.ts +49 -0
  22. package/src/components/agent-timeline/utils.ts +189 -0
  23. package/src/components/hitl-interactions/HITLInteractionRecord.tsx +139 -0
  24. package/src/components/hitl-interactions/HITLQuestionPanel.tsx +270 -0
  25. package/src/components/hitl-interactions/index.ts +18 -0
  26. package/src/components/inline-actions/ActionMarkdownRenderer.tsx +60 -0
  27. package/src/components/inline-actions/index.ts +18 -0
  28. package/src/components/inline-actions/parseResponseSegments.ts +66 -0
  29. package/src/components/inline-actions/prompts.ts +41 -0
  30. package/src/components/inline-actions/types.ts +57 -0
  31. package/src/components/user-prompt-input/UserPromptInput.tsx +13 -8
  32. package/src/components/user-prompt-input/types.ts +4 -0
  33. package/src/index.ts +42 -0
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var React7 = require('react');
3
+ var React11 = require('react');
4
4
  var core = require('@optilogic/core');
5
5
  var lucideReact = require('lucide-react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
@@ -24,10 +24,10 @@ function _interopNamespace(e) {
24
24
  return Object.freeze(n);
25
25
  }
26
26
 
27
- var React7__namespace = /*#__PURE__*/_interopNamespace(React7);
27
+ var React11__namespace = /*#__PURE__*/_interopNamespace(React11);
28
28
 
29
29
  // src/components/agent-response/AgentResponse.tsx
30
- var ActivityIndicators = React7__namespace.forwardRef(
30
+ var ActivityIndicators = React11__namespace.forwardRef(
31
31
  ({ toolCalls, knowledge, memory, statusUpdates = [], className, ...props }, ref) => {
32
32
  const hasAnyActivity = toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0 || statusUpdates.length > 0;
33
33
  if (!hasAnyActivity) return null;
@@ -146,7 +146,7 @@ function formatTotalTime(seconds) {
146
146
  const minutes = seconds / 60;
147
147
  return `${minutes.toFixed(1)}m`;
148
148
  }
149
- var MetadataRow = React7__namespace.forwardRef(
149
+ var MetadataRow = React11__namespace.forwardRef(
150
150
  ({
151
151
  hasThinking,
152
152
  isExpanded,
@@ -155,6 +155,7 @@ var MetadataRow = React7__namespace.forwardRef(
155
155
  knowledge,
156
156
  memory,
157
157
  statusUpdates = [],
158
+ statusContent,
158
159
  status,
159
160
  elapsedTime,
160
161
  className,
@@ -179,7 +180,7 @@ var MetadataRow = React7__namespace.forwardRef(
179
180
  return null;
180
181
  };
181
182
  const leftContent = renderLeftContent();
182
- if (!leftContent && !hasActivity) {
183
+ if (!leftContent && !hasActivity && !statusContent) {
183
184
  return null;
184
185
  }
185
186
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -193,10 +194,11 @@ var MetadataRow = React7__namespace.forwardRef(
193
194
  "button",
194
195
  {
195
196
  onClick: onToggle,
196
- 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",
197
+ 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",
197
198
  children: leftContent
198
199
  }
199
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5", children: leftContent }),
200
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 shrink-0", children: leftContent }),
201
+ statusContent && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0 mx-2", children: statusContent }),
200
202
  /* @__PURE__ */ jsxRuntime.jsx(
201
203
  ActivityIndicators,
202
204
  {
@@ -213,8 +215,8 @@ var MetadataRow = React7__namespace.forwardRef(
213
215
  );
214
216
  MetadataRow.displayName = "MetadataRow";
215
217
  var ThinkingStepItem = ({ step, renderMarkdown }) => {
216
- const [isCollapsed, setIsCollapsed] = React7.useState(step.isCollapsed ?? false);
217
- const toggleCollapse = React7.useCallback(() => {
218
+ const [isCollapsed, setIsCollapsed] = React11.useState(step.isCollapsed ?? false);
219
+ const toggleCollapse = React11.useCallback(() => {
218
220
  setIsCollapsed((prev) => !prev);
219
221
  }, []);
220
222
  const indentPadding = step.depth * 16;
@@ -241,7 +243,7 @@ var ThinkingStepItem = ({ step, renderMarkdown }) => {
241
243
  )
242
244
  ] });
243
245
  };
244
- var ThinkingSection = React7__namespace.forwardRef(
246
+ var ThinkingSection = React11__namespace.forwardRef(
245
247
  ({ content, isExpanded, renderMarkdown, className, ...props }, ref) => {
246
248
  if (!isExpanded || !content || Array.isArray(content) && content.length === 0) {
247
249
  return null;
@@ -266,7 +268,7 @@ var ThinkingSection = React7__namespace.forwardRef(
266
268
  }
267
269
  );
268
270
  ThinkingSection.displayName = "ThinkingSection";
269
- var ActionBar = React7__namespace.forwardRef(
271
+ var ActionBar = React11__namespace.forwardRef(
270
272
  ({
271
273
  response,
272
274
  isVisible,
@@ -277,8 +279,8 @@ var ActionBar = React7__namespace.forwardRef(
277
279
  className,
278
280
  ...props
279
281
  }, ref) => {
280
- const [copied, setCopied] = React7.useState(false);
281
- const handleCopy = React7.useCallback(async () => {
282
+ const [copied, setCopied] = React11.useState(false);
283
+ const handleCopy = React11.useCallback(async () => {
282
284
  try {
283
285
  await navigator.clipboard.writeText(response);
284
286
  setCopied(true);
@@ -288,11 +290,11 @@ var ActionBar = React7__namespace.forwardRef(
288
290
  console.error("Failed to copy response:", err);
289
291
  }
290
292
  }, [response, onResponseCopy]);
291
- const handleThumbsUp = React7.useCallback(() => {
293
+ const handleThumbsUp = React11.useCallback(() => {
292
294
  const newValue = feedback === "up" ? null : "up";
293
295
  onFeedbackChange?.(newValue);
294
296
  }, [feedback, onFeedbackChange]);
295
- const handleThumbsDown = React7.useCallback(() => {
297
+ const handleThumbsDown = React11.useCallback(() => {
296
298
  const newValue = feedback === "down" ? null : "down";
297
299
  onFeedbackChange?.(newValue);
298
300
  }, [feedback, onFeedbackChange]);
@@ -355,13 +357,320 @@ var ActionBar = React7__namespace.forwardRef(
355
357
  }
356
358
  );
357
359
  ActionBar.displayName = "ActionBar";
360
+ function buildResponseString(questions, selectedOptions, freeformText) {
361
+ const parts = [];
362
+ for (const q of questions) {
363
+ const answer = selectedOptions[q];
364
+ if (answer) {
365
+ parts.push(`Q: ${q}
366
+ A: ${answer}`);
367
+ }
368
+ }
369
+ const trimmed = freeformText.trim();
370
+ if (trimmed) {
371
+ parts.push(`Additional context: ${trimmed}`);
372
+ }
373
+ return parts.join("\n\n");
374
+ }
375
+ var HITLQuestionPanel = React11__namespace.forwardRef(({ question, onSubmit, onStop, className, ...props }, ref) => {
376
+ const [freeformText, setFreeformText] = React11.useState("");
377
+ const [selectedOptions, setSelectedOptions] = React11.useState({});
378
+ const hasTimeout = question.timeoutSeconds != null;
379
+ const [secondsLeft, setSecondsLeft] = React11.useState(
380
+ () => hasTimeout ? Math.max(
381
+ 0,
382
+ Math.round(
383
+ question.timeoutSeconds - (Date.now() - question.receivedAt) / 1e3
384
+ )
385
+ ) : Infinity
386
+ );
387
+ const textareaRef = React11.useRef(null);
388
+ React11.useEffect(() => {
389
+ textareaRef.current?.focus();
390
+ }, []);
391
+ React11.useEffect(() => {
392
+ if (!hasTimeout) return;
393
+ const interval = setInterval(() => {
394
+ const remaining = Math.max(
395
+ 0,
396
+ Math.round(
397
+ question.timeoutSeconds - (Date.now() - question.receivedAt) / 1e3
398
+ )
399
+ );
400
+ setSecondsLeft(remaining);
401
+ if (remaining <= 0) clearInterval(interval);
402
+ }, 1e3);
403
+ return () => clearInterval(interval);
404
+ }, [hasTimeout, question.timeoutSeconds, question.receivedAt]);
405
+ const timedOut = hasTimeout && secondsLeft <= 0;
406
+ const questionsWithOptions = React11.useMemo(
407
+ () => question.questions.filter((q) => question.options?.[q]?.length),
408
+ [question.questions, question.options]
409
+ );
410
+ const allOptionsSelected = questionsWithOptions.length > 0 && questionsWithOptions.every((q) => selectedOptions[q]);
411
+ const hasFreeformText = freeformText.trim().length > 0;
412
+ const canSubmit = !timedOut && (allOptionsSelected || hasFreeformText);
413
+ const handleOptionClick = React11.useCallback(
414
+ (questionText, option) => {
415
+ if (timedOut) return;
416
+ setSelectedOptions((prev) => {
417
+ if (prev[questionText] === option) {
418
+ const next = { ...prev };
419
+ delete next[questionText];
420
+ return next;
421
+ }
422
+ return { ...prev, [questionText]: option };
423
+ });
424
+ },
425
+ [timedOut]
426
+ );
427
+ const handleSubmit = React11.useCallback(() => {
428
+ if (!canSubmit) return;
429
+ const combined = buildResponseString(
430
+ question.questions,
431
+ selectedOptions,
432
+ freeformText
433
+ );
434
+ onSubmit(combined, { selectedOptions, freeformText });
435
+ }, [canSubmit, question.questions, selectedOptions, freeformText, onSubmit]);
436
+ const handleKeyDown = React11.useCallback(
437
+ (e) => {
438
+ if (e.key === "Enter" && !e.shiftKey) {
439
+ e.preventDefault();
440
+ handleSubmit();
441
+ }
442
+ },
443
+ [handleSubmit]
444
+ );
445
+ const formatTime2 = (s) => {
446
+ const m = Math.floor(s / 60);
447
+ const sec = s % 60;
448
+ return `${m}:${sec.toString().padStart(2, "0")}`;
449
+ };
450
+ return /* @__PURE__ */ jsxRuntime.jsxs(
451
+ "div",
452
+ {
453
+ ref,
454
+ className: core.cn(
455
+ "rounded-lg border border-border bg-muted p-4 space-y-3",
456
+ className
457
+ ),
458
+ ...props,
459
+ children: [
460
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
461
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: question.reason }) }),
462
+ hasTimeout && /* @__PURE__ */ jsxRuntime.jsx(
463
+ "span",
464
+ {
465
+ className: core.cn(
466
+ "text-xs font-mono whitespace-nowrap",
467
+ secondsLeft <= 30 ? "text-destructive" : "text-muted-foreground"
468
+ ),
469
+ children: timedOut ? "Timed out" : formatTime2(secondsLeft)
470
+ }
471
+ )
472
+ ] }),
473
+ question.context && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground bg-background rounded p-2 border border-border max-h-24 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "whitespace-pre-wrap font-mono", children: question.context }) }),
474
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: question.questions.map((q, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
475
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground", children: q }),
476
+ question.options?.[q] && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 mt-1.5", children: question.options[q].map((option, j) => {
477
+ const isSelected = selectedOptions[q] === option;
478
+ return /* @__PURE__ */ jsxRuntime.jsx(
479
+ core.Button,
480
+ {
481
+ variant: isSelected ? "primary" : "outline",
482
+ size: "sm",
483
+ onClick: () => handleOptionClick(q, option),
484
+ disabled: timedOut,
485
+ children: option
486
+ },
487
+ j
488
+ );
489
+ }) })
490
+ ] }, i)) }),
491
+ /* @__PURE__ */ jsxRuntime.jsx(
492
+ core.Textarea,
493
+ {
494
+ ref: textareaRef,
495
+ value: freeformText,
496
+ onChange: (e) => setFreeformText(e.target.value),
497
+ onKeyDown: handleKeyDown,
498
+ disabled: timedOut,
499
+ placeholder: "Add additional context or type a full response...",
500
+ rows: 2,
501
+ className: "resize-none"
502
+ }
503
+ ),
504
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
505
+ /* @__PURE__ */ jsxRuntime.jsx(
506
+ core.Button,
507
+ {
508
+ variant: "ghost",
509
+ size: "sm",
510
+ onClick: onStop,
511
+ className: "text-destructive hover:text-destructive hover:bg-destructive/10",
512
+ children: "Stop agent"
513
+ }
514
+ ),
515
+ /* @__PURE__ */ jsxRuntime.jsx(
516
+ core.Button,
517
+ {
518
+ variant: "primary",
519
+ size: "sm",
520
+ onClick: handleSubmit,
521
+ disabled: !canSubmit,
522
+ children: "Send response"
523
+ }
524
+ )
525
+ ] })
526
+ ]
527
+ }
528
+ );
529
+ });
530
+ HITLQuestionPanel.displayName = "HITLQuestionPanel";
531
+ function parseResponse(response) {
532
+ const answers = {};
533
+ let additionalContext = null;
534
+ const blocks = response.split("\n\n");
535
+ for (const block of blocks) {
536
+ const qaMatch = block.match(/^Q: (.+)\nA: (.+)$/s);
537
+ if (qaMatch) {
538
+ answers[qaMatch[1].trim()] = qaMatch[2].trim();
539
+ } else if (block.startsWith("Additional context: ")) {
540
+ additionalContext = block.slice("Additional context: ".length).trim();
541
+ }
542
+ }
543
+ return { answers, additionalContext };
544
+ }
545
+ var HITLInteractionRecord = React11__namespace.forwardRef(({ interaction, className, ...props }, ref) => {
546
+ const { question, response, respondedAt } = interaction;
547
+ const timestamp = new Date(respondedAt).toLocaleTimeString([], {
548
+ hour: "2-digit",
549
+ minute: "2-digit"
550
+ });
551
+ const { answers, additionalContext } = React11.useMemo(
552
+ () => parseResponse(response),
553
+ [response]
554
+ );
555
+ const hasParsedAnswers = Object.keys(answers).length > 0;
556
+ return /* @__PURE__ */ jsxRuntime.jsxs(
557
+ "div",
558
+ {
559
+ ref,
560
+ className: core.cn(
561
+ "rounded-lg border border-border bg-muted p-3 space-y-2 text-sm",
562
+ className
563
+ ),
564
+ ...props,
565
+ children: [
566
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
567
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-muted-foreground", children: "Clarifying Question" }),
568
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: timestamp })
569
+ ] }),
570
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground font-medium", children: question.reason }),
571
+ question.context && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground bg-background rounded p-2 border border-border", children: /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "whitespace-pre-wrap font-mono", children: question.context }) }),
572
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: question.questions.map((q, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
573
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground", children: q }),
574
+ question.options?.[q] && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground ml-2", children: [
575
+ "Options: ",
576
+ question.options[q].join(", ")
577
+ ] }),
578
+ hasParsedAnswers && answers[q] && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-foreground ml-2 mt-0.5 bg-background rounded px-2 py-1 border border-border", children: [
579
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Answer: " }),
580
+ answers[q]
581
+ ] })
582
+ ] }, i)) }),
583
+ hasParsedAnswers && additionalContext && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-border pt-2", children: [
584
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: "Additional context:" }),
585
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground mt-0.5", children: additionalContext })
586
+ ] }),
587
+ !hasParsedAnswers && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-border pt-2", children: [
588
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: "Response:" }),
589
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground mt-0.5", children: response })
590
+ ] })
591
+ ]
592
+ }
593
+ );
594
+ });
595
+ HITLInteractionRecord.displayName = "HITLInteractionRecord";
596
+ var HITLSection = React11__namespace.forwardRef(
597
+ ({
598
+ interactions,
599
+ defaultExpanded = false,
600
+ isExpanded: controlledExpanded,
601
+ onExpandedChange,
602
+ className,
603
+ ...props
604
+ }, ref) => {
605
+ const [uncontrolledExpanded, setUncontrolledExpanded] = React11.useState(defaultExpanded);
606
+ const isControlled = controlledExpanded !== void 0;
607
+ const isExpanded = isControlled ? controlledExpanded : uncontrolledExpanded;
608
+ const toggleExpanded = React11.useCallback(() => {
609
+ const newValue = !isExpanded;
610
+ if (isControlled) {
611
+ onExpandedChange?.(newValue);
612
+ } else {
613
+ setUncontrolledExpanded(newValue);
614
+ }
615
+ }, [isExpanded, isControlled, onExpandedChange]);
616
+ if (!interactions || interactions.length === 0) {
617
+ return null;
618
+ }
619
+ return /* @__PURE__ */ jsxRuntime.jsxs(
620
+ "div",
621
+ {
622
+ ref,
623
+ className: core.cn("border-t border-border", className),
624
+ ...props,
625
+ children: [
626
+ /* @__PURE__ */ jsxRuntime.jsxs(
627
+ "button",
628
+ {
629
+ onClick: toggleExpanded,
630
+ className: "w-full flex items-center gap-2 py-2 px-3 hover:bg-muted/50 transition-colors text-left",
631
+ children: [
632
+ isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "w-3.5 h-3.5 text-muted-foreground flex-shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "w-3.5 h-3.5 text-muted-foreground flex-shrink-0" }),
633
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageCircleQuestion, { className: "w-3.5 h-3.5 text-muted-foreground flex-shrink-0" }),
634
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium text-foreground/80", children: [
635
+ "Clarifying Questions (",
636
+ interactions.length,
637
+ ")"
638
+ ] })
639
+ ]
640
+ }
641
+ ),
642
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pb-3 space-y-2", children: interactions.map((interaction, i) => /* @__PURE__ */ jsxRuntime.jsx(HITLInteractionRecord, { interaction }, i)) })
643
+ ]
644
+ }
645
+ );
646
+ }
647
+ );
648
+ HITLSection.displayName = "HITLSection";
649
+ var TruncatedMessage = React11__namespace.forwardRef(
650
+ ({ message, className, ...props }, ref) => {
651
+ return /* @__PURE__ */ jsxRuntime.jsx(
652
+ "div",
653
+ {
654
+ ref,
655
+ className: core.cn(
656
+ "text-xs text-muted-foreground truncate min-w-0",
657
+ className
658
+ ),
659
+ title: message,
660
+ ...props,
661
+ children: message
662
+ }
663
+ );
664
+ }
665
+ );
666
+ TruncatedMessage.displayName = "TruncatedMessage";
358
667
  function useThinkingTimer({
359
668
  startTime,
360
669
  endTime,
361
670
  status
362
671
  }) {
363
- const [elapsed, setElapsed] = React7.useState(0);
364
- React7.useEffect(() => {
672
+ const [elapsed, setElapsed] = React11.useState(0);
673
+ React11.useEffect(() => {
365
674
  if (!startTime) {
366
675
  setElapsed(0);
367
676
  return;
@@ -390,17 +699,157 @@ var initialAgentResponseState = {
390
699
  knowledge: [],
391
700
  memory: [],
392
701
  statusUpdates: [],
702
+ potentialResponses: [],
703
+ customTimelineEntries: [],
393
704
  response: "",
394
705
  thinkingStartTime: null,
395
706
  responseCompleteTime: null,
396
707
  firstMessageTime: null
397
708
  };
398
709
 
710
+ // src/components/agent-timeline/utils.ts
711
+ function buildTimelineEntries(state) {
712
+ const entries = [];
713
+ if (state.thinkingSteps) {
714
+ let idx = 0;
715
+ for (const step of state.thinkingSteps) {
716
+ entries.push({
717
+ id: `tl-think-${idx++}`,
718
+ type: "thinking",
719
+ agentName: step.agentName ?? null,
720
+ parentAgent: step.parentAgent ?? null,
721
+ depth: step.depth ?? 0,
722
+ content: step.content,
723
+ title: step.label,
724
+ timestamp: step.timestamp ?? 0
725
+ });
726
+ }
727
+ } else if (state.thinking) {
728
+ entries.push({
729
+ id: "tl-think-0",
730
+ type: "thinking",
731
+ agentName: null,
732
+ parentAgent: null,
733
+ depth: 0,
734
+ content: state.thinking,
735
+ title: null,
736
+ timestamp: state.thinkingStartTime ?? 0
737
+ });
738
+ }
739
+ let toolIdx = 0;
740
+ for (const tool of state.toolCalls) {
741
+ entries.push({
742
+ id: `tl-tool-${toolIdx++}`,
743
+ type: "tool_call",
744
+ agentName: tool.agentName ?? null,
745
+ parentAgent: tool.parentAgent ?? null,
746
+ depth: tool.depth ?? 0,
747
+ content: tool.name,
748
+ title: null,
749
+ timestamp: tool.timestamp
750
+ });
751
+ }
752
+ let knowIdx = 0;
753
+ for (const item of state.knowledge) {
754
+ entries.push({
755
+ id: `tl-know-${knowIdx++}`,
756
+ type: "knowledge",
757
+ agentName: item.agentName ?? null,
758
+ parentAgent: item.parentAgent ?? null,
759
+ depth: item.depth ?? 0,
760
+ content: item.content,
761
+ title: item.source,
762
+ timestamp: item.timestamp
763
+ });
764
+ }
765
+ let memIdx = 0;
766
+ for (const item of state.memory) {
767
+ entries.push({
768
+ id: `tl-mem-${memIdx++}`,
769
+ type: "memory",
770
+ agentName: item.agentName ?? null,
771
+ parentAgent: item.parentAgent ?? null,
772
+ depth: item.depth ?? 0,
773
+ content: item.content,
774
+ title: item.type,
775
+ timestamp: item.timestamp
776
+ });
777
+ }
778
+ let statIdx = 0;
779
+ for (const item of state.statusUpdates) {
780
+ entries.push({
781
+ id: `tl-stat-${statIdx++}`,
782
+ type: "status_update",
783
+ agentName: item.agentName ?? item.agent ?? null,
784
+ parentAgent: item.parentAgent ?? null,
785
+ depth: item.depth ?? 0,
786
+ content: item.message,
787
+ title: null,
788
+ timestamp: item.timestamp
789
+ });
790
+ }
791
+ if (state.potentialResponses) {
792
+ let respIdx = 0;
793
+ for (const resp of state.potentialResponses) {
794
+ entries.push({
795
+ id: `tl-resp-${respIdx++}`,
796
+ type: "ai_response",
797
+ agentName: resp.agentName ?? null,
798
+ parentAgent: resp.parentAgent ?? null,
799
+ depth: resp.depth ?? 0,
800
+ content: resp.content,
801
+ title: null,
802
+ timestamp: resp.timestamp
803
+ });
804
+ }
805
+ }
806
+ if (state.customTimelineEntries) {
807
+ entries.push(...state.customTimelineEntries);
808
+ }
809
+ entries.sort((a, b) => a.timestamp - b.timestamp);
810
+ return entries;
811
+ }
812
+ function groupIntoAgentRuns(entries) {
813
+ const runs = [];
814
+ let currentRun = null;
815
+ for (const entry of entries) {
816
+ const name = entry.agentName || "Agent";
817
+ if (!currentRun || currentRun.agentName !== name) {
818
+ currentRun = {
819
+ agentName: name,
820
+ parentAgent: entry.parentAgent,
821
+ depth: entry.depth,
822
+ entries: []
823
+ };
824
+ runs.push(currentRun);
825
+ }
826
+ currentRun.entries.push({ entry, count: 1 });
827
+ }
828
+ for (const run of runs) {
829
+ run.entries = deduplicateEntries(run.entries);
830
+ }
831
+ return runs;
832
+ }
833
+ function deduplicateEntries(entries) {
834
+ if (entries.length === 0) return [];
835
+ const result = [entries[0]];
836
+ for (let i = 1; i < entries.length; i++) {
837
+ const prev = result[result.length - 1];
838
+ const curr = entries[i];
839
+ if (prev.entry.type === curr.entry.type && prev.entry.content === curr.entry.content) {
840
+ prev.count += curr.count;
841
+ } else {
842
+ result.push({ ...curr });
843
+ }
844
+ }
845
+ return result;
846
+ }
847
+
399
848
  // src/components/agent-response/hooks/useAgentResponseAccumulator.ts
400
849
  function useAgentResponseAccumulator(options) {
401
- const [state, setState] = React7.useState(initialAgentResponseState);
850
+ const [state, setState] = React11.useState(initialAgentResponseState);
402
851
  const topic = options?.topic;
403
- const handleMessage = React7.useCallback(
852
+ const handleMessage = React11.useCallback(
404
853
  (message) => {
405
854
  let payload;
406
855
  if (topic) {
@@ -429,28 +878,44 @@ function useAgentResponseAccumulator(options) {
429
878
  id: payload.thinkingStep.id || `step-${Date.now()}`,
430
879
  label: payload.thinkingStep.label,
431
880
  content: payload.thinkingStep.content,
432
- depth: payload.thinkingStep.depth ?? 0,
433
- isCollapsed: payload.thinkingStep.isCollapsed
881
+ depth: payload.thinkingStep.depth ?? payload.depth ?? 0,
882
+ isCollapsed: payload.thinkingStep.isCollapsed,
883
+ timestamp: Date.now(),
884
+ agentName: payload.agentName,
885
+ parentAgent: payload.parentAgent
434
886
  };
435
887
  const thinkingStartTime2 = prev.thinkingStartTime ?? Date.now();
436
- return {
888
+ const next2 = {
437
889
  ...prev,
438
890
  status: newStatus,
439
891
  thinkingSteps: [...prev.thinkingSteps || [], newStep],
440
892
  thinkingStartTime: thinkingStartTime2,
441
893
  firstMessageTime
442
894
  };
895
+ return { ...next2, timelineEntries: buildTimelineEntries(next2) };
443
896
  }
444
897
  const newThinking = payload.message || payload.content || "";
445
898
  const separator = prev.thinking && newThinking ? "\n\n" : "";
446
899
  const thinkingStartTime = prev.thinkingStartTime ?? (newThinking ? Date.now() : null);
447
- return {
900
+ const prevSteps = prev.thinkingSteps || [];
901
+ const plainStep = {
902
+ id: `step-${prevSteps.length}`,
903
+ label: newThinking,
904
+ content: newThinking,
905
+ depth: payload.depth ?? 0,
906
+ timestamp: Date.now(),
907
+ agentName: payload.agentName,
908
+ parentAgent: payload.parentAgent
909
+ };
910
+ const next = {
448
911
  ...prev,
449
912
  status: newStatus,
450
913
  thinking: prev.thinking + separator + newThinking,
914
+ thinkingSteps: [...prevSteps, plainStep],
451
915
  thinkingStartTime,
452
916
  firstMessageTime
453
917
  };
918
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
454
919
  }
455
920
  case "tool_call": {
456
921
  const toolName = payload.message || payload.tool?.name;
@@ -459,14 +924,18 @@ function useAgentResponseAccumulator(options) {
459
924
  id: payload.tool?.id || `tool-${Date.now()}`,
460
925
  name: toolName,
461
926
  arguments: payload.tool?.arguments,
462
- timestamp: Date.now()
927
+ timestamp: Date.now(),
928
+ agentName: payload.agentName,
929
+ parentAgent: payload.parentAgent,
930
+ depth: payload.depth
463
931
  };
464
- return {
932
+ const next = {
465
933
  ...prev,
466
934
  status: newStatus,
467
935
  toolCalls: [...prev.toolCalls, newToolCall],
468
936
  firstMessageTime
469
937
  };
938
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
470
939
  }
471
940
  return { ...prev, status: newStatus, firstMessageTime };
472
941
  }
@@ -477,14 +946,18 @@ function useAgentResponseAccumulator(options) {
477
946
  id: payload.knowledge?.id || `knowledge-${Date.now()}`,
478
947
  source: payload.knowledge?.source || "unknown",
479
948
  content: knowledgeContent,
480
- timestamp: Date.now()
949
+ timestamp: Date.now(),
950
+ agentName: payload.agentName,
951
+ parentAgent: payload.parentAgent,
952
+ depth: payload.depth
481
953
  };
482
- return {
954
+ const next = {
483
955
  ...prev,
484
956
  status: newStatus,
485
957
  knowledge: [...prev.knowledge, newKnowledge],
486
958
  firstMessageTime
487
959
  };
960
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
488
961
  }
489
962
  return { ...prev, status: newStatus, firstMessageTime };
490
963
  }
@@ -495,14 +968,18 @@ function useAgentResponseAccumulator(options) {
495
968
  id: payload.memory?.id || `memory-${Date.now()}`,
496
969
  type: payload.memory?.type || "unknown",
497
970
  content: memoryContent,
498
- timestamp: Date.now()
971
+ timestamp: Date.now(),
972
+ agentName: payload.agentName,
973
+ parentAgent: payload.parentAgent,
974
+ depth: payload.depth
499
975
  };
500
- return {
976
+ const next = {
501
977
  ...prev,
502
978
  status: newStatus,
503
979
  memory: [...prev.memory, newMemory],
504
980
  firstMessageTime
505
981
  };
982
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
506
983
  }
507
984
  return { ...prev, status: newStatus, firstMessageTime };
508
985
  }
@@ -521,14 +998,39 @@ function useAgentResponseAccumulator(options) {
521
998
  id: payload.statusUpdate?.id || `status-${Date.now()}`,
522
999
  message: statusMessage,
523
1000
  agent: payload.statusUpdate?.agent,
524
- timestamp: Date.now()
1001
+ timestamp: Date.now(),
1002
+ agentName: payload.agentName,
1003
+ parentAgent: payload.parentAgent,
1004
+ depth: payload.depth
525
1005
  };
526
- return {
1006
+ const next = {
527
1007
  ...prev,
528
1008
  status: newStatus,
529
1009
  statusUpdates: [...prev.statusUpdates, newStatusItem],
530
1010
  firstMessageTime
531
1011
  };
1012
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
1013
+ }
1014
+ return { ...prev, status: newStatus, firstMessageTime };
1015
+ }
1016
+ case "potential_response": {
1017
+ const respContent = payload.message || payload.content || "";
1018
+ if (respContent) {
1019
+ const newResp = {
1020
+ id: `resp-${Date.now()}`,
1021
+ content: respContent,
1022
+ timestamp: Date.now(),
1023
+ agentName: payload.agentName,
1024
+ parentAgent: payload.parentAgent,
1025
+ depth: payload.depth
1026
+ };
1027
+ const next = {
1028
+ ...prev,
1029
+ status: newStatus,
1030
+ potentialResponses: [...prev.potentialResponses || [], newResp],
1031
+ firstMessageTime
1032
+ };
1033
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
532
1034
  }
533
1035
  return { ...prev, status: newStatus, firstMessageTime };
534
1036
  }
@@ -539,12 +1041,293 @@ function useAgentResponseAccumulator(options) {
539
1041
  },
540
1042
  [topic]
541
1043
  );
542
- const reset = React7.useCallback(() => {
1044
+ const reset = React11.useCallback(() => {
543
1045
  setState(initialAgentResponseState);
544
1046
  }, []);
545
1047
  return { state, handleMessage, reset };
546
1048
  }
547
- var AgentResponse = React7__namespace.forwardRef(
1049
+ var ICON_MAP = {
1050
+ thinking: lucideReact.Brain,
1051
+ tool_call: lucideReact.Wrench,
1052
+ knowledge: lucideReact.BookOpen,
1053
+ memory: lucideReact.HardDrive,
1054
+ status_update: lucideReact.Activity,
1055
+ ai_response: lucideReact.MessageSquare,
1056
+ error: lucideReact.AlertCircle
1057
+ };
1058
+ function TimelineItem({
1059
+ displayEntry,
1060
+ renderMarkdown,
1061
+ isExpanded,
1062
+ onToggleExpanded
1063
+ }) {
1064
+ const { entry, count } = displayEntry;
1065
+ const Icon = ICON_MAP[entry.type] ?? lucideReact.Activity;
1066
+ const isLong = entry.content.length > 200 || entry.content.split("\n").length > 3;
1067
+ const canExpand = isLong || entry.type === "ai_response";
1068
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-1 flex items-start gap-2 group", children: [
1069
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "w-3.5 h-3.5 text-muted-foreground flex-shrink-0 mt-0.5" }),
1070
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: isExpanded && entry.type === "ai_response" && renderMarkdown ? (
1071
+ // Expanded AI response: rendered markdown
1072
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1073
+ renderMarkdown(entry.content),
1074
+ /* @__PURE__ */ jsxRuntime.jsx(
1075
+ "button",
1076
+ {
1077
+ onClick: onToggleExpanded,
1078
+ className: "text-[10px] text-muted-foreground/70 hover:text-muted-foreground mt-1",
1079
+ children: "Show less"
1080
+ }
1081
+ )
1082
+ ] })
1083
+ ) : isExpanded ? (
1084
+ // Expanded non-AI: plain text
1085
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1086
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-xs text-muted-foreground whitespace-pre-wrap font-mono", children: entry.content }),
1087
+ /* @__PURE__ */ jsxRuntime.jsx(
1088
+ "button",
1089
+ {
1090
+ onClick: onToggleExpanded,
1091
+ className: "text-[10px] text-muted-foreground/70 hover:text-muted-foreground mt-1",
1092
+ children: "Show less"
1093
+ }
1094
+ )
1095
+ ] })
1096
+ ) : (
1097
+ // Collapsed: truncated with optional expand
1098
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-1.5 min-w-0", children: [
1099
+ /* @__PURE__ */ jsxRuntime.jsx(
1100
+ "div",
1101
+ {
1102
+ className: `text-xs text-muted-foreground min-w-0 ${canExpand ? "line-clamp-2 cursor-pointer hover:text-foreground/80" : ""}`,
1103
+ onClick: canExpand ? onToggleExpanded : void 0,
1104
+ children: entry.content
1105
+ }
1106
+ ),
1107
+ count > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-muted-foreground/60 whitespace-nowrap flex-shrink-0", children: [
1108
+ "(x",
1109
+ count,
1110
+ ")"
1111
+ ] })
1112
+ ] })
1113
+ ) })
1114
+ ] });
1115
+ }
1116
+ function TimelineAgentBlock({
1117
+ block,
1118
+ renderMarkdown,
1119
+ isSingleAgent,
1120
+ isCollapsed,
1121
+ onToggleCollapsed,
1122
+ expandedItems,
1123
+ onToggleItemExpanded
1124
+ }) {
1125
+ const indentPx = block.depth * 16;
1126
+ if (isSingleAgent && block.depth === 0) {
1127
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { paddingLeft: `${indentPx}px` }, children: block.entries.map((displayEntry, i) => /* @__PURE__ */ jsxRuntime.jsx(
1128
+ TimelineItem,
1129
+ {
1130
+ displayEntry,
1131
+ renderMarkdown,
1132
+ isExpanded: expandedItems.has(displayEntry.entry.id),
1133
+ onToggleExpanded: () => onToggleItemExpanded(displayEntry.entry.id)
1134
+ },
1135
+ displayEntry.entry.id + "-" + i
1136
+ )) });
1137
+ }
1138
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { paddingLeft: `${indentPx}px` }, children: [
1139
+ /* @__PURE__ */ jsxRuntime.jsxs(
1140
+ "button",
1141
+ {
1142
+ onClick: onToggleCollapsed,
1143
+ 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",
1144
+ children: [
1145
+ isCollapsed ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "w-3 h-3 text-muted-foreground flex-shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "w-3 h-3 text-muted-foreground flex-shrink-0" }),
1146
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-foreground/80", children: block.agentName }),
1147
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-muted-foreground/60", children: [
1148
+ "(",
1149
+ block.entries.reduce((sum, e) => sum + e.count, 0),
1150
+ ")"
1151
+ ] })
1152
+ ]
1153
+ }
1154
+ ),
1155
+ !isCollapsed && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-4", children: block.entries.map((displayEntry, i) => /* @__PURE__ */ jsxRuntime.jsx(
1156
+ TimelineItem,
1157
+ {
1158
+ displayEntry,
1159
+ renderMarkdown,
1160
+ isExpanded: expandedItems.has(displayEntry.entry.id),
1161
+ onToggleExpanded: () => onToggleItemExpanded(displayEntry.entry.id)
1162
+ },
1163
+ displayEntry.entry.id + "-" + i
1164
+ )) })
1165
+ ] });
1166
+ }
1167
+ function createTimelineUIState() {
1168
+ return {
1169
+ expandedItems: /* @__PURE__ */ new Set(),
1170
+ collapsedRuns: /* @__PURE__ */ new Set(),
1171
+ activeFilters: /* @__PURE__ */ new Set()
1172
+ };
1173
+ }
1174
+ var TYPE_CONFIG = [
1175
+ { type: "status_update", icon: lucideReact.Activity, label: "Status" },
1176
+ { type: "thinking", icon: lucideReact.Brain, label: "Thinking" },
1177
+ { type: "tool_call", icon: lucideReact.Wrench, label: "Tools" },
1178
+ { type: "knowledge", icon: lucideReact.BookOpen, label: "Knowledge" },
1179
+ { type: "memory", icon: lucideReact.HardDrive, label: "Memory" },
1180
+ { type: "ai_response", icon: lucideReact.MessageSquare, label: "AI" },
1181
+ { type: "error", icon: lucideReact.AlertCircle, label: "Errors" }
1182
+ ];
1183
+ function AgentTimeline({ entries, renderMarkdown, uiState, maxHeight = "300px" }) {
1184
+ const containerRef = React11.useRef(null);
1185
+ const [renderTick, setRenderTick] = React11.useState(0);
1186
+ const forceRender = React11.useCallback(() => setRenderTick((t) => t + 1), []);
1187
+ const [internalExpandedItems] = React11.useState(() => /* @__PURE__ */ new Set());
1188
+ const [internalCollapsedRuns] = React11.useState(() => /* @__PURE__ */ new Set());
1189
+ const [internalActiveFilters] = React11.useState(() => /* @__PURE__ */ new Set());
1190
+ const expandedItems = uiState?.expandedItems ?? internalExpandedItems;
1191
+ const collapsedRuns = uiState?.collapsedRuns ?? internalCollapsedRuns;
1192
+ const activeFilters = uiState?.activeFilters ?? internalActiveFilters;
1193
+ const availableTypes = React11.useMemo(() => {
1194
+ const types = /* @__PURE__ */ new Set();
1195
+ for (const entry of entries) {
1196
+ types.add(entry.type);
1197
+ }
1198
+ return types;
1199
+ }, [entries]);
1200
+ const filteredEntries = React11.useMemo(
1201
+ () => activeFilters.size === 0 ? entries : entries.filter((e) => activeFilters.has(e.type)),
1202
+ [entries, activeFilters, renderTick]
1203
+ );
1204
+ const agentRuns = React11.useMemo(() => groupIntoAgentRuns(filteredEntries), [filteredEntries, renderTick]);
1205
+ const toggleFilter = React11.useCallback((type) => {
1206
+ if (activeFilters.has(type)) {
1207
+ activeFilters.delete(type);
1208
+ } else {
1209
+ activeFilters.add(type);
1210
+ }
1211
+ forceRender();
1212
+ }, [activeFilters, forceRender]);
1213
+ const clearFilters = React11.useCallback(() => {
1214
+ activeFilters.clear();
1215
+ forceRender();
1216
+ }, [activeFilters, forceRender]);
1217
+ const toggleItemExpanded = React11.useCallback((entryId) => {
1218
+ if (expandedItems.has(entryId)) {
1219
+ expandedItems.delete(entryId);
1220
+ } else {
1221
+ expandedItems.add(entryId);
1222
+ }
1223
+ forceRender();
1224
+ }, [expandedItems, forceRender]);
1225
+ const collapseAll = React11.useCallback(() => {
1226
+ collapsedRuns.clear();
1227
+ agentRuns.forEach((run, i) => {
1228
+ collapsedRuns.add(`${run.agentName}-${i}`);
1229
+ });
1230
+ expandedItems.clear();
1231
+ forceRender();
1232
+ }, [agentRuns, collapsedRuns, expandedItems, forceRender]);
1233
+ const expandAll = React11.useCallback(() => {
1234
+ collapsedRuns.clear();
1235
+ agentRuns.forEach((run, i) => {
1236
+ collapsedRuns.add(`${run.agentName}-${i}:expanded`);
1237
+ });
1238
+ forceRender();
1239
+ }, [agentRuns, collapsedRuns, forceRender]);
1240
+ if (entries.length === 0) return null;
1241
+ const isSingle = agentRuns.length === 1;
1242
+ const hasActiveFilter = activeFilters.size > 0;
1243
+ const scrollStyle = maxHeight !== "none" ? { maxHeight } : void 0;
1244
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1245
+ "div",
1246
+ {
1247
+ ref: containerRef,
1248
+ className: maxHeight !== "none" ? "overflow-y-auto" : "",
1249
+ style: scrollStyle,
1250
+ children: [
1251
+ /* @__PURE__ */ jsxRuntime.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 pl-2", children: [
1252
+ TYPE_CONFIG.filter((tc) => availableTypes.has(tc.type)).map((tc) => {
1253
+ const isActive = activeFilters.has(tc.type);
1254
+ const count = entries.filter((e) => e.type === tc.type).length;
1255
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1256
+ "button",
1257
+ {
1258
+ onClick: () => toggleFilter(tc.type),
1259
+ 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"}`,
1260
+ title: `${isActive ? "Hide" : "Show only"} ${tc.label}`,
1261
+ children: [
1262
+ /* @__PURE__ */ jsxRuntime.jsx(tc.icon, { className: "w-3 h-3" }),
1263
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: count })
1264
+ ]
1265
+ },
1266
+ tc.type
1267
+ );
1268
+ }),
1269
+ hasActiveFilter && /* @__PURE__ */ jsxRuntime.jsx(
1270
+ "button",
1271
+ {
1272
+ onClick: clearFilters,
1273
+ className: "text-[10px] text-muted-foreground/60 hover:text-muted-foreground px-1",
1274
+ children: "Clear"
1275
+ }
1276
+ ),
1277
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
1278
+ !isSingle && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1279
+ /* @__PURE__ */ jsxRuntime.jsx(
1280
+ "button",
1281
+ {
1282
+ onClick: collapseAll,
1283
+ 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",
1284
+ title: "Collapse all",
1285
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsDownUp, { className: "w-3 h-3" })
1286
+ }
1287
+ ),
1288
+ /* @__PURE__ */ jsxRuntime.jsx(
1289
+ "button",
1290
+ {
1291
+ onClick: expandAll,
1292
+ 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",
1293
+ title: "Expand all",
1294
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: "w-3 h-3" })
1295
+ }
1296
+ )
1297
+ ] })
1298
+ ] }),
1299
+ filteredEntries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[10px] text-muted-foreground/50 py-2 text-center", children: "No entries match the selected filters" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-0.5", children: agentRuns.map((run, i) => {
1300
+ const runKey = `${run.agentName}-${i}`;
1301
+ const defaultCollapsed = run.depth > 0;
1302
+ const isCollapsed = collapsedRuns.has(runKey) ? true : collapsedRuns.has(`${runKey}:expanded`) ? false : defaultCollapsed;
1303
+ return /* @__PURE__ */ jsxRuntime.jsx(
1304
+ TimelineAgentBlock,
1305
+ {
1306
+ block: run,
1307
+ renderMarkdown,
1308
+ isSingleAgent: isSingle,
1309
+ isCollapsed,
1310
+ onToggleCollapsed: () => {
1311
+ if (isCollapsed) {
1312
+ collapsedRuns.delete(runKey);
1313
+ collapsedRuns.add(`${runKey}:expanded`);
1314
+ } else {
1315
+ collapsedRuns.delete(`${runKey}:expanded`);
1316
+ collapsedRuns.add(runKey);
1317
+ }
1318
+ forceRender();
1319
+ },
1320
+ expandedItems,
1321
+ onToggleItemExpanded: toggleItemExpanded
1322
+ },
1323
+ runKey
1324
+ );
1325
+ }) })
1326
+ ]
1327
+ }
1328
+ );
1329
+ }
1330
+ var AgentResponse = React11__namespace.forwardRef(
548
1331
  ({
549
1332
  state,
550
1333
  id,
@@ -556,15 +1339,20 @@ var AgentResponse = React7__namespace.forwardRef(
556
1339
  thinkingExpanded: controlledThinkingExpanded,
557
1340
  onThinkingExpandedChange,
558
1341
  actionsVisible = "hover",
1342
+ hitlInteractions,
1343
+ defaultHITLExpanded = false,
1344
+ statusContent,
559
1345
  renderMarkdown,
560
1346
  renderThinkingMarkdown,
1347
+ timelineMaxHeight,
561
1348
  className,
562
1349
  ...props
563
1350
  }, ref) => {
564
- const [uncontrolledExpanded, setUncontrolledExpanded] = React7.useState(defaultThinkingExpanded);
1351
+ const timelineUIStateRef = React11.useRef(createTimelineUIState());
1352
+ const [uncontrolledExpanded, setUncontrolledExpanded] = React11.useState(defaultThinkingExpanded);
565
1353
  const isThinkingControlled = controlledThinkingExpanded !== void 0;
566
1354
  const thinkingExpanded = isThinkingControlled ? controlledThinkingExpanded : uncontrolledExpanded;
567
- const toggleThinking = React7.useCallback(() => {
1355
+ const toggleThinking = React11.useCallback(() => {
568
1356
  const newValue = !thinkingExpanded;
569
1357
  if (isThinkingControlled) {
570
1358
  onThinkingExpandedChange?.(newValue);
@@ -572,18 +1360,20 @@ var AgentResponse = React7__namespace.forwardRef(
572
1360
  setUncontrolledExpanded(newValue);
573
1361
  }
574
1362
  }, [thinkingExpanded, isThinkingControlled, onThinkingExpandedChange]);
575
- const [isHovered, setIsHovered] = React7.useState(false);
1363
+ const [isHovered, setIsHovered] = React11.useState(false);
576
1364
  const elapsedTime = useThinkingTimer({
577
1365
  startTime: state.thinkingStartTime,
578
1366
  endTime: state.responseCompleteTime,
579
1367
  status: state.status
580
1368
  });
581
- const totalTimeSeconds = React7.useMemo(() => {
1369
+ const totalTimeSeconds = React11.useMemo(() => {
582
1370
  if (!state.firstMessageTime || !state.responseCompleteTime) return 0;
583
1371
  return (state.responseCompleteTime - state.firstMessageTime) / 1e3;
584
1372
  }, [state.firstMessageTime, state.responseCompleteTime]);
585
- const hasThinkingContent = !!state.thinking || state.thinkingSteps && state.thinkingSteps.length > 0 || false;
586
- const hasAnyContent = hasThinkingContent || state.toolCalls.length > 0 || state.knowledge.length > 0 || state.memory.length > 0 || state.statusUpdates.length > 0 || state.response;
1373
+ const hasTimelineEntries = !!(state.timelineEntries && state.timelineEntries.length > 0);
1374
+ const hasThinkingContent = !!state.thinking || state.thinkingSteps && state.thinkingSteps.length > 0 || hasTimelineEntries || false;
1375
+ const hasHITLInteractions = hitlInteractions && hitlInteractions.length > 0;
1376
+ const hasAnyContent = hasThinkingContent || state.toolCalls.length > 0 || state.knowledge.length > 0 || state.memory.length > 0 || state.statusUpdates.length > 0 || hasHITLInteractions || state.response;
587
1377
  const showMetadataRow = hasThinkingContent || state.toolCalls.length > 0 || state.knowledge.length > 0 || state.memory.length > 0 || state.statusUpdates.length > 0 || state.status === "processing";
588
1378
  const showActionBar = state.status === "complete" && state.response;
589
1379
  const isActionBarVisible = actionsVisible === true || actionsVisible === "hover" && isHovered;
@@ -611,11 +1401,20 @@ var AgentResponse = React7__namespace.forwardRef(
611
1401
  knowledge: state.knowledge,
612
1402
  memory: state.memory,
613
1403
  statusUpdates: state.statusUpdates,
1404
+ statusContent,
614
1405
  status: state.status,
615
1406
  elapsedTime
616
1407
  }
617
1408
  ),
618
- /* @__PURE__ */ jsxRuntime.jsx(
1409
+ hasTimelineEntries ? thinkingExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pb-3 border-t border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
1410
+ AgentTimeline,
1411
+ {
1412
+ entries: state.timelineEntries,
1413
+ renderMarkdown: renderThinkingMarkdown,
1414
+ uiState: timelineUIStateRef.current,
1415
+ maxHeight: timelineMaxHeight
1416
+ }
1417
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(
619
1418
  ThinkingSection,
620
1419
  {
621
1420
  content: state.thinkingSteps && state.thinkingSteps.length > 0 ? state.thinkingSteps : state.thinking,
@@ -624,6 +1423,13 @@ var AgentResponse = React7__namespace.forwardRef(
624
1423
  }
625
1424
  )
626
1425
  ] }),
1426
+ hasHITLInteractions && /* @__PURE__ */ jsxRuntime.jsx(
1427
+ HITLSection,
1428
+ {
1429
+ interactions: hitlInteractions,
1430
+ defaultExpanded: defaultHITLExpanded
1431
+ }
1432
+ ),
627
1433
  state.response && /* @__PURE__ */ jsxRuntime.jsx(
628
1434
  "div",
629
1435
  {
@@ -652,7 +1458,7 @@ var AgentResponse = React7__namespace.forwardRef(
652
1458
  }
653
1459
  );
654
1460
  AgentResponse.displayName = "AgentResponse";
655
- var UserPrompt = React7__namespace.forwardRef(
1461
+ var UserPrompt = React11__namespace.forwardRef(
656
1462
  ({ content, timestamp, className, ...props }, ref) => {
657
1463
  return /* @__PURE__ */ jsxRuntime.jsxs(
658
1464
  "div",
@@ -751,7 +1557,7 @@ function CodeBlockLeaf({ attributes, children, leaf }) {
751
1557
  }
752
1558
  return /* @__PURE__ */ jsxRuntime.jsx("span", { ...attributes, children });
753
1559
  }
754
- var UserPromptInput = React7__namespace.forwardRef(
1560
+ var UserPromptInput = React11__namespace.forwardRef(
755
1561
  ({
756
1562
  value = "",
757
1563
  onChange,
@@ -761,6 +1567,8 @@ var UserPromptInput = React7__namespace.forwardRef(
761
1567
  disabled = false,
762
1568
  isSubmitting = false,
763
1569
  onStop,
1570
+ stopTooltip,
1571
+ stopClassName,
764
1572
  disableWhileSubmitting = true,
765
1573
  autoFocus = false,
766
1574
  refocusAfterSubmit = false,
@@ -774,14 +1582,14 @@ var UserPromptInput = React7__namespace.forwardRef(
774
1582
  className,
775
1583
  ...props
776
1584
  }, ref) => {
777
- const editorRef = React7__namespace.useRef(null);
778
- const [internalValue, setInternalValue] = React7__namespace.useState(value);
779
- const prevIsSubmitting = React7__namespace.useRef(isSubmitting);
780
- const hasEmittedReady = React7__namespace.useRef(false);
781
- React7__namespace.useEffect(() => {
1585
+ const editorRef = React11__namespace.useRef(null);
1586
+ const [internalValue, setInternalValue] = React11__namespace.useState(value);
1587
+ const prevIsSubmitting = React11__namespace.useRef(isSubmitting);
1588
+ const hasEmittedReady = React11__namespace.useRef(false);
1589
+ React11__namespace.useEffect(() => {
782
1590
  setInternalValue(value);
783
1591
  }, [value]);
784
- React7__namespace.useEffect(() => {
1592
+ React11__namespace.useEffect(() => {
785
1593
  if (autoFocus) {
786
1594
  requestAnimationFrame(() => {
787
1595
  requestAnimationFrame(() => {
@@ -790,7 +1598,7 @@ var UserPromptInput = React7__namespace.forwardRef(
790
1598
  });
791
1599
  }
792
1600
  }, [autoFocus]);
793
- React7__namespace.useEffect(() => {
1601
+ React11__namespace.useEffect(() => {
794
1602
  if (!hasEmittedReady.current && onReady) {
795
1603
  requestAnimationFrame(() => {
796
1604
  requestAnimationFrame(() => {
@@ -800,7 +1608,7 @@ var UserPromptInput = React7__namespace.forwardRef(
800
1608
  });
801
1609
  }
802
1610
  }, [onReady]);
803
- React7__namespace.useEffect(() => {
1611
+ React11__namespace.useEffect(() => {
804
1612
  if (refocusAfterSubmit && prevIsSubmitting.current && !isSubmitting) {
805
1613
  requestAnimationFrame(() => {
806
1614
  editorRef.current?.focus();
@@ -808,7 +1616,7 @@ var UserPromptInput = React7__namespace.forwardRef(
808
1616
  }
809
1617
  prevIsSubmitting.current = isSubmitting;
810
1618
  }, [isSubmitting, refocusAfterSubmit]);
811
- React7__namespace.useImperativeHandle(
1619
+ React11__namespace.useImperativeHandle(
812
1620
  ref,
813
1621
  () => ({
814
1622
  focus: () => {
@@ -831,14 +1639,14 @@ var UserPromptInput = React7__namespace.forwardRef(
831
1639
  }),
832
1640
  []
833
1641
  );
834
- const handleChange = React7__namespace.useCallback(
1642
+ const handleChange = React11__namespace.useCallback(
835
1643
  (newValue) => {
836
1644
  setInternalValue(newValue);
837
1645
  onChange?.(newValue);
838
1646
  },
839
1647
  [onChange]
840
1648
  );
841
- const handleSubmit = React7__namespace.useCallback(
1649
+ const handleSubmit = React11__namespace.useCallback(
842
1650
  (text) => {
843
1651
  if (disabled || isSubmitting) return;
844
1652
  if (!text.trim()) return;
@@ -850,7 +1658,7 @@ var UserPromptInput = React7__namespace.forwardRef(
850
1658
  },
851
1659
  [disabled, isSubmitting, onSubmit, clearOnSubmit]
852
1660
  );
853
- const handleSendClick = React7__namespace.useCallback(() => {
1661
+ const handleSendClick = React11__namespace.useCallback(() => {
854
1662
  const text = editorRef.current?.getText() ?? "";
855
1663
  handleSubmit(text);
856
1664
  }, [handleSubmit]);
@@ -887,16 +1695,17 @@ var UserPromptInput = React7__namespace.forwardRef(
887
1695
  ) }),
888
1696
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between pl-2 pr-1 pb-1 pt-1", children: [
889
1697
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: renderActions?.() }),
890
- isSubmitting && onStop ? /* @__PURE__ */ jsxRuntime.jsx(
1698
+ isSubmitting && onStop ? /* @__PURE__ */ jsxRuntime.jsx(core.Tooltip, { content: stopTooltip, disabled: !stopTooltip, children: /* @__PURE__ */ jsxRuntime.jsx(
891
1699
  core.IconButton,
892
1700
  {
893
1701
  icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, {}),
894
1702
  variant: "filled",
895
1703
  size: "sm",
896
- "aria-label": "Stop",
897
- onClick: onStop
1704
+ "aria-label": stopTooltip || "Stop",
1705
+ onClick: onStop,
1706
+ className: stopClassName
898
1707
  }
899
- ) : /* @__PURE__ */ jsxRuntime.jsx(
1708
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(
900
1709
  core.IconButton,
901
1710
  {
902
1711
  icon: isSubmitting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, {}),
@@ -915,16 +1724,138 @@ var UserPromptInput = React7__namespace.forwardRef(
915
1724
  );
916
1725
  UserPromptInput.displayName = "UserPromptInput";
917
1726
 
1727
+ // src/components/inline-actions/parseResponseSegments.ts
1728
+ var ACTION_BLOCK_REGEX = /```json:action\s*\n([\s\S]*?)```/g;
1729
+ function parseResponseSegments(text) {
1730
+ if (!text) return [];
1731
+ const segments = [];
1732
+ let lastIndex = 0;
1733
+ ACTION_BLOCK_REGEX.lastIndex = 0;
1734
+ let match;
1735
+ while ((match = ACTION_BLOCK_REGEX.exec(text)) !== null) {
1736
+ const before = text.slice(lastIndex, match.index);
1737
+ if (before.trim()) {
1738
+ segments.push({ kind: "markdown", content: before });
1739
+ }
1740
+ const jsonContent = match[1].trim();
1741
+ let parsed = null;
1742
+ try {
1743
+ parsed = JSON.parse(jsonContent);
1744
+ } catch {
1745
+ }
1746
+ if (parsed && typeof parsed === "object" && typeof parsed.type === "string") {
1747
+ segments.push({
1748
+ kind: "action",
1749
+ actionType: parsed.type,
1750
+ payload: parsed
1751
+ });
1752
+ } else {
1753
+ const rawBlock = match[0];
1754
+ segments.push({ kind: "markdown", content: rawBlock });
1755
+ }
1756
+ lastIndex = match.index + match[0].length;
1757
+ }
1758
+ const trailing = text.slice(lastIndex);
1759
+ if (trailing.trim()) {
1760
+ segments.push({ kind: "markdown", content: trailing });
1761
+ }
1762
+ return segments;
1763
+ }
1764
+ function ActionMarkdownRenderer({
1765
+ content,
1766
+ registry,
1767
+ renderMarkdown,
1768
+ onAction,
1769
+ isLatest
1770
+ }) {
1771
+ const segments = React11.useMemo(() => parseResponseSegments(content), [content]);
1772
+ if (segments.length === 1 && segments[0].kind === "markdown") {
1773
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderMarkdown(segments[0].content) });
1774
+ }
1775
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: segments.map((segment, index) => {
1776
+ if (segment.kind === "markdown") {
1777
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderMarkdown(segment.content) }, `md-${index}`);
1778
+ }
1779
+ const Component = registry[segment.actionType];
1780
+ if (!Component) {
1781
+ return /* @__PURE__ */ jsxRuntime.jsx(
1782
+ "pre",
1783
+ {
1784
+ className: "my-4 p-4 rounded-lg border border-border bg-muted text-sm font-mono overflow-x-auto",
1785
+ children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: JSON.stringify(segment.payload, null, 2) })
1786
+ },
1787
+ `action-fallback-${index}`
1788
+ );
1789
+ }
1790
+ return /* @__PURE__ */ jsxRuntime.jsx(
1791
+ Component,
1792
+ {
1793
+ payload: segment.payload,
1794
+ onAction,
1795
+ isLatest
1796
+ },
1797
+ `action-${segment.actionType}-${index}`
1798
+ );
1799
+ }) });
1800
+ }
1801
+
1802
+ // src/components/inline-actions/prompts.ts
1803
+ var INLINE_ACTION_PROMPT = `
1804
+ <inline_actions>
1805
+ When your response should include interactive components (like query viewers,
1806
+ data tables, or executable actions), embed them as fenced code blocks using
1807
+ the \`json:action\` language tag:
1808
+
1809
+ \`\`\`json:action
1810
+ {
1811
+ "type": "action-type-here",
1812
+ ...action-specific fields
1813
+ }
1814
+ \`\`\`
1815
+
1816
+ Rules:
1817
+ - Each block must contain valid JSON with a "type" field.
1818
+ - The "type" must match a registered action component on the frontend.
1819
+ - Multiple action blocks per response are allowed.
1820
+ - Surround action blocks with normal markdown text for user context.
1821
+ - The action block is rendered as an interactive component in the chat UI.
1822
+ - SQL strings inside JSON must be properly escaped (newlines as \\n, quotes as \\").
1823
+
1824
+ Available action types:
1825
+
1826
+ - "optimap-query": Displays SQL queries with a button to execute them and
1827
+ update the 3D globe map.
1828
+ Required fields:
1829
+ - type: "optimap-query"
1830
+ - locations_sql: string (the validated locations SQL query)
1831
+ - routes_sql: string (the validated routes SQL query)
1832
+ - database_name: string (the target database name)
1833
+ </inline_actions>
1834
+ `;
1835
+
918
1836
  exports.ActionBar = ActionBar;
1837
+ exports.ActionMarkdownRenderer = ActionMarkdownRenderer;
919
1838
  exports.ActivityIndicators = ActivityIndicators;
920
1839
  exports.AgentResponse = AgentResponse;
1840
+ exports.AgentTimeline = AgentTimeline;
1841
+ exports.HITLInteractionRecord = HITLInteractionRecord;
1842
+ exports.HITLQuestionPanel = HITLQuestionPanel;
1843
+ exports.HITLSection = HITLSection;
1844
+ exports.INLINE_ACTION_PROMPT = INLINE_ACTION_PROMPT;
921
1845
  exports.MetadataRow = MetadataRow;
922
1846
  exports.ThinkingSection = ThinkingSection;
1847
+ exports.TruncatedMessage = TruncatedMessage;
923
1848
  exports.UserPrompt = UserPrompt;
924
1849
  exports.UserPromptInput = UserPromptInput;
1850
+ exports.buildResponseString = buildResponseString;
1851
+ exports.buildTimelineEntries = buildTimelineEntries;
1852
+ exports.createTimelineUIState = createTimelineUIState;
1853
+ exports.deduplicateEntries = deduplicateEntries;
925
1854
  exports.formatTime = formatTime;
926
1855
  exports.formatTotalTime = formatTotalTime;
1856
+ exports.groupIntoAgentRuns = groupIntoAgentRuns;
927
1857
  exports.initialAgentResponseState = initialAgentResponseState;
1858
+ exports.parseResponseSegments = parseResponseSegments;
928
1859
  exports.useAgentResponseAccumulator = useAgentResponseAccumulator;
929
1860
  exports.useThinkingTimer = useThinkingTimer;
930
1861
  //# sourceMappingURL=index.cjs.map