@polpo-ai/dashboard 0.1.4 → 0.15.8

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.
Files changed (184) hide show
  1. package/LICENSE +3 -1
  2. package/README.md +13 -40
  3. package/dist/components.d.ts +53 -0
  4. package/dist/components.d.ts.map +1 -0
  5. package/dist/components.js +81 -0
  6. package/dist/components.js.map +1 -0
  7. package/dist/host.d.ts +24 -0
  8. package/dist/host.d.ts.map +1 -0
  9. package/dist/host.js +18 -0
  10. package/dist/host.js.map +1 -0
  11. package/dist/index.d.ts +13 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +11 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/test/setup.d.ts +2 -0
  16. package/dist/test/setup.d.ts.map +1 -0
  17. package/dist/test/setup.js +2 -0
  18. package/dist/test/setup.js.map +1 -0
  19. package/dist/views/agent-detail.d.ts +4 -0
  20. package/dist/views/agent-detail.d.ts.map +1 -0
  21. package/dist/views/agent-detail.js +31 -0
  22. package/dist/views/agent-detail.js.map +1 -0
  23. package/dist/views/agents.d.ts +2 -0
  24. package/dist/views/agents.d.ts.map +1 -0
  25. package/dist/views/agents.js +81 -0
  26. package/dist/views/agents.js.map +1 -0
  27. package/dist/views/files.d.ts +2 -0
  28. package/dist/views/files.d.ts.map +1 -0
  29. package/dist/views/files.js +30 -0
  30. package/dist/views/files.js.map +1 -0
  31. package/dist/views/memory.d.ts +2 -0
  32. package/dist/views/memory.d.ts.map +1 -0
  33. package/dist/views/memory.js +19 -0
  34. package/dist/views/memory.js.map +1 -0
  35. package/dist/views/playground.d.ts +4 -0
  36. package/dist/views/playground.d.ts.map +1 -0
  37. package/dist/views/playground.js +43 -0
  38. package/dist/views/playground.js.map +1 -0
  39. package/dist/views/session-detail.d.ts +4 -0
  40. package/dist/views/session-detail.d.ts.map +1 -0
  41. package/dist/views/session-detail.js +23 -0
  42. package/dist/views/session-detail.js.map +1 -0
  43. package/dist/views/sessions.d.ts +2 -0
  44. package/dist/views/sessions.d.ts.map +1 -0
  45. package/dist/views/sessions.js +17 -0
  46. package/dist/views/sessions.js.map +1 -0
  47. package/dist/views/skills.d.ts +2 -0
  48. package/dist/views/skills.d.ts.map +1 -0
  49. package/dist/views/skills.js +16 -0
  50. package/dist/views/skills.js.map +1 -0
  51. package/package.json +47 -83
  52. package/src/styles.css +169 -0
  53. package/app/(dashboard)/error.tsx +0 -47
  54. package/app/(dashboard)/layout.tsx +0 -41
  55. package/app/(dashboard)/loading.tsx +0 -30
  56. package/app/(dashboard)/projects/[id]/agents/[name]/memory/view.tsx +0 -36
  57. package/app/(dashboard)/projects/[id]/agents/[name]/models-view.tsx +0 -30
  58. package/app/(dashboard)/projects/[id]/agents/[name]/page.tsx +0 -51
  59. package/app/(dashboard)/projects/[id]/agents/[name]/prompt/view.tsx +0 -211
  60. package/app/(dashboard)/projects/[id]/agents/[name]/skills/view.tsx +0 -84
  61. package/app/(dashboard)/projects/[id]/agents/[name]/tools/view.tsx +0 -525
  62. package/app/(dashboard)/projects/[id]/agents/[name]/vault/view.tsx +0 -61
  63. package/app/(dashboard)/projects/[id]/agents/page.tsx +0 -39
  64. package/app/(dashboard)/projects/[id]/agents/view.tsx +0 -283
  65. package/app/(dashboard)/projects/[id]/layout.tsx +0 -11
  66. package/app/(dashboard)/projects/[id]/logs/page.tsx +0 -27
  67. package/app/(dashboard)/projects/[id]/logs/view.tsx +0 -184
  68. package/app/(dashboard)/projects/[id]/memory/page.tsx +0 -40
  69. package/app/(dashboard)/projects/[id]/memory/view.tsx +0 -17
  70. package/app/(dashboard)/projects/[id]/missions/[missionId]/page.tsx +0 -88
  71. package/app/(dashboard)/projects/[id]/missions/[missionId]/view.tsx +0 -569
  72. package/app/(dashboard)/projects/[id]/missions/page.tsx +0 -32
  73. package/app/(dashboard)/projects/[id]/missions/view.tsx +0 -282
  74. package/app/(dashboard)/projects/[id]/onboarding-checklist.tsx +0 -359
  75. package/app/(dashboard)/projects/[id]/page.tsx +0 -68
  76. package/app/(dashboard)/projects/[id]/playbooks/[name]/page.tsx +0 -5
  77. package/app/(dashboard)/projects/[id]/playbooks/[name]/view.tsx +0 -433
  78. package/app/(dashboard)/projects/[id]/playbooks/page.tsx +0 -5
  79. package/app/(dashboard)/projects/[id]/playbooks/view.tsx +0 -222
  80. package/app/(dashboard)/projects/[id]/schedules/page.tsx +0 -44
  81. package/app/(dashboard)/projects/[id]/schedules/view.tsx +0 -385
  82. package/app/(dashboard)/projects/[id]/sessions/[sessionId]/page.tsx +0 -55
  83. package/app/(dashboard)/projects/[id]/sessions/[sessionId]/raw-view.tsx +0 -373
  84. package/app/(dashboard)/projects/[id]/sessions/[sessionId]/view.tsx +0 -124
  85. package/app/(dashboard)/projects/[id]/sessions/page.tsx +0 -31
  86. package/app/(dashboard)/projects/[id]/sessions/view.tsx +0 -242
  87. package/app/(dashboard)/projects/[id]/settings/page.tsx +0 -52
  88. package/app/(dashboard)/projects/[id]/skills/[name]/page.tsx +0 -65
  89. package/app/(dashboard)/projects/[id]/skills/[name]/view.tsx +0 -227
  90. package/app/(dashboard)/projects/[id]/skills/page.tsx +0 -31
  91. package/app/(dashboard)/projects/[id]/skills/view.tsx +0 -243
  92. package/app/(dashboard)/projects/[id]/storage/page.tsx +0 -5
  93. package/app/(dashboard)/projects/[id]/storage/view.tsx +0 -385
  94. package/app/(dashboard)/projects/[id]/tasks/[taskId]/activity/view.tsx +0 -336
  95. package/app/(dashboard)/projects/[id]/tasks/[taskId]/assessment/view.tsx +0 -113
  96. package/app/(dashboard)/projects/[id]/tasks/[taskId]/blueprint.ts +0 -72
  97. package/app/(dashboard)/projects/[id]/tasks/[taskId]/output/view.tsx +0 -90
  98. package/app/(dashboard)/projects/[id]/tasks/[taskId]/page.tsx +0 -63
  99. package/app/(dashboard)/projects/[id]/tasks/[taskId]/view.tsx +0 -137
  100. package/app/(dashboard)/projects/[id]/tasks/page.tsx +0 -40
  101. package/app/(dashboard)/projects/[id]/tasks/view.tsx +0 -421
  102. package/app/(dashboard)/projects/[id]/view.tsx +0 -421
  103. package/app/(dashboard)/projects/[id]/welcome-banner.tsx +0 -156
  104. package/app/(dashboard)/projects/projects-list.tsx +0 -126
  105. package/app/(playground)/error.tsx +0 -42
  106. package/app/(playground)/layout.tsx +0 -27
  107. package/app/(playground)/projects/[id]/playground/page.tsx +0 -57
  108. package/app/(playground)/projects/[id]/playground/view.tsx +0 -167
  109. package/app/(playground)/projects/[id]/playground-legacy/page.tsx +0 -47
  110. package/app/(playground)/projects/[id]/playground-legacy/skeleton.tsx +0 -63
  111. package/app/(playground)/projects/[id]/playground-legacy/view.tsx +0 -237
  112. package/app/favicon.ico +0 -0
  113. package/app/globals.css +0 -385
  114. package/app/icon.svg +0 -6
  115. package/app/layout.tsx +0 -39
  116. package/app/page.tsx +0 -11
  117. package/components/ai-elements/code-block.tsx +0 -562
  118. package/components/dashboard/agent-capabilities.tsx +0 -326
  119. package/components/dashboard/agent-identity-header.tsx +0 -50
  120. package/components/dashboard/agent-model-picker.tsx +0 -195
  121. package/components/dashboard/agent-model-selector.tsx +0 -121
  122. package/components/dashboard/agent-picker-card.tsx +0 -46
  123. package/components/dashboard/agent-studio.tsx +0 -242
  124. package/components/dashboard/agents-table.tsx +0 -48
  125. package/components/dashboard/breadcrumb.tsx +0 -70
  126. package/components/dashboard/builder-chat.tsx +0 -418
  127. package/components/dashboard/chat-shell.tsx +0 -40
  128. package/components/dashboard/client-picker.tsx +0 -63
  129. package/components/dashboard/command-snippet.tsx +0 -77
  130. package/components/dashboard/connect-dialog.tsx +0 -680
  131. package/components/dashboard/copy-button.tsx +0 -45
  132. package/components/dashboard/copy-card.tsx +0 -48
  133. package/components/dashboard/file-browser.tsx +0 -161
  134. package/components/dashboard/hint.tsx +0 -22
  135. package/components/dashboard/inference-mode.tsx +0 -15
  136. package/components/dashboard/manual-refresh-button.tsx +0 -52
  137. package/components/dashboard/markdown.tsx +0 -31
  138. package/components/dashboard/mcp-install-panel.tsx +0 -196
  139. package/components/dashboard/mission-graph.tsx +0 -262
  140. package/components/dashboard/nav-tabs.tsx +0 -86
  141. package/components/dashboard/polpo-chat.tsx +0 -468
  142. package/components/dashboard/project-copilot.tsx +0 -253
  143. package/components/dashboard/sdk-snippet-panel.tsx +0 -194
  144. package/components/dashboard/section-header.tsx +0 -37
  145. package/components/dashboard/settings-form.tsx +0 -2477
  146. package/components/dashboard/sidebar.tsx +0 -385
  147. package/components/dashboard/skeletons.tsx +0 -1054
  148. package/components/dashboard/skills-install-wizard.tsx +0 -192
  149. package/components/dashboard/swarm-runs.tsx +0 -316
  150. package/components/dashboard/tab-toggle.tsx +0 -35
  151. package/components/dashboard/task-studio.tsx +0 -224
  152. package/components/dashboard/top-header.tsx +0 -203
  153. package/components/dashboard/webhook-deliveries.tsx +0 -234
  154. package/components/icons/coding-agents.tsx +0 -151
  155. package/components/json-ld.tsx +0 -8
  156. package/components/providers.tsx +0 -25
  157. package/components/ui/badge.tsx +0 -52
  158. package/components/ui/button.tsx +0 -60
  159. package/components/ui/card.tsx +0 -103
  160. package/components/ui/chart.tsx +0 -356
  161. package/components/ui/command.tsx +0 -196
  162. package/components/ui/dialog.tsx +0 -157
  163. package/components/ui/input-group.tsx +0 -158
  164. package/components/ui/input.tsx +0 -20
  165. package/components/ui/multi-select.tsx +0 -161
  166. package/components/ui/popover.tsx +0 -90
  167. package/components/ui/select.tsx +0 -201
  168. package/components/ui/separator.tsx +0 -25
  169. package/components/ui/sheet.tsx +0 -135
  170. package/components/ui/skeleton.tsx +0 -13
  171. package/components/ui/tabs.tsx +0 -56
  172. package/components/ui/textarea.tsx +0 -18
  173. package/components/ui/tooltip.tsx +0 -66
  174. package/hooks/use-desktop-sidebar.tsx +0 -50
  175. package/hooks/use-mobile-sidebar.tsx +0 -37
  176. package/index.ts +0 -17
  177. package/lib/api.ts +0 -194
  178. package/lib/builder-context.ts +0 -60
  179. package/lib/data-client.ts +0 -68
  180. package/lib/get-query-client.ts +0 -25
  181. package/lib/polpo-client.tsx +0 -49
  182. package/lib/tool-catalog.ts +0 -234
  183. package/lib/use-event-catalog.ts +0 -28
  184. package/lib/utils.ts +0 -6
