@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.js CHANGED
@@ -1,12 +1,12 @@
1
- import * as React7 from 'react';
2
- import { useState, useCallback, useMemo, useEffect } from 'react';
3
- import { cn, Popover, PopoverTrigger, PopoverContent, IconButton, LoadingSpinner } from '@optilogic/core';
4
- import { Activity, Wrench, Book, HardDrive, Check, Copy, ThumbsUp, ThumbsDown, Square, Loader2, Send, ChevronUp, ChevronDown, ChevronRight } 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, Tooltip, 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
6
  import { SlateEditor, Text } from '@optilogic/editor';
7
7
 
8
8
  // src/components/agent-response/AgentResponse.tsx
9
- var ActivityIndicators = React7.forwardRef(
9
+ var ActivityIndicators = React11.forwardRef(
10
10
  ({ toolCalls, knowledge, memory, statusUpdates = [], className, ...props }, ref) => {
11
11
  const hasAnyActivity = toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0 || statusUpdates.length > 0;
12
12
  if (!hasAnyActivity) return null;
@@ -125,7 +125,7 @@ function formatTotalTime(seconds) {
125
125
  const minutes = seconds / 60;
126
126
  return `${minutes.toFixed(1)}m`;
127
127
  }
128
- var MetadataRow = React7.forwardRef(
128
+ var MetadataRow = React11.forwardRef(
129
129
  ({
130
130
  hasThinking,
131
131
  isExpanded,
@@ -134,6 +134,7 @@ var MetadataRow = React7.forwardRef(
134
134
  knowledge,
135
135
  memory,
136
136
  statusUpdates = [],
137
+ statusContent,
137
138
  status,
138
139
  elapsedTime,
139
140
  className,
@@ -158,7 +159,7 @@ var MetadataRow = React7.forwardRef(
158
159
  return null;
159
160
  };
160
161
  const leftContent = renderLeftContent();
161
- if (!leftContent && !hasActivity) {
162
+ if (!leftContent && !hasActivity && !statusContent) {
162
163
  return null;
163
164
  }
164
165
  return /* @__PURE__ */ jsxs(
@@ -172,10 +173,11 @@ var MetadataRow = React7.forwardRef(
172
173
  "button",
173
174
  {
174
175
  onClick: onToggle,
175
- 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",
176
177
  children: leftContent
177
178
  }
178
- ) : /* @__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 }),
179
181
  /* @__PURE__ */ jsx(
180
182
  ActivityIndicators,
181
183
  {
@@ -220,7 +222,7 @@ var ThinkingStepItem = ({ step, renderMarkdown }) => {
220
222
  )
221
223
  ] });
222
224
  };
223
- var ThinkingSection = React7.forwardRef(
225
+ var ThinkingSection = React11.forwardRef(
224
226
  ({ content, isExpanded, renderMarkdown, className, ...props }, ref) => {
225
227
  if (!isExpanded || !content || Array.isArray(content) && content.length === 0) {
226
228
  return null;
@@ -245,7 +247,7 @@ var ThinkingSection = React7.forwardRef(
245
247
  }
246
248
  );
247
249
  ThinkingSection.displayName = "ThinkingSection";
248
- var ActionBar = React7.forwardRef(
250
+ var ActionBar = React11.forwardRef(
249
251
  ({
250
252
  response,
251
253
  isVisible,
@@ -334,6 +336,313 @@ var ActionBar = React7.forwardRef(
334
336
  }
335
337
  );
336
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";
337
646
  function useThinkingTimer({
338
647
  startTime,
339
648
  endTime,
@@ -369,12 +678,152 @@ var initialAgentResponseState = {
369
678
  knowledge: [],
370
679
  memory: [],
371
680
  statusUpdates: [],
681
+ potentialResponses: [],
682
+ customTimelineEntries: [],
372
683
  response: "",
373
684
  thinkingStartTime: null,
374
685
  responseCompleteTime: null,
375
686
  firstMessageTime: null
376
687
  };
377
688
 
689
+ // src/components/agent-timeline/utils.ts
690
+ function buildTimelineEntries(state) {
691
+ const entries = [];
692
+ if (state.thinkingSteps) {
693
+ let idx = 0;
694
+ for (const step of state.thinkingSteps) {
695
+ entries.push({
696
+ id: `tl-think-${idx++}`,
697
+ type: "thinking",
698
+ agentName: step.agentName ?? null,
699
+ parentAgent: step.parentAgent ?? null,
700
+ depth: step.depth ?? 0,
701
+ content: step.content,
702
+ title: step.label,
703
+ timestamp: step.timestamp ?? 0
704
+ });
705
+ }
706
+ } else if (state.thinking) {
707
+ entries.push({
708
+ id: "tl-think-0",
709
+ type: "thinking",
710
+ agentName: null,
711
+ parentAgent: null,
712
+ depth: 0,
713
+ content: state.thinking,
714
+ title: null,
715
+ timestamp: state.thinkingStartTime ?? 0
716
+ });
717
+ }
718
+ let toolIdx = 0;
719
+ for (const tool of state.toolCalls) {
720
+ entries.push({
721
+ id: `tl-tool-${toolIdx++}`,
722
+ type: "tool_call",
723
+ agentName: tool.agentName ?? null,
724
+ parentAgent: tool.parentAgent ?? null,
725
+ depth: tool.depth ?? 0,
726
+ content: tool.name,
727
+ title: null,
728
+ timestamp: tool.timestamp
729
+ });
730
+ }
731
+ let knowIdx = 0;
732
+ for (const item of state.knowledge) {
733
+ entries.push({
734
+ id: `tl-know-${knowIdx++}`,
735
+ type: "knowledge",
736
+ agentName: item.agentName ?? null,
737
+ parentAgent: item.parentAgent ?? null,
738
+ depth: item.depth ?? 0,
739
+ content: item.content,
740
+ title: item.source,
741
+ timestamp: item.timestamp
742
+ });
743
+ }
744
+ let memIdx = 0;
745
+ for (const item of state.memory) {
746
+ entries.push({
747
+ id: `tl-mem-${memIdx++}`,
748
+ type: "memory",
749
+ agentName: item.agentName ?? null,
750
+ parentAgent: item.parentAgent ?? null,
751
+ depth: item.depth ?? 0,
752
+ content: item.content,
753
+ title: item.type,
754
+ timestamp: item.timestamp
755
+ });
756
+ }
757
+ let statIdx = 0;
758
+ for (const item of state.statusUpdates) {
759
+ entries.push({
760
+ id: `tl-stat-${statIdx++}`,
761
+ type: "status_update",
762
+ agentName: item.agentName ?? item.agent ?? null,
763
+ parentAgent: item.parentAgent ?? null,
764
+ depth: item.depth ?? 0,
765
+ content: item.message,
766
+ title: null,
767
+ timestamp: item.timestamp
768
+ });
769
+ }
770
+ if (state.potentialResponses) {
771
+ let respIdx = 0;
772
+ for (const resp of state.potentialResponses) {
773
+ entries.push({
774
+ id: `tl-resp-${respIdx++}`,
775
+ type: "ai_response",
776
+ agentName: resp.agentName ?? null,
777
+ parentAgent: resp.parentAgent ?? null,
778
+ depth: resp.depth ?? 0,
779
+ content: resp.content,
780
+ title: null,
781
+ timestamp: resp.timestamp
782
+ });
783
+ }
784
+ }
785
+ if (state.customTimelineEntries) {
786
+ entries.push(...state.customTimelineEntries);
787
+ }
788
+ entries.sort((a, b) => a.timestamp - b.timestamp);
789
+ return entries;
790
+ }
791
+ function groupIntoAgentRuns(entries) {
792
+ const runs = [];
793
+ let currentRun = null;
794
+ for (const entry of entries) {
795
+ const name = entry.agentName || "Agent";
796
+ if (!currentRun || currentRun.agentName !== name) {
797
+ currentRun = {
798
+ agentName: name,
799
+ parentAgent: entry.parentAgent,
800
+ depth: entry.depth,
801
+ entries: []
802
+ };
803
+ runs.push(currentRun);
804
+ }
805
+ currentRun.entries.push({ entry, count: 1 });
806
+ }
807
+ for (const run of runs) {
808
+ run.entries = deduplicateEntries(run.entries);
809
+ }
810
+ return runs;
811
+ }
812
+ function deduplicateEntries(entries) {
813
+ if (entries.length === 0) return [];
814
+ const result = [entries[0]];
815
+ for (let i = 1; i < entries.length; i++) {
816
+ const prev = result[result.length - 1];
817
+ const curr = entries[i];
818
+ if (prev.entry.type === curr.entry.type && prev.entry.content === curr.entry.content) {
819
+ prev.count += curr.count;
820
+ } else {
821
+ result.push({ ...curr });
822
+ }
823
+ }
824
+ return result;
825
+ }
826
+
378
827
  // src/components/agent-response/hooks/useAgentResponseAccumulator.ts
379
828
  function useAgentResponseAccumulator(options) {
380
829
  const [state, setState] = useState(initialAgentResponseState);
@@ -408,28 +857,44 @@ function useAgentResponseAccumulator(options) {
408
857
  id: payload.thinkingStep.id || `step-${Date.now()}`,
409
858
  label: payload.thinkingStep.label,
410
859
  content: payload.thinkingStep.content,
411
- depth: payload.thinkingStep.depth ?? 0,
412
- isCollapsed: payload.thinkingStep.isCollapsed
860
+ depth: payload.thinkingStep.depth ?? payload.depth ?? 0,
861
+ isCollapsed: payload.thinkingStep.isCollapsed,
862
+ timestamp: Date.now(),
863
+ agentName: payload.agentName,
864
+ parentAgent: payload.parentAgent
413
865
  };
414
866
  const thinkingStartTime2 = prev.thinkingStartTime ?? Date.now();
415
- return {
867
+ const next2 = {
416
868
  ...prev,
417
869
  status: newStatus,
418
870
  thinkingSteps: [...prev.thinkingSteps || [], newStep],
419
871
  thinkingStartTime: thinkingStartTime2,
420
872
  firstMessageTime
421
873
  };
874
+ return { ...next2, timelineEntries: buildTimelineEntries(next2) };
422
875
  }
423
876
  const newThinking = payload.message || payload.content || "";
424
877
  const separator = prev.thinking && newThinking ? "\n\n" : "";
425
878
  const thinkingStartTime = prev.thinkingStartTime ?? (newThinking ? Date.now() : null);
426
- return {
879
+ const prevSteps = prev.thinkingSteps || [];
880
+ const plainStep = {
881
+ id: `step-${prevSteps.length}`,
882
+ label: newThinking,
883
+ content: newThinking,
884
+ depth: payload.depth ?? 0,
885
+ timestamp: Date.now(),
886
+ agentName: payload.agentName,
887
+ parentAgent: payload.parentAgent
888
+ };
889
+ const next = {
427
890
  ...prev,
428
891
  status: newStatus,
429
892
  thinking: prev.thinking + separator + newThinking,
893
+ thinkingSteps: [...prevSteps, plainStep],
430
894
  thinkingStartTime,
431
895
  firstMessageTime
432
896
  };
897
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
433
898
  }
434
899
  case "tool_call": {
435
900
  const toolName = payload.message || payload.tool?.name;
@@ -438,14 +903,18 @@ function useAgentResponseAccumulator(options) {
438
903
  id: payload.tool?.id || `tool-${Date.now()}`,
439
904
  name: toolName,
440
905
  arguments: payload.tool?.arguments,
441
- timestamp: Date.now()
906
+ timestamp: Date.now(),
907
+ agentName: payload.agentName,
908
+ parentAgent: payload.parentAgent,
909
+ depth: payload.depth
442
910
  };
443
- return {
911
+ const next = {
444
912
  ...prev,
445
913
  status: newStatus,
446
914
  toolCalls: [...prev.toolCalls, newToolCall],
447
915
  firstMessageTime
448
916
  };
917
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
449
918
  }
450
919
  return { ...prev, status: newStatus, firstMessageTime };
451
920
  }
@@ -456,14 +925,18 @@ function useAgentResponseAccumulator(options) {
456
925
  id: payload.knowledge?.id || `knowledge-${Date.now()}`,
457
926
  source: payload.knowledge?.source || "unknown",
458
927
  content: knowledgeContent,
459
- timestamp: Date.now()
928
+ timestamp: Date.now(),
929
+ agentName: payload.agentName,
930
+ parentAgent: payload.parentAgent,
931
+ depth: payload.depth
460
932
  };
461
- return {
933
+ const next = {
462
934
  ...prev,
463
935
  status: newStatus,
464
936
  knowledge: [...prev.knowledge, newKnowledge],
465
937
  firstMessageTime
466
938
  };
939
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
467
940
  }
468
941
  return { ...prev, status: newStatus, firstMessageTime };
469
942
  }
@@ -474,14 +947,18 @@ function useAgentResponseAccumulator(options) {
474
947
  id: payload.memory?.id || `memory-${Date.now()}`,
475
948
  type: payload.memory?.type || "unknown",
476
949
  content: memoryContent,
477
- timestamp: Date.now()
950
+ timestamp: Date.now(),
951
+ agentName: payload.agentName,
952
+ parentAgent: payload.parentAgent,
953
+ depth: payload.depth
478
954
  };
479
- return {
955
+ const next = {
480
956
  ...prev,
481
957
  status: newStatus,
482
958
  memory: [...prev.memory, newMemory],
483
959
  firstMessageTime
484
960
  };
961
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
485
962
  }
486
963
  return { ...prev, status: newStatus, firstMessageTime };
487
964
  }
@@ -500,14 +977,39 @@ function useAgentResponseAccumulator(options) {
500
977
  id: payload.statusUpdate?.id || `status-${Date.now()}`,
501
978
  message: statusMessage,
502
979
  agent: payload.statusUpdate?.agent,
503
- timestamp: Date.now()
980
+ timestamp: Date.now(),
981
+ agentName: payload.agentName,
982
+ parentAgent: payload.parentAgent,
983
+ depth: payload.depth
504
984
  };
505
- return {
985
+ const next = {
506
986
  ...prev,
507
987
  status: newStatus,
508
988
  statusUpdates: [...prev.statusUpdates, newStatusItem],
509
989
  firstMessageTime
510
990
  };
991
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
992
+ }
993
+ return { ...prev, status: newStatus, firstMessageTime };
994
+ }
995
+ case "potential_response": {
996
+ const respContent = payload.message || payload.content || "";
997
+ if (respContent) {
998
+ const newResp = {
999
+ id: `resp-${Date.now()}`,
1000
+ content: respContent,
1001
+ timestamp: Date.now(),
1002
+ agentName: payload.agentName,
1003
+ parentAgent: payload.parentAgent,
1004
+ depth: payload.depth
1005
+ };
1006
+ const next = {
1007
+ ...prev,
1008
+ status: newStatus,
1009
+ potentialResponses: [...prev.potentialResponses || [], newResp],
1010
+ firstMessageTime
1011
+ };
1012
+ return { ...next, timelineEntries: buildTimelineEntries(next) };
511
1013
  }
512
1014
  return { ...prev, status: newStatus, firstMessageTime };
513
1015
  }
@@ -523,7 +1025,288 @@ function useAgentResponseAccumulator(options) {
523
1025
  }, []);
524
1026
  return { state, handleMessage, reset };
525
1027
  }
526
- var AgentResponse = React7.forwardRef(
1028
+ var ICON_MAP = {
1029
+ thinking: Brain,
1030
+ tool_call: Wrench,
1031
+ knowledge: BookOpen,
1032
+ memory: HardDrive,
1033
+ status_update: Activity,
1034
+ ai_response: MessageSquare,
1035
+ error: AlertCircle
1036
+ };
1037
+ function TimelineItem({
1038
+ displayEntry,
1039
+ renderMarkdown,
1040
+ isExpanded,
1041
+ onToggleExpanded
1042
+ }) {
1043
+ const { entry, count } = displayEntry;
1044
+ const Icon = ICON_MAP[entry.type] ?? Activity;
1045
+ const isLong = entry.content.length > 200 || entry.content.split("\n").length > 3;
1046
+ const canExpand = isLong || entry.type === "ai_response";
1047
+ return /* @__PURE__ */ jsxs("div", { className: "py-1 flex items-start gap-2 group", children: [
1048
+ /* @__PURE__ */ jsx(Icon, { className: "w-3.5 h-3.5 text-muted-foreground flex-shrink-0 mt-0.5" }),
1049
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: isExpanded && entry.type === "ai_response" && renderMarkdown ? (
1050
+ // Expanded AI response: rendered markdown
1051
+ /* @__PURE__ */ jsxs("div", { children: [
1052
+ renderMarkdown(entry.content),
1053
+ /* @__PURE__ */ jsx(
1054
+ "button",
1055
+ {
1056
+ onClick: onToggleExpanded,
1057
+ className: "text-[10px] text-muted-foreground/70 hover:text-muted-foreground mt-1",
1058
+ children: "Show less"
1059
+ }
1060
+ )
1061
+ ] })
1062
+ ) : isExpanded ? (
1063
+ // Expanded non-AI: plain text
1064
+ /* @__PURE__ */ jsxs("div", { children: [
1065
+ /* @__PURE__ */ jsx("pre", { className: "text-xs text-muted-foreground whitespace-pre-wrap font-mono", children: entry.content }),
1066
+ /* @__PURE__ */ jsx(
1067
+ "button",
1068
+ {
1069
+ onClick: onToggleExpanded,
1070
+ className: "text-[10px] text-muted-foreground/70 hover:text-muted-foreground mt-1",
1071
+ children: "Show less"
1072
+ }
1073
+ )
1074
+ ] })
1075
+ ) : (
1076
+ // Collapsed: truncated with optional expand
1077
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5 min-w-0", children: [
1078
+ /* @__PURE__ */ jsx(
1079
+ "div",
1080
+ {
1081
+ className: `text-xs text-muted-foreground min-w-0 ${canExpand ? "line-clamp-2 cursor-pointer hover:text-foreground/80" : ""}`,
1082
+ onClick: canExpand ? onToggleExpanded : void 0,
1083
+ children: entry.content
1084
+ }
1085
+ ),
1086
+ count > 1 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground/60 whitespace-nowrap flex-shrink-0", children: [
1087
+ "(x",
1088
+ count,
1089
+ ")"
1090
+ ] })
1091
+ ] })
1092
+ ) })
1093
+ ] });
1094
+ }
1095
+ function TimelineAgentBlock({
1096
+ block,
1097
+ renderMarkdown,
1098
+ isSingleAgent,
1099
+ isCollapsed,
1100
+ onToggleCollapsed,
1101
+ expandedItems,
1102
+ onToggleItemExpanded
1103
+ }) {
1104
+ const indentPx = block.depth * 16;
1105
+ if (isSingleAgent && block.depth === 0) {
1106
+ return /* @__PURE__ */ jsx("div", { style: { paddingLeft: `${indentPx}px` }, children: block.entries.map((displayEntry, i) => /* @__PURE__ */ jsx(
1107
+ TimelineItem,
1108
+ {
1109
+ displayEntry,
1110
+ renderMarkdown,
1111
+ isExpanded: expandedItems.has(displayEntry.entry.id),
1112
+ onToggleExpanded: () => onToggleItemExpanded(displayEntry.entry.id)
1113
+ },
1114
+ displayEntry.entry.id + "-" + i
1115
+ )) });
1116
+ }
1117
+ return /* @__PURE__ */ jsxs("div", { style: { paddingLeft: `${indentPx}px` }, children: [
1118
+ /* @__PURE__ */ jsxs(
1119
+ "button",
1120
+ {
1121
+ onClick: onToggleCollapsed,
1122
+ 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",
1123
+ children: [
1124
+ 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" }),
1125
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground/80", children: block.agentName }),
1126
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground/60", children: [
1127
+ "(",
1128
+ block.entries.reduce((sum, e) => sum + e.count, 0),
1129
+ ")"
1130
+ ] })
1131
+ ]
1132
+ }
1133
+ ),
1134
+ !isCollapsed && /* @__PURE__ */ jsx("div", { className: "ml-4", children: block.entries.map((displayEntry, i) => /* @__PURE__ */ jsx(
1135
+ TimelineItem,
1136
+ {
1137
+ displayEntry,
1138
+ renderMarkdown,
1139
+ isExpanded: expandedItems.has(displayEntry.entry.id),
1140
+ onToggleExpanded: () => onToggleItemExpanded(displayEntry.entry.id)
1141
+ },
1142
+ displayEntry.entry.id + "-" + i
1143
+ )) })
1144
+ ] });
1145
+ }
1146
+ function createTimelineUIState() {
1147
+ return {
1148
+ expandedItems: /* @__PURE__ */ new Set(),
1149
+ collapsedRuns: /* @__PURE__ */ new Set(),
1150
+ activeFilters: /* @__PURE__ */ new Set()
1151
+ };
1152
+ }
1153
+ var TYPE_CONFIG = [
1154
+ { type: "status_update", icon: Activity, label: "Status" },
1155
+ { type: "thinking", icon: Brain, label: "Thinking" },
1156
+ { type: "tool_call", icon: Wrench, label: "Tools" },
1157
+ { type: "knowledge", icon: BookOpen, label: "Knowledge" },
1158
+ { type: "memory", icon: HardDrive, label: "Memory" },
1159
+ { type: "ai_response", icon: MessageSquare, label: "AI" },
1160
+ { type: "error", icon: AlertCircle, label: "Errors" }
1161
+ ];
1162
+ function AgentTimeline({ entries, renderMarkdown, uiState, maxHeight = "300px" }) {
1163
+ const containerRef = useRef(null);
1164
+ const [renderTick, setRenderTick] = useState(0);
1165
+ const forceRender = useCallback(() => setRenderTick((t) => t + 1), []);
1166
+ const [internalExpandedItems] = useState(() => /* @__PURE__ */ new Set());
1167
+ const [internalCollapsedRuns] = useState(() => /* @__PURE__ */ new Set());
1168
+ const [internalActiveFilters] = useState(() => /* @__PURE__ */ new Set());
1169
+ const expandedItems = uiState?.expandedItems ?? internalExpandedItems;
1170
+ const collapsedRuns = uiState?.collapsedRuns ?? internalCollapsedRuns;
1171
+ const activeFilters = uiState?.activeFilters ?? internalActiveFilters;
1172
+ const availableTypes = useMemo(() => {
1173
+ const types = /* @__PURE__ */ new Set();
1174
+ for (const entry of entries) {
1175
+ types.add(entry.type);
1176
+ }
1177
+ return types;
1178
+ }, [entries]);
1179
+ const filteredEntries = useMemo(
1180
+ () => activeFilters.size === 0 ? entries : entries.filter((e) => activeFilters.has(e.type)),
1181
+ [entries, activeFilters, renderTick]
1182
+ );
1183
+ const agentRuns = useMemo(() => groupIntoAgentRuns(filteredEntries), [filteredEntries, renderTick]);
1184
+ const toggleFilter = useCallback((type) => {
1185
+ if (activeFilters.has(type)) {
1186
+ activeFilters.delete(type);
1187
+ } else {
1188
+ activeFilters.add(type);
1189
+ }
1190
+ forceRender();
1191
+ }, [activeFilters, forceRender]);
1192
+ const clearFilters = useCallback(() => {
1193
+ activeFilters.clear();
1194
+ forceRender();
1195
+ }, [activeFilters, forceRender]);
1196
+ const toggleItemExpanded = useCallback((entryId) => {
1197
+ if (expandedItems.has(entryId)) {
1198
+ expandedItems.delete(entryId);
1199
+ } else {
1200
+ expandedItems.add(entryId);
1201
+ }
1202
+ forceRender();
1203
+ }, [expandedItems, forceRender]);
1204
+ const collapseAll = useCallback(() => {
1205
+ collapsedRuns.clear();
1206
+ agentRuns.forEach((run, i) => {
1207
+ collapsedRuns.add(`${run.agentName}-${i}`);
1208
+ });
1209
+ expandedItems.clear();
1210
+ forceRender();
1211
+ }, [agentRuns, collapsedRuns, expandedItems, forceRender]);
1212
+ const expandAll = useCallback(() => {
1213
+ collapsedRuns.clear();
1214
+ agentRuns.forEach((run, i) => {
1215
+ collapsedRuns.add(`${run.agentName}-${i}:expanded`);
1216
+ });
1217
+ forceRender();
1218
+ }, [agentRuns, collapsedRuns, forceRender]);
1219
+ if (entries.length === 0) return null;
1220
+ const isSingle = agentRuns.length === 1;
1221
+ const hasActiveFilter = activeFilters.size > 0;
1222
+ const scrollStyle = maxHeight !== "none" ? { maxHeight } : void 0;
1223
+ return /* @__PURE__ */ jsxs(
1224
+ "div",
1225
+ {
1226
+ ref: containerRef,
1227
+ className: maxHeight !== "none" ? "overflow-y-auto" : "",
1228
+ style: scrollStyle,
1229
+ children: [
1230
+ /* @__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 pl-2", children: [
1231
+ TYPE_CONFIG.filter((tc) => availableTypes.has(tc.type)).map((tc) => {
1232
+ const isActive = activeFilters.has(tc.type);
1233
+ const count = entries.filter((e) => e.type === tc.type).length;
1234
+ return /* @__PURE__ */ jsxs(
1235
+ "button",
1236
+ {
1237
+ onClick: () => toggleFilter(tc.type),
1238
+ 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"}`,
1239
+ title: `${isActive ? "Hide" : "Show only"} ${tc.label}`,
1240
+ children: [
1241
+ /* @__PURE__ */ jsx(tc.icon, { className: "w-3 h-3" }),
1242
+ /* @__PURE__ */ jsx("span", { children: count })
1243
+ ]
1244
+ },
1245
+ tc.type
1246
+ );
1247
+ }),
1248
+ hasActiveFilter && /* @__PURE__ */ jsx(
1249
+ "button",
1250
+ {
1251
+ onClick: clearFilters,
1252
+ className: "text-[10px] text-muted-foreground/60 hover:text-muted-foreground px-1",
1253
+ children: "Clear"
1254
+ }
1255
+ ),
1256
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
1257
+ !isSingle && /* @__PURE__ */ jsxs(Fragment, { children: [
1258
+ /* @__PURE__ */ jsx(
1259
+ "button",
1260
+ {
1261
+ onClick: collapseAll,
1262
+ 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",
1263
+ title: "Collapse all",
1264
+ children: /* @__PURE__ */ jsx(ChevronsDownUp, { className: "w-3 h-3" })
1265
+ }
1266
+ ),
1267
+ /* @__PURE__ */ jsx(
1268
+ "button",
1269
+ {
1270
+ onClick: expandAll,
1271
+ 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",
1272
+ title: "Expand all",
1273
+ children: /* @__PURE__ */ jsx(ChevronsUpDown, { className: "w-3 h-3" })
1274
+ }
1275
+ )
1276
+ ] })
1277
+ ] }),
1278
+ 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) => {
1279
+ const runKey = `${run.agentName}-${i}`;
1280
+ const defaultCollapsed = run.depth > 0;
1281
+ const isCollapsed = collapsedRuns.has(runKey) ? true : collapsedRuns.has(`${runKey}:expanded`) ? false : defaultCollapsed;
1282
+ return /* @__PURE__ */ jsx(
1283
+ TimelineAgentBlock,
1284
+ {
1285
+ block: run,
1286
+ renderMarkdown,
1287
+ isSingleAgent: isSingle,
1288
+ isCollapsed,
1289
+ onToggleCollapsed: () => {
1290
+ if (isCollapsed) {
1291
+ collapsedRuns.delete(runKey);
1292
+ collapsedRuns.add(`${runKey}:expanded`);
1293
+ } else {
1294
+ collapsedRuns.delete(`${runKey}:expanded`);
1295
+ collapsedRuns.add(runKey);
1296
+ }
1297
+ forceRender();
1298
+ },
1299
+ expandedItems,
1300
+ onToggleItemExpanded: toggleItemExpanded
1301
+ },
1302
+ runKey
1303
+ );
1304
+ }) })
1305
+ ]
1306
+ }
1307
+ );
1308
+ }
1309
+ var AgentResponse = React11.forwardRef(
527
1310
  ({
528
1311
  state,
529
1312
  id,
@@ -535,11 +1318,16 @@ var AgentResponse = React7.forwardRef(
535
1318
  thinkingExpanded: controlledThinkingExpanded,
536
1319
  onThinkingExpandedChange,
537
1320
  actionsVisible = "hover",
1321
+ hitlInteractions,
1322
+ defaultHITLExpanded = false,
1323
+ statusContent,
538
1324
  renderMarkdown,
539
1325
  renderThinkingMarkdown,
1326
+ timelineMaxHeight,
540
1327
  className,
541
1328
  ...props
542
1329
  }, ref) => {
1330
+ const timelineUIStateRef = useRef(createTimelineUIState());
543
1331
  const [uncontrolledExpanded, setUncontrolledExpanded] = useState(defaultThinkingExpanded);
544
1332
  const isThinkingControlled = controlledThinkingExpanded !== void 0;
545
1333
  const thinkingExpanded = isThinkingControlled ? controlledThinkingExpanded : uncontrolledExpanded;
@@ -561,8 +1349,10 @@ var AgentResponse = React7.forwardRef(
561
1349
  if (!state.firstMessageTime || !state.responseCompleteTime) return 0;
562
1350
  return (state.responseCompleteTime - state.firstMessageTime) / 1e3;
563
1351
  }, [state.firstMessageTime, state.responseCompleteTime]);
564
- const hasThinkingContent = !!state.thinking || state.thinkingSteps && state.thinkingSteps.length > 0 || false;
565
- const hasAnyContent = hasThinkingContent || state.toolCalls.length > 0 || state.knowledge.length > 0 || state.memory.length > 0 || state.statusUpdates.length > 0 || state.response;
1352
+ const hasTimelineEntries = !!(state.timelineEntries && state.timelineEntries.length > 0);
1353
+ const hasThinkingContent = !!state.thinking || state.thinkingSteps && state.thinkingSteps.length > 0 || hasTimelineEntries || false;
1354
+ const hasHITLInteractions = hitlInteractions && hitlInteractions.length > 0;
1355
+ const hasAnyContent = hasThinkingContent || state.toolCalls.length > 0 || state.knowledge.length > 0 || state.memory.length > 0 || state.statusUpdates.length > 0 || hasHITLInteractions || state.response;
566
1356
  const showMetadataRow = hasThinkingContent || state.toolCalls.length > 0 || state.knowledge.length > 0 || state.memory.length > 0 || state.statusUpdates.length > 0 || state.status === "processing";
567
1357
  const showActionBar = state.status === "complete" && state.response;
568
1358
  const isActionBarVisible = actionsVisible === true || actionsVisible === "hover" && isHovered;
@@ -590,11 +1380,20 @@ var AgentResponse = React7.forwardRef(
590
1380
  knowledge: state.knowledge,
591
1381
  memory: state.memory,
592
1382
  statusUpdates: state.statusUpdates,
1383
+ statusContent,
593
1384
  status: state.status,
594
1385
  elapsedTime
595
1386
  }
596
1387
  ),
597
- /* @__PURE__ */ jsx(
1388
+ hasTimelineEntries ? thinkingExpanded && /* @__PURE__ */ jsx("div", { className: "pb-3 border-t border-border", children: /* @__PURE__ */ jsx(
1389
+ AgentTimeline,
1390
+ {
1391
+ entries: state.timelineEntries,
1392
+ renderMarkdown: renderThinkingMarkdown,
1393
+ uiState: timelineUIStateRef.current,
1394
+ maxHeight: timelineMaxHeight
1395
+ }
1396
+ ) }) : /* @__PURE__ */ jsx(
598
1397
  ThinkingSection,
599
1398
  {
600
1399
  content: state.thinkingSteps && state.thinkingSteps.length > 0 ? state.thinkingSteps : state.thinking,
@@ -603,6 +1402,13 @@ var AgentResponse = React7.forwardRef(
603
1402
  }
604
1403
  )
605
1404
  ] }),
