@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/README.md
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# @polpo-ai/chat
|
|
2
|
+
|
|
3
|
+
Composable chat UI components for [Polpo](https://polpo.sh) AI agents. Built on top of `@polpo-ai/sdk` and `@polpo-ai/react`.
|
|
4
|
+
|
|
5
|
+
Three levels of composition — from zero-config to full control.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @polpo-ai/chat
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer dependencies:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @polpo-ai/sdk @polpo-ai/react react react-virtuoso lucide-react
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Level 1 — Zero Config
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { Chat } from "@polpo-ai/chat";
|
|
25
|
+
|
|
26
|
+
function ChatPage() {
|
|
27
|
+
return <Chat sessionId="session_abc" agent="coder" />;
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
That's it. Messages, streaming, scroll-to-bottom, tool calls, typing dots, skeleton loading — all included.
|
|
32
|
+
|
|
33
|
+
### Level 2 — Compose
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { Chat, useChatContext } from "@polpo-ai/chat";
|
|
37
|
+
|
|
38
|
+
function ChatInput() {
|
|
39
|
+
const { sendMessage, isStreaming, abort } = useChatContext();
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<form onSubmit={(e) => { e.preventDefault(); sendMessage(input); }}>
|
|
43
|
+
<textarea />
|
|
44
|
+
{isStreaming ? <button onClick={abort}>Stop</button> : <button type="submit">Send</button>}
|
|
45
|
+
</form>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ChatPage() {
|
|
50
|
+
return (
|
|
51
|
+
<Chat sessionId="session_abc" agent="coder" avatar={<Avatar />} agentName="Coder">
|
|
52
|
+
<ChatInput />
|
|
53
|
+
</Chat>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Children render below the message list. Use `useChatContext()` to access chat state from any child.
|
|
59
|
+
|
|
60
|
+
### Level 3 — Primitives
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { useChat } from "@polpo-ai/react";
|
|
64
|
+
import { ChatMessage, ToolCallChip, ChatSkeleton } from "@polpo-ai/chat";
|
|
65
|
+
|
|
66
|
+
function MyChat() {
|
|
67
|
+
const { messages, sendMessage, isStreaming } = useChat({ agent: "coder" });
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div>
|
|
71
|
+
{messages.map((msg, i) => (
|
|
72
|
+
<ChatMessage key={msg.id} msg={msg} isLast={i === messages.length - 1} isStreaming={isStreaming} />
|
|
73
|
+
))}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Use individual components and hooks to build completely custom layouts.
|
|
80
|
+
|
|
81
|
+
## Components
|
|
82
|
+
|
|
83
|
+
### `<Chat>`
|
|
84
|
+
|
|
85
|
+
Root compound component. Wraps `ChatProvider` + `ChatMessages` + `ChatScrollButton`.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
<Chat
|
|
89
|
+
sessionId="session_abc" // Existing session ID (omit for new chats)
|
|
90
|
+
agent="coder" // Agent name
|
|
91
|
+
onSessionCreated={(id) => {}} // Called when server creates a new session
|
|
92
|
+
avatar={<Avatar />} // ReactNode for assistant messages
|
|
93
|
+
agentName="Coder" // Display name for assistant
|
|
94
|
+
streamdownComponents={...} // Custom code block renderer
|
|
95
|
+
skeletonCount={3} // Loading skeleton count
|
|
96
|
+
className="flex-1" // Outer container class
|
|
97
|
+
>
|
|
98
|
+
{children} {/* Rendered below messages (e.g. input bar) */}
|
|
99
|
+
</Chat>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `<ChatMessage>`
|
|
103
|
+
|
|
104
|
+
Renders a single message. Dispatches to `ChatUserMessage` or `ChatAssistantMessage` based on role.
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<ChatMessage
|
|
108
|
+
msg={message} // ChatMessageItemData
|
|
109
|
+
isLast={true} // Is this the last message?
|
|
110
|
+
isStreaming={false} // Is the chat currently streaming?
|
|
111
|
+
avatar={<Avatar />} // Assistant avatar (ReactNode)
|
|
112
|
+
agentName="Coder" // Assistant display name
|
|
113
|
+
streamdownComponents={...} // Markdown renderer override
|
|
114
|
+
/>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `<ChatMessages>`
|
|
118
|
+
|
|
119
|
+
Virtuoso-powered scrollable message list with auto-scroll, scroll-to-bottom button, and skeleton loading.
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
<ChatMessages
|
|
123
|
+
renderItem={(msg, index, isLast, isStreaming) => <ChatMessage msg={msg} ... />}
|
|
124
|
+
skeletonCount={3}
|
|
125
|
+
className="flex-1"
|
|
126
|
+
/>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Uses `useChatContext()` internally. Must be inside a `<ChatProvider>`.
|
|
130
|
+
|
|
131
|
+
### `<ChatScrollButton>`
|
|
132
|
+
|
|
133
|
+
Scroll-to-bottom button with new message indicator.
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<ChatScrollButton isAtBottom={false} showNewMessage={true} onClick={scrollToBottom} />
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### `<ChatSkeleton>`
|
|
140
|
+
|
|
141
|
+
Loading skeleton matching the message layout.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
<ChatSkeleton count={3} />
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `<ChatTyping>`
|
|
148
|
+
|
|
149
|
+
Animated typing dots.
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<ChatTyping className="text-gray-400" />
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Tool Calls
|
|
156
|
+
|
|
157
|
+
Built-in renderers for common Polpo tools:
|
|
158
|
+
|
|
159
|
+
| Tool | Renderer | What it shows |
|
|
160
|
+
|------|----------|--------------|
|
|
161
|
+
| `read` | `ToolRead` | File path + content with line numbers |
|
|
162
|
+
| `write` / `edit` | `ToolWrite` | File path + content preview in green |
|
|
163
|
+
| `bash` | `ToolBash` | Command with `$` prompt + dark terminal output |
|
|
164
|
+
| `grep` / `glob` | `ToolSearch` | Pattern + matched results list |
|
|
165
|
+
| `http_fetch` / `search_web` | `ToolHttp` | URL + response preview |
|
|
166
|
+
| `email_send` | `ToolEmail` | To, subject, body preview |
|
|
167
|
+
| `ask_user_question` | `ToolAskUser` | Questions with answered state |
|
|
168
|
+
| (any other) | `ToolCallShell` | Generic with expand/collapse |
|
|
169
|
+
|
|
170
|
+
### Custom Tool Renderers
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
import { ToolCallShell } from "@polpo-ai/chat/tools";
|
|
174
|
+
import { Database } from "lucide-react";
|
|
175
|
+
|
|
176
|
+
function ToolDatabaseQuery({ tool }) {
|
|
177
|
+
const query = tool.arguments?.query;
|
|
178
|
+
return (
|
|
179
|
+
<ToolCallShell tool={tool} icon={Database} label="Query" summary={query}>
|
|
180
|
+
<pre>{tool.result}</pre>
|
|
181
|
+
</ToolCallShell>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Hooks
|
|
187
|
+
|
|
188
|
+
### `useSubmitHandler(sendMessage, uploadFile)`
|
|
189
|
+
|
|
190
|
+
Handles file uploads and sends messages with `ContentPart[]`.
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { useSubmitHandler } from "@polpo-ai/chat/hooks";
|
|
194
|
+
|
|
195
|
+
const handleSubmit = useSubmitHandler(sendMessage, uploadFile);
|
|
196
|
+
// handleSubmit({ text: "Analyze this", files: [...] })
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### `useDocumentDrag()`
|
|
200
|
+
|
|
201
|
+
Tracks document-level drag state for drop overlay feedback.
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
import { useDocumentDrag } from "@polpo-ai/chat/hooks";
|
|
205
|
+
|
|
206
|
+
const dragging = useDocumentDrag();
|
|
207
|
+
// dragging: boolean — true when files are being dragged over the page
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Utilities
|
|
211
|
+
|
|
212
|
+
### `getTextContent(content)`
|
|
213
|
+
|
|
214
|
+
Extracts text from `string | ContentPart[]`.
|
|
215
|
+
|
|
216
|
+
### `relativeTime(isoString)`
|
|
217
|
+
|
|
218
|
+
Formats timestamps as "Just now", "2m ago", "An hour ago", or full date.
|
|
219
|
+
|
|
220
|
+
## Styling
|
|
221
|
+
|
|
222
|
+
The package uses Tailwind utility classes. Colors reference CSS custom properties:
|
|
223
|
+
|
|
224
|
+
```css
|
|
225
|
+
:root {
|
|
226
|
+
--bg: #FAFAF7;
|
|
227
|
+
--surface: #FFFFFF;
|
|
228
|
+
--ink: #0F0F0F;
|
|
229
|
+
--ink-2: #555;
|
|
230
|
+
--ink-3: #999;
|
|
231
|
+
--p-accent: #E2733D;
|
|
232
|
+
--accent-light: #FDF0E8;
|
|
233
|
+
--green: #1A7F52;
|
|
234
|
+
--green-light: #E8F5EE;
|
|
235
|
+
--warm: #F3F0EB;
|
|
236
|
+
--line: #E5E1DA;
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Override these in your `globals.css` to match your brand. No CSS is shipped — everything is Tailwind inline.
|
|
241
|
+
|
|
242
|
+
## Keyframe Animations
|
|
243
|
+
|
|
244
|
+
Add these to your Tailwind config or `globals.css`:
|
|
245
|
+
|
|
246
|
+
```css
|
|
247
|
+
@keyframes typing-dot {
|
|
248
|
+
0%, 60%, 100% { opacity: .3; transform: translateY(0); }
|
|
249
|
+
30% { opacity: 1; transform: translateY(-3px); }
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
MIT
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// src/tools/index.tsx
|
|
2
|
+
import { Wrench } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
// src/tools/tool-call-shell.tsx
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { ChevronRight, Loader2, Check, AlertCircle } from "lucide-react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
function ToolCallShell({ tool, icon: Icon, label, summary, children }) {
|
|
9
|
+
const [expanded, setExpanded] = useState(false);
|
|
10
|
+
const isPending = tool.state === "calling" || tool.state === "preparing";
|
|
11
|
+
const isError = tool.state === "error";
|
|
12
|
+
const isDone = tool.state === "completed";
|
|
13
|
+
const hasContent = isDone && (children || tool.result);
|
|
14
|
+
return /* @__PURE__ */ jsxs("div", { className: `flex flex-col rounded-lg bg-p-warm border border-p-line text-[13px] text-p-ink-2 overflow-hidden ${isError ? "border-destructive/20 bg-destructive/5" : ""}`, children: [
|
|
15
|
+
/* @__PURE__ */ jsxs(
|
|
16
|
+
"button",
|
|
17
|
+
{
|
|
18
|
+
className: `flex items-center gap-2 px-3 py-2 border-none bg-transparent font-inherit text-inherit text-left w-full ${hasContent ? "cursor-pointer" : "cursor-default"}`,
|
|
19
|
+
onClick: () => hasContent && setExpanded(!expanded),
|
|
20
|
+
children: [
|
|
21
|
+
/* @__PURE__ */ jsx(Icon, { size: 14, className: "shrink-0" }),
|
|
22
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-p-ink whitespace-nowrap", children: label }),
|
|
23
|
+
summary ? /* @__PURE__ */ jsx("span", { className: "text-p-ink-3 text-xs overflow-hidden text-ellipsis whitespace-nowrap max-w-[300px]", children: summary }) : null,
|
|
24
|
+
isPending ? /* @__PURE__ */ jsx(Loader2, { size: 14, className: "animate-spin text-p-accent shrink-0" }) : null,
|
|
25
|
+
isDone ? /* @__PURE__ */ jsx(Check, { size: 14, className: "text-p-green shrink-0" }) : null,
|
|
26
|
+
isError ? /* @__PURE__ */ jsx(AlertCircle, { size: 14, className: "text-destructive shrink-0" }) : null,
|
|
27
|
+
hasContent ? /* @__PURE__ */ jsx(ChevronRight, { size: 12, className: `ml-auto text-p-ink-3 shrink-0 transition-transform duration-150 ${expanded ? "rotate-90" : ""}` }) : null
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
),
|
|
31
|
+
expanded ? /* @__PURE__ */ jsx("div", { className: "border-t border-p-line", children: children || /* @__PURE__ */ jsx("pre", { className: "m-0 px-2.5 py-2 text-[11px] leading-normal font-mono text-p-ink-2 bg-p-bg whitespace-pre-wrap break-all max-h-[180px] overflow-y-auto", children: tool.result }) }) : null
|
|
32
|
+
] });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/tools/tool-read.tsx
|
|
36
|
+
import { FileText } from "lucide-react";
|
|
37
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
38
|
+
function ToolRead({ tool }) {
|
|
39
|
+
const path = tool.arguments?.path || tool.arguments?.file_path;
|
|
40
|
+
const lines = tool.result?.split("\n") || [];
|
|
41
|
+
const maxLines = 12;
|
|
42
|
+
const truncated = lines.length > maxLines;
|
|
43
|
+
return /* @__PURE__ */ jsx2(ToolCallShell, { tool, icon: FileText, label: "Read", summary: path, children: tool.result && /* @__PURE__ */ jsxs2("div", { className: "bg-p-bg max-h-[220px] overflow-y-auto", children: [
|
|
44
|
+
/* @__PURE__ */ jsx2("table", { className: "w-full text-[11px] leading-relaxed font-mono border-collapse", children: /* @__PURE__ */ jsx2("tbody", { children: lines.slice(0, maxLines).map((line, i) => /* @__PURE__ */ jsxs2("tr", { className: "hover:bg-p-warm/50", children: [
|
|
45
|
+
/* @__PURE__ */ jsx2("td", { className: "text-right text-p-ink-3 select-none px-2.5 py-0 w-[1%] whitespace-nowrap", children: i + 1 }),
|
|
46
|
+
/* @__PURE__ */ jsx2("td", { className: "text-p-ink-2 px-2.5 py-0 whitespace-pre-wrap break-all", children: line })
|
|
47
|
+
] }, i)) }) }),
|
|
48
|
+
truncated && /* @__PURE__ */ jsxs2("div", { className: "px-2.5 py-1 text-[10px] text-p-ink-3 border-t border-p-line", children: [
|
|
49
|
+
"+",
|
|
50
|
+
lines.length - maxLines,
|
|
51
|
+
" more lines"
|
|
52
|
+
] })
|
|
53
|
+
] }) });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/tools/tool-write.tsx
|
|
57
|
+
import { Pen } from "lucide-react";
|
|
58
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
59
|
+
function ToolWrite({ tool }) {
|
|
60
|
+
const path = tool.arguments?.path || tool.arguments?.file_path;
|
|
61
|
+
const content = tool.arguments?.content || tool.arguments?.new_string;
|
|
62
|
+
const preview = content?.slice(0, 200);
|
|
63
|
+
return /* @__PURE__ */ jsx3(ToolCallShell, { tool, icon: Pen, label: tool.name === "edit" ? "Edit" : "Write", summary: path, children: preview && /* @__PURE__ */ jsx3("div", { className: "bg-p-bg max-h-[180px] overflow-y-auto", children: /* @__PURE__ */ jsxs3("pre", { className: "m-0 px-2.5 py-2 text-[11px] leading-normal font-mono text-p-green whitespace-pre-wrap break-all", children: [
|
|
64
|
+
preview,
|
|
65
|
+
content && content.length > 200 ? "\n\u2026" : ""
|
|
66
|
+
] }) }) });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/tools/tool-bash.tsx
|
|
70
|
+
import { Terminal } from "lucide-react";
|
|
71
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
72
|
+
function ToolBash({ tool }) {
|
|
73
|
+
const command = tool.arguments?.command;
|
|
74
|
+
const lines = tool.result?.split("\n") || [];
|
|
75
|
+
const maxLines = 10;
|
|
76
|
+
const truncated = lines.length > maxLines;
|
|
77
|
+
return /* @__PURE__ */ jsx4(ToolCallShell, { tool, icon: Terminal, label: "Bash", summary: command?.slice(0, 60), children: /* @__PURE__ */ jsxs4("div", { className: "bg-[#1a1a1a] max-h-[220px] overflow-y-auto", children: [
|
|
78
|
+
command && /* @__PURE__ */ jsxs4("div", { className: "px-3 py-1.5 text-[11px] font-mono text-emerald-400 border-b border-white/10", children: [
|
|
79
|
+
/* @__PURE__ */ jsx4("span", { className: "text-p-ink-3 select-none", children: "$ " }),
|
|
80
|
+
command
|
|
81
|
+
] }),
|
|
82
|
+
tool.result && /* @__PURE__ */ jsxs4("pre", { className: "m-0 px-3 py-1.5 text-[11px] leading-normal font-mono text-neutral-300 whitespace-pre-wrap break-all", children: [
|
|
83
|
+
lines.slice(0, maxLines).join("\n"),
|
|
84
|
+
truncated ? `
|
|
85
|
+
\u2026 +${lines.length - maxLines} lines` : ""
|
|
86
|
+
] })
|
|
87
|
+
] }) });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/tools/tool-search.tsx
|
|
91
|
+
import { Search, FolderSearch } from "lucide-react";
|
|
92
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
93
|
+
function ToolSearch({ tool }) {
|
|
94
|
+
const isGlob = tool.name === "glob";
|
|
95
|
+
const pattern = tool.arguments?.pattern || tool.arguments?.query || tool.arguments?.q;
|
|
96
|
+
const path = tool.arguments?.path || tool.arguments?.root;
|
|
97
|
+
const summary = pattern ? `${pattern}${path ? ` in ${path}` : ""}` : path;
|
|
98
|
+
const lines = tool.result?.split("\n").filter(Boolean) || [];
|
|
99
|
+
const maxItems = 8;
|
|
100
|
+
const truncated = lines.length > maxItems;
|
|
101
|
+
return /* @__PURE__ */ jsx5(ToolCallShell, { tool, icon: isGlob ? FolderSearch : Search, label: isGlob ? "Glob" : tool.name === "grep" ? "Grep" : "Search", summary, children: lines.length > 0 && /* @__PURE__ */ jsxs5("div", { className: "bg-p-bg max-h-[200px] overflow-y-auto", children: [
|
|
102
|
+
/* @__PURE__ */ jsx5("ul", { className: "list-none m-0 p-0", children: lines.slice(0, maxItems).map((line, i) => /* @__PURE__ */ jsx5("li", { className: "px-2.5 py-0.5 text-xs font-mono text-p-ink-2 border-b border-p-line/50 last:border-b-0 hover:bg-p-warm/50 truncate", children: line }, i)) }),
|
|
103
|
+
truncated && /* @__PURE__ */ jsxs5("div", { className: "px-2.5 py-1 text-[10px] text-p-ink-3 border-t border-p-line", children: [
|
|
104
|
+
"+",
|
|
105
|
+
lines.length - maxItems,
|
|
106
|
+
" more results"
|
|
107
|
+
] })
|
|
108
|
+
] }) });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/tools/tool-http.tsx
|
|
112
|
+
import { Globe } from "lucide-react";
|
|
113
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
114
|
+
function ToolHttp({ tool }) {
|
|
115
|
+
const url = tool.arguments?.url;
|
|
116
|
+
const method = tool.arguments?.method?.toUpperCase() || "GET";
|
|
117
|
+
const summary = url ? `${method} ${url}` : null;
|
|
118
|
+
return /* @__PURE__ */ jsx6(ToolCallShell, { tool, icon: Globe, label: tool.name === "search_web" ? "Search Web" : "HTTP", summary, children: tool.result && /* @__PURE__ */ jsxs6("pre", { className: "m-0 px-2.5 py-2 text-[11px] leading-normal font-mono text-p-ink-2 bg-p-bg whitespace-pre-wrap break-all max-h-[180px] overflow-y-auto", children: [
|
|
119
|
+
tool.result.slice(0, 500),
|
|
120
|
+
tool.result.length > 500 ? "\n\u2026" : ""
|
|
121
|
+
] }) });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/tools/tool-email.tsx
|
|
125
|
+
import { Mail } from "lucide-react";
|
|
126
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
127
|
+
function ToolEmail({ tool }) {
|
|
128
|
+
const to = tool.arguments?.to || tool.arguments?.recipient;
|
|
129
|
+
const subject = tool.arguments?.subject;
|
|
130
|
+
const body = tool.arguments?.body || tool.arguments?.content;
|
|
131
|
+
const summary = to ? `\u2192 ${to}` : null;
|
|
132
|
+
return /* @__PURE__ */ jsx7(ToolCallShell, { tool, icon: Mail, label: "Email", summary, children: /* @__PURE__ */ jsxs7("div", { className: "bg-p-bg px-2.5 py-2 text-xs max-h-[180px] overflow-y-auto", children: [
|
|
133
|
+
subject && /* @__PURE__ */ jsxs7("div", { className: "mb-1", children: [
|
|
134
|
+
/* @__PURE__ */ jsx7("span", { className: "text-p-ink-3", children: "Subject: " }),
|
|
135
|
+
/* @__PURE__ */ jsx7("span", { className: "text-p-ink font-medium", children: subject })
|
|
136
|
+
] }),
|
|
137
|
+
to && /* @__PURE__ */ jsxs7("div", { className: "mb-1.5", children: [
|
|
138
|
+
/* @__PURE__ */ jsx7("span", { className: "text-p-ink-3", children: "To: " }),
|
|
139
|
+
/* @__PURE__ */ jsx7("span", { className: "text-p-ink-2", children: to })
|
|
140
|
+
] }),
|
|
141
|
+
body && /* @__PURE__ */ jsxs7("p", { className: "m-0 text-p-ink-2 leading-relaxed whitespace-pre-wrap", children: [
|
|
142
|
+
body.slice(0, 300),
|
|
143
|
+
body.length > 300 ? "\u2026" : ""
|
|
144
|
+
] })
|
|
145
|
+
] }) });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/tools/tool-ask-user.tsx
|
|
149
|
+
import { MessageSquareMore, Check as Check2 } from "lucide-react";
|
|
150
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
151
|
+
function ToolAskUser({ tool }) {
|
|
152
|
+
const questions = tool.arguments?.questions || [];
|
|
153
|
+
const isAnswered = tool.state === "completed" || tool.state === "interrupted";
|
|
154
|
+
let answers = [];
|
|
155
|
+
if (tool.result) {
|
|
156
|
+
try {
|
|
157
|
+
const parsed = JSON.parse(tool.result);
|
|
158
|
+
answers = parsed.answers || [];
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return /* @__PURE__ */ jsx8(
|
|
163
|
+
ToolCallShell,
|
|
164
|
+
{
|
|
165
|
+
tool,
|
|
166
|
+
icon: MessageSquareMore,
|
|
167
|
+
label: "Question",
|
|
168
|
+
summary: isAnswered ? `${questions.length} answered` : `${questions.length} question${questions.length > 1 ? "s" : ""}`,
|
|
169
|
+
children: /* @__PURE__ */ jsx8("div", { className: "bg-p-bg px-3 py-2 text-xs space-y-2", children: questions.map((q) => {
|
|
170
|
+
const answer = answers.find((a) => a.questionId === q.id);
|
|
171
|
+
return /* @__PURE__ */ jsxs8("div", { className: "flex items-start gap-2", children: [
|
|
172
|
+
isAnswered ? /* @__PURE__ */ jsx8(Check2, { size: 12, className: "text-p-green shrink-0 mt-0.5" }) : /* @__PURE__ */ jsx8(MessageSquareMore, { size: 12, className: "text-p-accent shrink-0 mt-0.5" }),
|
|
173
|
+
/* @__PURE__ */ jsxs8("div", { className: "min-w-0", children: [
|
|
174
|
+
/* @__PURE__ */ jsx8("p", { className: "text-p-ink font-medium", children: q.question }),
|
|
175
|
+
answer && answer.selected.length > 0 && /* @__PURE__ */ jsx8("p", { className: "text-p-ink-3 mt-0.5", children: answer.selected.join(", ") }),
|
|
176
|
+
!answer && isAnswered && /* @__PURE__ */ jsx8("p", { className: "text-p-ink-3/50 italic mt-0.5", children: "Skipped" })
|
|
177
|
+
] })
|
|
178
|
+
] }, q.id);
|
|
179
|
+
}) })
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/tools/index.tsx
|
|
185
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
186
|
+
var TOOL_COMPONENTS = {
|
|
187
|
+
ask_user_question: ToolAskUser,
|
|
188
|
+
read: ToolRead,
|
|
189
|
+
read_attachment: ToolRead,
|
|
190
|
+
write: ToolWrite,
|
|
191
|
+
edit: ToolWrite,
|
|
192
|
+
bash: ToolBash,
|
|
193
|
+
grep: ToolSearch,
|
|
194
|
+
glob: ToolSearch,
|
|
195
|
+
search_web: ToolHttp,
|
|
196
|
+
http_fetch: ToolHttp,
|
|
197
|
+
http_download: ToolHttp,
|
|
198
|
+
email_send: ToolEmail
|
|
199
|
+
};
|
|
200
|
+
var TOOL_PREFIX_COMPONENTS = [
|
|
201
|
+
{ prefix: "browser_", component: ToolHttp },
|
|
202
|
+
{ prefix: "email_", component: ToolEmail },
|
|
203
|
+
{ prefix: "search_", component: ToolSearch }
|
|
204
|
+
];
|
|
205
|
+
function getToolLabel(name) {
|
|
206
|
+
return name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
207
|
+
}
|
|
208
|
+
function ToolCallChip({ tool }) {
|
|
209
|
+
const Exact = TOOL_COMPONENTS[tool.name];
|
|
210
|
+
if (Exact) return /* @__PURE__ */ jsx9(Exact, { tool });
|
|
211
|
+
for (const { prefix, component: Prefixed } of TOOL_PREFIX_COMPONENTS) {
|
|
212
|
+
if (tool.name.startsWith(prefix)) return /* @__PURE__ */ jsx9(Prefixed, { tool });
|
|
213
|
+
}
|
|
214
|
+
const summary = tool.arguments ? Object.values(tool.arguments).find((v) => typeof v === "string" && v.length > 0) : void 0;
|
|
215
|
+
return /* @__PURE__ */ jsx9(
|
|
216
|
+
ToolCallShell,
|
|
217
|
+
{
|
|
218
|
+
tool,
|
|
219
|
+
icon: Wrench,
|
|
220
|
+
label: getToolLabel(tool.name),
|
|
221
|
+
summary: summary?.slice(0, 80)
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export {
|
|
227
|
+
ToolCallShell,
|
|
228
|
+
ToolCallChip
|
|
229
|
+
};
|
|
230
|
+
//# sourceMappingURL=chunk-CCSIMOXD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/index.tsx","../src/tools/tool-call-shell.tsx","../src/tools/tool-read.tsx","../src/tools/tool-write.tsx","../src/tools/tool-bash.tsx","../src/tools/tool-search.tsx","../src/tools/tool-http.tsx","../src/tools/tool-email.tsx","../src/tools/tool-ask-user.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ToolCallEvent } from \"@polpo-ai/sdk\";\nimport { Wrench } from \"lucide-react\";\nimport { ToolCallShell } from \"./tool-call-shell\";\nimport { ToolRead } from \"./tool-read\";\nimport { ToolWrite } from \"./tool-write\";\nimport { ToolBash } from \"./tool-bash\";\nimport { ToolSearch } from \"./tool-search\";\nimport { ToolHttp } from \"./tool-http\";\nimport { ToolEmail } from \"./tool-email\";\nimport { ToolAskUser } from \"./tool-ask-user\";\n\n// ── Tool name → component map ──\n\nconst TOOL_COMPONENTS: Record<string, React.ComponentType<{ tool: ToolCallEvent }>> = {\n ask_user_question: ToolAskUser,\n read: ToolRead,\n read_attachment: ToolRead,\n write: ToolWrite,\n edit: ToolWrite,\n bash: ToolBash,\n grep: ToolSearch,\n glob: ToolSearch,\n search_web: ToolHttp,\n http_fetch: ToolHttp,\n http_download: ToolHttp,\n email_send: ToolEmail,\n};\n\n// Prefix-based matching for tools like browser_*, memory_*, etc.\nconst TOOL_PREFIX_COMPONENTS: { prefix: string; component: React.ComponentType<{ tool: ToolCallEvent }> }[] = [\n { prefix: \"browser_\", component: ToolHttp },\n { prefix: \"email_\", component: ToolEmail },\n { prefix: \"search_\", component: ToolSearch },\n];\n\nfunction getToolLabel(name: string) {\n return name.replace(/_/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n// ── Dispatcher ──\n\nexport function ToolCallChip({ tool }: { tool: ToolCallEvent }) {\n // Exact match\n const Exact = TOOL_COMPONENTS[tool.name];\n if (Exact) return <Exact tool={tool} />;\n\n // Prefix match\n for (const { prefix, component: Prefixed } of TOOL_PREFIX_COMPONENTS) {\n if (tool.name.startsWith(prefix)) return <Prefixed tool={tool} />;\n }\n\n // Generic fallback\n const summary = tool.arguments\n ? Object.values(tool.arguments).find((v) => typeof v === \"string\" && v.length > 0) as string | undefined\n : undefined;\n\n return (\n <ToolCallShell\n tool={tool}\n icon={Wrench}\n label={getToolLabel(tool.name)}\n summary={summary?.slice(0, 80)}\n />\n );\n}\n\n// Re-export shell for custom usage\nexport { ToolCallShell } from \"./tool-call-shell\";\n","\"use client\";\n\nimport { useState, type ReactNode } from \"react\";\nimport type { ToolCallEvent } from \"@polpo-ai/sdk\";\nimport { ChevronRight, Loader2, Check, AlertCircle, type LucideIcon } from \"lucide-react\";\n\n// ── Shell props ──\n\nexport interface ToolCallShellProps {\n tool: ToolCallEvent;\n icon: LucideIcon;\n label: string;\n /** One-line summary shown next to the label */\n summary?: string | null;\n /** Custom expanded content — replaces default raw result */\n children?: ReactNode;\n}\n\n// ── Shell ──\n\nexport function ToolCallShell({ tool, icon: Icon, label, summary, children }: ToolCallShellProps) {\n const [expanded, setExpanded] = useState(false);\n const isPending = tool.state === \"calling\" || tool.state === \"preparing\";\n const isError = tool.state === \"error\";\n const isDone = tool.state === \"completed\";\n const hasContent = isDone && (children || tool.result);\n\n return (\n <div className={`flex flex-col rounded-lg bg-p-warm border border-p-line text-[13px] text-p-ink-2 overflow-hidden ${isError ? \"border-destructive/20 bg-destructive/5\" : \"\"}`}>\n <button\n className={`flex items-center gap-2 px-3 py-2 border-none bg-transparent font-inherit text-inherit text-left w-full ${hasContent ? \"cursor-pointer\" : \"cursor-default\"}`}\n onClick={() => hasContent && setExpanded(!expanded)}\n >\n <Icon size={14} className=\"shrink-0\" />\n <span className=\"font-medium text-p-ink whitespace-nowrap\">{label}</span>\n {summary ? <span className=\"text-p-ink-3 text-xs overflow-hidden text-ellipsis whitespace-nowrap max-w-[300px]\">{summary}</span> : null}\n {isPending ? <Loader2 size={14} className=\"animate-spin text-p-accent shrink-0\" /> : null}\n {isDone ? <Check size={14} className=\"text-p-green shrink-0\" /> : null}\n {isError ? <AlertCircle size={14} className=\"text-destructive shrink-0\" /> : null}\n {hasContent ? (\n <ChevronRight size={12} className={`ml-auto text-p-ink-3 shrink-0 transition-transform duration-150 ${expanded ? \"rotate-90\" : \"\"}`} />\n ) : null}\n </button>\n {expanded ? (\n <div className=\"border-t border-p-line\">\n {children || (\n <pre className=\"m-0 px-2.5 py-2 text-[11px] leading-normal font-mono text-p-ink-2 bg-p-bg whitespace-pre-wrap break-all max-h-[180px] overflow-y-auto\">\n {tool.result}\n </pre>\n )}\n </div>\n ) : null}\n </div>\n );\n}\n","\"use client\";\n\nimport type { ToolCallEvent } from \"@polpo-ai/sdk\";\nimport { FileText } from \"lucide-react\";\nimport { ToolCallShell } from \"./tool-call-shell\";\n\n/** Read tool — shows file path and truncated content with line numbers */\nexport function ToolRead({ tool }: { tool: ToolCallEvent }) {\n const path = (tool.arguments?.path || tool.arguments?.file_path) as string | undefined;\n const lines = tool.result?.split(\"\\n\") || [];\n const maxLines = 12;\n const truncated = lines.length > maxLines;\n\n return (\n <ToolCallShell tool={tool} icon={FileText} label=\"Read\" summary={path}>\n {tool.result && (\n <div className=\"bg-p-bg max-h-[220px] overflow-y-auto\">\n <table className=\"w-full text-[11px] leading-relaxed font-mono border-collapse\">\n <tbody>\n {lines.slice(0, maxLines).map((line, i) => (\n <tr key={i} className=\"hover:bg-p-warm/50\">\n <td className=\"text-right text-p-ink-3 select-none px-2.5 py-0 w-[1%] whitespace-nowrap\">{i + 1}</td>\n <td className=\"text-p-ink-2 px-2.5 py-0 whitespace-pre-wrap break-all\">{line}</td>\n </tr>\n ))}\n </tbody>\n </table>\n {truncated && (\n <div className=\"px-2.5 py-1 text-[10px] text-p-ink-3 border-t border-p-line\">\n +{lines.length - maxLines} more lines\n </div>\n )}\n </div>\n )}\n </ToolCallShell>\n );\n}\n","\"use client\";\n\nimport type { ToolCallEvent } from \"@polpo-ai/sdk\";\nimport { Pen } from \"lucide-react\";\nimport { ToolCallShell } from \"./tool-call-shell\";\n\n/** Write/Edit tool — shows file path and a diff-like preview */\nexport function ToolWrite({ tool }: { tool: ToolCallEvent }) {\n const path = (tool.arguments?.path || tool.arguments?.file_path) as string | undefined;\n const content = (tool.arguments?.content || tool.arguments?.new_string) as string | undefined;\n const preview = content?.slice(0, 200);\n\n return (\n <ToolCallShell tool={tool} icon={Pen} label={tool.name === \"edit\" ? \"Edit\" : \"Write\"} summary={path}>\n {preview && (\n <div className=\"bg-p-bg max-h-[180px] overflow-y-auto\">\n <pre className=\"m-0 px-2.5 py-2 text-[11px] leading-normal font-mono text-p-green whitespace-pre-wrap break-all\">\n {preview}{content && content.length > 200 ? \"\\n…\" : \"\"}\n </pre>\n </div>\n )}\n </ToolCallShell>\n );\n}\n","\"use client\";\n\nimport type { ToolCallEvent } from \"@polpo-ai/sdk\";\nimport { Terminal } from \"lucide-react\";\nimport { ToolCallShell } from \"./tool-call-shell\";\n\n/** Bash tool — shows command and output */\nexport function ToolBash({ tool }: { tool: ToolCallEvent }) {\n const command = (tool.arguments?.command) as string | undefined;\n const lines = tool.result?.split(\"\\n\") || [];\n const maxLines = 10;\n const truncated = lines.length > maxLines;\n\n return (\n <ToolCallShell tool={tool} icon={Terminal} label=\"Bash\" summary={command?.slice(0, 60)}>\n <div className=\"bg-[#1a1a1a] max-h-[220px] overflow-y-auto\">\n {command && (\n <div className=\"px-3 py-1.5 text-[11px] font-mono text-emerald-400 border-b border-white/10\">\n <span className=\"text-p-ink-3 select-none\">$ </span>{command}\n </div>\n )}\n {tool.result && (\n <pre className=\"m-0 px-3 py-1.5 text-[11px] leading-normal font-mono text-neutral-300 whitespace-pre-wrap break-all\">\n {lines.slice(0, maxLines).join(\"\\n\")}\n {truncated ? `\\n… +${lines.length - maxLines} lines` : \"\"}\n </pre>\n )}\n </div>\n </ToolCallShell>\n );\n}\n","\"use client\";\n\nimport type { ToolCallEvent } from \"@polpo-ai/sdk\";\nimport { Search, FolderSearch } from \"lucide-react\";\nimport { ToolCallShell } from \"./tool-call-shell\";\n\n/** Grep/Glob/Search tool — shows query/pattern and matched results */\nexport function ToolSearch({ tool }: { tool: ToolCallEvent }) {\n const isGlob = tool.name === \"glob\";\n const pattern = (tool.arguments?.pattern || tool.arguments?.query || tool.arguments?.q) as string | undefined;\n const path = (tool.arguments?.path || tool.arguments?.root) as string | undefined;\n const summary = pattern ? `${pattern}${path ? ` in ${path}` : \"\"}` : path;\n\n const lines = tool.result?.split(\"\\n\").filter(Boolean) || [];\n const maxItems = 8;\n const truncated = lines.length > maxItems;\n\n return (\n <ToolCallShell tool={tool} icon={isGlob ? FolderSearch : Search} label={isGlob ? \"Glob\" : tool.name === \"grep\" ? \"Grep\" : \"Search\"} summary={summary}>\n {lines.length > 0 && (\n <div className=\"bg-p-bg max-h-[200px] overflow-y-auto\">\n <ul className=\"list-none m-0 p-0\">\n {lines.slice(0, maxItems).map((line, i) => (\n <li key={i} className=\"px-2.5 py-0.5 text-xs font-mono text-p-ink-2 border-b border-p-line/50 last:border-b-0 hover:bg-p-warm/50 truncate\">\n {line}\n </li>\n ))}\n </ul>\n {truncated && (\n <div className=\"px-2.5 py-1 text-[10px] text-p-ink-3 border-t border-p-line\">\n +{lines.length - maxItems} more results\n </div>\n )}\n </div>\n )}\n </ToolCallShell>\n );\n}\n","\"use client\";\n\nimport type { ToolCallEvent } from \"@polpo-ai/sdk\";\nimport { Globe } from \"lucide-react\";\nimport { ToolCallShell } from \"./tool-call-shell\";\n\n/** HTTP fetch/download/search_web tool — shows URL and response preview */\nexport function ToolHttp({ tool }: { tool: ToolCallEvent }) {\n const url = (tool.arguments?.url) as string | undefined;\n const method = (tool.arguments?.method as string)?.toUpperCase() || \"GET\";\n const summary = url ? `${method} ${url}` : null;\n\n return (\n <ToolCallShell tool={tool} icon={Globe} label={tool.name === \"search_web\" ? \"Search Web\" : \"HTTP\"} summary={summary}>\n {tool.result && (\n <pre className=\"m-0 px-2.5 py-2 text-[11px] leading-normal font-mono text-p-ink-2 bg-p-bg whitespace-pre-wrap break-all max-h-[180px] overflow-y-auto\">\n {tool.result.slice(0, 500)}{tool.result.length > 500 ? \"\\n…\" : \"\"}\n </pre>\n )}\n </ToolCallShell>\n );\n}\n","\"use client\";\n\nimport type { ToolCallEvent } from \"@polpo-ai/sdk\";\nimport { Mail } from \"lucide-react\";\nimport { ToolCallShell } from \"./tool-call-shell\";\n\n/** Email send tool — shows recipient, subject, and body preview */\nexport function ToolEmail({ tool }: { tool: ToolCallEvent }) {\n const to = (tool.arguments?.to || tool.arguments?.recipient) as string | undefined;\n const subject = (tool.arguments?.subject) as string | undefined;\n const body = (tool.arguments?.body || tool.arguments?.content) as string | undefined;\n const summary = to ? `→ ${to}` : null;\n\n return (\n <ToolCallShell tool={tool} icon={Mail} label=\"Email\" summary={summary}>\n <div className=\"bg-p-bg px-2.5 py-2 text-xs max-h-[180px] overflow-y-auto\">\n {subject && (\n <div className=\"mb-1\">\n <span className=\"text-p-ink-3\">Subject: </span>\n <span className=\"text-p-ink font-medium\">{subject}</span>\n </div>\n )}\n {to && (\n <div className=\"mb-1.5\">\n <span className=\"text-p-ink-3\">To: </span>\n <span className=\"text-p-ink-2\">{to}</span>\n </div>\n )}\n {body && (\n <p className=\"m-0 text-p-ink-2 leading-relaxed whitespace-pre-wrap\">\n {body.slice(0, 300)}{body.length > 300 ? \"…\" : \"\"}\n </p>\n )}\n </div>\n </ToolCallShell>\n );\n}\n","\"use client\";\n\nimport type { ToolCallEvent } from \"@polpo-ai/sdk\";\nimport { MessageSquareMore, Check } from \"lucide-react\";\nimport { ToolCallShell } from \"./tool-call-shell\";\n\ninterface AskUserQuestion {\n id: string;\n question: string;\n header?: string;\n options?: { label: string; description?: string }[];\n}\n\n/** Ask user question tool — shows the questions and answers after completion */\nexport function ToolAskUser({ tool }: { tool: ToolCallEvent }) {\n const questions = (tool.arguments?.questions || []) as AskUserQuestion[];\n const isAnswered = tool.state === \"completed\" || tool.state === \"interrupted\";\n\n // Try to parse the result as answers\n let answers: { questionId: string; selected: string[] }[] = [];\n if (tool.result) {\n try {\n const parsed = JSON.parse(tool.result);\n answers = parsed.answers || [];\n } catch {\n // result might be plain text — not structured\n }\n }\n\n return (\n <ToolCallShell\n tool={tool}\n icon={MessageSquareMore}\n label=\"Question\"\n summary={isAnswered ? `${questions.length} answered` : `${questions.length} question${questions.length > 1 ? \"s\" : \"\"}`}\n >\n <div className=\"bg-p-bg px-3 py-2 text-xs space-y-2\">\n {questions.map((q) => {\n const answer = answers.find((a) => a.questionId === q.id);\n return (\n <div key={q.id} className=\"flex items-start gap-2\">\n {isAnswered ? (\n <Check size={12} className=\"text-p-green shrink-0 mt-0.5\" />\n ) : (\n <MessageSquareMore size={12} className=\"text-p-accent shrink-0 mt-0.5\" />\n )}\n <div className=\"min-w-0\">\n <p className=\"text-p-ink font-medium\">{q.question}</p>\n {answer && answer.selected.length > 0 && (\n <p className=\"text-p-ink-3 mt-0.5\">{answer.selected.join(\", \")}</p>\n )}\n {!answer && isAnswered && (\n <p className=\"text-p-ink-3/50 italic mt-0.5\">Skipped</p>\n )}\n </div>\n </div>\n );\n })}\n </div>\n </ToolCallShell>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","Check","jsx","jsxs","Check","jsx"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// src/hooks/use-submit-handler.ts
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
function useSubmitHandler(sendMessage, uploadFile) {
|
|
4
|
+
return useCallback(async (message) => {
|
|
5
|
+
const text = message.text.trim();
|
|
6
|
+
const files = message.files || [];
|
|
7
|
+
if (!text && files.length === 0) return;
|
|
8
|
+
if (files.length > 0) {
|
|
9
|
+
const parts = [];
|
|
10
|
+
if (text) parts.push({ type: "text", text });
|
|
11
|
+
for (const f of files) {
|
|
12
|
+
const name = f.filename || "upload";
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(f.url);
|
|
15
|
+
const blob = await res.blob();
|
|
16
|
+
await uploadFile("workspace", blob, name);
|
|
17
|
+
parts.push({ type: "file", file_id: `workspace/${name}` });
|
|
18
|
+
} catch {
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (parts.length > 0) sendMessage(parts);
|
|
22
|
+
} else {
|
|
23
|
+
sendMessage(text);
|
|
24
|
+
}
|
|
25
|
+
}, [sendMessage, uploadFile]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/hooks/use-document-drag.ts
|
|
29
|
+
import { useState, useRef, useEffect } from "react";
|
|
30
|
+
function useDocumentDrag() {
|
|
31
|
+
const [dragging, setDragging] = useState(false);
|
|
32
|
+
const counterRef = useRef(0);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const onEnter = (e) => {
|
|
35
|
+
if (e.dataTransfer?.types?.includes("Files")) {
|
|
36
|
+
counterRef.current++;
|
|
37
|
+
setDragging(true);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const onLeave = () => {
|
|
41
|
+
counterRef.current--;
|
|
42
|
+
if (counterRef.current === 0) setDragging(false);
|
|
43
|
+
};
|
|
44
|
+
const onDrop = () => {
|
|
45
|
+
counterRef.current = 0;
|
|
46
|
+
setDragging(false);
|
|
47
|
+
};
|
|
48
|
+
document.addEventListener("dragenter", onEnter);
|
|
49
|
+
document.addEventListener("dragleave", onLeave);
|
|
50
|
+
document.addEventListener("drop", onDrop);
|
|
51
|
+
return () => {
|
|
52
|
+
document.removeEventListener("dragenter", onEnter);
|
|
53
|
+
document.removeEventListener("dragleave", onLeave);
|
|
54
|
+
document.removeEventListener("drop", onDrop);
|
|
55
|
+
};
|
|
56
|
+
}, []);
|
|
57
|
+
return dragging;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
useSubmitHandler,
|
|
62
|
+
useDocumentDrag
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=chunk-LTLIBITC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-submit-handler.ts","../src/hooks/use-document-drag.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback } from \"react\";\nimport type { ContentPart } from \"@polpo-ai/sdk\";\n\n/** Shape of the message emitted by the PromptInput component. */\nexport interface PromptInputMessage {\n text: string;\n files: { url: string; filename?: string }[];\n}\n\n/** Shared submit handler — uploads files via SDK then sends ContentPart[] */\nexport function useSubmitHandler(\n sendMessage: (content: string | ContentPart[]) => Promise<void>,\n uploadFile: (destPath: string, file: Blob, filename: string) => Promise<unknown>,\n) {\n return useCallback(async (message: PromptInputMessage) => {\n const text = message.text.trim();\n const files = message.files || [];\n if (!text && files.length === 0) return;\n\n if (files.length > 0) {\n const parts: ContentPart[] = [];\n if (text) parts.push({ type: \"text\", text });\n for (const f of files) {\n const name = f.filename || \"upload\";\n try {\n const res = await fetch(f.url);\n const blob = await res.blob();\n await uploadFile(\"workspace\", blob, name);\n parts.push({ type: \"file\", file_id: `workspace/${name}` });\n } catch { /* skip failed uploads */ }\n }\n if (parts.length > 0) sendMessage(parts);\n } else {\n sendMessage(text);\n }\n }, [sendMessage, uploadFile]);\n}\n","\"use client\";\n\nimport { useState, useRef, useEffect } from \"react\";\n\n/** Track document-level drag state for visual feedback */\nexport function useDocumentDrag() {\n const [dragging, setDragging] = useState(false);\n const counterRef = useRef(0);\n useEffect(() => {\n const onEnter = (e: DragEvent) => { if (e.dataTransfer?.types?.includes(\"Files\")) { counterRef.current++; setDragging(true); } };\n const onLeave = () => { counterRef.current--; if (counterRef.current === 0) setDragging(false); };\n const onDrop = () => { counterRef.current = 0; setDragging(false); };\n document.addEventListener(\"dragenter\", onEnter);\n document.addEventListener(\"dragleave\", onLeave);\n document.addEventListener(\"drop\", onDrop);\n return () => { document.removeEventListener(\"dragenter\", onEnter); document.removeEventListener(\"dragleave\", onLeave); document.removeEventListener(\"drop\", onDrop); };\n }, []);\n return dragging;\n}\n"],"mappings":";AAEA,SAAS,mBAAmB;AAUrB,SAAS,iBACd,aACA,YACA;AACA,SAAO,YAAY,OAAO,YAAgC;AACxD,UAAM,OAAO,QAAQ,KAAK,KAAK;AAC/B,UAAM,QAAQ,QAAQ,SAAS,CAAC;AAChC,QAAI,CAAC,QAAQ,MAAM,WAAW,EAAG;AAEjC,QAAI,MAAM,SAAS,GAAG;AACpB,YAAM,QAAuB,CAAC;AAC9B,UAAI,KAAM,OAAM,KAAK,EAAE,MAAM,QAAQ,KAAK,CAAC;AAC3C,iBAAW,KAAK,OAAO;AACrB,cAAM,OAAO,EAAE,YAAY;AAC3B,YAAI;AACF,gBAAM,MAAM,MAAM,MAAM,EAAE,GAAG;AAC7B,gBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,gBAAM,WAAW,aAAa,MAAM,IAAI;AACxC,gBAAM,KAAK,EAAE,MAAM,QAAQ,SAAS,aAAa,IAAI,GAAG,CAAC;AAAA,QAC3D,QAAQ;AAAA,QAA4B;AAAA,MACtC;AACA,UAAI,MAAM,SAAS,EAAG,aAAY,KAAK;AAAA,IACzC,OAAO;AACL,kBAAY,IAAI;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,aAAa,UAAU,CAAC;AAC9B;;;ACpCA,SAAS,UAAU,QAAQ,iBAAiB;AAGrC,SAAS,kBAAkB;AAChC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,aAAa,OAAO,CAAC;AAC3B,YAAU,MAAM;AACd,UAAM,UAAU,CAAC,MAAiB;AAAE,UAAI,EAAE,cAAc,OAAO,SAAS,OAAO,GAAG;AAAE,mBAAW;AAAW,oBAAY,IAAI;AAAA,MAAG;AAAA,IAAE;AAC/H,UAAM,UAAU,MAAM;AAAE,iBAAW;AAAW,UAAI,WAAW,YAAY,EAAG,aAAY,KAAK;AAAA,IAAG;AAChG,UAAM,SAAS,MAAM;AAAE,iBAAW,UAAU;AAAG,kBAAY,KAAK;AAAA,IAAG;AACnE,aAAS,iBAAiB,aAAa,OAAO;AAC9C,aAAS,iBAAiB,aAAa,OAAO;AAC9C,aAAS,iBAAiB,QAAQ,MAAM;AACxC,WAAO,MAAM;AAAE,eAAS,oBAAoB,aAAa,OAAO;AAAG,eAAS,oBAAoB,aAAa,OAAO;AAAG,eAAS,oBAAoB,QAAQ,MAAM;AAAA,IAAG;AAAA,EACvK,GAAG,CAAC,CAAC;AACL,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ContentPart } from '@polpo-ai/sdk';
|
|
2
|
+
|
|
3
|
+
/** Shape of the message emitted by the PromptInput component. */
|
|
4
|
+
interface PromptInputMessage {
|
|
5
|
+
text: string;
|
|
6
|
+
files: {
|
|
7
|
+
url: string;
|
|
8
|
+
filename?: string;
|
|
9
|
+
}[];
|
|
10
|
+
}
|
|
11
|
+
/** Shared submit handler — uploads files via SDK then sends ContentPart[] */
|
|
12
|
+
declare function useSubmitHandler(sendMessage: (content: string | ContentPart[]) => Promise<void>, uploadFile: (destPath: string, file: Blob, filename: string) => Promise<unknown>): (message: PromptInputMessage) => Promise<void>;
|
|
13
|
+
|
|
14
|
+
/** Track document-level drag state for visual feedback */
|
|
15
|
+
declare function useDocumentDrag(): boolean;
|
|
16
|
+
|
|
17
|
+
export { useDocumentDrag, useSubmitHandler };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|