@optilogic/chat 1.0.0-beta.1
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/LICENSE +21 -0
- package/dist/index.cjs +567 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +378 -0
- package/dist/index.d.ts +378 -0
- package/dist/index.js +537 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
- package/src/components/agent-response/AgentResponse.tsx +232 -0
- package/src/components/agent-response/components/ActionBar.tsx +150 -0
- package/src/components/agent-response/components/ActivityIndicators.tsx +140 -0
- package/src/components/agent-response/components/MetadataRow.tsx +145 -0
- package/src/components/agent-response/components/ThinkingSection.tsx +48 -0
- package/src/components/agent-response/components/index.ts +15 -0
- package/src/components/agent-response/hooks/index.ts +12 -0
- package/src/components/agent-response/hooks/useAgentResponseAccumulator.ts +186 -0
- package/src/components/agent-response/hooks/useThinkingTimer.ts +52 -0
- package/src/components/agent-response/index.ts +48 -0
- package/src/components/agent-response/types.ts +124 -0
- package/src/components/agent-response/utils.ts +48 -0
- package/src/index.ts +46 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata Row Component
|
|
3
|
+
*
|
|
4
|
+
* Displays thinking toggle, timer, and activity indicators
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
9
|
+
import { cn, LoadingSpinner } from "@optilogic/core";
|
|
10
|
+
import { ActivityIndicators } from "./ActivityIndicators";
|
|
11
|
+
import { formatTime } from "../utils";
|
|
12
|
+
import type { AgentResponseStatus, ToolCall, KnowledgeItem, MemoryItem } from "../types";
|
|
13
|
+
|
|
14
|
+
export interface MetadataRowProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
15
|
+
/** Whether there is thinking content */
|
|
16
|
+
hasThinking: boolean;
|
|
17
|
+
/** Whether the thinking section is expanded */
|
|
18
|
+
isExpanded: boolean;
|
|
19
|
+
/** Toggle callback for thinking expansion */
|
|
20
|
+
onToggle: () => void;
|
|
21
|
+
/** Tool calls to display */
|
|
22
|
+
toolCalls: ToolCall[];
|
|
23
|
+
/** Knowledge items to display */
|
|
24
|
+
knowledge: KnowledgeItem[];
|
|
25
|
+
/** Memory items to display */
|
|
26
|
+
memory: MemoryItem[];
|
|
27
|
+
/** Current response status */
|
|
28
|
+
status: AgentResponseStatus;
|
|
29
|
+
/** Elapsed time in seconds */
|
|
30
|
+
elapsedTime: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* MetadataRow Component
|
|
35
|
+
*
|
|
36
|
+
* Displays the metadata row with thinking toggle, timer, and activity indicators.
|
|
37
|
+
* When thinking content is present, the row is clickable to toggle expansion.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* <MetadataRow
|
|
41
|
+
* hasThinking={!!state.thinking}
|
|
42
|
+
* isExpanded={thinkingExpanded}
|
|
43
|
+
* onToggle={toggleThinking}
|
|
44
|
+
* toolCalls={state.toolCalls}
|
|
45
|
+
* knowledge={state.knowledge}
|
|
46
|
+
* memory={state.memory}
|
|
47
|
+
* status={state.status}
|
|
48
|
+
* elapsedTime={elapsedTime}
|
|
49
|
+
* />
|
|
50
|
+
*/
|
|
51
|
+
const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
52
|
+
(
|
|
53
|
+
{
|
|
54
|
+
hasThinking,
|
|
55
|
+
isExpanded,
|
|
56
|
+
onToggle,
|
|
57
|
+
toolCalls,
|
|
58
|
+
knowledge,
|
|
59
|
+
memory,
|
|
60
|
+
status,
|
|
61
|
+
elapsedTime,
|
|
62
|
+
className,
|
|
63
|
+
...props
|
|
64
|
+
},
|
|
65
|
+
ref
|
|
66
|
+
) => {
|
|
67
|
+
const isProcessing = status === "processing";
|
|
68
|
+
const isComplete = status === "complete";
|
|
69
|
+
const hasActivity =
|
|
70
|
+
toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0;
|
|
71
|
+
|
|
72
|
+
// Determine what to show on the left side
|
|
73
|
+
const renderLeftContent = () => {
|
|
74
|
+
// If we have thinking text, show collapse toggle + label + timer
|
|
75
|
+
if (hasThinking) {
|
|
76
|
+
return (
|
|
77
|
+
<div className="flex items-center gap-1.5">
|
|
78
|
+
{isExpanded ? (
|
|
79
|
+
<ChevronUp className="w-3.5 h-3.5 text-muted-foreground" />
|
|
80
|
+
) : (
|
|
81
|
+
<ChevronDown className="w-3.5 h-3.5 text-muted-foreground" />
|
|
82
|
+
)}
|
|
83
|
+
<span className="text-xs text-muted-foreground">
|
|
84
|
+
{isComplete
|
|
85
|
+
? `Thought for ${formatTime(elapsedTime, true)}`
|
|
86
|
+
: `Thinking... ${formatTime(elapsedTime, false)}`}
|
|
87
|
+
</span>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// If processing but no thinking text yet, show spinner
|
|
93
|
+
if (isProcessing) {
|
|
94
|
+
return (
|
|
95
|
+
<div className="flex items-center gap-1.5">
|
|
96
|
+
<LoadingSpinner size="sm" variant="muted" className="w-3.5 h-3.5" />
|
|
97
|
+
<span className="text-xs text-muted-foreground">Processing</span>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Complete with no thinking - show nothing on left (just activity on right)
|
|
103
|
+
return null;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const leftContent = renderLeftContent();
|
|
107
|
+
|
|
108
|
+
// If nothing to show (no thinking, not processing, no activity), hide the row
|
|
109
|
+
if (!leftContent && !hasActivity) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Always use a div for the row to avoid nesting buttons
|
|
114
|
+
// When there's thinking, only the left side is clickable
|
|
115
|
+
return (
|
|
116
|
+
<div
|
|
117
|
+
ref={ref}
|
|
118
|
+
className={cn("w-full flex items-center justify-between px-3 py-2", className)}
|
|
119
|
+
{...props}
|
|
120
|
+
>
|
|
121
|
+
{/* Left content - clickable when there's thinking */}
|
|
122
|
+
{hasThinking ? (
|
|
123
|
+
<button
|
|
124
|
+
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"
|
|
126
|
+
>
|
|
127
|
+
{leftContent}
|
|
128
|
+
</button>
|
|
129
|
+
) : (
|
|
130
|
+
<div className="flex items-center gap-1.5">
|
|
131
|
+
{leftContent}
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
<ActivityIndicators
|
|
135
|
+
toolCalls={toolCalls}
|
|
136
|
+
knowledge={knowledge}
|
|
137
|
+
memory={memory}
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
MetadataRow.displayName = "MetadataRow";
|
|
144
|
+
|
|
145
|
+
export { MetadataRow };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thinking Section Component
|
|
3
|
+
*
|
|
4
|
+
* Collapsible section for displaying agent thinking/reasoning content
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { cn } from "@optilogic/core";
|
|
9
|
+
|
|
10
|
+
export interface ThinkingSectionProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
11
|
+
/** The thinking content to display */
|
|
12
|
+
content: string;
|
|
13
|
+
/** Whether the section is expanded */
|
|
14
|
+
isExpanded: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* ThinkingSection Component
|
|
19
|
+
*
|
|
20
|
+
* Displays the agent's thinking/reasoning content in a collapsible panel.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* <ThinkingSection content={state.thinking} isExpanded={isExpanded} />
|
|
24
|
+
*/
|
|
25
|
+
const ThinkingSection = React.forwardRef<HTMLDivElement, ThinkingSectionProps>(
|
|
26
|
+
({ content, isExpanded, className, ...props }, ref) => {
|
|
27
|
+
if (!isExpanded || !content) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
ref={ref}
|
|
34
|
+
className={cn("px-3 pb-3 border-t border-border", className)}
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
<div className="mt-2 max-h-[200px] overflow-y-auto">
|
|
38
|
+
<pre className="text-xs text-muted-foreground whitespace-pre-wrap font-mono">
|
|
39
|
+
{content}
|
|
40
|
+
</pre>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
ThinkingSection.displayName = "ThinkingSection";
|
|
47
|
+
|
|
48
|
+
export { ThinkingSection };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Response Sub-Components
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { ActivityIndicators } from "./ActivityIndicators";
|
|
6
|
+
export type { ActivityIndicatorsProps } from "./ActivityIndicators";
|
|
7
|
+
|
|
8
|
+
export { MetadataRow } from "./MetadataRow";
|
|
9
|
+
export type { MetadataRowProps } from "./MetadataRow";
|
|
10
|
+
|
|
11
|
+
export { ThinkingSection } from "./ThinkingSection";
|
|
12
|
+
export type { ThinkingSectionProps } from "./ThinkingSection";
|
|
13
|
+
|
|
14
|
+
export { ActionBar } from "./ActionBar";
|
|
15
|
+
export type { ActionBarProps } from "./ActionBar";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Response Hooks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { useThinkingTimer } from "./useThinkingTimer";
|
|
6
|
+
export type { UseThinkingTimerOptions } from "./useThinkingTimer";
|
|
7
|
+
|
|
8
|
+
export { useAgentResponseAccumulator } from "./useAgentResponseAccumulator";
|
|
9
|
+
export type {
|
|
10
|
+
UseAgentResponseAccumulatorOptions,
|
|
11
|
+
UseAgentResponseAccumulatorReturn,
|
|
12
|
+
} from "./useAgentResponseAccumulator";
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAgentResponseAccumulator Hook
|
|
3
|
+
*
|
|
4
|
+
* Accumulates agent response messages into a unified state
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useCallback } from "react";
|
|
8
|
+
import {
|
|
9
|
+
initialAgentResponseState,
|
|
10
|
+
type AgentResponseState,
|
|
11
|
+
type AgentMessage,
|
|
12
|
+
type GenericWebSocketMessage,
|
|
13
|
+
type ToolCall,
|
|
14
|
+
type KnowledgeItem,
|
|
15
|
+
type MemoryItem,
|
|
16
|
+
} from "../types";
|
|
17
|
+
|
|
18
|
+
export interface UseAgentResponseAccumulatorOptions {
|
|
19
|
+
/** WebSocket topic to filter messages (optional, for convenience) */
|
|
20
|
+
topic?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UseAgentResponseAccumulatorReturn {
|
|
24
|
+
/** Current accumulated state */
|
|
25
|
+
state: AgentResponseState;
|
|
26
|
+
|
|
27
|
+
/** Handler to process incoming messages */
|
|
28
|
+
handleMessage: (message: unknown) => void;
|
|
29
|
+
|
|
30
|
+
/** Reset state to initial */
|
|
31
|
+
reset: () => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hook for accumulating agent response messages into a unified state
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const { state, handleMessage, reset } = useAgentResponseAccumulator({ topic: "agent" });
|
|
39
|
+
*
|
|
40
|
+
* // In your WebSocket handler:
|
|
41
|
+
* websocket.onmessage = (event) => {
|
|
42
|
+
* handleMessage(JSON.parse(event.data));
|
|
43
|
+
* };
|
|
44
|
+
*/
|
|
45
|
+
export function useAgentResponseAccumulator(
|
|
46
|
+
options?: UseAgentResponseAccumulatorOptions
|
|
47
|
+
): UseAgentResponseAccumulatorReturn {
|
|
48
|
+
const [state, setState] = useState<AgentResponseState>(initialAgentResponseState);
|
|
49
|
+
const topic = options?.topic;
|
|
50
|
+
|
|
51
|
+
const handleMessage = useCallback(
|
|
52
|
+
(message: unknown) => {
|
|
53
|
+
// If topic filter is provided, check for matching topic
|
|
54
|
+
let payload: AgentMessage;
|
|
55
|
+
|
|
56
|
+
if (topic) {
|
|
57
|
+
const msg = message as GenericWebSocketMessage;
|
|
58
|
+
if (msg.topic !== topic) return;
|
|
59
|
+
payload = msg.message;
|
|
60
|
+
} else {
|
|
61
|
+
// Assume message is the payload directly
|
|
62
|
+
payload = message as AgentMessage;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setState((prev) => {
|
|
66
|
+
// If we receive a non-status message while idle, transition to processing
|
|
67
|
+
let newStatus = prev.status;
|
|
68
|
+
const isFirstMessage = prev.status === "idle" && payload.type !== "status";
|
|
69
|
+
if (isFirstMessage) {
|
|
70
|
+
newStatus = "processing";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Track first message time for total time calculation
|
|
74
|
+
const firstMessageTime =
|
|
75
|
+
prev.firstMessageTime ?? (isFirstMessage ? Date.now() : null);
|
|
76
|
+
|
|
77
|
+
switch (payload.type) {
|
|
78
|
+
case "status":
|
|
79
|
+
// "Harness connected" resets to idle
|
|
80
|
+
if (
|
|
81
|
+
payload.message === "Harness connected" ||
|
|
82
|
+
payload.status === "Harness connected"
|
|
83
|
+
) {
|
|
84
|
+
return { ...initialAgentResponseState };
|
|
85
|
+
}
|
|
86
|
+
return { ...prev, status: newStatus };
|
|
87
|
+
|
|
88
|
+
case "thinking": {
|
|
89
|
+
const newThinking = payload.message || payload.content || "";
|
|
90
|
+
// Add line break between thinking messages
|
|
91
|
+
const separator = prev.thinking && newThinking ? "\n\n" : "";
|
|
92
|
+
// Set thinkingStartTime on first thinking message
|
|
93
|
+
const thinkingStartTime =
|
|
94
|
+
prev.thinkingStartTime ?? (newThinking ? Date.now() : null);
|
|
95
|
+
return {
|
|
96
|
+
...prev,
|
|
97
|
+
status: newStatus,
|
|
98
|
+
thinking: prev.thinking + separator + newThinking,
|
|
99
|
+
thinkingStartTime,
|
|
100
|
+
firstMessageTime,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
case "tool_call": {
|
|
105
|
+
// Handle both formats: { message: "ToolName" } or { tool: { id, name, arguments } }
|
|
106
|
+
const toolName = payload.message || payload.tool?.name;
|
|
107
|
+
if (toolName) {
|
|
108
|
+
const newToolCall: ToolCall = {
|
|
109
|
+
id: payload.tool?.id || `tool-${Date.now()}`,
|
|
110
|
+
name: toolName,
|
|
111
|
+
arguments: payload.tool?.arguments,
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
...prev,
|
|
116
|
+
status: newStatus,
|
|
117
|
+
toolCalls: [...prev.toolCalls, newToolCall],
|
|
118
|
+
firstMessageTime,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
case "knowledge": {
|
|
125
|
+
// Handle both formats: { message: "content" } or { knowledge: { id, source, content } }
|
|
126
|
+
const knowledgeContent = payload.message || payload.knowledge?.content;
|
|
127
|
+
if (knowledgeContent) {
|
|
128
|
+
const newKnowledge: KnowledgeItem = {
|
|
129
|
+
id: payload.knowledge?.id || `knowledge-${Date.now()}`,
|
|
130
|
+
source: payload.knowledge?.source || "unknown",
|
|
131
|
+
content: knowledgeContent,
|
|
132
|
+
timestamp: Date.now(),
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
...prev,
|
|
136
|
+
status: newStatus,
|
|
137
|
+
knowledge: [...prev.knowledge, newKnowledge],
|
|
138
|
+
firstMessageTime,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
case "memory": {
|
|
145
|
+
// Handle both formats: { message: "content" } or { memory: { id, type, content } }
|
|
146
|
+
const memoryContent = payload.message || payload.memory?.content;
|
|
147
|
+
if (memoryContent) {
|
|
148
|
+
const newMemory: MemoryItem = {
|
|
149
|
+
id: payload.memory?.id || `memory-${Date.now()}`,
|
|
150
|
+
type: payload.memory?.type || "unknown",
|
|
151
|
+
content: memoryContent,
|
|
152
|
+
timestamp: Date.now(),
|
|
153
|
+
};
|
|
154
|
+
return {
|
|
155
|
+
...prev,
|
|
156
|
+
status: newStatus,
|
|
157
|
+
memory: [...prev.memory, newMemory],
|
|
158
|
+
firstMessageTime,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case "response":
|
|
165
|
+
return {
|
|
166
|
+
...prev,
|
|
167
|
+
status: "complete",
|
|
168
|
+
response: payload.message || payload.content || "",
|
|
169
|
+
responseCompleteTime: Date.now(),
|
|
170
|
+
firstMessageTime: prev.firstMessageTime ?? Date.now(),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
default:
|
|
174
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
[topic]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const reset = useCallback(() => {
|
|
182
|
+
setState(initialAgentResponseState);
|
|
183
|
+
}, []);
|
|
184
|
+
|
|
185
|
+
return { state, handleMessage, reset };
|
|
186
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useThinkingTimer Hook
|
|
3
|
+
*
|
|
4
|
+
* Tracks elapsed time during agent thinking/processing
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useEffect } from "react";
|
|
8
|
+
import type { AgentResponseStatus } from "../types";
|
|
9
|
+
|
|
10
|
+
export interface UseThinkingTimerOptions {
|
|
11
|
+
startTime: number | null;
|
|
12
|
+
endTime: number | null;
|
|
13
|
+
status: AgentResponseStatus;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Custom hook for thinking timer
|
|
18
|
+
* Returns elapsed time in seconds
|
|
19
|
+
*/
|
|
20
|
+
export function useThinkingTimer({
|
|
21
|
+
startTime,
|
|
22
|
+
endTime,
|
|
23
|
+
status,
|
|
24
|
+
}: UseThinkingTimerOptions): number {
|
|
25
|
+
const [elapsed, setElapsed] = useState(0);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!startTime) {
|
|
29
|
+
setElapsed(0);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// If complete, calculate final elapsed time
|
|
34
|
+
if (status === "complete" && endTime) {
|
|
35
|
+
setElapsed((endTime - startTime) / 1000);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// If still processing, update every second
|
|
40
|
+
if (status === "processing") {
|
|
41
|
+
const updateElapsed = () => {
|
|
42
|
+
setElapsed((Date.now() - startTime) / 1000);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
updateElapsed(); // Initial update
|
|
46
|
+
const interval = setInterval(updateElapsed, 1000);
|
|
47
|
+
return () => clearInterval(interval);
|
|
48
|
+
}
|
|
49
|
+
}, [startTime, endTime, status]);
|
|
50
|
+
|
|
51
|
+
return elapsed;
|
|
52
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Response Component Library
|
|
3
|
+
*
|
|
4
|
+
* A library-ready component for displaying AI agent responses with
|
|
5
|
+
* thinking, tool calls, knowledge retrieval, memory access, and final response.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Main component
|
|
9
|
+
export { AgentResponse } from "./AgentResponse";
|
|
10
|
+
export type { AgentResponseProps } from "./AgentResponse";
|
|
11
|
+
|
|
12
|
+
// Sub-components (for advanced customization)
|
|
13
|
+
export {
|
|
14
|
+
ActivityIndicators,
|
|
15
|
+
MetadataRow,
|
|
16
|
+
ThinkingSection,
|
|
17
|
+
ActionBar,
|
|
18
|
+
type ActivityIndicatorsProps,
|
|
19
|
+
type MetadataRowProps,
|
|
20
|
+
type ThinkingSectionProps,
|
|
21
|
+
type ActionBarProps,
|
|
22
|
+
} from "./components";
|
|
23
|
+
|
|
24
|
+
// Hooks
|
|
25
|
+
export {
|
|
26
|
+
useAgentResponseAccumulator,
|
|
27
|
+
useThinkingTimer,
|
|
28
|
+
type UseAgentResponseAccumulatorOptions,
|
|
29
|
+
type UseAgentResponseAccumulatorReturn,
|
|
30
|
+
type UseThinkingTimerOptions,
|
|
31
|
+
} from "./hooks";
|
|
32
|
+
|
|
33
|
+
// Types
|
|
34
|
+
export type {
|
|
35
|
+
AgentResponseState,
|
|
36
|
+
AgentResponseStatus,
|
|
37
|
+
FeedbackValue,
|
|
38
|
+
ToolCall,
|
|
39
|
+
KnowledgeItem,
|
|
40
|
+
MemoryItem,
|
|
41
|
+
AgentMessage,
|
|
42
|
+
GenericWebSocketMessage,
|
|
43
|
+
} from "./types";
|
|
44
|
+
|
|
45
|
+
export { initialAgentResponseState } from "./types";
|
|
46
|
+
|
|
47
|
+
// Utilities
|
|
48
|
+
export { formatTime, formatTotalTime } from "./utils";
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Response Component Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the library-ready agent response component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Status of the agent response cycle
|
|
9
|
+
*/
|
|
10
|
+
export type AgentResponseStatus = "idle" | "processing" | "complete";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Feedback value for the response
|
|
14
|
+
*/
|
|
15
|
+
export type FeedbackValue = "up" | "down" | null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tool call information from the agent
|
|
19
|
+
*/
|
|
20
|
+
export interface ToolCall {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
arguments?: Record<string, unknown>;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Knowledge retrieval information
|
|
29
|
+
*/
|
|
30
|
+
export interface KnowledgeItem {
|
|
31
|
+
id: string;
|
|
32
|
+
source: string;
|
|
33
|
+
content: string;
|
|
34
|
+
timestamp: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Memory access information
|
|
39
|
+
*/
|
|
40
|
+
export interface MemoryItem {
|
|
41
|
+
id: string;
|
|
42
|
+
type: string;
|
|
43
|
+
content: string;
|
|
44
|
+
timestamp: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* State shape for the agent response component
|
|
49
|
+
*/
|
|
50
|
+
export interface AgentResponseState {
|
|
51
|
+
/** Current status of the response cycle */
|
|
52
|
+
status: AgentResponseStatus;
|
|
53
|
+
/** Accumulated thinking/reasoning text */
|
|
54
|
+
thinking: string;
|
|
55
|
+
/** Tool calls made during processing */
|
|
56
|
+
toolCalls: ToolCall[];
|
|
57
|
+
/** Knowledge items retrieved */
|
|
58
|
+
knowledge: KnowledgeItem[];
|
|
59
|
+
/** Memory items accessed */
|
|
60
|
+
memory: MemoryItem[];
|
|
61
|
+
/** Final response text */
|
|
62
|
+
response: string;
|
|
63
|
+
/** Timestamp when first thinking message was received (for timer) */
|
|
64
|
+
thinkingStartTime: number | null;
|
|
65
|
+
/** Timestamp when response was completed (for final timer display) */
|
|
66
|
+
responseCompleteTime: number | null;
|
|
67
|
+
/** Timestamp when first message of any type was received (for total time) */
|
|
68
|
+
firstMessageTime: number | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* WebSocket message payload for agent responses
|
|
73
|
+
*/
|
|
74
|
+
export interface AgentMessage {
|
|
75
|
+
type: "status" | "thinking" | "tool_call" | "knowledge" | "memory" | "response";
|
|
76
|
+
/** Message content - for simple string payloads */
|
|
77
|
+
message?: string;
|
|
78
|
+
/** Alternative content field */
|
|
79
|
+
content?: string;
|
|
80
|
+
/** For status messages */
|
|
81
|
+
status?: string;
|
|
82
|
+
/** For tool_call messages */
|
|
83
|
+
tool?: {
|
|
84
|
+
id: string;
|
|
85
|
+
name: string;
|
|
86
|
+
arguments?: Record<string, unknown>;
|
|
87
|
+
};
|
|
88
|
+
/** For knowledge messages */
|
|
89
|
+
knowledge?: {
|
|
90
|
+
id: string;
|
|
91
|
+
source: string;
|
|
92
|
+
content: string;
|
|
93
|
+
};
|
|
94
|
+
/** For memory messages */
|
|
95
|
+
memory?: {
|
|
96
|
+
id: string;
|
|
97
|
+
type: string;
|
|
98
|
+
content: string;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generic websocket message wrapper type
|
|
104
|
+
*/
|
|
105
|
+
export interface GenericWebSocketMessage {
|
|
106
|
+
topic: string;
|
|
107
|
+
message: AgentMessage;
|
|
108
|
+
accountId?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Initial state for the agent response component
|
|
113
|
+
*/
|
|
114
|
+
export const initialAgentResponseState: AgentResponseState = {
|
|
115
|
+
status: "idle",
|
|
116
|
+
thinking: "",
|
|
117
|
+
toolCalls: [],
|
|
118
|
+
knowledge: [],
|
|
119
|
+
memory: [],
|
|
120
|
+
response: "",
|
|
121
|
+
thinkingStartTime: null,
|
|
122
|
+
responseCompleteTime: null,
|
|
123
|
+
firstMessageTime: null,
|
|
124
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Response Utility Functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format elapsed time for display
|
|
7
|
+
* - Under 60s: "Xs" (e.g., "23s")
|
|
8
|
+
* - 60+ seconds while active: "M:SS" (e.g., "1:23")
|
|
9
|
+
* - Complete under 60s: "Xs" (e.g., "45s")
|
|
10
|
+
* - Complete 60-119s: "1.X min" (e.g., "1.2 min")
|
|
11
|
+
* - Complete 120+ s: "X.X min" (e.g., "2.5 min")
|
|
12
|
+
*/
|
|
13
|
+
export function formatTime(seconds: number, isComplete: boolean): string {
|
|
14
|
+
if (seconds < 1) {
|
|
15
|
+
return isComplete ? "<1s" : "0s";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (isComplete) {
|
|
19
|
+
// Completed state formatting
|
|
20
|
+
if (seconds < 60) {
|
|
21
|
+
return `${Math.round(seconds)}s`;
|
|
22
|
+
}
|
|
23
|
+
const minutes = seconds / 60;
|
|
24
|
+
return `${minutes.toFixed(1)} min`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Active state formatting
|
|
28
|
+
if (seconds < 60) {
|
|
29
|
+
return `${Math.floor(seconds)}s`;
|
|
30
|
+
}
|
|
31
|
+
const mins = Math.floor(seconds / 60);
|
|
32
|
+
const secs = Math.floor(seconds % 60);
|
|
33
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Format total time for action bar display
|
|
38
|
+
*/
|
|
39
|
+
export function formatTotalTime(seconds: number): string {
|
|
40
|
+
if (seconds < 1) {
|
|
41
|
+
return "<1s";
|
|
42
|
+
}
|
|
43
|
+
if (seconds < 60) {
|
|
44
|
+
return `${seconds.toFixed(1)}s`;
|
|
45
|
+
}
|
|
46
|
+
const minutes = seconds / 60;
|
|
47
|
+
return `${minutes.toFixed(1)}m`;
|
|
48
|
+
}
|