@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
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@optilogic/chat",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "Chat UI components for Optilogic - AgentResponse and related components for LLM interactions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"src",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@optilogic/core": "1.0.0-beta.1"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"lucide-react": "^0.400.0",
|
|
31
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
32
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/react": "^18.3.0",
|
|
36
|
+
"@types/react-dom": "^18.3.0",
|
|
37
|
+
"lucide-react": "^0.468.0",
|
|
38
|
+
"react": "^18.3.1",
|
|
39
|
+
"react-dom": "^18.3.1",
|
|
40
|
+
"tsup": "^8.3.5",
|
|
41
|
+
"typescript": "^5.7.2"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"react",
|
|
45
|
+
"chat",
|
|
46
|
+
"llm",
|
|
47
|
+
"ai",
|
|
48
|
+
"agent",
|
|
49
|
+
"optilogic"
|
|
50
|
+
],
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "https://github.com/optilogic/opti-ui"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsup",
|
|
61
|
+
"dev": "tsup --watch",
|
|
62
|
+
"typecheck": "tsc --noEmit",
|
|
63
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
64
|
+
"clean": "rm -rf dist .turbo"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentResponse Component
|
|
3
|
+
*
|
|
4
|
+
* A library-ready presentational component for displaying AI agent responses.
|
|
5
|
+
* Displays thinking, tool calls, knowledge retrieval, memory access, and final response.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { useState, useMemo, useCallback } from "react";
|
|
10
|
+
import { cn } from "@optilogic/core";
|
|
11
|
+
import { MetadataRow, ThinkingSection, ActionBar } from "./components";
|
|
12
|
+
import { useThinkingTimer } from "./hooks";
|
|
13
|
+
import type { AgentResponseState, FeedbackValue } from "./types";
|
|
14
|
+
|
|
15
|
+
export interface AgentResponseProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
16
|
+
/** The response state to render */
|
|
17
|
+
state: AgentResponseState;
|
|
18
|
+
|
|
19
|
+
/** Optional unique ID (for list keys) */
|
|
20
|
+
id?: string;
|
|
21
|
+
|
|
22
|
+
/** Optional timestamp to display */
|
|
23
|
+
timestamp?: Date;
|
|
24
|
+
|
|
25
|
+
/** Feedback state (controlled) */
|
|
26
|
+
feedback?: FeedbackValue;
|
|
27
|
+
onFeedbackChange?: (feedback: FeedbackValue) => void;
|
|
28
|
+
|
|
29
|
+
/** Callback when the response is copied via the action bar */
|
|
30
|
+
onResponseCopy?: (response: string) => void;
|
|
31
|
+
|
|
32
|
+
/** Thinking section expansion (controlled or uncontrolled) */
|
|
33
|
+
defaultThinkingExpanded?: boolean;
|
|
34
|
+
thinkingExpanded?: boolean;
|
|
35
|
+
onThinkingExpandedChange?: (expanded: boolean) => void;
|
|
36
|
+
|
|
37
|
+
/** Action bar visibility mode */
|
|
38
|
+
actionsVisible?: boolean | "hover";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Custom markdown renderer for the response content.
|
|
42
|
+
* If not provided, the response will be rendered as plain text.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* <AgentResponse
|
|
46
|
+
* state={state}
|
|
47
|
+
* renderMarkdown={(content) => <MyMarkdownRenderer content={content} />}
|
|
48
|
+
* />
|
|
49
|
+
*/
|
|
50
|
+
renderMarkdown?: (content: string) => React.ReactNode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* AgentResponse Component
|
|
55
|
+
*
|
|
56
|
+
* A complete component for displaying AI agent responses including:
|
|
57
|
+
* - Thinking/reasoning content (collapsible)
|
|
58
|
+
* - Tool calls, knowledge retrieval, and memory access indicators
|
|
59
|
+
* - Final response with optional markdown rendering
|
|
60
|
+
* - Action bar with copy and feedback buttons
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // Basic usage with useAgentResponseAccumulator hook
|
|
64
|
+
* const { state, handleMessage } = useAgentResponseAccumulator();
|
|
65
|
+
*
|
|
66
|
+
* <AgentResponse state={state} />
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* // With markdown rendering and feedback
|
|
70
|
+
* <AgentResponse
|
|
71
|
+
* state={state}
|
|
72
|
+
* renderMarkdown={(content) => <ReactMarkdown>{content}</ReactMarkdown>}
|
|
73
|
+
* feedback={feedback}
|
|
74
|
+
* onFeedbackChange={setFeedback}
|
|
75
|
+
* />
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // Controlled thinking expansion
|
|
79
|
+
* <AgentResponse
|
|
80
|
+
* state={state}
|
|
81
|
+
* thinkingExpanded={isExpanded}
|
|
82
|
+
* onThinkingExpandedChange={setIsExpanded}
|
|
83
|
+
* />
|
|
84
|
+
*/
|
|
85
|
+
const AgentResponse = React.forwardRef<HTMLDivElement, AgentResponseProps>(
|
|
86
|
+
(
|
|
87
|
+
{
|
|
88
|
+
state,
|
|
89
|
+
id,
|
|
90
|
+
timestamp,
|
|
91
|
+
feedback,
|
|
92
|
+
onFeedbackChange,
|
|
93
|
+
onResponseCopy,
|
|
94
|
+
defaultThinkingExpanded = false,
|
|
95
|
+
thinkingExpanded: controlledThinkingExpanded,
|
|
96
|
+
onThinkingExpandedChange,
|
|
97
|
+
actionsVisible = "hover",
|
|
98
|
+
renderMarkdown,
|
|
99
|
+
className,
|
|
100
|
+
...props
|
|
101
|
+
},
|
|
102
|
+
ref
|
|
103
|
+
) => {
|
|
104
|
+
// Uncontrolled thinking expanded state
|
|
105
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = useState(defaultThinkingExpanded);
|
|
106
|
+
|
|
107
|
+
// Determine if thinking is controlled
|
|
108
|
+
const isThinkingControlled = controlledThinkingExpanded !== undefined;
|
|
109
|
+
const thinkingExpanded = isThinkingControlled
|
|
110
|
+
? controlledThinkingExpanded
|
|
111
|
+
: uncontrolledExpanded;
|
|
112
|
+
|
|
113
|
+
// Toggle thinking handler
|
|
114
|
+
const toggleThinking = useCallback(() => {
|
|
115
|
+
const newValue = !thinkingExpanded;
|
|
116
|
+
if (isThinkingControlled) {
|
|
117
|
+
onThinkingExpandedChange?.(newValue);
|
|
118
|
+
} else {
|
|
119
|
+
setUncontrolledExpanded(newValue);
|
|
120
|
+
}
|
|
121
|
+
}, [thinkingExpanded, isThinkingControlled, onThinkingExpandedChange]);
|
|
122
|
+
|
|
123
|
+
// Hover state for action bar visibility
|
|
124
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
125
|
+
|
|
126
|
+
// Thinking timer
|
|
127
|
+
const elapsedTime = useThinkingTimer({
|
|
128
|
+
startTime: state.thinkingStartTime,
|
|
129
|
+
endTime: state.responseCompleteTime,
|
|
130
|
+
status: state.status,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Calculate total time (from first message to response complete)
|
|
134
|
+
const totalTimeSeconds = useMemo(() => {
|
|
135
|
+
if (!state.firstMessageTime || !state.responseCompleteTime) return 0;
|
|
136
|
+
return (state.responseCompleteTime - state.firstMessageTime) / 1000;
|
|
137
|
+
}, [state.firstMessageTime, state.responseCompleteTime]);
|
|
138
|
+
|
|
139
|
+
// Derived state: has any content been received?
|
|
140
|
+
const hasAnyContent =
|
|
141
|
+
state.thinking ||
|
|
142
|
+
state.toolCalls.length > 0 ||
|
|
143
|
+
state.knowledge.length > 0 ||
|
|
144
|
+
state.memory.length > 0 ||
|
|
145
|
+
state.response;
|
|
146
|
+
|
|
147
|
+
// Derived state: should show metadata row?
|
|
148
|
+
const showMetadataRow =
|
|
149
|
+
state.thinking ||
|
|
150
|
+
state.toolCalls.length > 0 ||
|
|
151
|
+
state.knowledge.length > 0 ||
|
|
152
|
+
state.memory.length > 0 ||
|
|
153
|
+
state.status === "processing";
|
|
154
|
+
|
|
155
|
+
// Determine action bar visibility
|
|
156
|
+
const showActionBar = state.status === "complete" && state.response;
|
|
157
|
+
const isActionBarVisible =
|
|
158
|
+
actionsVisible === true ||
|
|
159
|
+
(actionsVisible === "hover" && isHovered);
|
|
160
|
+
|
|
161
|
+
// If no content, render nothing
|
|
162
|
+
if (!hasAnyContent) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div
|
|
168
|
+
ref={ref}
|
|
169
|
+
className={className}
|
|
170
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
171
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
172
|
+
{...props}
|
|
173
|
+
>
|
|
174
|
+
{/* Message Content Container */}
|
|
175
|
+
<div className="border border-border rounded-lg overflow-hidden">
|
|
176
|
+
{/* Metadata Row - show if there's any metadata or thinking */}
|
|
177
|
+
{showMetadataRow && (
|
|
178
|
+
<>
|
|
179
|
+
<MetadataRow
|
|
180
|
+
hasThinking={!!state.thinking}
|
|
181
|
+
isExpanded={thinkingExpanded}
|
|
182
|
+
onToggle={toggleThinking}
|
|
183
|
+
toolCalls={state.toolCalls}
|
|
184
|
+
knowledge={state.knowledge}
|
|
185
|
+
memory={state.memory}
|
|
186
|
+
status={state.status}
|
|
187
|
+
elapsedTime={elapsedTime}
|
|
188
|
+
/>
|
|
189
|
+
|
|
190
|
+
{/* Thinking Content - collapsible with max-height */}
|
|
191
|
+
<ThinkingSection
|
|
192
|
+
content={state.thinking}
|
|
193
|
+
isExpanded={thinkingExpanded}
|
|
194
|
+
/>
|
|
195
|
+
</>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
{/* Response Section */}
|
|
199
|
+
{state.response && (
|
|
200
|
+
<div
|
|
201
|
+
className={cn(
|
|
202
|
+
"bg-muted/50 p-4",
|
|
203
|
+
showMetadataRow && "border-t border-border"
|
|
204
|
+
)}
|
|
205
|
+
>
|
|
206
|
+
{renderMarkdown ? (
|
|
207
|
+
renderMarkdown(state.response)
|
|
208
|
+
) : (
|
|
209
|
+
<span className="whitespace-pre-wrap">{state.response}</span>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{/* Action Bar - outside the message container, visible on hover when complete */}
|
|
216
|
+
{showActionBar && (
|
|
217
|
+
<ActionBar
|
|
218
|
+
response={state.response}
|
|
219
|
+
isVisible={isActionBarVisible}
|
|
220
|
+
totalTimeSeconds={totalTimeSeconds}
|
|
221
|
+
feedback={feedback}
|
|
222
|
+
onFeedbackChange={onFeedbackChange}
|
|
223
|
+
onResponseCopy={onResponseCopy}
|
|
224
|
+
/>
|
|
225
|
+
)}
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
AgentResponse.displayName = "AgentResponse";
|
|
231
|
+
|
|
232
|
+
export { AgentResponse };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action Bar Component
|
|
3
|
+
*
|
|
4
|
+
* Displays copy, feedback, and timing actions for the response
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { useState, useCallback } from "react";
|
|
9
|
+
import { Copy, Check, ThumbsUp, ThumbsDown } from "lucide-react";
|
|
10
|
+
import { cn } from "@optilogic/core";
|
|
11
|
+
import { formatTotalTime } from "../utils";
|
|
12
|
+
import type { FeedbackValue } from "../types";
|
|
13
|
+
|
|
14
|
+
export interface ActionBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
15
|
+
/** The response text (for copying) */
|
|
16
|
+
response: string;
|
|
17
|
+
/** Whether the action bar is visible */
|
|
18
|
+
isVisible: boolean;
|
|
19
|
+
/** Total time in seconds */
|
|
20
|
+
totalTimeSeconds: number;
|
|
21
|
+
/** Current feedback value */
|
|
22
|
+
feedback?: FeedbackValue;
|
|
23
|
+
/** Callback when feedback changes */
|
|
24
|
+
onFeedbackChange?: (feedback: FeedbackValue) => void;
|
|
25
|
+
/** Callback when response is copied */
|
|
26
|
+
onResponseCopy?: (response: string) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* ActionBar Component
|
|
31
|
+
*
|
|
32
|
+
* Displays action buttons for copying the response, providing feedback,
|
|
33
|
+
* and showing total response time.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* <ActionBar
|
|
37
|
+
* response={state.response}
|
|
38
|
+
* isVisible={isHovered}
|
|
39
|
+
* totalTimeSeconds={totalTime}
|
|
40
|
+
* feedback={feedback}
|
|
41
|
+
* onFeedbackChange={setFeedback}
|
|
42
|
+
* onCopy={handleCopy}
|
|
43
|
+
* />
|
|
44
|
+
*/
|
|
45
|
+
const ActionBar = React.forwardRef<HTMLDivElement, ActionBarProps>(
|
|
46
|
+
(
|
|
47
|
+
{
|
|
48
|
+
response,
|
|
49
|
+
isVisible,
|
|
50
|
+
totalTimeSeconds,
|
|
51
|
+
feedback,
|
|
52
|
+
onFeedbackChange,
|
|
53
|
+
onResponseCopy,
|
|
54
|
+
className,
|
|
55
|
+
...props
|
|
56
|
+
},
|
|
57
|
+
ref
|
|
58
|
+
) => {
|
|
59
|
+
const [copied, setCopied] = useState(false);
|
|
60
|
+
|
|
61
|
+
const handleCopy = useCallback(async () => {
|
|
62
|
+
try {
|
|
63
|
+
await navigator.clipboard.writeText(response);
|
|
64
|
+
setCopied(true);
|
|
65
|
+
setTimeout(() => setCopied(false), 2000);
|
|
66
|
+
onResponseCopy?.(response);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error("Failed to copy response:", err);
|
|
69
|
+
}
|
|
70
|
+
}, [response, onResponseCopy]);
|
|
71
|
+
|
|
72
|
+
const handleThumbsUp = useCallback(() => {
|
|
73
|
+
const newValue = feedback === "up" ? null : "up";
|
|
74
|
+
onFeedbackChange?.(newValue);
|
|
75
|
+
}, [feedback, onFeedbackChange]);
|
|
76
|
+
|
|
77
|
+
const handleThumbsDown = useCallback(() => {
|
|
78
|
+
const newValue = feedback === "down" ? null : "down";
|
|
79
|
+
onFeedbackChange?.(newValue);
|
|
80
|
+
}, [feedback, onFeedbackChange]);
|
|
81
|
+
|
|
82
|
+
const isThumbsUp = feedback === "up";
|
|
83
|
+
const isThumbsDown = feedback === "down";
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
ref={ref}
|
|
88
|
+
className={cn(
|
|
89
|
+
"flex items-center justify-between px-4 py-2",
|
|
90
|
+
"transition-opacity duration-200",
|
|
91
|
+
isVisible ? "opacity-100" : "opacity-0 pointer-events-none",
|
|
92
|
+
className
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
>
|
|
96
|
+
{/* Left side - action buttons */}
|
|
97
|
+
<div className="flex items-center gap-1">
|
|
98
|
+
{/* Copy button */}
|
|
99
|
+
<button
|
|
100
|
+
onClick={handleCopy}
|
|
101
|
+
className="p-1.5 rounded hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
|
|
102
|
+
title={copied ? "Copied!" : "Copy response"}
|
|
103
|
+
>
|
|
104
|
+
{copied ? (
|
|
105
|
+
<Check className="w-4 h-4 text-green-500" />
|
|
106
|
+
) : (
|
|
107
|
+
<Copy className="w-4 h-4" />
|
|
108
|
+
)}
|
|
109
|
+
</button>
|
|
110
|
+
|
|
111
|
+
{/* Thumbs up */}
|
|
112
|
+
<button
|
|
113
|
+
onClick={handleThumbsUp}
|
|
114
|
+
className={cn(
|
|
115
|
+
"p-1.5 rounded hover:bg-muted transition-colors",
|
|
116
|
+
isThumbsUp
|
|
117
|
+
? "text-green-500"
|
|
118
|
+
: "text-muted-foreground hover:text-foreground"
|
|
119
|
+
)}
|
|
120
|
+
title="Good response"
|
|
121
|
+
>
|
|
122
|
+
<ThumbsUp className={cn("w-4 h-4", isThumbsUp && "fill-current")} />
|
|
123
|
+
</button>
|
|
124
|
+
|
|
125
|
+
{/* Thumbs down */}
|
|
126
|
+
<button
|
|
127
|
+
onClick={handleThumbsDown}
|
|
128
|
+
className={cn(
|
|
129
|
+
"p-1.5 rounded hover:bg-muted transition-colors",
|
|
130
|
+
isThumbsDown
|
|
131
|
+
? "text-red-500"
|
|
132
|
+
: "text-muted-foreground hover:text-foreground"
|
|
133
|
+
)}
|
|
134
|
+
title="Poor response"
|
|
135
|
+
>
|
|
136
|
+
<ThumbsDown className={cn("w-4 h-4", isThumbsDown && "fill-current")} />
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{/* Right side - timing info */}
|
|
141
|
+
<span className="text-xs text-muted-foreground">
|
|
142
|
+
Total time: {formatTotalTime(totalTimeSeconds)}
|
|
143
|
+
</span>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
ActionBar.displayName = "ActionBar";
|
|
149
|
+
|
|
150
|
+
export { ActionBar };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Indicators Component
|
|
3
|
+
*
|
|
4
|
+
* Displays tool, knowledge, memory icons with counts and popovers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { Wrench, Book, HardDrive } from "lucide-react";
|
|
9
|
+
import { cn, Popover, PopoverTrigger, PopoverContent } from "@optilogic/core";
|
|
10
|
+
import type { ToolCall, KnowledgeItem, MemoryItem } from "../types";
|
|
11
|
+
|
|
12
|
+
export interface ActivityIndicatorsProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
13
|
+
/** Tool calls to display */
|
|
14
|
+
toolCalls: ToolCall[];
|
|
15
|
+
/** Knowledge items to display */
|
|
16
|
+
knowledge: KnowledgeItem[];
|
|
17
|
+
/** Memory items to display */
|
|
18
|
+
memory: MemoryItem[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* ActivityIndicators Component
|
|
23
|
+
*
|
|
24
|
+
* Displays icons with counts for tool calls, knowledge retrieval, and memory access.
|
|
25
|
+
* Each icon has a popover showing details when clicked.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <ActivityIndicators
|
|
29
|
+
* toolCalls={state.toolCalls}
|
|
30
|
+
* knowledge={state.knowledge}
|
|
31
|
+
* memory={state.memory}
|
|
32
|
+
* />
|
|
33
|
+
*/
|
|
34
|
+
const ActivityIndicators = React.forwardRef<HTMLDivElement, ActivityIndicatorsProps>(
|
|
35
|
+
({ toolCalls, knowledge, memory, className, ...props }, ref) => {
|
|
36
|
+
const hasAnyActivity =
|
|
37
|
+
toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0;
|
|
38
|
+
|
|
39
|
+
if (!hasAnyActivity) return null;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div ref={ref} className={cn("flex items-center gap-2", className)} {...props}>
|
|
43
|
+
{/* Tool Calls */}
|
|
44
|
+
{toolCalls.length > 0 && (
|
|
45
|
+
<Popover>
|
|
46
|
+
<PopoverTrigger asChild>
|
|
47
|
+
<button
|
|
48
|
+
className="flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors"
|
|
49
|
+
onClick={(e) => e.stopPropagation()}
|
|
50
|
+
>
|
|
51
|
+
<Wrench className="w-3.5 h-3.5" />
|
|
52
|
+
<span className="text-xs">{toolCalls.length}</span>
|
|
53
|
+
</button>
|
|
54
|
+
</PopoverTrigger>
|
|
55
|
+
<PopoverContent className="w-80">
|
|
56
|
+
<div className="space-y-2">
|
|
57
|
+
<h4 className="font-medium text-sm">Tool Calls</h4>
|
|
58
|
+
<div className="space-y-2 max-h-60 overflow-auto">
|
|
59
|
+
{toolCalls.map((tool) => (
|
|
60
|
+
<div key={tool.id} className="p-2 bg-muted rounded text-xs">
|
|
61
|
+
<div className="font-medium">{tool.name}</div>
|
|
62
|
+
{tool.arguments && (
|
|
63
|
+
<pre className="mt-1 text-muted-foreground overflow-x-auto">
|
|
64
|
+
{JSON.stringify(tool.arguments, null, 2)}
|
|
65
|
+
</pre>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</PopoverContent>
|
|
72
|
+
</Popover>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{/* Knowledge */}
|
|
76
|
+
{knowledge.length > 0 && (
|
|
77
|
+
<Popover>
|
|
78
|
+
<PopoverTrigger asChild>
|
|
79
|
+
<button
|
|
80
|
+
className="flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors"
|
|
81
|
+
onClick={(e) => e.stopPropagation()}
|
|
82
|
+
>
|
|
83
|
+
<Book className="w-3.5 h-3.5" />
|
|
84
|
+
<span className="text-xs">{knowledge.length}</span>
|
|
85
|
+
</button>
|
|
86
|
+
</PopoverTrigger>
|
|
87
|
+
<PopoverContent className="w-80">
|
|
88
|
+
<div className="space-y-2">
|
|
89
|
+
<h4 className="font-medium text-sm">Knowledge Retrieved</h4>
|
|
90
|
+
<div className="space-y-2 max-h-60 overflow-auto">
|
|
91
|
+
{knowledge.map((item) => (
|
|
92
|
+
<div key={item.id} className="p-2 bg-muted rounded text-xs">
|
|
93
|
+
<div className="font-medium">{item.source}</div>
|
|
94
|
+
<div className="mt-1 text-muted-foreground">
|
|
95
|
+
{item.content}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
))}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</PopoverContent>
|
|
102
|
+
</Popover>
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
{/* Memory */}
|
|
106
|
+
{memory.length > 0 && (
|
|
107
|
+
<Popover>
|
|
108
|
+
<PopoverTrigger asChild>
|
|
109
|
+
<button
|
|
110
|
+
className="flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors"
|
|
111
|
+
onClick={(e) => e.stopPropagation()}
|
|
112
|
+
>
|
|
113
|
+
<HardDrive className="w-3.5 h-3.5" />
|
|
114
|
+
<span className="text-xs">{memory.length}</span>
|
|
115
|
+
</button>
|
|
116
|
+
</PopoverTrigger>
|
|
117
|
+
<PopoverContent className="w-80">
|
|
118
|
+
<div className="space-y-2">
|
|
119
|
+
<h4 className="font-medium text-sm">Memory Accessed</h4>
|
|
120
|
+
<div className="space-y-2 max-h-60 overflow-auto">
|
|
121
|
+
{memory.map((item) => (
|
|
122
|
+
<div key={item.id} className="p-2 bg-muted rounded text-xs">
|
|
123
|
+
<div className="font-medium">{item.type}</div>
|
|
124
|
+
<div className="mt-1 text-muted-foreground">
|
|
125
|
+
{item.content}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
))}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</PopoverContent>
|
|
132
|
+
</Popover>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
ActivityIndicators.displayName = "ActivityIndicators";
|
|
139
|
+
|
|
140
|
+
export { ActivityIndicators };
|