@optilogic/chat 1.0.0-beta.1 → 1.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -0
- package/dist/index.cjs +725 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +319 -6
- package/dist/index.d.ts +319 -6
- package/dist/index.js +708 -25
- package/dist/index.js.map +1 -1
- package/package.json +15 -9
- package/src/components/agent-response/AgentResponse.tsx +76 -5
- package/src/components/agent-response/components/ActivityIndicators.tsx +36 -4
- package/src/components/agent-response/components/HITLSection.tsx +95 -0
- package/src/components/agent-response/components/MetadataRow.tsx +21 -6
- package/src/components/agent-response/components/ThinkingSection.tsx +101 -9
- package/src/components/agent-response/components/TruncatedMessage.tsx +52 -0
- package/src/components/agent-response/components/index.ts +6 -0
- package/src/components/agent-response/hooks/useAgentResponseAccumulator.ts +41 -0
- package/src/components/agent-response/index.ts +7 -0
- package/src/components/agent-response/types.ts +54 -1
- package/src/components/hitl-interactions/HITLInteractionRecord.tsx +139 -0
- package/src/components/hitl-interactions/HITLQuestionPanel.tsx +263 -0
- package/src/components/hitl-interactions/index.ts +18 -0
- package/src/components/user-prompt/UserPrompt.tsx +60 -0
- package/src/components/user-prompt/index.ts +1 -0
- package/src/components/user-prompt-input/UserPromptInput.tsx +326 -0
- package/src/components/user-prompt-input/index.ts +2 -0
- package/src/components/user-prompt-input/types.ts +52 -0
- package/src/index.ts +28 -0
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as React from "react";
|
|
8
|
-
import { Wrench, Book, HardDrive } from "lucide-react";
|
|
8
|
+
import { Wrench, Book, HardDrive, Activity } from "lucide-react";
|
|
9
9
|
import { cn, Popover, PopoverTrigger, PopoverContent } from "@optilogic/core";
|
|
10
|
-
import type { ToolCall, KnowledgeItem, MemoryItem } from "../types";
|
|
10
|
+
import type { ToolCall, KnowledgeItem, MemoryItem, StatusItem } from "../types";
|
|
11
11
|
|
|
12
12
|
export interface ActivityIndicatorsProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
13
13
|
/** Tool calls to display */
|
|
@@ -16,6 +16,8 @@ export interface ActivityIndicatorsProps extends React.HTMLAttributes<HTMLDivEle
|
|
|
16
16
|
knowledge: KnowledgeItem[];
|
|
17
17
|
/** Memory items to display */
|
|
18
18
|
memory: MemoryItem[];
|
|
19
|
+
/** Status updates to display */
|
|
20
|
+
statusUpdates?: StatusItem[];
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -32,14 +34,44 @@ export interface ActivityIndicatorsProps extends React.HTMLAttributes<HTMLDivEle
|
|
|
32
34
|
* />
|
|
33
35
|
*/
|
|
34
36
|
const ActivityIndicators = React.forwardRef<HTMLDivElement, ActivityIndicatorsProps>(
|
|
35
|
-
({ toolCalls, knowledge, memory, className, ...props }, ref) => {
|
|
37
|
+
({ toolCalls, knowledge, memory, statusUpdates = [], className, ...props }, ref) => {
|
|
36
38
|
const hasAnyActivity =
|
|
37
|
-
toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0;
|
|
39
|
+
toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0 || statusUpdates.length > 0;
|
|
38
40
|
|
|
39
41
|
if (!hasAnyActivity) return null;
|
|
40
42
|
|
|
41
43
|
return (
|
|
42
44
|
<div ref={ref} className={cn("flex items-center gap-2", className)} {...props}>
|
|
45
|
+
{/* Status Updates */}
|
|
46
|
+
{statusUpdates.length > 0 && (
|
|
47
|
+
<Popover>
|
|
48
|
+
<PopoverTrigger asChild>
|
|
49
|
+
<button
|
|
50
|
+
className="flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors"
|
|
51
|
+
onClick={(e) => e.stopPropagation()}
|
|
52
|
+
>
|
|
53
|
+
<Activity className="w-3.5 h-3.5" />
|
|
54
|
+
<span className="text-xs">{statusUpdates.length}</span>
|
|
55
|
+
</button>
|
|
56
|
+
</PopoverTrigger>
|
|
57
|
+
<PopoverContent className="w-80">
|
|
58
|
+
<div className="space-y-2">
|
|
59
|
+
<h4 className="font-medium text-sm">Status Updates</h4>
|
|
60
|
+
<div className="space-y-2 max-h-60 overflow-auto">
|
|
61
|
+
{statusUpdates.map((item) => (
|
|
62
|
+
<div key={item.id} className="p-2 bg-muted rounded text-xs">
|
|
63
|
+
{item.agent && (
|
|
64
|
+
<div className="font-medium text-muted-foreground">{item.agent}</div>
|
|
65
|
+
)}
|
|
66
|
+
<div className={item.agent ? "mt-1" : ""}>{item.message}</div>
|
|
67
|
+
</div>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</PopoverContent>
|
|
72
|
+
</Popover>
|
|
73
|
+
)}
|
|
74
|
+
|
|
43
75
|
{/* Tool Calls */}
|
|
44
76
|
{toolCalls.length > 0 && (
|
|
45
77
|
<Popover>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HITL Section Component
|
|
3
|
+
*
|
|
4
|
+
* Collapsible section for displaying completed HITL (Human-in-the-Loop)
|
|
5
|
+
* interactions within an AgentResponse. Follows the same pattern as
|
|
6
|
+
* ThinkingSection for consistent UX.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { useState, useCallback } from "react";
|
|
11
|
+
import { ChevronDown, ChevronRight, MessageCircleQuestion } from "lucide-react";
|
|
12
|
+
import { cn } from "@optilogic/core";
|
|
13
|
+
import { HITLInteractionRecord } from "../../hitl-interactions";
|
|
14
|
+
import type { HITLInteraction } from "../../hitl-interactions";
|
|
15
|
+
|
|
16
|
+
export interface HITLSectionProps
|
|
17
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
18
|
+
/** The HITL interactions to display */
|
|
19
|
+
interactions: HITLInteraction[];
|
|
20
|
+
/** Whether the section starts expanded (uncontrolled) */
|
|
21
|
+
defaultExpanded?: boolean;
|
|
22
|
+
/** Whether the section is expanded (controlled) */
|
|
23
|
+
isExpanded?: boolean;
|
|
24
|
+
/** Callback when expansion state changes (controlled) */
|
|
25
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const HITLSection = React.forwardRef<HTMLDivElement, HITLSectionProps>(
|
|
29
|
+
(
|
|
30
|
+
{
|
|
31
|
+
interactions,
|
|
32
|
+
defaultExpanded = false,
|
|
33
|
+
isExpanded: controlledExpanded,
|
|
34
|
+
onExpandedChange,
|
|
35
|
+
className,
|
|
36
|
+
...props
|
|
37
|
+
},
|
|
38
|
+
ref
|
|
39
|
+
) => {
|
|
40
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] =
|
|
41
|
+
useState(defaultExpanded);
|
|
42
|
+
|
|
43
|
+
const isControlled = controlledExpanded !== undefined;
|
|
44
|
+
const isExpanded = isControlled ? controlledExpanded : uncontrolledExpanded;
|
|
45
|
+
|
|
46
|
+
const toggleExpanded = useCallback(() => {
|
|
47
|
+
const newValue = !isExpanded;
|
|
48
|
+
if (isControlled) {
|
|
49
|
+
onExpandedChange?.(newValue);
|
|
50
|
+
} else {
|
|
51
|
+
setUncontrolledExpanded(newValue);
|
|
52
|
+
}
|
|
53
|
+
}, [isExpanded, isControlled, onExpandedChange]);
|
|
54
|
+
|
|
55
|
+
if (!interactions || interactions.length === 0) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
ref={ref}
|
|
62
|
+
className={cn("border-t border-border", className)}
|
|
63
|
+
{...props}
|
|
64
|
+
>
|
|
65
|
+
{/* Collapsible header */}
|
|
66
|
+
<button
|
|
67
|
+
onClick={toggleExpanded}
|
|
68
|
+
className="w-full flex items-center gap-2 py-2 px-3 hover:bg-muted/50 transition-colors text-left"
|
|
69
|
+
>
|
|
70
|
+
{isExpanded ? (
|
|
71
|
+
<ChevronDown className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
|
72
|
+
) : (
|
|
73
|
+
<ChevronRight className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
|
74
|
+
)}
|
|
75
|
+
<MessageCircleQuestion className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
|
76
|
+
<span className="text-xs font-medium text-foreground/80">
|
|
77
|
+
Clarifying Questions ({interactions.length})
|
|
78
|
+
</span>
|
|
79
|
+
</button>
|
|
80
|
+
|
|
81
|
+
{/* Expanded content */}
|
|
82
|
+
{isExpanded && (
|
|
83
|
+
<div className="px-3 pb-3 space-y-2">
|
|
84
|
+
{interactions.map((interaction, i) => (
|
|
85
|
+
<HITLInteractionRecord key={i} interaction={interaction} />
|
|
86
|
+
))}
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
HITLSection.displayName = "HITLSection";
|
|
94
|
+
|
|
95
|
+
export { HITLSection };
|
|
@@ -9,7 +9,7 @@ import { ChevronDown, ChevronUp } from "lucide-react";
|
|
|
9
9
|
import { cn, LoadingSpinner } from "@optilogic/core";
|
|
10
10
|
import { ActivityIndicators } from "./ActivityIndicators";
|
|
11
11
|
import { formatTime } from "../utils";
|
|
12
|
-
import type { AgentResponseStatus, ToolCall, KnowledgeItem, MemoryItem } from "../types";
|
|
12
|
+
import type { AgentResponseStatus, ToolCall, KnowledgeItem, MemoryItem, StatusItem } from "../types";
|
|
13
13
|
|
|
14
14
|
export interface MetadataRowProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
15
15
|
/** Whether there is thinking content */
|
|
@@ -24,6 +24,10 @@ export interface MetadataRowProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
24
24
|
knowledge: KnowledgeItem[];
|
|
25
25
|
/** Memory items to display */
|
|
26
26
|
memory: MemoryItem[];
|
|
27
|
+
/** Status updates to display */
|
|
28
|
+
statusUpdates?: StatusItem[];
|
|
29
|
+
/** Optional content to display in the middle area between left content and activity indicators */
|
|
30
|
+
statusContent?: React.ReactNode;
|
|
27
31
|
/** Current response status */
|
|
28
32
|
status: AgentResponseStatus;
|
|
29
33
|
/** Elapsed time in seconds */
|
|
@@ -57,6 +61,8 @@ const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
|
57
61
|
toolCalls,
|
|
58
62
|
knowledge,
|
|
59
63
|
memory,
|
|
64
|
+
statusUpdates = [],
|
|
65
|
+
statusContent,
|
|
60
66
|
status,
|
|
61
67
|
elapsedTime,
|
|
62
68
|
className,
|
|
@@ -67,7 +73,7 @@ const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
|
67
73
|
const isProcessing = status === "processing";
|
|
68
74
|
const isComplete = status === "complete";
|
|
69
75
|
const hasActivity =
|
|
70
|
-
toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0;
|
|
76
|
+
toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0 || statusUpdates.length > 0;
|
|
71
77
|
|
|
72
78
|
// Determine what to show on the left side
|
|
73
79
|
const renderLeftContent = () => {
|
|
@@ -105,8 +111,8 @@ const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
|
105
111
|
|
|
106
112
|
const leftContent = renderLeftContent();
|
|
107
113
|
|
|
108
|
-
// If nothing to show (no thinking, not processing, no activity), hide the row
|
|
109
|
-
if (!leftContent && !hasActivity) {
|
|
114
|
+
// If nothing to show (no thinking, not processing, no activity, no status content), hide the row
|
|
115
|
+
if (!leftContent && !hasActivity && !statusContent) {
|
|
110
116
|
return null;
|
|
111
117
|
}
|
|
112
118
|
|
|
@@ -122,19 +128,28 @@ const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
|
122
128
|
{hasThinking ? (
|
|
123
129
|
<button
|
|
124
130
|
onClick={onToggle}
|
|
125
|
-
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"
|
|
131
|
+
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"
|
|
126
132
|
>
|
|
127
133
|
{leftContent}
|
|
128
134
|
</button>
|
|
129
135
|
) : (
|
|
130
|
-
<div className="flex items-center gap-1.5">
|
|
136
|
+
<div className="flex items-center gap-1.5 shrink-0">
|
|
131
137
|
{leftContent}
|
|
132
138
|
</div>
|
|
133
139
|
)}
|
|
140
|
+
|
|
141
|
+
{/* Middle content - status content slot */}
|
|
142
|
+
{statusContent && (
|
|
143
|
+
<div className="flex-1 min-w-0 mx-2">
|
|
144
|
+
{statusContent}
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
|
|
134
148
|
<ActivityIndicators
|
|
135
149
|
toolCalls={toolCalls}
|
|
136
150
|
knowledge={knowledge}
|
|
137
151
|
memory={memory}
|
|
152
|
+
statusUpdates={statusUpdates}
|
|
138
153
|
/>
|
|
139
154
|
</div>
|
|
140
155
|
);
|
|
@@ -1,33 +1,109 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Thinking Section Component
|
|
3
3
|
*
|
|
4
|
-
* Collapsible section for displaying agent thinking/reasoning content
|
|
4
|
+
* Collapsible section for displaying agent thinking/reasoning content.
|
|
5
|
+
* Supports both plain text and structured collapsible sub-sections.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import * as React from "react";
|
|
9
|
+
import { useState, useCallback } from "react";
|
|
10
|
+
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
8
11
|
import { cn } from "@optilogic/core";
|
|
12
|
+
import type { ThinkingStep } from "../types";
|
|
9
13
|
|
|
10
|
-
export interface ThinkingSectionProps
|
|
11
|
-
|
|
12
|
-
content
|
|
14
|
+
export interface ThinkingSectionProps
|
|
15
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "content"> {
|
|
16
|
+
/** The thinking content to display (string or structured steps) */
|
|
17
|
+
content: string | ThinkingStep[];
|
|
13
18
|
/** Whether the section is expanded */
|
|
14
19
|
isExpanded: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Custom markdown renderer for the thinking content.
|
|
22
|
+
* If not provided, the content will be rendered as plain preformatted text.
|
|
23
|
+
*/
|
|
24
|
+
renderMarkdown?: (content: string) => React.ReactNode;
|
|
15
25
|
}
|
|
16
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Internal component for rendering a collapsible thinking step
|
|
29
|
+
*/
|
|
30
|
+
interface ThinkingStepItemProps {
|
|
31
|
+
step: ThinkingStep;
|
|
32
|
+
renderMarkdown?: (content: string) => React.ReactNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ThinkingStepItem: React.FC<ThinkingStepItemProps> = ({ step, renderMarkdown }) => {
|
|
36
|
+
const [isCollapsed, setIsCollapsed] = useState(step.isCollapsed ?? false);
|
|
37
|
+
|
|
38
|
+
const toggleCollapse = useCallback(() => {
|
|
39
|
+
setIsCollapsed((prev) => !prev);
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const indentPadding = step.depth * 16;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="border-b border-border/50 last:border-b-0">
|
|
46
|
+
<button
|
|
47
|
+
onClick={toggleCollapse}
|
|
48
|
+
className="w-full flex items-center gap-1.5 py-1.5 px-2 hover:bg-muted/50 transition-colors text-left"
|
|
49
|
+
style={{ paddingLeft: `${indentPadding + 8}px` }}
|
|
50
|
+
>
|
|
51
|
+
{isCollapsed ? (
|
|
52
|
+
<ChevronRight className="w-3 h-3 text-muted-foreground flex-shrink-0" />
|
|
53
|
+
) : (
|
|
54
|
+
<ChevronDown className="w-3 h-3 text-muted-foreground flex-shrink-0" />
|
|
55
|
+
)}
|
|
56
|
+
<span className="text-xs font-medium text-foreground/80">{step.label}</span>
|
|
57
|
+
</button>
|
|
58
|
+
|
|
59
|
+
{!isCollapsed && (
|
|
60
|
+
<div
|
|
61
|
+
className="pb-2 px-2"
|
|
62
|
+
style={{ paddingLeft: `${indentPadding + 28}px` }}
|
|
63
|
+
>
|
|
64
|
+
{renderMarkdown ? (
|
|
65
|
+
<div className="text-xs text-muted-foreground">
|
|
66
|
+
{renderMarkdown(step.content)}
|
|
67
|
+
</div>
|
|
68
|
+
) : (
|
|
69
|
+
<pre className="text-xs text-muted-foreground whitespace-pre-wrap font-mono">
|
|
70
|
+
{step.content}
|
|
71
|
+
</pre>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
17
79
|
/**
|
|
18
80
|
* ThinkingSection Component
|
|
19
81
|
*
|
|
20
82
|
* Displays the agent's thinking/reasoning content in a collapsible panel.
|
|
83
|
+
* Supports both plain text content and structured collapsible sub-sections.
|
|
21
84
|
*
|
|
22
85
|
* @example
|
|
86
|
+
* // Plain text content
|
|
23
87
|
* <ThinkingSection content={state.thinking} isExpanded={isExpanded} />
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // Structured content with sub-sections
|
|
91
|
+
* <ThinkingSection
|
|
92
|
+
* content={[
|
|
93
|
+
* { id: "1", label: "Analysis", content: "...", depth: 0 },
|
|
94
|
+
* { id: "2", label: "Sub-analysis", content: "...", depth: 1 },
|
|
95
|
+
* ]}
|
|
96
|
+
* isExpanded={isExpanded}
|
|
97
|
+
* />
|
|
24
98
|
*/
|
|
25
99
|
const ThinkingSection = React.forwardRef<HTMLDivElement, ThinkingSectionProps>(
|
|
26
|
-
({ content, isExpanded, className, ...props }, ref) => {
|
|
27
|
-
if (!isExpanded || !content) {
|
|
100
|
+
({ content, isExpanded, renderMarkdown, className, ...props }, ref) => {
|
|
101
|
+
if (!isExpanded || !content || (Array.isArray(content) && content.length === 0)) {
|
|
28
102
|
return null;
|
|
29
103
|
}
|
|
30
104
|
|
|
105
|
+
const isStructured = Array.isArray(content);
|
|
106
|
+
|
|
31
107
|
return (
|
|
32
108
|
<div
|
|
33
109
|
ref={ref}
|
|
@@ -35,9 +111,25 @@ const ThinkingSection = React.forwardRef<HTMLDivElement, ThinkingSectionProps>(
|
|
|
35
111
|
{...props}
|
|
36
112
|
>
|
|
37
113
|
<div className="mt-2 max-h-[200px] overflow-y-auto">
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
114
|
+
{isStructured ? (
|
|
115
|
+
<div className="space-y-0">
|
|
116
|
+
{content.map((step) => (
|
|
117
|
+
<ThinkingStepItem
|
|
118
|
+
key={step.id}
|
|
119
|
+
step={step}
|
|
120
|
+
renderMarkdown={renderMarkdown}
|
|
121
|
+
/>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
) : renderMarkdown ? (
|
|
125
|
+
<div className="text-xs text-muted-foreground">
|
|
126
|
+
{renderMarkdown(content)}
|
|
127
|
+
</div>
|
|
128
|
+
) : (
|
|
129
|
+
<pre className="text-xs text-muted-foreground whitespace-pre-wrap font-mono">
|
|
130
|
+
{content}
|
|
131
|
+
</pre>
|
|
132
|
+
)}
|
|
41
133
|
</div>
|
|
42
134
|
</div>
|
|
43
135
|
);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncated Message Component
|
|
3
|
+
*
|
|
4
|
+
* Renders a single-line text message with CSS-based truncation (text-overflow: ellipsis).
|
|
5
|
+
* Designed as a standalone utility that can be used anywhere, including as
|
|
6
|
+
* the statusContent slot in MetadataRow.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { cn } from "@optilogic/core";
|
|
11
|
+
|
|
12
|
+
export interface TruncatedMessageProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
13
|
+
/** The message string to display (truncated with ellipsis if it overflows) */
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* TruncatedMessage Component
|
|
19
|
+
*
|
|
20
|
+
* Displays a single-line text message that truncates with an ellipsis when
|
|
21
|
+
* it overflows its container. Uses CSS text-overflow for zero-JS truncation.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* <TruncatedMessage message="Searching the knowledge base for relevant documents..." />
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Inside MetadataRow's statusContent slot
|
|
28
|
+
* <AgentResponse
|
|
29
|
+
* state={state}
|
|
30
|
+
* statusContent={<TruncatedMessage message="Running analysis..." />}
|
|
31
|
+
* />
|
|
32
|
+
*/
|
|
33
|
+
const TruncatedMessage = React.forwardRef<HTMLDivElement, TruncatedMessageProps>(
|
|
34
|
+
({ message, className, ...props }, ref) => {
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
ref={ref}
|
|
38
|
+
className={cn(
|
|
39
|
+
"text-xs text-muted-foreground truncate min-w-0",
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
title={message}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
{message}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
TruncatedMessage.displayName = "TruncatedMessage";
|
|
51
|
+
|
|
52
|
+
export { TruncatedMessage };
|
|
@@ -13,3 +13,9 @@ export type { ThinkingSectionProps } from "./ThinkingSection";
|
|
|
13
13
|
|
|
14
14
|
export { ActionBar } from "./ActionBar";
|
|
15
15
|
export type { ActionBarProps } from "./ActionBar";
|
|
16
|
+
|
|
17
|
+
export { HITLSection } from "./HITLSection";
|
|
18
|
+
export type { HITLSectionProps } from "./HITLSection";
|
|
19
|
+
|
|
20
|
+
export { TruncatedMessage } from "./TruncatedMessage";
|
|
21
|
+
export type { TruncatedMessageProps } from "./TruncatedMessage";
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
type ToolCall,
|
|
14
14
|
type KnowledgeItem,
|
|
15
15
|
type MemoryItem,
|
|
16
|
+
type StatusItem,
|
|
17
|
+
type ThinkingStep,
|
|
16
18
|
} from "../types";
|
|
17
19
|
|
|
18
20
|
export interface UseAgentResponseAccumulatorOptions {
|
|
@@ -86,6 +88,26 @@ export function useAgentResponseAccumulator(
|
|
|
86
88
|
return { ...prev, status: newStatus };
|
|
87
89
|
|
|
88
90
|
case "thinking": {
|
|
91
|
+
// Check if this is a structured thinking step
|
|
92
|
+
if (payload.thinkingStep) {
|
|
93
|
+
const newStep: ThinkingStep = {
|
|
94
|
+
id: payload.thinkingStep.id || `step-${Date.now()}`,
|
|
95
|
+
label: payload.thinkingStep.label,
|
|
96
|
+
content: payload.thinkingStep.content,
|
|
97
|
+
depth: payload.thinkingStep.depth ?? 0,
|
|
98
|
+
isCollapsed: payload.thinkingStep.isCollapsed,
|
|
99
|
+
};
|
|
100
|
+
const thinkingStartTime = prev.thinkingStartTime ?? Date.now();
|
|
101
|
+
return {
|
|
102
|
+
...prev,
|
|
103
|
+
status: newStatus,
|
|
104
|
+
thinkingSteps: [...(prev.thinkingSteps || []), newStep],
|
|
105
|
+
thinkingStartTime,
|
|
106
|
+
firstMessageTime,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Plain text thinking (existing behavior)
|
|
89
111
|
const newThinking = payload.message || payload.content || "";
|
|
90
112
|
// Add line break between thinking messages
|
|
91
113
|
const separator = prev.thinking && newThinking ? "\n\n" : "";
|
|
@@ -170,6 +192,25 @@ export function useAgentResponseAccumulator(
|
|
|
170
192
|
firstMessageTime: prev.firstMessageTime ?? Date.now(),
|
|
171
193
|
};
|
|
172
194
|
|
|
195
|
+
case "status_update": {
|
|
196
|
+
const statusMessage = payload.message || payload.statusUpdate?.message;
|
|
197
|
+
if (statusMessage) {
|
|
198
|
+
const newStatusItem: StatusItem = {
|
|
199
|
+
id: payload.statusUpdate?.id || `status-${Date.now()}`,
|
|
200
|
+
message: statusMessage,
|
|
201
|
+
agent: payload.statusUpdate?.agent,
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
...prev,
|
|
206
|
+
status: newStatus,
|
|
207
|
+
statusUpdates: [...prev.statusUpdates, newStatusItem],
|
|
208
|
+
firstMessageTime,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
212
|
+
}
|
|
213
|
+
|
|
173
214
|
default:
|
|
174
215
|
return { ...prev, status: newStatus, firstMessageTime };
|
|
175
216
|
}
|
|
@@ -15,10 +15,14 @@ export {
|
|
|
15
15
|
MetadataRow,
|
|
16
16
|
ThinkingSection,
|
|
17
17
|
ActionBar,
|
|
18
|
+
HITLSection,
|
|
19
|
+
TruncatedMessage,
|
|
18
20
|
type ActivityIndicatorsProps,
|
|
19
21
|
type MetadataRowProps,
|
|
20
22
|
type ThinkingSectionProps,
|
|
21
23
|
type ActionBarProps,
|
|
24
|
+
type HITLSectionProps,
|
|
25
|
+
type TruncatedMessageProps,
|
|
22
26
|
} from "./components";
|
|
23
27
|
|
|
24
28
|
// Hooks
|
|
@@ -38,6 +42,9 @@ export type {
|
|
|
38
42
|
ToolCall,
|
|
39
43
|
KnowledgeItem,
|
|
40
44
|
MemoryItem,
|
|
45
|
+
StatusItem,
|
|
46
|
+
ThinkingStep,
|
|
47
|
+
ThinkingContent,
|
|
41
48
|
AgentMessage,
|
|
42
49
|
GenericWebSocketMessage,
|
|
43
50
|
} from "./types";
|
|
@@ -44,6 +44,40 @@ export interface MemoryItem {
|
|
|
44
44
|
timestamp: number;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Status update information from the agent
|
|
49
|
+
*/
|
|
50
|
+
export interface StatusItem {
|
|
51
|
+
id: string;
|
|
52
|
+
message: string;
|
|
53
|
+
timestamp: number;
|
|
54
|
+
/** Optional agent name if in multi-agent scenario */
|
|
55
|
+
agent?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A single step in structured thinking content
|
|
60
|
+
*/
|
|
61
|
+
export interface ThinkingStep {
|
|
62
|
+
/** Unique identifier for the step */
|
|
63
|
+
id: string;
|
|
64
|
+
/** Label/title shown in the collapsible header */
|
|
65
|
+
label: string;
|
|
66
|
+
/** Content of the thinking step */
|
|
67
|
+
content: string;
|
|
68
|
+
/** Nesting depth (0 = root level, 1 = first indent, etc.) */
|
|
69
|
+
depth: number;
|
|
70
|
+
/** Whether this step should start collapsed (default: false) */
|
|
71
|
+
isCollapsed?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Union type for thinking content
|
|
76
|
+
* - string: plain text (backward compatible)
|
|
77
|
+
* - ThinkingStep[]: structured with collapsible sub-sections
|
|
78
|
+
*/
|
|
79
|
+
export type ThinkingContent = string | ThinkingStep[];
|
|
80
|
+
|
|
47
81
|
/**
|
|
48
82
|
* State shape for the agent response component
|
|
49
83
|
*/
|
|
@@ -52,12 +86,16 @@ export interface AgentResponseState {
|
|
|
52
86
|
status: AgentResponseStatus;
|
|
53
87
|
/** Accumulated thinking/reasoning text */
|
|
54
88
|
thinking: string;
|
|
89
|
+
/** Structured thinking steps (if provided, takes precedence over thinking string) */
|
|
90
|
+
thinkingSteps?: ThinkingStep[];
|
|
55
91
|
/** Tool calls made during processing */
|
|
56
92
|
toolCalls: ToolCall[];
|
|
57
93
|
/** Knowledge items retrieved */
|
|
58
94
|
knowledge: KnowledgeItem[];
|
|
59
95
|
/** Memory items accessed */
|
|
60
96
|
memory: MemoryItem[];
|
|
97
|
+
/** Status updates from the agent */
|
|
98
|
+
statusUpdates: StatusItem[];
|
|
61
99
|
/** Final response text */
|
|
62
100
|
response: string;
|
|
63
101
|
/** Timestamp when first thinking message was received (for timer) */
|
|
@@ -72,7 +110,7 @@ export interface AgentResponseState {
|
|
|
72
110
|
* WebSocket message payload for agent responses
|
|
73
111
|
*/
|
|
74
112
|
export interface AgentMessage {
|
|
75
|
-
type: "status" | "thinking" | "tool_call" | "knowledge" | "memory" | "response";
|
|
113
|
+
type: "status" | "thinking" | "tool_call" | "knowledge" | "memory" | "response" | "status_update";
|
|
76
114
|
/** Message content - for simple string payloads */
|
|
77
115
|
message?: string;
|
|
78
116
|
/** Alternative content field */
|
|
@@ -97,6 +135,20 @@ export interface AgentMessage {
|
|
|
97
135
|
type: string;
|
|
98
136
|
content: string;
|
|
99
137
|
};
|
|
138
|
+
/** For status_update messages */
|
|
139
|
+
statusUpdate?: {
|
|
140
|
+
id: string;
|
|
141
|
+
message: string;
|
|
142
|
+
agent?: string;
|
|
143
|
+
};
|
|
144
|
+
/** For structured thinking step messages */
|
|
145
|
+
thinkingStep?: {
|
|
146
|
+
id?: string;
|
|
147
|
+
label: string;
|
|
148
|
+
content: string;
|
|
149
|
+
depth?: number;
|
|
150
|
+
isCollapsed?: boolean;
|
|
151
|
+
};
|
|
100
152
|
}
|
|
101
153
|
|
|
102
154
|
/**
|
|
@@ -117,6 +169,7 @@ export const initialAgentResponseState: AgentResponseState = {
|
|
|
117
169
|
toolCalls: [],
|
|
118
170
|
knowledge: [],
|
|
119
171
|
memory: [],
|
|
172
|
+
statusUpdates: [],
|
|
120
173
|
response: "",
|
|
121
174
|
thinkingStartTime: null,
|
|
122
175
|
responseCompleteTime: null,
|