@@ -1,468 +0,0 @@
1
- "use client";
2
-
3
- /**
4
- * Shared Polpo chat surface — the single source of truth for the chat
5
- * composition used by BOTH the Playground and the Agent Builder so they
6
- * are guaranteed identical.
7
- *
8
- * Composition (mirrors lumea-agents components/screens/chat.tsx):
9
- * PolpoProvider (session-cookie fetch, no API key in the browser)
10
- * → FilesPanel (workspace files, slides in from the right)
11
- * → ChatSurface = usePolpoChatAdapter + ChatProvider
12
- * + ToolCallVariantProvider variant="task"
13
- * + <Chat> with a custom <InputBar> (Skills menu, slash trigger,
14
- * attachments, ask-user handoff)
15
- *
16
- * `<PolpoChat>` is the high-level wrapper: pass `baseUrl` + the selected
17
- * `agent`, it owns the session lifecycle (?session= persistence, reset on
18
- * agent switch) and the cookie fetch. Files panel open state is optional
19
- * (controlled) so a parent toolbar can toggle it.
20
- */
21
-
22
- import {
23
- useCallback,
24
- useEffect,
25
- useMemo,
26
- useRef,
27
- useState,
28
- type FormEvent,
29
- type KeyboardEvent,
30
- } from "react";
31
- import { useRouter, useSearchParams } from "next/navigation";
32
- import { PolpoProvider } from "@polpo-ai/react";
33
- import {
34
- usePolpoChatAdapter,
35
- PolpoChatCapabilitiesMenu,
36
- usePolpoChatSlashItems,
37
- } from "@lumea-labs/chat-polpo";
38
- import {
39
- ChatAskUser,
40
- ChatTriggerMenu,
41
- useChatContext,
42
- useChatTriggerMenu,
43
- useSubmitHandler,
44
- type AskUserQuestion,
45
- type PromptInputMessage,
46
- } from "@lumea-labs/chat";
47
- import { ChatShell } from "../../components/dashboard/chat-shell";
48
- import {
49
- FileManagerProvider,
50
- FileList,
51
- FileToolbar,
52
- FilePreviewDialog,
53
- FilePreviewRouter,
54
- defaultFmLabels,
55
- } from "@lumea-labs/file-manager";
56
- import {
57
- PolpoFilePreview,
58
- usePolpoFileAdapter,
59
- } from "@lumea-labs/file-manager-polpo";
60
- import { pdfHandler } from "@lumea-labs/file-manager-polpo/handlers/pdf-handler";
61
- import { imageHandler } from "@lumea-labs/file-manager-polpo/handlers/image-handler";
62
- import { audioHandler } from "@lumea-labs/file-manager-polpo/handlers/audio-handler";
63
- import { videoHandler } from "@lumea-labs/file-manager-polpo/handlers/video-handler";
64
- import { NextIntlClientProvider } from "next-intl";
65
- import { ArrowUp, Paperclip, Square, X } from "lucide-react";
66
- import { Sheet, SheetContent, SheetTitle } from "../../components/ui/sheet";
67
-
68
- /* ------------------------------------------------------------------ */
69
- /* PolpoChat — high-level wrapper (providers + session lifecycle) */
70
- /* ------------------------------------------------------------------ */
71
-
72
- export function PolpoChat({
73
- baseUrl,
74
- agent,
75
- filesOpen,
76
- onFilesOpenChange,
77
- }: {
78
- baseUrl: string;
79
- agent: string | undefined;
80
- /** Controlled files-panel open state. Optional — defaults to internal. */
81
- filesOpen?: boolean;
82
- onFilesOpenChange?: (open: boolean) => void;
83
- }) {
84
- const router = useRouter();
85
- const searchParams = useSearchParams();
86
-
87
- // Session lifecycle — capture ?session= at mount, never re-pass as
88
- // prop, commit new ids to the URL on stream finish so we don't trigger
89
- // a mid-stream reload. Reset on agent switch.
90
- const initialSessionIdRef = useRef<string | undefined>(
91
- searchParams.get("session") ?? undefined,
92
- );
93
- const pendingSessionIdRef = useRef<string | null>(null);
94
-
95
- const commitSession = useRef<(id: string) => void>(() => {});
96
- commitSession.current = (id: string) => {
97
- const current = searchParams.get("session");
98
- if (current === id) return;
99
- const params = new URLSearchParams(Array.from(searchParams.entries()));
100
- params.set("session", id);
101
- router.replace(`?${params.toString()}`, { scroll: false });
102
- };
103
-
104
- const handleSessionCreated = useCallback((id: string) => {
105
- pendingSessionIdRef.current = id;
106
- }, []);
107
-
108
- const handleFinish = useCallback(() => {
109
- const id = pendingSessionIdRef.current;
110
- if (id) {
111
- pendingSessionIdRef.current = null;
112
- commitSession.current(id);
113
- }
114
- }, []);
115
-
116
- // On agent switch (not initial mount) clear the session so a fresh one
117
- // starts. The `key={agent}` on ChatSurface remounts the tier-2 wiring.
118
- const firstRender = useRef(true);
119
- useEffect(() => {
120
- if (firstRender.current) {
121
- firstRender.current = false;
122
- return;
123
- }
124
- initialSessionIdRef.current = undefined;
125
- pendingSessionIdRef.current = null;
126
- if (searchParams.get("session")) {
127
- const params = new URLSearchParams(Array.from(searchParams.entries()));
128
- params.delete("session");
129
- const qs = params.toString();
130
- router.replace(qs ? `?${qs}` : "?", { scroll: false });
131
- }
132
- // eslint-disable-next-line react-hooks/exhaustive-deps
133
- }, [agent]);
134
-
135
- const cookieFetch = useMemo<typeof globalThis.fetch>(
136
- () =>
137
- ((input, init) =>
138
- globalThis.fetch(input, {
139
- ...init,
140
- credentials: "include",
141
- })) as typeof globalThis.fetch,
142
- [],
143
- );
144
-
145
- const [internalFiles, setInternalFiles] = useState(false);
146
- const isOpen = filesOpen ?? internalFiles;
147
- const setOpen = onFilesOpenChange ?? setInternalFiles;
148
-
149
- return (
150
- <NextIntlClientProvider
151
- locale="en"
152
- messages={{}}
153
- onError={() => {}}
154
- getMessageFallback={({ key }) => key}
155
- >
156
- <PolpoProvider
157
- baseUrl={baseUrl}
158
- apiPrefix="/v1"
159
- fetch={cookieFetch}
160
- autoConnect={false}
161
- >
162
- <FilesPanel open={isOpen} onOpenChange={setOpen} />
163
- <ChatSurface
164
- key={agent}
165
- sessionId={initialSessionIdRef.current}
166
- agent={agent}
167
- onSessionCreated={handleSessionCreated}
168
- onFinish={handleFinish}
169
- />
170
- </PolpoProvider>
171
- </NextIntlClientProvider>
172
- );
173
- }
174
-
175
- /* ------------------------------------------------------------------ */
176
- /* FilesPanel — workspace files (slides in from the right) */
177
- /* ------------------------------------------------------------------ */
178
-
179
- export function FilesPanel({
180
- open,
181
- onOpenChange,
182
- }: {
183
- open: boolean;
184
- onOpenChange: (v: boolean) => void;
185
- }) {
186
- const adapter = usePolpoFileAdapter();
187
-
188
- return (
189
- <FileManagerProvider adapter={adapter} layout="compact" labels={defaultFmLabels}>
190
- <Sheet open={open} onOpenChange={onOpenChange}>
191
- <SheetContent
192
- side="right"
193
- className="w-full sm:!w-[560px] sm:!max-w-[560px] lg:!w-[640px] lg:!max-w-[640px] p-0 gap-0 flex flex-col bg-card"
194
- >
195
- <div className="shrink-0 border-b border-border px-4 py-3">
196
- <SheetTitle className="text-sm font-semibold tracking-tight text-foreground">
197
- Files
198
- </SheetTitle>
199
- <p className="mt-0.5 text-[11px] text-muted-foreground">
200
- Project workspace — files written by agents and uploads
201
- </p>
202
- </div>
203
- <div className="shrink-0 border-b border-border px-2 py-1.5">
204
- <FileToolbar />
205
- </div>
206
- <div className="flex-1 min-h-0 overflow-auto">
207
- <FileList />
208
- </div>
209
- </SheetContent>
210
- </Sheet>
211
-
212
- <FilePreviewDialog>
213
- <FilePreviewRouter
214
- handlers={[pdfHandler, imageHandler, audioHandler, videoHandler]}
215
- >
216
- <PolpoFilePreview />
217
- </FilePreviewRouter>
218
- </FilePreviewDialog>
219
- </FileManagerProvider>
220
- );
221
- }
222
-
223
- /* ------------------------------------------------------------------ */
224
- /* ChatSurface — usePolpoChatAdapter + ChatProvider + Chat */
225
- /* ------------------------------------------------------------------ */
226
-
227
- function ChatSurface({
228
- sessionId,
229
- agent,
230
- onSessionCreated,
231
- onFinish,
232
- }: {
233
- sessionId: string | undefined;
234
- agent: string | undefined;
235
- onSessionCreated: (id: string) => void;
236
- onFinish: () => void;
237
- }) {
238
- const adapter = usePolpoChatAdapter({
239
- sessionId,
240
- agent,
241
- onSessionCreated,
242
- onFinish,
243
- });
244
- return (
245
- <ChatShell adapter={adapter}>
246
- <InputBar agent={agent} />
247
- </ChatShell>
248
- );
249
- }
250
-
251
- /* ------------------------------------------------------------------ */
252
- /* InputBar — custom composer (skills, slash, attachments, ask-user) */
253
- /* ------------------------------------------------------------------ */
254
-
255
- function InputBar({ agent }: { agent: string | undefined }) {
256
- const {
257
- sendMessage,
258
- isStreaming,
259
- abort,
260
- uploadFile,
261
- isUploading,
262
- pendingToolCall,
263
- } = useChatContext();
264
-
265
- const [text, setText] = useState("");
266
- const [pendingFiles, setPendingFiles] = useState<File[]>([]);
267
- const [sendError, setSendError] = useState<string | null>(null);
268
- const fileInputRef = useRef<HTMLInputElement>(null);
269
-
270
- const askUserQuestions =
271
- pendingToolCall?.toolName === "ask_user_question"
272
- ? ((pendingToolCall.arguments?.questions ?? []) as AskUserQuestion[])
273
- : null;
274
-
275
- const submit = useSubmitHandler(sendMessage, uploadFile);
276
-
277
- // Scope the "/" skills menu to the selected agent's own skills.
278
- const slash = usePolpoChatSlashItems({ agent, skillsScope: "assigned" });
279
- const slashMenu = useChatTriggerMenu({
280
- triggers: slash.triggers,
281
- onSelect: (item) => {
282
- void slash.onSelect(item);
283
- },
284
- });
285
-
286
- async function handleSend() {
287
- const trimmed = text.trim();
288
- if (!trimmed && pendingFiles.length === 0) return;
289
-
290
- const message: PromptInputMessage = {
291
- text: trimmed,
292
- files: pendingFiles.map((f) => ({
293
- url: URL.createObjectURL(f),
294
- filename: f.name,
295
- })),
296
- };
297
-
298
- setText("");
299
- setPendingFiles([]);
300
- setSendError(null);
301
-
302
- try {
303
- await submit(message);
304
- } catch (err) {
305
- const msg = err instanceof Error ? err.message : String(err);
306
- setSendError(msg || "Send failed");
307
- // eslint-disable-next-line no-console
308
- console.error("[polpo-chat] send failed:", err);
309
- } finally {
310
- message.files.forEach((f) => URL.revokeObjectURL(f.url));
311
- }
312
- }
313
-
314
- function handleSubmit(e: FormEvent) {
315
- e.preventDefault();
316
- if (isStreaming) {
317
- abort();
318
- } else {
319
- void handleSend();
320
- }
321
- }
322
-
323
- function handleKeyDown(e: KeyboardEvent<HTMLTextAreaElement>) {
324
- if (e.key === "Enter" && !e.shiftKey) {
325
- e.preventDefault();
326
- handleSubmit(e as unknown as FormEvent);
327
- }
328
- }
329
-
330
- function handleFiles(files: FileList | null) {
331
- if (!files) return;
332
- setPendingFiles((prev) => [...prev, ...Array.from(files)]);
333
- }
334
-
335
- if (askUserQuestions && askUserQuestions.length > 0) {
336
- return (
337
- <div className="shrink-0">
338
- <div className="mx-auto max-w-3xl px-4 py-3">
339
- <ChatAskUser
340
- variant="command"
341
- questions={askUserQuestions}
342
- onSubmit={(answers) => {
343
- void sendMessage(JSON.stringify({ answers }));
344
- }}
345
- />
346
- </div>
347
- </div>
348
- );
349
- }
350
-
351
- return (
352
- <form onSubmit={handleSubmit} className="shrink-0">
353
- <div className="mx-auto max-w-3xl px-4 py-3">
354
- {sendError && (
355
- <div className="mb-2 flex items-start justify-between gap-2 border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs text-destructive">
356
- <span className="flex-1">
357
- <span className="font-semibold">Send failed.</span>{" "}
358
- <span className="opacity-80">{sendError}</span>
359
- </span>
360
- <button
361
- type="button"
362
- onClick={() => setSendError(null)}
363
- aria-label="Dismiss"
364
- className="shrink-0 text-destructive/70 hover:text-destructive"
365
- >
366
- <X className="h-3.5 w-3.5" />
367
- </button>
368
- </div>
369
- )}
370
-
371
- {pendingFiles.length > 0 && (
372
- <div className="mb-2 flex flex-wrap gap-2">
373
- {pendingFiles.map((f, i) => (
374
- <span
375
- key={`${f.name}-${i}`}
376
- className="inline-flex items-center gap-1.5 rounded border border-border bg-card px-2 py-1 text-xs font-mono"
377
- >
378
- {f.name}
379
- <button
380
- type="button"
381
- onClick={() =>
382
- setPendingFiles((prev) => prev.filter((_, j) => j !== i))
383
- }
384
- aria-label={`Remove ${f.name}`}
385
- className="text-muted-foreground hover:text-foreground"
386
- >
387
- <X className="h-3 w-3" />
388
- </button>
389
- </span>
390
- ))}
391
- </div>
392
- )}
393
-
394
- <div className="relative rounded border border-border bg-card focus-within:border-foreground/40 transition-colors">
395
- <textarea
396
- ref={slashMenu.inputRef as React.Ref<HTMLTextAreaElement>}
397
- value={text}
398
- onChange={(e) => {
399
- setText(e.target.value);
400
- slashMenu.onChange(e);
401
- }}
402
- onKeyDown={(e) => {
403
- slashMenu.onKeyDown(e);
404
- if (!e.defaultPrevented) handleKeyDown(e);
405
- }}
406
- placeholder="Send a message…"
407
- rows={2}
408
- className="block w-full resize-none bg-transparent px-3 py-2.5 text-sm placeholder:text-muted-foreground/50 focus:outline-none"
409
- />
410
-
411
- <ChatTriggerMenu {...slashMenu.menuProps} />
412
-
413
- <div className="flex items-center gap-1 border-t border-border px-2 py-1.5">
414
- <input
415
- ref={fileInputRef}
416
- type="file"
417
- multiple
418
- hidden
419
- onChange={(e) => {
420
- handleFiles(e.target.files);
421
- e.target.value = "";
422
- }}
423
- />
424
- <button
425
- type="button"
426
- onClick={() => fileInputRef.current?.click()}
427
- disabled={isUploading}
428
- className="inline-flex h-7 w-7 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-foreground/5 hover:text-foreground disabled:opacity-50"
429
- aria-label="Attach file"
430
- >
431
- <Paperclip className="h-4 w-4" />
432
- </button>
433
-
434
- <PolpoChatCapabilitiesMenu
435
- agent={agent}
436
- label="Skills"
437
- variant="icon"
438
- searchable
439
- />
440
-
441
- <span
442
- aria-hidden
443
- className="ml-1 hidden items-center gap-1 font-mono text-[10.5px] uppercase tracking-[0.14em] text-muted-foreground sm:inline-flex"
444
- >
445
- <kbd className="rounded border border-border bg-card px-1.5 py-px font-mono text-[10px] text-muted-foreground">
446
- /
447
- </kbd>
448
- skills
449
- </span>
450
-
451
- <button
452
- type="submit"
453
- disabled={!isStreaming && !text.trim() && pendingFiles.length === 0}
454
- className="ml-auto inline-flex h-7 w-7 items-center justify-center rounded bg-foreground text-background transition-opacity hover:opacity-90 disabled:opacity-40"
455
- aria-label={isStreaming ? "Stop" : "Send"}
456
- >
457
- {isStreaming ? (
458
- <Square className="h-3.5 w-3.5" />
459
- ) : (
460
- <ArrowUp className="h-3.5 w-3.5" />
461
- )}
462
- </button>
463
- </div>
464
- </div>
465
- </div>
466
- </form>
467
- );
468
- }