1405
+ hasHITLInteractions && /* @__PURE__ */ jsx(
1406
+ HITLSection,
1407
+ {
1408
+ interactions: hitlInteractions,
1409
+ defaultExpanded: defaultHITLExpanded
1410
+ }
1411
+ ),
606
1412
  state.response && /* @__PURE__ */ jsx(
607
1413
  "div",
608
1414
  {
@@ -631,7 +1437,7 @@ var AgentResponse = React7.forwardRef(
631
1437
  }
632
1438
  );
633
1439
  AgentResponse.displayName = "AgentResponse";
634
- var UserPrompt = React7.forwardRef(
1440
+ var UserPrompt = React11.forwardRef(
635
1441
  ({ content, timestamp, className, ...props }, ref) => {
636
1442
  return /* @__PURE__ */ jsxs(
637
1443
  "div",
@@ -730,7 +1536,7 @@ function CodeBlockLeaf({ attributes, children, leaf }) {
730
1536
  }
731
1537
  return /* @__PURE__ */ jsx("span", { ...attributes, children });
732
1538
  }
733
- var UserPromptInput = React7.forwardRef(
1539
+ var UserPromptInput = React11.forwardRef(
734
1540
  ({
735
1541
  value = "",
736
1542
  onChange,
@@ -740,6 +1546,8 @@ var UserPromptInput = React7.forwardRef(
740
1546
  disabled = false,
741
1547
  isSubmitting = false,
742
1548
  onStop,
1549
+ stopTooltip,
1550
+ stopClassName,
743
1551
  disableWhileSubmitting = true,
744
1552
  autoFocus = false,
745
1553
  refocusAfterSubmit = false,
@@ -753,14 +1561,14 @@ var UserPromptInput = React7.forwardRef(
753
1561
  className,
754
1562
  ...props
755
1563
  }, ref) => {
756
- const editorRef = React7.useRef(null);
757
- const [internalValue, setInternalValue] = React7.useState(value);
758
- const prevIsSubmitting = React7.useRef(isSubmitting);
759
- const hasEmittedReady = React7.useRef(false);
760
- React7.useEffect(() => {
1564
+ const editorRef = React11.useRef(null);
1565
+ const [internalValue, setInternalValue] = React11.useState(value);
1566
+ const prevIsSubmitting = React11.useRef(isSubmitting);
1567
+ const hasEmittedReady = React11.useRef(false);
1568
+ React11.useEffect(() => {
761
1569
  setInternalValue(value);
762
1570
  }, [value]);
763
- React7.useEffect(() => {
1571
+ React11.useEffect(() => {
764
1572
  if (autoFocus) {
765
1573
  requestAnimationFrame(() => {
766
1574
  requestAnimationFrame(() => {
@@ -769,7 +1577,7 @@ var UserPromptInput = React7.forwardRef(
769
1577
  });
770
1578
  }
771
1579
  }, [autoFocus]);
772
- React7.useEffect(() => {
1580
+ React11.useEffect(() => {
773
1581
  if (!hasEmittedReady.current && onReady) {
774
1582
  requestAnimationFrame(() => {
775
1583
  requestAnimationFrame(() => {
@@ -779,7 +1587,7 @@ var UserPromptInput = React7.forwardRef(
779
1587
  });
780
1588
  }
781
1589
  }, [onReady]);
782
- React7.useEffect(() => {
1590
+ React11.useEffect(() => {
783
1591
  if (refocusAfterSubmit && prevIsSubmitting.current && !isSubmitting) {
784
1592
  requestAnimationFrame(() => {
785
1593
  editorRef.current?.focus();
@@ -787,7 +1595,7 @@ var UserPromptInput = React7.forwardRef(
787
1595
  }
788
1596
  prevIsSubmitting.current = isSubmitting;
789
1597
  }, [isSubmitting, refocusAfterSubmit]);
790
- React7.useImperativeHandle(
1598
+ React11.useImperativeHandle(
791
1599
  ref,
792
1600
  () => ({
793
1601
  focus: () => {
@@ -810,14 +1618,14 @@ var UserPromptInput = React7.forwardRef(
810
1618
  }),
811
1619
  []
812
1620
  );
813
- const handleChange = React7.useCallback(
1621
+ const handleChange = React11.useCallback(
814
1622
  (newValue) => {
815
1623
  setInternalValue(newValue);
816
1624
  onChange?.(newValue);
817
1625
  },
818
1626
  [onChange]
819
1627
  );
820
- const handleSubmit = React7.useCallback(
1628
+ const handleSubmit = React11.useCallback(
821
1629
  (text) => {
822
1630
  if (disabled || isSubmitting) return;
823
1631
  if (!text.trim()) return;
@@ -829,7 +1637,7 @@ var UserPromptInput = React7.forwardRef(
829
1637
  },
830
1638
  [disabled, isSubmitting, onSubmit, clearOnSubmit]
831
1639
  );
832
- const handleSendClick = React7.useCallback(() => {
1640
+ const handleSendClick = React11.useCallback(() => {
833
1641
  const text = editorRef.current?.getText() ?? "";
834
1642
  handleSubmit(text);
835
1643
  }, [handleSubmit]);
@@ -866,16 +1674,17 @@ var UserPromptInput = React7.forwardRef(
866
1674
  ) }),
867
1675
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pl-2 pr-1 pb-1 pt-1", children: [
868
1676
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: renderActions?.() }),
869
- isSubmitting && onStop ? /* @__PURE__ */ jsx(
1677
+ isSubmitting && onStop ? /* @__PURE__ */ jsx(Tooltip, { content: stopTooltip, disabled: !stopTooltip, children: /* @__PURE__ */ jsx(
870
1678
  IconButton,
871
1679
  {
872
1680
  icon: /* @__PURE__ */ jsx(Square, {}),
873
1681
  variant: "filled",
874
1682
  size: "sm",
875
- "aria-label": "Stop",
876
- onClick: onStop
1683
+ "aria-label": stopTooltip || "Stop",
1684
+ onClick: onStop,
1685
+ className: stopClassName
877
1686
  }
878
- ) : /* @__PURE__ */ jsx(
1687
+ ) }) : /* @__PURE__ */ jsx(
879
1688
  IconButton,
880
1689
  {
881
1690
  icon: isSubmitting ? /* @__PURE__ */ jsx(Loader2, { className: "animate-spin" }) : /* @__PURE__ */ jsx(Send, {}),
@@ -894,6 +1703,115 @@ var UserPromptInput = React7.forwardRef(
894
1703
  );
895
1704
  UserPromptInput.displayName = "UserPromptInput";
896
1705
 
897
- export { ActionBar, ActivityIndicators, AgentResponse, MetadataRow, ThinkingSection, UserPrompt, UserPromptInput, formatTime, formatTotalTime, initialAgentResponseState, useAgentResponseAccumulator, useThinkingTimer };
1706
+ // src/components/inline-actions/parseResponseSegments.ts
1707
+ var ACTION_BLOCK_REGEX = /```json:action\s*\n([\s\S]*?)```/g;
1708
+ function parseResponseSegments(text) {
1709
+ if (!text) return [];
1710
+ const segments = [];
1711
+ let lastIndex = 0;
1712
+ ACTION_BLOCK_REGEX.lastIndex = 0;
1713
+ let match;
1714
+ while ((match = ACTION_BLOCK_REGEX.exec(text)) !== null) {
1715
+ const before = text.slice(lastIndex, match.index);
1716
+ if (before.trim()) {
1717
+ segments.push({ kind: "markdown", content: before });
1718
+ }
1719
+ const jsonContent = match[1].trim();
1720
+ let parsed = null;
1721
+ try {
1722
+ parsed = JSON.parse(jsonContent);
1723
+ } catch {
1724
+ }
1725
+ if (parsed && typeof parsed === "object" && typeof parsed.type === "string") {
1726
+ segments.push({
1727
+ kind: "action",
1728
+ actionType: parsed.type,
1729
+ payload: parsed
1730
+ });
1731
+ } else {
1732
+ const rawBlock = match[0];
1733
+ segments.push({ kind: "markdown", content: rawBlock });
1734
+ }
1735
+ lastIndex = match.index + match[0].length;
1736
+ }
1737
+ const trailing = text.slice(lastIndex);
1738
+ if (trailing.trim()) {
1739
+ segments.push({ kind: "markdown", content: trailing });
1740
+ }
1741
+ return segments;
1742
+ }
1743
+ function ActionMarkdownRenderer({
1744
+ content,
1745
+ registry,
1746
+ renderMarkdown,
1747
+ onAction,
1748
+ isLatest
1749
+ }) {
1750
+ const segments = useMemo(() => parseResponseSegments(content), [content]);
1751
+ if (segments.length === 1 && segments[0].kind === "markdown") {
1752
+ return /* @__PURE__ */ jsx(Fragment, { children: renderMarkdown(segments[0].content) });
1753
+ }
1754
+ return /* @__PURE__ */ jsx(Fragment, { children: segments.map((segment, index) => {
1755
+ if (segment.kind === "markdown") {
1756
+ return /* @__PURE__ */ jsx("div", { children: renderMarkdown(segment.content) }, `md-${index}`);
1757
+ }
1758
+ const Component = registry[segment.actionType];
1759
+ if (!Component) {
1760
+ return /* @__PURE__ */ jsx(
1761
+ "pre",
1762
+ {
1763
+ className: "my-4 p-4 rounded-lg border border-border bg-muted text-sm font-mono overflow-x-auto",
1764
+ children: /* @__PURE__ */ jsx("code", { children: JSON.stringify(segment.payload, null, 2) })
1765
+ },
1766
+ `action-fallback-${index}`
1767
+ );
1768
+ }
1769
+ return /* @__PURE__ */ jsx(
1770
+ Component,
1771
+ {
1772
+ payload: segment.payload,
1773
+ onAction,
1774
+ isLatest
1775
+ },
1776
+ `action-${segment.actionType}-${index}`
1777
+ );
1778
+ }) });
1779
+ }
1780
+
1781
+ // src/components/inline-actions/prompts.ts
1782
+ var INLINE_ACTION_PROMPT = `
1783
+ <inline_actions>
1784
+ When your response should include interactive components (like query viewers,
1785
+ data tables, or executable actions), embed them as fenced code blocks using
1786
+ the \`json:action\` language tag:
1787
+
1788
+ \`\`\`json:action
1789
+ {
1790
+ "type": "action-type-here",
1791
+ ...action-specific fields
1792
+ }
1793
+ \`\`\`
1794
+
1795
+ Rules:
1796
+ - Each block must contain valid JSON with a "type" field.
1797
+ - The "type" must match a registered action component on the frontend.
1798
+ - Multiple action blocks per response are allowed.
1799
+ - Surround action blocks with normal markdown text for user context.
1800
+ - The action block is rendered as an interactive component in the chat UI.
1801
+ - SQL strings inside JSON must be properly escaped (newlines as \\n, quotes as \\").
1802
+
1803
+ Available action types:
1804
+
1805
+ - "optimap-query": Displays SQL queries with a button to execute them and
1806
+ update the 3D globe map.
1807
+ Required fields:
1808
+ - type: "optimap-query"
1809
+ - locations_sql: string (the validated locations SQL query)
1810
+ - routes_sql: string (the validated routes SQL query)
1811
+ - database_name: string (the target database name)
1812
+ </inline_actions>
1813
+ `;
1814
+
1815
+ 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 };
898
1816
  //# sourceMappingURL=index.js.map
899
1817
  //# sourceMappingURL=index.js.map