@polpo-ai/chat 0.1.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.
- package/README.md +255 -0
- package/dist/chunk-CCSIMOXD.js +230 -0
- package/dist/chunk-CCSIMOXD.js.map +1 -0
- package/dist/chunk-LTLIBITC.js +64 -0
- package/dist/chunk-LTLIBITC.js.map +1 -0
- package/dist/hooks/index.d.ts +17 -0
- package/dist/hooks/index.js +9 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +198 -0
- package/dist/index.js +737 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/index.d.ts +21 -0
- package/dist/tools/index.js +10 -0
- package/dist/tools/index.js.map +1 -0
- package/package.json +55 -0
- package/registry.json +188 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ToolCallChip,
|
|
3
|
+
ToolCallShell
|
|
4
|
+
} from "./chunk-CCSIMOXD.js";
|
|
5
|
+
import {
|
|
6
|
+
useDocumentDrag,
|
|
7
|
+
useSubmitHandler
|
|
8
|
+
} from "./chunk-LTLIBITC.js";
|
|
9
|
+
|
|
10
|
+
// src/components/chat.tsx
|
|
11
|
+
import { forwardRef as forwardRef2 } from "react";
|
|
12
|
+
|
|
13
|
+
// src/components/chat-provider.tsx
|
|
14
|
+
import {
|
|
15
|
+
createContext,
|
|
16
|
+
useContext
|
|
17
|
+
} from "react";
|
|
18
|
+
import { useChat } from "@polpo-ai/react";
|
|
19
|
+
import { useFiles } from "@polpo-ai/react";
|
|
20
|
+
import { jsx } from "react/jsx-runtime";
|
|
21
|
+
var ChatContext = createContext(null);
|
|
22
|
+
function ChatProvider({
|
|
23
|
+
sessionId,
|
|
24
|
+
agent,
|
|
25
|
+
onSessionCreated,
|
|
26
|
+
onUpdate,
|
|
27
|
+
children
|
|
28
|
+
}) {
|
|
29
|
+
const chat = useChat({
|
|
30
|
+
sessionId,
|
|
31
|
+
agent,
|
|
32
|
+
onSessionCreated,
|
|
33
|
+
onUpdate
|
|
34
|
+
});
|
|
35
|
+
const { uploadFile, isUploading } = useFiles();
|
|
36
|
+
const value = {
|
|
37
|
+
...chat,
|
|
38
|
+
uploadFile,
|
|
39
|
+
isUploading
|
|
40
|
+
};
|
|
41
|
+
return /* @__PURE__ */ jsx(ChatContext.Provider, { value, children });
|
|
42
|
+
}
|
|
43
|
+
function useChatContext() {
|
|
44
|
+
const ctx = useContext(ChatContext);
|
|
45
|
+
if (!ctx) {
|
|
46
|
+
throw new Error("useChatContext must be used within a <ChatProvider>");
|
|
47
|
+
}
|
|
48
|
+
return ctx;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/components/chat-messages.tsx
|
|
52
|
+
import {
|
|
53
|
+
forwardRef,
|
|
54
|
+
useCallback,
|
|
55
|
+
useEffect,
|
|
56
|
+
useImperativeHandle,
|
|
57
|
+
useRef,
|
|
58
|
+
useState
|
|
59
|
+
} from "react";
|
|
60
|
+
import { Virtuoso } from "react-virtuoso";
|
|
61
|
+
|
|
62
|
+
// src/components/chat-skeleton.tsx
|
|
63
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
64
|
+
function Bone({ width, height = 14 }) {
|
|
65
|
+
return /* @__PURE__ */ jsx2(
|
|
66
|
+
"div",
|
|
67
|
+
{
|
|
68
|
+
className: "bg-p-line animate-pulse",
|
|
69
|
+
style: { width, height, borderRadius: height > 20 ? 12 : 6 }
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
function MessageSkeleton({ lines = 3 }) {
|
|
74
|
+
const widths = ["85%", "70%", "55%", "90%", "40%"];
|
|
75
|
+
return /* @__PURE__ */ jsx2("div", { className: "w-full px-6 pt-4 pb-6", children: /* @__PURE__ */ jsxs("div", { className: "max-w-3xl mx-auto", children: [
|
|
76
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
|
|
77
|
+
/* @__PURE__ */ jsx2("div", { className: "size-6 rounded-md bg-p-line animate-pulse shrink-0" }),
|
|
78
|
+
/* @__PURE__ */ jsx2(Bone, { width: "80px", height: 13 })
|
|
79
|
+
] }),
|
|
80
|
+
/* @__PURE__ */ jsx2("div", { className: "flex flex-col gap-2", children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx2(Bone, { width: widths[i % widths.length] }, i)) })
|
|
81
|
+
] }) });
|
|
82
|
+
}
|
|
83
|
+
function UserMessageSkeleton() {
|
|
84
|
+
return /* @__PURE__ */ jsx2("div", { className: "w-full px-6 py-3", children: /* @__PURE__ */ jsx2("div", { className: "max-w-3xl mx-auto flex justify-end", children: /* @__PURE__ */ jsx2("div", { className: "w-[45%] min-w-[120px] h-[42px] rounded-[18px_18px_4px_18px] bg-p-line animate-pulse" }) }) });
|
|
85
|
+
}
|
|
86
|
+
function ChatSkeleton({ count = 3 }) {
|
|
87
|
+
return /* @__PURE__ */ jsx2("div", { className: "py-2", children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxs("div", { children: [
|
|
88
|
+
/* @__PURE__ */ jsx2(UserMessageSkeleton, {}),
|
|
89
|
+
/* @__PURE__ */ jsx2(MessageSkeleton, { lines: i === 0 ? 2 : i === 1 ? 4 : 3 })
|
|
90
|
+
] }, i)) });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/components/chat-scroll-button.tsx
|
|
94
|
+
import { ArrowDown } from "lucide-react";
|
|
95
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
96
|
+
function ChatScrollButton({
|
|
97
|
+
isAtBottom,
|
|
98
|
+
showNewMessage,
|
|
99
|
+
onClick,
|
|
100
|
+
className
|
|
101
|
+
}) {
|
|
102
|
+
if (isAtBottom) return null;
|
|
103
|
+
return /* @__PURE__ */ jsxs2(
|
|
104
|
+
"button",
|
|
105
|
+
{
|
|
106
|
+
type: "button",
|
|
107
|
+
"aria-label": "Scroll to bottom",
|
|
108
|
+
onClick,
|
|
109
|
+
className: `absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex h-8 w-8 items-center justify-center rounded-full border border-[var(--polpo-border)] bg-[var(--polpo-bg,#fff)] shadow-md transition-colors hover:bg-[var(--polpo-bg-hover,#f5f5f5)] ${className ?? ""}`,
|
|
110
|
+
children: [
|
|
111
|
+
/* @__PURE__ */ jsx3(ArrowDown, { className: "h-4 w-4 text-[var(--polpo-fg,#333)]" }),
|
|
112
|
+
showNewMessage && /* @__PURE__ */ jsxs2("span", { className: "absolute -top-1 -right-1 flex h-3 w-3", children: [
|
|
113
|
+
/* @__PURE__ */ jsx3("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-[var(--polpo-accent,#3b82f6)] opacity-75" }),
|
|
114
|
+
/* @__PURE__ */ jsx3("span", { className: "relative inline-flex h-3 w-3 rounded-full bg-[var(--polpo-accent,#3b82f6)]" })
|
|
115
|
+
] })
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/lib/get-text-content.ts
|
|
122
|
+
function getTextContent(content) {
|
|
123
|
+
if (typeof content === "string") return content;
|
|
124
|
+
return content.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/components/chat-messages.tsx
|
|
128
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
129
|
+
function DefaultMessageItem({ msg }) {
|
|
130
|
+
const text = getTextContent(msg.content);
|
|
131
|
+
return /* @__PURE__ */ jsx4("div", { className: "w-full px-6 py-3", children: /* @__PURE__ */ jsxs3("div", { className: "max-w-3xl mx-auto", children: [
|
|
132
|
+
/* @__PURE__ */ jsx4("p", { className: "text-xs font-medium opacity-50 mb-1", children: msg.role === "user" ? "You" : "Assistant" }),
|
|
133
|
+
/* @__PURE__ */ jsx4("p", { className: "whitespace-pre-wrap", children: text })
|
|
134
|
+
] }) });
|
|
135
|
+
}
|
|
136
|
+
function VirtuosoFooter() {
|
|
137
|
+
return /* @__PURE__ */ jsx4("div", { className: "h-px", "aria-hidden": "true" });
|
|
138
|
+
}
|
|
139
|
+
var ChatMessages = forwardRef(
|
|
140
|
+
function ChatMessages2({ renderItem, className, skeletonCount = 3 }, ref) {
|
|
141
|
+
const { messages, isStreaming, status } = useChatContext();
|
|
142
|
+
const virtuosoRef = useRef(null);
|
|
143
|
+
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
144
|
+
const [showNewMessage, setShowNewMessage] = useState(false);
|
|
145
|
+
const prevMessageCountRef = useRef(0);
|
|
146
|
+
const hasInitialScrollRef = useRef(false);
|
|
147
|
+
const scrollToBottom = useCallback(
|
|
148
|
+
(behavior = "smooth") => {
|
|
149
|
+
virtuosoRef.current?.scrollToIndex({
|
|
150
|
+
index: "LAST",
|
|
151
|
+
align: "end",
|
|
152
|
+
behavior
|
|
153
|
+
});
|
|
154
|
+
setShowNewMessage(false);
|
|
155
|
+
},
|
|
156
|
+
[]
|
|
157
|
+
);
|
|
158
|
+
useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
|
|
159
|
+
const handleAtBottomStateChange = useCallback((atBottom) => {
|
|
160
|
+
setIsAtBottom(atBottom);
|
|
161
|
+
if (atBottom) setShowNewMessage(false);
|
|
162
|
+
}, []);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (messages.length > 0 && !hasInitialScrollRef.current) {
|
|
165
|
+
hasInitialScrollRef.current = true;
|
|
166
|
+
requestAnimationFrame(
|
|
167
|
+
() => virtuosoRef.current?.scrollToIndex({
|
|
168
|
+
index: "LAST",
|
|
169
|
+
align: "end",
|
|
170
|
+
behavior: "auto"
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}, [messages.length]);
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
hasInitialScrollRef.current = false;
|
|
177
|
+
}, [status]);
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
const cur = messages.length;
|
|
180
|
+
const prev = prevMessageCountRef.current;
|
|
181
|
+
if (cur > prev && !isAtBottom && prev > 0) {
|
|
182
|
+
setShowNewMessage(true);
|
|
183
|
+
}
|
|
184
|
+
prevMessageCountRef.current = cur;
|
|
185
|
+
}, [messages.length, isAtBottom]);
|
|
186
|
+
const itemContent = useCallback(
|
|
187
|
+
(index, msg) => {
|
|
188
|
+
const isLast = index === messages.length - 1;
|
|
189
|
+
if (renderItem) {
|
|
190
|
+
return renderItem(msg, index, isLast, isStreaming);
|
|
191
|
+
}
|
|
192
|
+
return /* @__PURE__ */ jsx4(DefaultMessageItem, { msg });
|
|
193
|
+
},
|
|
194
|
+
[messages.length, isStreaming, renderItem]
|
|
195
|
+
);
|
|
196
|
+
if (status === "loading") {
|
|
197
|
+
return /* @__PURE__ */ jsx4("div", { className: `flex-1 overflow-hidden ${className ?? ""}`, children: /* @__PURE__ */ jsx4(ChatSkeleton, { count: skeletonCount }) });
|
|
198
|
+
}
|
|
199
|
+
return /* @__PURE__ */ jsxs3("div", { className: `relative flex-1 min-h-0 ${className ?? ""}`, children: [
|
|
200
|
+
/* @__PURE__ */ jsx4(
|
|
201
|
+
Virtuoso,
|
|
202
|
+
{
|
|
203
|
+
ref: virtuosoRef,
|
|
204
|
+
data: messages,
|
|
205
|
+
followOutput: "auto",
|
|
206
|
+
atBottomStateChange: handleAtBottomStateChange,
|
|
207
|
+
atBottomThreshold: 100,
|
|
208
|
+
defaultItemHeight: 120,
|
|
209
|
+
overscan: 500,
|
|
210
|
+
increaseViewportBy: { top: 300, bottom: 300 },
|
|
211
|
+
skipAnimationFrameInResizeObserver: true,
|
|
212
|
+
itemContent,
|
|
213
|
+
className: "h-full",
|
|
214
|
+
components: { Footer: VirtuosoFooter }
|
|
215
|
+
}
|
|
216
|
+
),
|
|
217
|
+
/* @__PURE__ */ jsx4(
|
|
218
|
+
ChatScrollButton,
|
|
219
|
+
{
|
|
220
|
+
isAtBottom,
|
|
221
|
+
showNewMessage,
|
|
222
|
+
onClick: () => scrollToBottom()
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
] });
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// src/components/chat-message.tsx
|
|
230
|
+
import {
|
|
231
|
+
memo,
|
|
232
|
+
useState as useState2,
|
|
233
|
+
useCallback as useCallback2
|
|
234
|
+
} from "react";
|
|
235
|
+
import { Copy, Check, FileCode } from "lucide-react";
|
|
236
|
+
import { Streamdown } from "streamdown";
|
|
237
|
+
|
|
238
|
+
// src/lib/relative-time.ts
|
|
239
|
+
function relativeTime(iso) {
|
|
240
|
+
const now = Date.now();
|
|
241
|
+
const then = new Date(iso).getTime();
|
|
242
|
+
const diff = Math.floor((now - then) / 1e3);
|
|
243
|
+
if (diff < 10) return "Just now";
|
|
244
|
+
if (diff < 60) return `${diff}s ago`;
|
|
245
|
+
const mins = Math.floor(diff / 60);
|
|
246
|
+
if (mins < 60) return mins === 1 ? "A minute ago" : `${mins}m ago`;
|
|
247
|
+
const hours = Math.floor(mins / 60);
|
|
248
|
+
if (hours < 24) return hours === 1 ? "An hour ago" : `${hours}h ago`;
|
|
249
|
+
return new Date(iso).toLocaleString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/components/chat-typing.tsx
|
|
253
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
254
|
+
var dotBase = "inline-block h-1.5 w-1.5 rounded-full bg-current opacity-40 animate-[typing-dot_1.4s_ease-in-out_infinite]";
|
|
255
|
+
function ChatTyping({ className }) {
|
|
256
|
+
return /* @__PURE__ */ jsxs4(
|
|
257
|
+
"span",
|
|
258
|
+
{
|
|
259
|
+
role: "status",
|
|
260
|
+
"aria-label": "Typing",
|
|
261
|
+
className: `inline-flex items-center gap-1 ${className ?? ""}`,
|
|
262
|
+
children: [
|
|
263
|
+
/* @__PURE__ */ jsx5("span", { className: dotBase, style: { animationDelay: "0ms" } }),
|
|
264
|
+
/* @__PURE__ */ jsx5("span", { className: dotBase, style: { animationDelay: "200ms" } }),
|
|
265
|
+
/* @__PURE__ */ jsx5("span", { className: dotBase, style: { animationDelay: "400ms" } })
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/components/chat-message.tsx
|
|
272
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
273
|
+
function CopyButton({ text }) {
|
|
274
|
+
const [copied, setCopied] = useState2(false);
|
|
275
|
+
const handleCopy = useCallback2(() => {
|
|
276
|
+
navigator.clipboard.writeText(text);
|
|
277
|
+
setCopied(true);
|
|
278
|
+
setTimeout(() => setCopied(false), 1500);
|
|
279
|
+
}, [text]);
|
|
280
|
+
return /* @__PURE__ */ jsx6(
|
|
281
|
+
"button",
|
|
282
|
+
{
|
|
283
|
+
type: "button",
|
|
284
|
+
onClick: handleCopy,
|
|
285
|
+
"aria-label": "Copy message",
|
|
286
|
+
className: "inline-flex items-center justify-center rounded-md p-1 text-[var(--ink-3)] hover:text-[var(--ink)] hover:bg-[var(--warm)] transition-colors",
|
|
287
|
+
children: copied ? /* @__PURE__ */ jsx6(Check, { className: "size-3.5" }) : /* @__PURE__ */ jsx6(Copy, { className: "size-3.5" })
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
function ContentParts({
|
|
292
|
+
parts,
|
|
293
|
+
align
|
|
294
|
+
}) {
|
|
295
|
+
const nonText = parts.filter((p) => p.type !== "text");
|
|
296
|
+
if (nonText.length === 0) return null;
|
|
297
|
+
return /* @__PURE__ */ jsx6(
|
|
298
|
+
"div",
|
|
299
|
+
{
|
|
300
|
+
className: `flex flex-wrap gap-1.5 mb-1 ${align === "end" ? "justify-end" : ""}`,
|
|
301
|
+
children: nonText.map((part, i) => {
|
|
302
|
+
if (part.type === "image_url") {
|
|
303
|
+
return /* @__PURE__ */ jsx6(
|
|
304
|
+
"a",
|
|
305
|
+
{
|
|
306
|
+
href: part.image_url.url,
|
|
307
|
+
target: "_blank",
|
|
308
|
+
rel: "noopener noreferrer",
|
|
309
|
+
className: "rounded-lg overflow-hidden max-w-[200px]",
|
|
310
|
+
children: /* @__PURE__ */ jsx6(
|
|
311
|
+
"img",
|
|
312
|
+
{
|
|
313
|
+
src: part.image_url.url,
|
|
314
|
+
alt: "",
|
|
315
|
+
className: "w-full h-auto block"
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
},
|
|
319
|
+
i
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
if (part.type === "file") {
|
|
323
|
+
const fileId = part.file_id;
|
|
324
|
+
const fn = fileId.split("/").pop() || fileId;
|
|
325
|
+
return /* @__PURE__ */ jsxs5(
|
|
326
|
+
"a",
|
|
327
|
+
{
|
|
328
|
+
href: `/api/polpo/files/read?path=${encodeURIComponent(fileId)}`,
|
|
329
|
+
target: "_blank",
|
|
330
|
+
rel: "noopener noreferrer",
|
|
331
|
+
className: "flex items-center gap-1.5 bg-[var(--warm)] border border-[var(--line)] rounded-lg px-2.5 py-1.5 text-xs text-[var(--ink)] hover:border-[var(--ink-3)] transition-colors",
|
|
332
|
+
children: [
|
|
333
|
+
/* @__PURE__ */ jsx6(FileCode, { size: 13 }),
|
|
334
|
+
/* @__PURE__ */ jsx6("span", { className: "truncate max-w-[120px]", children: fn })
|
|
335
|
+
]
|
|
336
|
+
},
|
|
337
|
+
i
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
var ChatUserMessage = memo(
|
|
346
|
+
function ChatUserMessage2({
|
|
347
|
+
msg,
|
|
348
|
+
isLast,
|
|
349
|
+
isStreaming
|
|
350
|
+
}) {
|
|
351
|
+
const text = getTextContent(msg.content);
|
|
352
|
+
return /* @__PURE__ */ jsx6("div", { className: "w-full px-6 py-3", children: /* @__PURE__ */ jsx6("div", { className: "max-w-3xl mx-auto", children: /* @__PURE__ */ jsxs5("div", { className: "group flex w-full flex-col gap-2 ml-auto justify-end", children: [
|
|
353
|
+
Array.isArray(msg.content) && /* @__PURE__ */ jsx6(ContentParts, { parts: msg.content, align: "end" }),
|
|
354
|
+
/* @__PURE__ */ jsx6("div", { className: "w-fit max-w-[80%] ml-auto rounded-[18px_18px_4px_18px] bg-[var(--warm)] px-4 py-3", children: text ? /* @__PURE__ */ jsx6("p", { className: "whitespace-pre-wrap break-words text-[var(--ink)]", children: text }) : null }),
|
|
355
|
+
text && (!isLast || !isStreaming) && /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-end gap-1.5 h-6", children: [
|
|
356
|
+
/* @__PURE__ */ jsx6("span", { className: "text-[11px] text-[var(--ink-3)] opacity-0 group-hover:opacity-100 transition-opacity", children: msg.ts ? relativeTime(msg.ts) : "" }),
|
|
357
|
+
/* @__PURE__ */ jsx6("div", { className: "opacity-0 group-hover:opacity-100 transition-opacity", children: /* @__PURE__ */ jsx6(CopyButton, { text }) })
|
|
358
|
+
] })
|
|
359
|
+
] }) }) });
|
|
360
|
+
},
|
|
361
|
+
(prev, next) => prev.isLast === next.isLast && prev.isStreaming === next.isStreaming && prev.msg.id === next.msg.id && prev.msg.content === next.msg.content
|
|
362
|
+
);
|
|
363
|
+
var ChatAssistantMessage = memo(
|
|
364
|
+
function ChatAssistantMessage2({
|
|
365
|
+
msg,
|
|
366
|
+
isLast,
|
|
367
|
+
isStreaming,
|
|
368
|
+
avatar,
|
|
369
|
+
agentName,
|
|
370
|
+
streamdownComponents: components
|
|
371
|
+
}) {
|
|
372
|
+
const text = getTextContent(msg.content);
|
|
373
|
+
const filteredToolCalls = msg.toolCalls?.filter(
|
|
374
|
+
(tc) => tc.name !== "ask_user_question"
|
|
375
|
+
);
|
|
376
|
+
return /* @__PURE__ */ jsx6("div", { className: "w-full px-6 pt-4 pb-6", children: /* @__PURE__ */ jsx6("div", { className: "max-w-3xl mx-auto", children: /* @__PURE__ */ jsxs5("div", { className: "group flex w-full flex-col gap-2", children: [
|
|
377
|
+
(avatar || agentName) && /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 mb-1", children: [
|
|
378
|
+
avatar,
|
|
379
|
+
agentName && /* @__PURE__ */ jsx6("span", { className: "font-display text-[13px] font-semibold text-[var(--ink)]", children: agentName })
|
|
380
|
+
] }),
|
|
381
|
+
filteredToolCalls && filteredToolCalls.length > 0 && /* @__PURE__ */ jsx6("div", { className: "flex flex-col gap-1 mb-1", children: filteredToolCalls.map((tc) => /* @__PURE__ */ jsx6(ToolCallChip, { tool: tc }, tc.id)) }),
|
|
382
|
+
Array.isArray(msg.content) && /* @__PURE__ */ jsx6(ContentParts, { parts: msg.content, align: "start" }),
|
|
383
|
+
/* @__PURE__ */ jsx6("div", { className: "w-full text-[var(--ink)]", children: text ? components ? /* @__PURE__ */ jsx6(
|
|
384
|
+
Streamdown,
|
|
385
|
+
{
|
|
386
|
+
className: "size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
|
|
387
|
+
components,
|
|
388
|
+
children: text
|
|
389
|
+
}
|
|
390
|
+
) : /* @__PURE__ */ jsx6("p", { className: "whitespace-pre-wrap break-words", children: text }) : !filteredToolCalls?.length && /* @__PURE__ */ jsx6(ChatTyping, { className: "pt-1" }) }),
|
|
391
|
+
text && (!isLast || !isStreaming) && /* @__PURE__ */ jsx6("div", { className: "h-6 flex items-center opacity-0 group-hover:opacity-100 transition-opacity", children: /* @__PURE__ */ jsx6(CopyButton, { text }) })
|
|
392
|
+
] }) }) });
|
|
393
|
+
},
|
|
394
|
+
(prev, next) => prev.avatar === next.avatar && prev.agentName === next.agentName && prev.isLast === next.isLast && prev.isStreaming === next.isStreaming && prev.streamdownComponents === next.streamdownComponents && prev.msg.id === next.msg.id && prev.msg.content === next.msg.content && prev.msg.toolCalls?.length === next.msg.toolCalls?.length && JSON.stringify(prev.msg.toolCalls?.map((t) => t.state)) === JSON.stringify(next.msg.toolCalls?.map((t) => t.state))
|
|
395
|
+
);
|
|
396
|
+
var ChatMessage = memo(
|
|
397
|
+
function ChatMessage2({
|
|
398
|
+
msg,
|
|
399
|
+
isLast,
|
|
400
|
+
isStreaming,
|
|
401
|
+
avatar,
|
|
402
|
+
agentName,
|
|
403
|
+
streamdownComponents: streamdownComponents2
|
|
404
|
+
}) {
|
|
405
|
+
if (msg.role === "user") {
|
|
406
|
+
return /* @__PURE__ */ jsx6(ChatUserMessage, { msg, isLast, isStreaming });
|
|
407
|
+
}
|
|
408
|
+
return /* @__PURE__ */ jsx6(
|
|
409
|
+
ChatAssistantMessage,
|
|
410
|
+
{
|
|
411
|
+
msg,
|
|
412
|
+
isLast,
|
|
413
|
+
isStreaming,
|
|
414
|
+
avatar,
|
|
415
|
+
agentName,
|
|
416
|
+
streamdownComponents: streamdownComponents2
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
},
|
|
420
|
+
(prev, next) => prev.avatar === next.avatar && prev.agentName === next.agentName && prev.isLast === next.isLast && prev.isStreaming === next.isStreaming && prev.streamdownComponents === next.streamdownComponents && prev.msg.id === next.msg.id && prev.msg.content === next.msg.content && prev.msg.role === next.msg.role && prev.msg.toolCalls?.length === next.msg.toolCalls?.length && JSON.stringify(prev.msg.toolCalls?.map((t) => t.state)) === JSON.stringify(next.msg.toolCalls?.map((t) => t.state))
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// src/components/chat-input.tsx
|
|
424
|
+
import {
|
|
425
|
+
useState as useState3,
|
|
426
|
+
useRef as useRef2,
|
|
427
|
+
useCallback as useCallback3,
|
|
428
|
+
useEffect as useEffect2
|
|
429
|
+
} from "react";
|
|
430
|
+
import { ArrowUp, Square, Plus, X, Upload } from "lucide-react";
|
|
431
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
432
|
+
function ChatInput({
|
|
433
|
+
placeholder = "Type a message\u2026",
|
|
434
|
+
hint,
|
|
435
|
+
allowAttachments = true,
|
|
436
|
+
className,
|
|
437
|
+
renderSubmit
|
|
438
|
+
}) {
|
|
439
|
+
const { sendMessage, isStreaming, abort, uploadFile } = useChatContext();
|
|
440
|
+
const handleSubmit = useSubmitHandler(sendMessage, uploadFile);
|
|
441
|
+
const dragging = useDocumentDrag();
|
|
442
|
+
const [text, setText] = useState3("");
|
|
443
|
+
const [files, setFiles] = useState3([]);
|
|
444
|
+
const [isSending, setIsSending] = useState3(false);
|
|
445
|
+
const textareaRef = useRef2(null);
|
|
446
|
+
const fileInputRef = useRef2(null);
|
|
447
|
+
useEffect2(() => {
|
|
448
|
+
const el = textareaRef.current;
|
|
449
|
+
if (!el) return;
|
|
450
|
+
el.style.height = "auto";
|
|
451
|
+
el.style.height = `${Math.min(el.scrollHeight, 200)}px`;
|
|
452
|
+
}, [text]);
|
|
453
|
+
const addFiles = useCallback3((incoming) => {
|
|
454
|
+
const arr = Array.from(incoming);
|
|
455
|
+
setFiles((prev) => {
|
|
456
|
+
const existing = new Set(prev.map((f) => f.file.name));
|
|
457
|
+
const fresh = arr.filter((f) => !existing.has(f.name));
|
|
458
|
+
return [
|
|
459
|
+
...prev,
|
|
460
|
+
...fresh.map((file) => ({
|
|
461
|
+
id: Math.random().toString(36).slice(2),
|
|
462
|
+
file,
|
|
463
|
+
url: URL.createObjectURL(file)
|
|
464
|
+
}))
|
|
465
|
+
];
|
|
466
|
+
});
|
|
467
|
+
}, []);
|
|
468
|
+
const removeFile = useCallback3((id) => {
|
|
469
|
+
setFiles((prev) => {
|
|
470
|
+
const f = prev.find((p) => p.id === id);
|
|
471
|
+
if (f) URL.revokeObjectURL(f.url);
|
|
472
|
+
return prev.filter((p) => p.id !== id);
|
|
473
|
+
});
|
|
474
|
+
}, []);
|
|
475
|
+
useEffect2(
|
|
476
|
+
() => () => {
|
|
477
|
+
files.forEach((f) => URL.revokeObjectURL(f.url));
|
|
478
|
+
},
|
|
479
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
480
|
+
[]
|
|
481
|
+
);
|
|
482
|
+
const submit = useCallback3(async () => {
|
|
483
|
+
if (isSending) return;
|
|
484
|
+
const trimmed = text.trim();
|
|
485
|
+
if (!trimmed && files.length === 0) return;
|
|
486
|
+
setIsSending(true);
|
|
487
|
+
try {
|
|
488
|
+
await handleSubmit({
|
|
489
|
+
text: trimmed,
|
|
490
|
+
files: files.map((f) => ({ url: f.url, filename: f.file.name }))
|
|
491
|
+
});
|
|
492
|
+
setText("");
|
|
493
|
+
setFiles([]);
|
|
494
|
+
} finally {
|
|
495
|
+
setIsSending(false);
|
|
496
|
+
}
|
|
497
|
+
setTimeout(() => textareaRef.current?.focus(), 0);
|
|
498
|
+
}, [text, files, handleSubmit, isSending]);
|
|
499
|
+
const onKeyDown = useCallback3(
|
|
500
|
+
(e) => {
|
|
501
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
502
|
+
e.preventDefault();
|
|
503
|
+
submit();
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
[submit]
|
|
507
|
+
);
|
|
508
|
+
const onFileChange = useCallback3(
|
|
509
|
+
(e) => {
|
|
510
|
+
if (e.target.files) addFiles(e.target.files);
|
|
511
|
+
e.target.value = "";
|
|
512
|
+
},
|
|
513
|
+
[addFiles]
|
|
514
|
+
);
|
|
515
|
+
const onDrop = useCallback3(
|
|
516
|
+
(e) => {
|
|
517
|
+
e.preventDefault();
|
|
518
|
+
if (e.dataTransfer.files.length > 0) {
|
|
519
|
+
addFiles(e.dataTransfer.files);
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
[addFiles]
|
|
523
|
+
);
|
|
524
|
+
return /* @__PURE__ */ jsx7("div", { className: `shrink-0 ${className || ""}`, children: /* @__PURE__ */ jsx7("div", { className: "w-full px-6 py-3", children: /* @__PURE__ */ jsxs6("div", { className: "max-w-3xl mx-auto relative", children: [
|
|
525
|
+
allowAttachments && dragging && /* @__PURE__ */ jsxs6("div", { className: "absolute inset-0 z-10 bg-blue-50 border-2 border-dashed border-blue-400 rounded-2xl flex items-center justify-center gap-2 text-blue-600 text-sm font-medium pointer-events-none", children: [
|
|
526
|
+
/* @__PURE__ */ jsx7(Upload, { className: "size-4" }),
|
|
527
|
+
" Drop files to attach"
|
|
528
|
+
] }),
|
|
529
|
+
/* @__PURE__ */ jsxs6(
|
|
530
|
+
"div",
|
|
531
|
+
{
|
|
532
|
+
className: "rounded-2xl border border-gray-200 shadow-sm focus-within:border-blue-400 focus-within:shadow-md transition-all bg-white",
|
|
533
|
+
onDrop: allowAttachments ? onDrop : void 0,
|
|
534
|
+
onDragOver: allowAttachments ? (e) => e.preventDefault() : void 0,
|
|
535
|
+
children: [
|
|
536
|
+
files.length > 0 && /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-2 px-4 pt-3", children: files.map((f) => {
|
|
537
|
+
const isImage = f.file.type.startsWith("image/");
|
|
538
|
+
return /* @__PURE__ */ jsxs6(
|
|
539
|
+
"div",
|
|
540
|
+
{
|
|
541
|
+
className: "relative flex items-center gap-2 bg-gray-50 border border-gray-200 rounded-lg px-2.5 py-1.5 text-xs",
|
|
542
|
+
children: [
|
|
543
|
+
isImage ? /* @__PURE__ */ jsx7("img", { src: f.url, alt: f.file.name, className: "size-8 rounded object-cover" }) : /* @__PURE__ */ jsx7("div", { className: "size-8 rounded bg-gray-200 flex items-center justify-center text-gray-500 text-[10px] font-mono uppercase", children: f.file.name.split(".").pop()?.slice(0, 4) }),
|
|
544
|
+
/* @__PURE__ */ jsx7("span", { className: "truncate max-w-[120px]", children: f.file.name }),
|
|
545
|
+
/* @__PURE__ */ jsx7(
|
|
546
|
+
"button",
|
|
547
|
+
{
|
|
548
|
+
type: "button",
|
|
549
|
+
onClick: () => removeFile(f.id),
|
|
550
|
+
className: "size-4 rounded-full bg-gray-300/50 flex items-center justify-center hover:bg-red-500 hover:text-white transition-colors",
|
|
551
|
+
children: /* @__PURE__ */ jsx7(X, { className: "size-2.5" })
|
|
552
|
+
}
|
|
553
|
+
)
|
|
554
|
+
]
|
|
555
|
+
},
|
|
556
|
+
f.id
|
|
557
|
+
);
|
|
558
|
+
}) }),
|
|
559
|
+
/* @__PURE__ */ jsx7(
|
|
560
|
+
"textarea",
|
|
561
|
+
{
|
|
562
|
+
ref: textareaRef,
|
|
563
|
+
value: text,
|
|
564
|
+
onChange: (e) => setText(e.target.value),
|
|
565
|
+
onKeyDown,
|
|
566
|
+
placeholder,
|
|
567
|
+
rows: 1,
|
|
568
|
+
className: "w-full resize-none bg-transparent px-5 pt-4 pb-2 text-sm outline-none placeholder:text-gray-400"
|
|
569
|
+
}
|
|
570
|
+
),
|
|
571
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between px-3 pb-3", children: [
|
|
572
|
+
allowAttachments ? /* @__PURE__ */ jsx7(
|
|
573
|
+
"button",
|
|
574
|
+
{
|
|
575
|
+
type: "button",
|
|
576
|
+
onClick: () => fileInputRef.current?.click(),
|
|
577
|
+
className: "flex items-center justify-center size-8 rounded-lg text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors",
|
|
578
|
+
"aria-label": "Attach file",
|
|
579
|
+
children: /* @__PURE__ */ jsx7(Plus, { className: "size-4" })
|
|
580
|
+
}
|
|
581
|
+
) : /* @__PURE__ */ jsx7("div", {}),
|
|
582
|
+
renderSubmit ? renderSubmit({ isStreaming, onStop: abort }) : /* @__PURE__ */ jsx7(
|
|
583
|
+
"button",
|
|
584
|
+
{
|
|
585
|
+
type: "button",
|
|
586
|
+
onClick: isStreaming ? abort : submit,
|
|
587
|
+
disabled: isSending && !isStreaming,
|
|
588
|
+
className: "flex items-center justify-center size-8 rounded-lg bg-gray-900 text-white hover:bg-gray-700 disabled:opacity-40 transition-colors",
|
|
589
|
+
"aria-label": isStreaming ? "Stop" : "Send",
|
|
590
|
+
children: isStreaming ? /* @__PURE__ */ jsx7(Square, { className: "size-3.5" }) : /* @__PURE__ */ jsx7(ArrowUp, { className: "size-4" })
|
|
591
|
+
}
|
|
592
|
+
)
|
|
593
|
+
] })
|
|
594
|
+
]
|
|
595
|
+
}
|
|
596
|
+
),
|
|
597
|
+
allowAttachments && /* @__PURE__ */ jsx7(
|
|
598
|
+
"input",
|
|
599
|
+
{
|
|
600
|
+
ref: fileInputRef,
|
|
601
|
+
type: "file",
|
|
602
|
+
multiple: true,
|
|
603
|
+
onChange: onFileChange,
|
|
604
|
+
className: "hidden"
|
|
605
|
+
}
|
|
606
|
+
),
|
|
607
|
+
hint && /* @__PURE__ */ jsx7("p", { className: "text-center text-xs text-gray-400 mt-2", children: hint })
|
|
608
|
+
] }) }) });
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/components/chat.tsx
|
|
612
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
613
|
+
var Chat = forwardRef2(function Chat2({
|
|
614
|
+
sessionId,
|
|
615
|
+
agent,
|
|
616
|
+
onSessionCreated,
|
|
617
|
+
onUpdate,
|
|
618
|
+
renderMessage,
|
|
619
|
+
avatar,
|
|
620
|
+
agentName,
|
|
621
|
+
streamdownComponents: streamdownComponents2,
|
|
622
|
+
skeletonCount,
|
|
623
|
+
inputPlaceholder,
|
|
624
|
+
inputHint,
|
|
625
|
+
allowAttachments,
|
|
626
|
+
children,
|
|
627
|
+
className
|
|
628
|
+
}, ref) {
|
|
629
|
+
const hasChildren = children !== void 0 && children !== null;
|
|
630
|
+
const defaultRender = (msg, _index, isLast, isStreaming) => /* @__PURE__ */ jsx8(
|
|
631
|
+
ChatMessage,
|
|
632
|
+
{
|
|
633
|
+
msg,
|
|
634
|
+
isLast,
|
|
635
|
+
isStreaming,
|
|
636
|
+
avatar,
|
|
637
|
+
agentName,
|
|
638
|
+
streamdownComponents: streamdownComponents2
|
|
639
|
+
}
|
|
640
|
+
);
|
|
641
|
+
return /* @__PURE__ */ jsx8(
|
|
642
|
+
ChatProvider,
|
|
643
|
+
{
|
|
644
|
+
sessionId,
|
|
645
|
+
agent,
|
|
646
|
+
onSessionCreated,
|
|
647
|
+
onUpdate,
|
|
648
|
+
children: /* @__PURE__ */ jsxs7("div", { className: `flex flex-col min-w-0 min-h-0 ${className || ""}`, children: [
|
|
649
|
+
/* @__PURE__ */ jsx8(
|
|
650
|
+
ChatMessages,
|
|
651
|
+
{
|
|
652
|
+
ref,
|
|
653
|
+
renderItem: renderMessage || defaultRender,
|
|
654
|
+
skeletonCount,
|
|
655
|
+
className: "flex-1"
|
|
656
|
+
}
|
|
657
|
+
),
|
|
658
|
+
hasChildren ? children : /* @__PURE__ */ jsx8(
|
|
659
|
+
ChatInput,
|
|
660
|
+
{
|
|
661
|
+
placeholder: inputPlaceholder,
|
|
662
|
+
hint: inputHint,
|
|
663
|
+
allowAttachments
|
|
664
|
+
}
|
|
665
|
+
)
|
|
666
|
+
] })
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
// src/components/streamdown-code.tsx
|
|
672
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
673
|
+
function FallbackCodeBlock({ code, language }) {
|
|
674
|
+
return /* @__PURE__ */ jsx9(
|
|
675
|
+
"pre",
|
|
676
|
+
{
|
|
677
|
+
"data-language": language,
|
|
678
|
+
style: {
|
|
679
|
+
margin: "0.75rem 0",
|
|
680
|
+
padding: "0.875rem 1rem",
|
|
681
|
+
borderRadius: "10px",
|
|
682
|
+
overflowX: "auto",
|
|
683
|
+
border: "1px solid var(--line, #e5e7eb)",
|
|
684
|
+
background: "var(--bg, #f9fafb)",
|
|
685
|
+
color: "var(--ink, inherit)",
|
|
686
|
+
fontSize: "0.75rem",
|
|
687
|
+
lineHeight: "1.625"
|
|
688
|
+
},
|
|
689
|
+
children: /* @__PURE__ */ jsx9("code", { className: `language-${language}`, children: code })
|
|
690
|
+
}
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
function createStreamdownComponents(CodeBlockComponent) {
|
|
694
|
+
const Block = CodeBlockComponent ?? FallbackCodeBlock;
|
|
695
|
+
function StreamdownCode(props) {
|
|
696
|
+
const {
|
|
697
|
+
children,
|
|
698
|
+
className,
|
|
699
|
+
node: _,
|
|
700
|
+
"data-block": dataBlock,
|
|
701
|
+
...rest
|
|
702
|
+
} = props;
|
|
703
|
+
if (dataBlock !== void 0) {
|
|
704
|
+
const match = /language-(\w+)/.exec(className || "");
|
|
705
|
+
const lang = match?.[1] || "text";
|
|
706
|
+
const code = String(children).replace(/\n$/, "");
|
|
707
|
+
return /* @__PURE__ */ jsx9(Block, { code, language: lang });
|
|
708
|
+
}
|
|
709
|
+
return /* @__PURE__ */ jsx9("code", { className, ...rest, children });
|
|
710
|
+
}
|
|
711
|
+
return { code: StreamdownCode };
|
|
712
|
+
}
|
|
713
|
+
var streamdownComponents = createStreamdownComponents();
|
|
714
|
+
export {
|
|
715
|
+
Chat,
|
|
716
|
+
ChatAssistantMessage,
|
|
717
|
+
ChatInput,
|
|
718
|
+
ChatMessage,
|
|
719
|
+
ChatMessages,
|
|
720
|
+
ChatProvider,
|
|
721
|
+
ChatScrollButton,
|
|
722
|
+
ChatSkeleton,
|
|
723
|
+
ChatTyping,
|
|
724
|
+
ChatUserMessage,
|
|
725
|
+
MessageSkeleton,
|
|
726
|
+
ToolCallChip,
|
|
727
|
+
ToolCallShell,
|
|
728
|
+
UserMessageSkeleton,
|
|
729
|
+
createStreamdownComponents,
|
|
730
|
+
getTextContent,
|
|
731
|
+
relativeTime,
|
|
732
|
+
streamdownComponents,
|
|
733
|
+
useChatContext,
|
|
734
|
+
useDocumentDrag,
|
|
735
|
+
useSubmitHandler
|
|
736
|
+
};
|
|
737
|
+
//# sourceMappingURL=index.js.map
|