@nikitadmitrieff/feedback-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.
@@ -0,0 +1,2294 @@
1
+ "use client";
2
+
3
+ // src/client/feedback-panel.tsx
4
+ import { useState as useState8, useEffect as useEffect4, useRef as useRef4, useCallback as useCallback4 } from "react";
5
+ import { DefaultChatTransport } from "ai";
6
+ import { AssistantRuntimeProvider, useThreadRuntime as useThreadRuntime3 } from "@assistant-ui/react";
7
+ import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
8
+ import { ArrowUp, PanelRight, X as X3, AlertCircle, ArrowRight, Loader2 as Loader22, Lightbulb } from "lucide-react";
9
+
10
+ // src/client/attachment.tsx
11
+ import { useEffect, useState } from "react";
12
+ import { XIcon as XIcon2, PlusIcon, FileText } from "lucide-react";
13
+ import {
14
+ AttachmentPrimitive,
15
+ ComposerPrimitive,
16
+ MessagePrimitive,
17
+ useAuiState,
18
+ useAui
19
+ } from "@assistant-ui/react";
20
+
21
+ // ../../node_modules/zustand/esm/vanilla/shallow.mjs
22
+ var isIterable = (obj) => Symbol.iterator in obj;
23
+ var hasIterableEntries = (value) => (
24
+ // HACK: avoid checking entries type
25
+ "entries" in value
26
+ );
27
+ var compareEntries = (valueA, valueB) => {
28
+ const mapA = valueA instanceof Map ? valueA : new Map(valueA.entries());
29
+ const mapB = valueB instanceof Map ? valueB : new Map(valueB.entries());
30
+ if (mapA.size !== mapB.size) {
31
+ return false;
32
+ }
33
+ for (const [key, value] of mapA) {
34
+ if (!mapB.has(key) || !Object.is(value, mapB.get(key))) {
35
+ return false;
36
+ }
37
+ }
38
+ return true;
39
+ };
40
+ var compareIterables = (valueA, valueB) => {
41
+ const iteratorA = valueA[Symbol.iterator]();
42
+ const iteratorB = valueB[Symbol.iterator]();
43
+ let nextA = iteratorA.next();
44
+ let nextB = iteratorB.next();
45
+ while (!nextA.done && !nextB.done) {
46
+ if (!Object.is(nextA.value, nextB.value)) {
47
+ return false;
48
+ }
49
+ nextA = iteratorA.next();
50
+ nextB = iteratorB.next();
51
+ }
52
+ return !!nextA.done && !!nextB.done;
53
+ };
54
+ function shallow(valueA, valueB) {
55
+ if (Object.is(valueA, valueB)) {
56
+ return true;
57
+ }
58
+ if (typeof valueA !== "object" || valueA === null || typeof valueB !== "object" || valueB === null) {
59
+ return false;
60
+ }
61
+ if (Object.getPrototypeOf(valueA) !== Object.getPrototypeOf(valueB)) {
62
+ return false;
63
+ }
64
+ if (isIterable(valueA) && isIterable(valueB)) {
65
+ if (hasIterableEntries(valueA) && hasIterableEntries(valueB)) {
66
+ return compareEntries(valueA, valueB);
67
+ }
68
+ return compareIterables(valueA, valueB);
69
+ }
70
+ return compareEntries(
71
+ { entries: () => Object.entries(valueA) },
72
+ { entries: () => Object.entries(valueB) }
73
+ );
74
+ }
75
+
76
+ // ../../node_modules/zustand/esm/react/shallow.mjs
77
+ import React from "react";
78
+ function useShallow(selector) {
79
+ const prev = React.useRef(void 0);
80
+ return (state) => {
81
+ const next = selector(state);
82
+ return shallow(prev.current, next) ? prev.current : prev.current = next;
83
+ };
84
+ }
85
+
86
+ // src/client/ui/tooltip.tsx
87
+ import { Tooltip as TooltipPrimitive } from "radix-ui";
88
+
89
+ // src/client/ui/utils.ts
90
+ import { clsx } from "clsx";
91
+ import { twMerge } from "tailwind-merge";
92
+ function cn(...inputs) {
93
+ return twMerge(clsx(inputs));
94
+ }
95
+
96
+ // src/client/ui/tooltip.tsx
97
+ import { jsx, jsxs } from "react/jsx-runtime";
98
+ function Tooltip({
99
+ ...props
100
+ }) {
101
+ return /* @__PURE__ */ jsx(TooltipPrimitive.Root, { "data-slot": "tooltip", ...props });
102
+ }
103
+ function TooltipTrigger({
104
+ ...props
105
+ }) {
106
+ return /* @__PURE__ */ jsx(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
107
+ }
108
+ function TooltipContent({
109
+ className,
110
+ sideOffset = 0,
111
+ children,
112
+ ...props
113
+ }) {
114
+ return /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
115
+ TooltipPrimitive.Content,
116
+ {
117
+ "data-slot": "tooltip-content",
118
+ sideOffset,
119
+ className: cn(
120
+ "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
121
+ className
122
+ ),
123
+ ...props,
124
+ children: [
125
+ children,
126
+ /* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { className: "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })
127
+ ]
128
+ }
129
+ ) });
130
+ }
131
+
132
+ // src/client/ui/dialog.tsx
133
+ import { XIcon } from "lucide-react";
134
+ import { Dialog as DialogPrimitive } from "radix-ui";
135
+
136
+ // src/client/ui/button.tsx
137
+ import { cva } from "class-variance-authority";
138
+ import { Slot } from "radix-ui";
139
+ import { jsx as jsx2 } from "react/jsx-runtime";
140
+ var buttonVariants = cva(
141
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
142
+ {
143
+ variants: {
144
+ variant: {
145
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
146
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
147
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
148
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
149
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
150
+ link: "text-primary underline-offset-4 hover:underline"
151
+ },
152
+ size: {
153
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
154
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
155
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
156
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
157
+ icon: "size-9",
158
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
159
+ "icon-sm": "size-8",
160
+ "icon-lg": "size-10"
161
+ }
162
+ },
163
+ defaultVariants: {
164
+ variant: "default",
165
+ size: "default"
166
+ }
167
+ }
168
+ );
169
+ function Button({
170
+ className,
171
+ variant = "default",
172
+ size = "default",
173
+ asChild = false,
174
+ ...props
175
+ }) {
176
+ const Comp = asChild ? Slot.Root : "button";
177
+ return /* @__PURE__ */ jsx2(
178
+ Comp,
179
+ {
180
+ "data-slot": "button",
181
+ "data-variant": variant,
182
+ "data-size": size,
183
+ className: cn(buttonVariants({ variant, size, className })),
184
+ ...props
185
+ }
186
+ );
187
+ }
188
+
189
+ // src/client/ui/dialog.tsx
190
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
191
+ function Dialog({
192
+ ...props
193
+ }) {
194
+ return /* @__PURE__ */ jsx3(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
195
+ }
196
+ function DialogTrigger({
197
+ ...props
198
+ }) {
199
+ return /* @__PURE__ */ jsx3(DialogPrimitive.Trigger, { "data-slot": "dialog-trigger", ...props });
200
+ }
201
+ function DialogPortal({
202
+ ...props
203
+ }) {
204
+ return /* @__PURE__ */ jsx3(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
205
+ }
206
+ function DialogOverlay({
207
+ className,
208
+ ...props
209
+ }) {
210
+ return /* @__PURE__ */ jsx3(
211
+ DialogPrimitive.Overlay,
212
+ {
213
+ "data-slot": "dialog-overlay",
214
+ className: cn(
215
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
216
+ className
217
+ ),
218
+ ...props
219
+ }
220
+ );
221
+ }
222
+ function DialogContent({
223
+ className,
224
+ children,
225
+ showCloseButton = true,
226
+ ...props
227
+ }) {
228
+ return /* @__PURE__ */ jsxs2(DialogPortal, { "data-slot": "dialog-portal", children: [
229
+ /* @__PURE__ */ jsx3(DialogOverlay, {}),
230
+ /* @__PURE__ */ jsxs2(
231
+ DialogPrimitive.Content,
232
+ {
233
+ "data-slot": "dialog-content",
234
+ className: cn(
235
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
236
+ className
237
+ ),
238
+ ...props,
239
+ children: [
240
+ children,
241
+ showCloseButton && /* @__PURE__ */ jsxs2(
242
+ DialogPrimitive.Close,
243
+ {
244
+ "data-slot": "dialog-close",
245
+ className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
246
+ children: [
247
+ /* @__PURE__ */ jsx3(XIcon, {}),
248
+ /* @__PURE__ */ jsx3("span", { className: "sr-only", children: "Close" })
249
+ ]
250
+ }
251
+ )
252
+ ]
253
+ }
254
+ )
255
+ ] });
256
+ }
257
+ function DialogTitle({
258
+ className,
259
+ ...props
260
+ }) {
261
+ return /* @__PURE__ */ jsx3(
262
+ DialogPrimitive.Title,
263
+ {
264
+ "data-slot": "dialog-title",
265
+ className: cn("text-lg leading-none font-semibold", className),
266
+ ...props
267
+ }
268
+ );
269
+ }
270
+
271
+ // src/client/ui/avatar.tsx
272
+ import { Avatar as AvatarPrimitive } from "radix-ui";
273
+ import { jsx as jsx4 } from "react/jsx-runtime";
274
+ function Avatar({
275
+ className,
276
+ size = "default",
277
+ ...props
278
+ }) {
279
+ return /* @__PURE__ */ jsx4(
280
+ AvatarPrimitive.Root,
281
+ {
282
+ "data-slot": "avatar",
283
+ "data-size": size,
284
+ className: cn(
285
+ "group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
286
+ className
287
+ ),
288
+ ...props
289
+ }
290
+ );
291
+ }
292
+ function AvatarImage({
293
+ className,
294
+ ...props
295
+ }) {
296
+ return /* @__PURE__ */ jsx4(
297
+ AvatarPrimitive.Image,
298
+ {
299
+ "data-slot": "avatar-image",
300
+ className: cn("aspect-square size-full", className),
301
+ ...props
302
+ }
303
+ );
304
+ }
305
+ function AvatarFallback({
306
+ className,
307
+ ...props
308
+ }) {
309
+ return /* @__PURE__ */ jsx4(
310
+ AvatarPrimitive.Fallback,
311
+ {
312
+ "data-slot": "avatar-fallback",
313
+ className: cn(
314
+ "bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs",
315
+ className
316
+ ),
317
+ ...props
318
+ }
319
+ );
320
+ }
321
+
322
+ // src/client/tooltip-icon-button.tsx
323
+ import { forwardRef as forwardRef2 } from "react";
324
+
325
+ // ../../node_modules/@radix-ui/react-slot/dist/index.mjs
326
+ import * as React2 from "react";
327
+ import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
328
+ var use = React2[" use ".trim().toString()];
329
+ var SLOTTABLE_IDENTIFIER = /* @__PURE__ */ Symbol("radix.slottable");
330
+ // @__NO_SIDE_EFFECTS__
331
+ function createSlottable(ownerName) {
332
+ const Slottable2 = ({ children }) => {
333
+ return /* @__PURE__ */ jsx5(Fragment2, { children });
334
+ };
335
+ Slottable2.displayName = `${ownerName}.Slottable`;
336
+ Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
337
+ return Slottable2;
338
+ }
339
+ var Slottable = /* @__PURE__ */ createSlottable("Slottable");
340
+
341
+ // src/client/tooltip-icon-button.tsx
342
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
343
+ var TooltipIconButton = forwardRef2(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
344
+ return /* @__PURE__ */ jsxs3(Tooltip, { children: [
345
+ /* @__PURE__ */ jsx6(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs3(
346
+ Button,
347
+ {
348
+ variant: "ghost",
349
+ size: "icon",
350
+ ...rest,
351
+ className: cn("aui-button-icon size-6 p-1", className),
352
+ ref,
353
+ children: [
354
+ /* @__PURE__ */ jsx6(Slottable, { children }),
355
+ /* @__PURE__ */ jsx6("span", { className: "aui-sr-only sr-only", children: tooltip })
356
+ ]
357
+ }
358
+ ) }),
359
+ /* @__PURE__ */ jsx6(TooltipContent, { side, children: tooltip })
360
+ ] });
361
+ });
362
+ TooltipIconButton.displayName = "TooltipIconButton";
363
+
364
+ // src/client/attachment.tsx
365
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
366
+ var useFileSrc = (file) => {
367
+ const [src, setSrc] = useState(void 0);
368
+ useEffect(() => {
369
+ if (!file) return;
370
+ const objectUrl = URL.createObjectURL(file);
371
+ setSrc(objectUrl);
372
+ return () => {
373
+ URL.revokeObjectURL(objectUrl);
374
+ setSrc(void 0);
375
+ };
376
+ }, [file]);
377
+ return src;
378
+ };
379
+ var useAttachmentSrc = () => {
380
+ const { file, src } = useAuiState(
381
+ useShallow((s) => {
382
+ if (s.attachment.type !== "image") return {};
383
+ if (s.attachment.file) return { file: s.attachment.file };
384
+ const src2 = s.attachment.content?.filter((c) => c.type === "image")[0]?.image;
385
+ if (!src2) return {};
386
+ return { src: src2 };
387
+ })
388
+ );
389
+ return useFileSrc(file) ?? src;
390
+ };
391
+ var AttachmentPreview = ({ src }) => {
392
+ const [isLoaded, setIsLoaded] = useState(false);
393
+ return /* @__PURE__ */ jsx7(
394
+ "img",
395
+ {
396
+ src,
397
+ alt: "Image Preview",
398
+ className: cn(
399
+ "block h-auto max-h-[80vh] w-auto max-w-full object-contain",
400
+ isLoaded ? "aui-attachment-preview-image-loaded" : "aui-attachment-preview-image-loading invisible"
401
+ ),
402
+ onLoad: () => setIsLoaded(true)
403
+ }
404
+ );
405
+ };
406
+ var AttachmentPreviewDialog = ({ children }) => {
407
+ const src = useAttachmentSrc();
408
+ if (!src) return children;
409
+ return /* @__PURE__ */ jsxs4(Dialog, { children: [
410
+ /* @__PURE__ */ jsx7(
411
+ DialogTrigger,
412
+ {
413
+ className: "aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50",
414
+ asChild: true,
415
+ children
416
+ }
417
+ ),
418
+ /* @__PURE__ */ jsxs4(DialogContent, { className: "aui-attachment-preview-dialog-content p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:bg-foreground/60 [&>button]:p-1 [&>button]:opacity-100 [&>button]:ring-0! [&_svg]:text-background [&>button]:hover:[&_svg]:text-destructive", children: [
419
+ /* @__PURE__ */ jsx7(DialogTitle, { className: "aui-sr-only sr-only", children: "Image Attachment Preview" }),
420
+ /* @__PURE__ */ jsx7("div", { className: "aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background", children: /* @__PURE__ */ jsx7(AttachmentPreview, { src }) })
421
+ ] })
422
+ ] });
423
+ };
424
+ var AttachmentThumb = () => {
425
+ const isImage = useAuiState((s) => s.attachment.type === "image");
426
+ const src = useAttachmentSrc();
427
+ return /* @__PURE__ */ jsxs4(Avatar, { className: "aui-attachment-tile-avatar h-full w-full rounded-none", children: [
428
+ /* @__PURE__ */ jsx7(
429
+ AvatarImage,
430
+ {
431
+ src,
432
+ alt: "Attachment preview",
433
+ className: "aui-attachment-tile-image object-cover"
434
+ }
435
+ ),
436
+ /* @__PURE__ */ jsx7(AvatarFallback, { delayMs: isImage ? 200 : 0, children: /* @__PURE__ */ jsx7(FileText, { className: "aui-attachment-tile-fallback-icon size-8 text-muted-foreground" }) })
437
+ ] });
438
+ };
439
+ var AttachmentUI = () => {
440
+ const aui = useAui();
441
+ const isComposer = aui.attachment.source === "composer";
442
+ const isImage = useAuiState((s) => s.attachment.type === "image");
443
+ const typeLabel = useAuiState((s) => {
444
+ const type = s.attachment.type;
445
+ switch (type) {
446
+ case "image":
447
+ return "Image";
448
+ case "document":
449
+ return "Document";
450
+ case "file":
451
+ return "File";
452
+ default:
453
+ const _exhaustiveCheck = type;
454
+ throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
455
+ }
456
+ });
457
+ return /* @__PURE__ */ jsxs4(Tooltip, { children: [
458
+ /* @__PURE__ */ jsxs4(
459
+ AttachmentPrimitive.Root,
460
+ {
461
+ className: cn(
462
+ "aui-attachment-root relative",
463
+ isImage && "aui-attachment-root-composer only:[&>#attachment-tile]:size-24"
464
+ ),
465
+ children: [
466
+ /* @__PURE__ */ jsx7(AttachmentPreviewDialog, { children: /* @__PURE__ */ jsx7(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx7(
467
+ "div",
468
+ {
469
+ className: cn(
470
+ "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
471
+ isComposer && "aui-attachment-tile-composer border-foreground/20"
472
+ ),
473
+ role: "button",
474
+ id: "attachment-tile",
475
+ "aria-label": `${typeLabel} attachment`,
476
+ children: /* @__PURE__ */ jsx7(AttachmentThumb, {})
477
+ }
478
+ ) }) }),
479
+ isComposer && /* @__PURE__ */ jsx7(AttachmentRemove, {})
480
+ ]
481
+ }
482
+ ),
483
+ /* @__PURE__ */ jsx7(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx7(AttachmentPrimitive.Name, {}) })
484
+ ] });
485
+ };
486
+ var AttachmentRemove = () => {
487
+ return /* @__PURE__ */ jsx7(AttachmentPrimitive.Remove, { asChild: true, children: /* @__PURE__ */ jsx7(
488
+ TooltipIconButton,
489
+ {
490
+ tooltip: "Remove file",
491
+ className: "aui-attachment-tile-remove absolute top-1.5 right-1.5 size-3.5 rounded-full bg-white text-muted-foreground opacity-100 shadow-sm hover:bg-white! [&_svg]:text-black hover:[&_svg]:text-destructive",
492
+ side: "top",
493
+ children: /* @__PURE__ */ jsx7(XIcon2, { className: "aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" })
494
+ }
495
+ ) });
496
+ };
497
+ var UserMessageAttachments = () => {
498
+ return /* @__PURE__ */ jsx7("div", { className: "aui-user-message-attachments-end col-span-full col-start-1 row-start-1 flex w-full flex-row justify-end gap-2", children: /* @__PURE__ */ jsx7(MessagePrimitive.Attachments, { components: { Attachment: AttachmentUI } }) });
499
+ };
500
+ var ComposerAttachments = () => {
501
+ return /* @__PURE__ */ jsx7("div", { className: "aui-composer-attachments mb-2 flex w-full flex-row items-center gap-2 overflow-x-auto px-1.5 pt-0.5 pb-1 empty:hidden", children: /* @__PURE__ */ jsx7(
502
+ ComposerPrimitive.Attachments,
503
+ {
504
+ components: { Attachment: AttachmentUI }
505
+ }
506
+ ) });
507
+ };
508
+ var ComposerAddAttachment = () => {
509
+ return /* @__PURE__ */ jsx7(ComposerPrimitive.AddAttachment, { asChild: true, children: /* @__PURE__ */ jsx7(
510
+ TooltipIconButton,
511
+ {
512
+ tooltip: "Add Attachment",
513
+ side: "bottom",
514
+ variant: "ghost",
515
+ size: "icon",
516
+ className: "aui-composer-add-attachment size-8.5 rounded-full p-1 font-semibold text-xs hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30",
517
+ "aria-label": "Add Attachment",
518
+ children: /* @__PURE__ */ jsx7(PlusIcon, { className: "aui-attachment-add-icon size-5 stroke-[1.5px]" })
519
+ }
520
+ ) });
521
+ };
522
+
523
+ // src/client/markdown-text.tsx
524
+ import "@assistant-ui/react-markdown/styles/dot.css";
525
+ import {
526
+ MarkdownTextPrimitive,
527
+ unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
528
+ useIsMarkdownCodeBlock
529
+ } from "@assistant-ui/react-markdown";
530
+ import remarkGfm from "remark-gfm";
531
+ import { memo, useState as useState2 } from "react";
532
+ import { CheckIcon, CopyIcon } from "lucide-react";
533
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
534
+ var MarkdownTextImpl = () => {
535
+ return /* @__PURE__ */ jsx8(
536
+ MarkdownTextPrimitive,
537
+ {
538
+ remarkPlugins: [remarkGfm],
539
+ className: "aui-md",
540
+ components: defaultComponents
541
+ }
542
+ );
543
+ };
544
+ var MarkdownText = memo(MarkdownTextImpl);
545
+ var CodeHeader = ({ language, code }) => {
546
+ const { isCopied, copyToClipboard } = useCopyToClipboard();
547
+ const onCopy = () => {
548
+ if (!code || isCopied) return;
549
+ copyToClipboard(code);
550
+ };
551
+ return /* @__PURE__ */ jsxs5("div", { className: "aui-code-header-root mt-2.5 flex items-center justify-between rounded-t-lg border border-border/50 border-b-0 bg-muted/50 px-3 py-1.5 text-xs", children: [
552
+ /* @__PURE__ */ jsx8("span", { className: "aui-code-header-language font-medium text-muted-foreground lowercase", children: language }),
553
+ /* @__PURE__ */ jsxs5(TooltipIconButton, { tooltip: "Copy", onClick: onCopy, children: [
554
+ !isCopied && /* @__PURE__ */ jsx8(CopyIcon, {}),
555
+ isCopied && /* @__PURE__ */ jsx8(CheckIcon, {})
556
+ ] })
557
+ ] });
558
+ };
559
+ var useCopyToClipboard = ({
560
+ copiedDuration = 3e3
561
+ } = {}) => {
562
+ const [isCopied, setIsCopied] = useState2(false);
563
+ const copyToClipboard = (value) => {
564
+ if (!value) return;
565
+ navigator.clipboard.writeText(value).then(() => {
566
+ setIsCopied(true);
567
+ setTimeout(() => setIsCopied(false), copiedDuration);
568
+ });
569
+ };
570
+ return { isCopied, copyToClipboard };
571
+ };
572
+ var defaultComponents = memoizeMarkdownComponents({
573
+ h1: ({ className, ...props }) => /* @__PURE__ */ jsx8(
574
+ "h1",
575
+ {
576
+ className: cn(
577
+ "aui-md-h1 mb-2 scroll-m-20 font-semibold text-base first:mt-0 last:mb-0",
578
+ className
579
+ ),
580
+ ...props
581
+ }
582
+ ),
583
+ h2: ({ className, ...props }) => /* @__PURE__ */ jsx8(
584
+ "h2",
585
+ {
586
+ className: cn(
587
+ "aui-md-h2 mt-3 mb-1.5 scroll-m-20 font-semibold text-sm first:mt-0 last:mb-0",
588
+ className
589
+ ),
590
+ ...props
591
+ }
592
+ ),
593
+ h3: ({ className, ...props }) => /* @__PURE__ */ jsx8(
594
+ "h3",
595
+ {
596
+ className: cn(
597
+ "aui-md-h3 mt-2.5 mb-1 scroll-m-20 font-semibold text-sm first:mt-0 last:mb-0",
598
+ className
599
+ ),
600
+ ...props
601
+ }
602
+ ),
603
+ h4: ({ className, ...props }) => /* @__PURE__ */ jsx8(
604
+ "h4",
605
+ {
606
+ className: cn(
607
+ "aui-md-h4 mt-2 mb-1 scroll-m-20 font-medium text-sm first:mt-0 last:mb-0",
608
+ className
609
+ ),
610
+ ...props
611
+ }
612
+ ),
613
+ h5: ({ className, ...props }) => /* @__PURE__ */ jsx8(
614
+ "h5",
615
+ {
616
+ className: cn(
617
+ "aui-md-h5 mt-2 mb-1 font-medium text-sm first:mt-0 last:mb-0",
618
+ className
619
+ ),
620
+ ...props
621
+ }
622
+ ),
623
+ h6: ({ className, ...props }) => /* @__PURE__ */ jsx8(
624
+ "h6",
625
+ {
626
+ className: cn(
627
+ "aui-md-h6 mt-2 mb-1 font-medium text-sm first:mt-0 last:mb-0",
628
+ className
629
+ ),
630
+ ...props
631
+ }
632
+ ),
633
+ p: ({ className, ...props }) => /* @__PURE__ */ jsx8(
634
+ "p",
635
+ {
636
+ className: cn(
637
+ "aui-md-p my-2.5 leading-normal first:mt-0 last:mb-0",
638
+ className
639
+ ),
640
+ ...props
641
+ }
642
+ ),
643
+ a: ({ className, ...props }) => /* @__PURE__ */ jsx8(
644
+ "a",
645
+ {
646
+ className: cn(
647
+ "aui-md-a text-primary underline underline-offset-2 hover:text-primary/80",
648
+ className
649
+ ),
650
+ ...props
651
+ }
652
+ ),
653
+ blockquote: ({ className, ...props }) => /* @__PURE__ */ jsx8(
654
+ "blockquote",
655
+ {
656
+ className: cn(
657
+ "aui-md-blockquote my-2.5 border-muted-foreground/30 border-l-2 pl-3 text-muted-foreground italic",
658
+ className
659
+ ),
660
+ ...props
661
+ }
662
+ ),
663
+ ul: ({ className, ...props }) => /* @__PURE__ */ jsx8(
664
+ "ul",
665
+ {
666
+ className: cn(
667
+ "aui-md-ul my-2 ml-4 list-disc marker:text-muted-foreground [&>li]:mt-1",
668
+ className
669
+ ),
670
+ ...props
671
+ }
672
+ ),
673
+ ol: ({ className, ...props }) => /* @__PURE__ */ jsx8(
674
+ "ol",
675
+ {
676
+ className: cn(
677
+ "aui-md-ol my-2 ml-4 list-decimal marker:text-muted-foreground [&>li]:mt-1",
678
+ className
679
+ ),
680
+ ...props
681
+ }
682
+ ),
683
+ hr: ({ className, ...props }) => /* @__PURE__ */ jsx8(
684
+ "hr",
685
+ {
686
+ className: cn("aui-md-hr my-2 border-muted-foreground/20", className),
687
+ ...props
688
+ }
689
+ ),
690
+ table: ({ className, ...props }) => /* @__PURE__ */ jsx8(
691
+ "table",
692
+ {
693
+ className: cn(
694
+ "aui-md-table my-2 w-full border-separate border-spacing-0 overflow-y-auto",
695
+ className
696
+ ),
697
+ ...props
698
+ }
699
+ ),
700
+ th: ({ className, ...props }) => /* @__PURE__ */ jsx8(
701
+ "th",
702
+ {
703
+ className: cn(
704
+ "aui-md-th bg-muted px-2 py-1 text-left font-medium first:rounded-tl-lg last:rounded-tr-lg [[align=center]]:text-center [[align=right]]:text-right",
705
+ className
706
+ ),
707
+ ...props
708
+ }
709
+ ),
710
+ td: ({ className, ...props }) => /* @__PURE__ */ jsx8(
711
+ "td",
712
+ {
713
+ className: cn(
714
+ "aui-md-td border-muted-foreground/20 border-b border-l px-2 py-1 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
715
+ className
716
+ ),
717
+ ...props
718
+ }
719
+ ),
720
+ tr: ({ className, ...props }) => /* @__PURE__ */ jsx8(
721
+ "tr",
722
+ {
723
+ className: cn(
724
+ "aui-md-tr m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg",
725
+ className
726
+ ),
727
+ ...props
728
+ }
729
+ ),
730
+ li: ({ className, ...props }) => /* @__PURE__ */ jsx8("li", { className: cn("aui-md-li leading-normal", className), ...props }),
731
+ sup: ({ className, ...props }) => /* @__PURE__ */ jsx8(
732
+ "sup",
733
+ {
734
+ className: cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className),
735
+ ...props
736
+ }
737
+ ),
738
+ pre: ({ className, ...props }) => /* @__PURE__ */ jsx8(
739
+ "pre",
740
+ {
741
+ className: cn(
742
+ "aui-md-pre overflow-x-auto rounded-t-none rounded-b-lg border border-border/50 border-t-0 bg-muted/30 p-3 text-xs leading-relaxed",
743
+ className
744
+ ),
745
+ ...props
746
+ }
747
+ ),
748
+ code: function Code({ className, ...props }) {
749
+ const isCodeBlock = useIsMarkdownCodeBlock();
750
+ return /* @__PURE__ */ jsx8(
751
+ "code",
752
+ {
753
+ className: cn(
754
+ !isCodeBlock && "aui-md-inline-code rounded-md border border-border/50 bg-muted/50 px-1.5 py-0.5 font-mono text-[0.85em]",
755
+ className
756
+ ),
757
+ ...props
758
+ }
759
+ );
760
+ },
761
+ CodeHeader
762
+ });
763
+
764
+ // src/client/tool-fallback.tsx
765
+ import { memo as memo2, useCallback, useRef, useState as useState3 } from "react";
766
+ import {
767
+ AlertCircleIcon,
768
+ CheckIcon as CheckIcon2,
769
+ ChevronDownIcon,
770
+ LoaderIcon,
771
+ XCircleIcon
772
+ } from "lucide-react";
773
+ import {
774
+ useScrollLock
775
+ } from "@assistant-ui/react";
776
+
777
+ // src/client/ui/collapsible.tsx
778
+ import { Collapsible as CollapsiblePrimitive } from "radix-ui";
779
+ import { jsx as jsx9 } from "react/jsx-runtime";
780
+ function Collapsible({
781
+ ...props
782
+ }) {
783
+ return /* @__PURE__ */ jsx9(CollapsiblePrimitive.Root, { "data-slot": "collapsible", ...props });
784
+ }
785
+ function CollapsibleTrigger({
786
+ ...props
787
+ }) {
788
+ return /* @__PURE__ */ jsx9(
789
+ CollapsiblePrimitive.CollapsibleTrigger,
790
+ {
791
+ "data-slot": "collapsible-trigger",
792
+ ...props
793
+ }
794
+ );
795
+ }
796
+ function CollapsibleContent({
797
+ ...props
798
+ }) {
799
+ return /* @__PURE__ */ jsx9(
800
+ CollapsiblePrimitive.CollapsibleContent,
801
+ {
802
+ "data-slot": "collapsible-content",
803
+ ...props
804
+ }
805
+ );
806
+ }
807
+
808
+ // src/client/tool-fallback.tsx
809
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
810
+ var ANIMATION_DURATION = 200;
811
+ function ToolFallbackRoot({
812
+ className,
813
+ open: controlledOpen,
814
+ onOpenChange: controlledOnOpenChange,
815
+ defaultOpen = false,
816
+ children,
817
+ ...props
818
+ }) {
819
+ const collapsibleRef = useRef(null);
820
+ const [uncontrolledOpen, setUncontrolledOpen] = useState3(defaultOpen);
821
+ const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION);
822
+ const isControlled = controlledOpen !== void 0;
823
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
824
+ const handleOpenChange = useCallback(
825
+ (open) => {
826
+ if (!open) {
827
+ lockScroll();
828
+ }
829
+ if (!isControlled) {
830
+ setUncontrolledOpen(open);
831
+ }
832
+ controlledOnOpenChange?.(open);
833
+ },
834
+ [lockScroll, isControlled, controlledOnOpenChange]
835
+ );
836
+ return /* @__PURE__ */ jsx10(
837
+ Collapsible,
838
+ {
839
+ ref: collapsibleRef,
840
+ "data-slot": "tool-fallback-root",
841
+ open: isOpen,
842
+ onOpenChange: handleOpenChange,
843
+ className: cn(
844
+ "aui-tool-fallback-root group/tool-fallback-root w-full rounded-lg border py-3",
845
+ className
846
+ ),
847
+ style: {
848
+ "--animation-duration": `${ANIMATION_DURATION}ms`
849
+ },
850
+ ...props,
851
+ children
852
+ }
853
+ );
854
+ }
855
+ var statusIconMap = {
856
+ running: LoaderIcon,
857
+ complete: CheckIcon2,
858
+ incomplete: XCircleIcon,
859
+ "requires-action": AlertCircleIcon
860
+ };
861
+ function ToolFallbackTrigger({
862
+ toolName,
863
+ status,
864
+ className,
865
+ ...props
866
+ }) {
867
+ const statusType = status?.type ?? "complete";
868
+ const isRunning = statusType === "running";
869
+ const isCancelled = status?.type === "incomplete" && status.reason === "cancelled";
870
+ const Icon = statusIconMap[statusType];
871
+ const label = isCancelled ? "Cancelled tool" : "Used tool";
872
+ return /* @__PURE__ */ jsxs6(
873
+ CollapsibleTrigger,
874
+ {
875
+ "data-slot": "tool-fallback-trigger",
876
+ className: cn(
877
+ "aui-tool-fallback-trigger group/trigger flex w-full items-center gap-2 px-4 text-sm transition-colors",
878
+ className
879
+ ),
880
+ ...props,
881
+ children: [
882
+ /* @__PURE__ */ jsx10(
883
+ Icon,
884
+ {
885
+ "data-slot": "tool-fallback-trigger-icon",
886
+ className: cn(
887
+ "aui-tool-fallback-trigger-icon size-4 shrink-0",
888
+ isCancelled && "text-muted-foreground",
889
+ isRunning && "animate-spin"
890
+ )
891
+ }
892
+ ),
893
+ /* @__PURE__ */ jsxs6(
894
+ "span",
895
+ {
896
+ "data-slot": "tool-fallback-trigger-label",
897
+ className: cn(
898
+ "aui-tool-fallback-trigger-label-wrapper relative inline-block grow text-left leading-none",
899
+ isCancelled && "text-muted-foreground line-through"
900
+ ),
901
+ children: [
902
+ /* @__PURE__ */ jsxs6("span", { children: [
903
+ label,
904
+ ": ",
905
+ /* @__PURE__ */ jsx10("b", { children: toolName })
906
+ ] }),
907
+ isRunning && /* @__PURE__ */ jsxs6(
908
+ "span",
909
+ {
910
+ "aria-hidden": true,
911
+ "data-slot": "tool-fallback-trigger-shimmer",
912
+ className: "aui-tool-fallback-trigger-shimmer shimmer pointer-events-none absolute inset-0 motion-reduce:animate-none",
913
+ children: [
914
+ label,
915
+ ": ",
916
+ /* @__PURE__ */ jsx10("b", { children: toolName })
917
+ ]
918
+ }
919
+ )
920
+ ]
921
+ }
922
+ ),
923
+ /* @__PURE__ */ jsx10(
924
+ ChevronDownIcon,
925
+ {
926
+ "data-slot": "tool-fallback-trigger-chevron",
927
+ className: cn(
928
+ "aui-tool-fallback-trigger-chevron size-4 shrink-0",
929
+ "transition-transform duration-(--animation-duration) ease-out",
930
+ "group-data-[state=closed]/trigger:-rotate-90",
931
+ "group-data-[state=open]/trigger:rotate-0"
932
+ )
933
+ }
934
+ )
935
+ ]
936
+ }
937
+ );
938
+ }
939
+ function ToolFallbackContent({
940
+ className,
941
+ children,
942
+ ...props
943
+ }) {
944
+ return /* @__PURE__ */ jsx10(
945
+ CollapsibleContent,
946
+ {
947
+ "data-slot": "tool-fallback-content",
948
+ className: cn(
949
+ "aui-tool-fallback-content relative overflow-hidden text-sm outline-none",
950
+ "group/collapsible-content ease-out",
951
+ "data-[state=closed]:animate-collapsible-up",
952
+ "data-[state=open]:animate-collapsible-down",
953
+ "data-[state=closed]:fill-mode-forwards",
954
+ "data-[state=closed]:pointer-events-none",
955
+ "data-[state=open]:duration-(--animation-duration)",
956
+ "data-[state=closed]:duration-(--animation-duration)",
957
+ className
958
+ ),
959
+ ...props,
960
+ children: /* @__PURE__ */ jsx10("div", { className: "mt-3 flex flex-col gap-2 border-t pt-2", children })
961
+ }
962
+ );
963
+ }
964
+ function ToolFallbackArgs({
965
+ argsText,
966
+ className,
967
+ ...props
968
+ }) {
969
+ if (!argsText) return null;
970
+ return /* @__PURE__ */ jsx10(
971
+ "div",
972
+ {
973
+ "data-slot": "tool-fallback-args",
974
+ className: cn("aui-tool-fallback-args px-4", className),
975
+ ...props,
976
+ children: /* @__PURE__ */ jsx10("pre", { className: "aui-tool-fallback-args-value whitespace-pre-wrap", children: argsText })
977
+ }
978
+ );
979
+ }
980
+ function ToolFallbackResult({
981
+ result,
982
+ className,
983
+ ...props
984
+ }) {
985
+ if (result === void 0) return null;
986
+ return /* @__PURE__ */ jsxs6(
987
+ "div",
988
+ {
989
+ "data-slot": "tool-fallback-result",
990
+ className: cn(
991
+ "aui-tool-fallback-result border-t border-dashed px-4 pt-2",
992
+ className
993
+ ),
994
+ ...props,
995
+ children: [
996
+ /* @__PURE__ */ jsx10("p", { className: "aui-tool-fallback-result-header font-semibold", children: "Result:" }),
997
+ /* @__PURE__ */ jsx10("pre", { className: "aui-tool-fallback-result-content whitespace-pre-wrap", children: typeof result === "string" ? result : JSON.stringify(result, null, 2) })
998
+ ]
999
+ }
1000
+ );
1001
+ }
1002
+ function ToolFallbackError({
1003
+ status,
1004
+ className,
1005
+ ...props
1006
+ }) {
1007
+ if (status?.type !== "incomplete") return null;
1008
+ const error = status.error;
1009
+ const errorText = error ? typeof error === "string" ? error : JSON.stringify(error) : null;
1010
+ if (!errorText) return null;
1011
+ const isCancelled = status.reason === "cancelled";
1012
+ const headerText = isCancelled ? "Cancelled reason:" : "Error:";
1013
+ return /* @__PURE__ */ jsxs6(
1014
+ "div",
1015
+ {
1016
+ "data-slot": "tool-fallback-error",
1017
+ className: cn("aui-tool-fallback-error px-4", className),
1018
+ ...props,
1019
+ children: [
1020
+ /* @__PURE__ */ jsx10("p", { className: "aui-tool-fallback-error-header font-semibold text-muted-foreground", children: headerText }),
1021
+ /* @__PURE__ */ jsx10("p", { className: "aui-tool-fallback-error-reason text-muted-foreground", children: errorText })
1022
+ ]
1023
+ }
1024
+ );
1025
+ }
1026
+ var ToolFallbackImpl = ({
1027
+ toolName,
1028
+ argsText,
1029
+ result,
1030
+ status
1031
+ }) => {
1032
+ const isCancelled = status?.type === "incomplete" && status.reason === "cancelled";
1033
+ return /* @__PURE__ */ jsxs6(
1034
+ ToolFallbackRoot,
1035
+ {
1036
+ className: cn(isCancelled && "border-muted-foreground/30 bg-muted/30"),
1037
+ children: [
1038
+ /* @__PURE__ */ jsx10(ToolFallbackTrigger, { toolName, status }),
1039
+ /* @__PURE__ */ jsxs6(ToolFallbackContent, { children: [
1040
+ /* @__PURE__ */ jsx10(ToolFallbackError, { status }),
1041
+ /* @__PURE__ */ jsx10(
1042
+ ToolFallbackArgs,
1043
+ {
1044
+ argsText,
1045
+ className: cn(isCancelled && "opacity-60")
1046
+ }
1047
+ ),
1048
+ !isCancelled && /* @__PURE__ */ jsx10(ToolFallbackResult, { result })
1049
+ ] })
1050
+ ]
1051
+ }
1052
+ );
1053
+ };
1054
+ var ToolFallback = memo2(
1055
+ ToolFallbackImpl
1056
+ );
1057
+ ToolFallback.displayName = "ToolFallback";
1058
+ ToolFallback.Root = ToolFallbackRoot;
1059
+ ToolFallback.Trigger = ToolFallbackTrigger;
1060
+ ToolFallback.Content = ToolFallbackContent;
1061
+ ToolFallback.Args = ToolFallbackArgs;
1062
+ ToolFallback.Result = ToolFallbackResult;
1063
+ ToolFallback.Error = ToolFallbackError;
1064
+
1065
+ // src/client/thread.tsx
1066
+ import {
1067
+ ActionBarMorePrimitive,
1068
+ ActionBarPrimitive,
1069
+ AuiIf,
1070
+ BranchPickerPrimitive,
1071
+ ComposerPrimitive as ComposerPrimitive2,
1072
+ ErrorPrimitive,
1073
+ MessagePrimitive as MessagePrimitive2,
1074
+ SuggestionPrimitive,
1075
+ ThreadPrimitive
1076
+ } from "@assistant-ui/react";
1077
+ import {
1078
+ ArrowDownIcon,
1079
+ ArrowUpIcon,
1080
+ CheckIcon as CheckIcon3,
1081
+ ChevronLeftIcon,
1082
+ ChevronRightIcon,
1083
+ CopyIcon as CopyIcon2,
1084
+ DownloadIcon,
1085
+ MoreHorizontalIcon,
1086
+ PencilIcon,
1087
+ RefreshCwIcon,
1088
+ SquareIcon
1089
+ } from "lucide-react";
1090
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
1091
+ var Thread = () => {
1092
+ return /* @__PURE__ */ jsx11(
1093
+ ThreadPrimitive.Root,
1094
+ {
1095
+ className: "aui-root aui-thread-root @container flex h-full flex-col bg-transparent",
1096
+ style: {
1097
+ ["--thread-max-width"]: "44rem"
1098
+ },
1099
+ children: /* @__PURE__ */ jsxs7(
1100
+ ThreadPrimitive.Viewport,
1101
+ {
1102
+ turnAnchor: "top",
1103
+ className: "aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4",
1104
+ children: [
1105
+ /* @__PURE__ */ jsx11(AuiIf, { condition: (s) => s.thread.isEmpty, children: /* @__PURE__ */ jsx11(ThreadWelcome, {}) }),
1106
+ /* @__PURE__ */ jsx11(
1107
+ ThreadPrimitive.Messages,
1108
+ {
1109
+ components: {
1110
+ UserMessage,
1111
+ EditComposer,
1112
+ AssistantMessage
1113
+ }
1114
+ }
1115
+ ),
1116
+ /* @__PURE__ */ jsxs7(ThreadPrimitive.ViewportFooter, { className: "aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible pb-3", children: [
1117
+ /* @__PURE__ */ jsx11(ThreadScrollToBottom, {}),
1118
+ /* @__PURE__ */ jsx11(Composer, {})
1119
+ ] })
1120
+ ]
1121
+ }
1122
+ )
1123
+ }
1124
+ );
1125
+ };
1126
+ var ThreadScrollToBottom = () => {
1127
+ return /* @__PURE__ */ jsx11(ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ jsx11(
1128
+ TooltipIconButton,
1129
+ {
1130
+ tooltip: "Scroll to bottom",
1131
+ variant: "outline",
1132
+ className: "aui-thread-scroll-to-bottom absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent",
1133
+ children: /* @__PURE__ */ jsx11(ArrowDownIcon, {})
1134
+ }
1135
+ ) });
1136
+ };
1137
+ var ThreadWelcome = () => {
1138
+ return /* @__PURE__ */ jsxs7("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
1139
+ /* @__PURE__ */ jsx11("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ jsxs7("div", { className: "aui-thread-welcome-message flex size-full flex-col items-center justify-center gap-1 px-4 text-center", children: [
1140
+ /* @__PURE__ */ jsx11("h1", { className: "aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both text-sm font-medium text-foreground duration-200", children: "Share an idea" }),
1141
+ /* @__PURE__ */ jsx11("p", { className: "aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both text-muted-foreground text-xs delay-75 duration-200", children: "Describe what you'd like to improve." })
1142
+ ] }) }),
1143
+ /* @__PURE__ */ jsx11(ThreadSuggestions, {})
1144
+ ] });
1145
+ };
1146
+ var ThreadSuggestions = () => {
1147
+ return /* @__PURE__ */ jsx11("div", { className: "aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4", children: /* @__PURE__ */ jsx11(
1148
+ ThreadPrimitive.Suggestions,
1149
+ {
1150
+ components: {
1151
+ Suggestion: ThreadSuggestionItem
1152
+ }
1153
+ }
1154
+ ) });
1155
+ };
1156
+ var ThreadSuggestionItem = () => {
1157
+ return /* @__PURE__ */ jsx11("div", { className: "aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 @md:nth-[n+3]:block nth-[n+3]:hidden animate-in fill-mode-both duration-200", children: /* @__PURE__ */ jsx11(SuggestionPrimitive.Trigger, { send: true, asChild: true, children: /* @__PURE__ */ jsxs7(
1158
+ Button,
1159
+ {
1160
+ variant: "ghost",
1161
+ className: "aui-thread-welcome-suggestion h-auto w-full @md:flex-col flex-wrap items-start justify-start gap-1 rounded-2xl border px-4 py-3 text-left text-sm transition-colors hover:bg-muted",
1162
+ children: [
1163
+ /* @__PURE__ */ jsx11("span", { className: "aui-thread-welcome-suggestion-text-1 font-medium", children: /* @__PURE__ */ jsx11(SuggestionPrimitive.Title, {}) }),
1164
+ /* @__PURE__ */ jsx11("span", { className: "aui-thread-welcome-suggestion-text-2 text-muted-foreground", children: /* @__PURE__ */ jsx11(SuggestionPrimitive.Description, {}) })
1165
+ ]
1166
+ }
1167
+ ) }) });
1168
+ };
1169
+ var Composer = () => {
1170
+ return /* @__PURE__ */ jsx11(ComposerPrimitive2.Root, { className: "aui-composer-root relative flex w-full flex-col", children: /* @__PURE__ */ jsxs7(ComposerPrimitive2.AttachmentDropzone, { className: "aui-composer-attachment-dropzone flex w-full flex-col rounded-3xl border border-border bg-card px-2 pt-2 shadow-[0_8px_30px_rgba(0,0,0,0.24)] outline-none transition-all duration-300 has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-2 has-[textarea:focus-visible]:ring-ring/20 data-[dragging=true]:border-ring data-[dragging=true]:border-dashed data-[dragging=true]:bg-accent/50", children: [
1171
+ /* @__PURE__ */ jsx11(ComposerAttachments, {}),
1172
+ /* @__PURE__ */ jsx11(
1173
+ ComposerPrimitive2.Input,
1174
+ {
1175
+ placeholder: "Describe your idea...",
1176
+ className: "aui-composer-input mb-1 max-h-32 min-h-14 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-0",
1177
+ rows: 1,
1178
+ autoFocus: true,
1179
+ "aria-label": "Message input"
1180
+ }
1181
+ ),
1182
+ /* @__PURE__ */ jsx11(ComposerAction, {})
1183
+ ] }) });
1184
+ };
1185
+ var ComposerAction = () => {
1186
+ return /* @__PURE__ */ jsxs7("div", { className: "aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between", children: [
1187
+ /* @__PURE__ */ jsx11(ComposerAddAttachment, {}),
1188
+ /* @__PURE__ */ jsx11(AuiIf, { condition: (s) => !s.thread.isRunning, children: /* @__PURE__ */ jsx11(ComposerPrimitive2.Send, { asChild: true, children: /* @__PURE__ */ jsx11(
1189
+ TooltipIconButton,
1190
+ {
1191
+ tooltip: "Send message",
1192
+ side: "bottom",
1193
+ type: "submit",
1194
+ variant: "default",
1195
+ size: "icon",
1196
+ className: "aui-composer-send size-8 rounded-full",
1197
+ "aria-label": "Send message",
1198
+ children: /* @__PURE__ */ jsx11(ArrowUpIcon, { className: "aui-composer-send-icon size-4" })
1199
+ }
1200
+ ) }) }),
1201
+ /* @__PURE__ */ jsx11(AuiIf, { condition: (s) => s.thread.isRunning, children: /* @__PURE__ */ jsx11(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx11(
1202
+ Button,
1203
+ {
1204
+ type: "button",
1205
+ variant: "default",
1206
+ size: "icon",
1207
+ className: "aui-composer-cancel size-8 rounded-full",
1208
+ "aria-label": "Stop generating",
1209
+ children: /* @__PURE__ */ jsx11(SquareIcon, { className: "aui-composer-cancel-icon size-3 fill-current" })
1210
+ }
1211
+ ) }) })
1212
+ ] });
1213
+ };
1214
+ var MessageError = () => {
1215
+ return /* @__PURE__ */ jsx11(MessagePrimitive2.Error, { children: /* @__PURE__ */ jsx11(ErrorPrimitive.Root, { className: "aui-message-error-root mt-2 rounded-xl border border-destructive bg-destructive/10 p-3 text-destructive text-sm dark:bg-destructive/5 dark:text-red-200", children: /* @__PURE__ */ jsx11(ErrorPrimitive.Message, { className: "aui-message-error-message line-clamp-2" }) }) });
1216
+ };
1217
+ var AssistantMessage = () => {
1218
+ return /* @__PURE__ */ jsxs7(
1219
+ MessagePrimitive2.Root,
1220
+ {
1221
+ className: "aui-assistant-message-root fade-in slide-in-from-bottom-1 relative mx-auto w-full max-w-(--thread-max-width) animate-in py-2 duration-150",
1222
+ "data-role": "assistant",
1223
+ children: [
1224
+ /* @__PURE__ */ jsxs7("div", { className: "aui-assistant-message-content wrap-break-word px-2 text-[13px] text-foreground leading-relaxed", children: [
1225
+ /* @__PURE__ */ jsx11(
1226
+ MessagePrimitive2.Parts,
1227
+ {
1228
+ components: {
1229
+ Text: MarkdownText,
1230
+ tools: { Fallback: ToolFallback }
1231
+ }
1232
+ }
1233
+ ),
1234
+ /* @__PURE__ */ jsx11(MessageError, {})
1235
+ ] }),
1236
+ /* @__PURE__ */ jsxs7("div", { className: "aui-assistant-message-footer mt-0.5 ml-2 flex", children: [
1237
+ /* @__PURE__ */ jsx11(BranchPicker, {}),
1238
+ /* @__PURE__ */ jsx11(AssistantActionBar, {})
1239
+ ] })
1240
+ ]
1241
+ }
1242
+ );
1243
+ };
1244
+ var AssistantActionBar = () => {
1245
+ return /* @__PURE__ */ jsxs7(
1246
+ ActionBarPrimitive.Root,
1247
+ {
1248
+ hideWhenRunning: true,
1249
+ autohide: "not-last",
1250
+ autohideFloat: "single-branch",
1251
+ className: "aui-assistant-action-bar-root col-start-3 row-start-2 -ml-1 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-xl data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm",
1252
+ children: [
1253
+ /* @__PURE__ */ jsx11(ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ jsxs7(TooltipIconButton, { tooltip: "Copy", children: [
1254
+ /* @__PURE__ */ jsx11(AuiIf, { condition: (s) => s.message.isCopied, children: /* @__PURE__ */ jsx11(CheckIcon3, {}) }),
1255
+ /* @__PURE__ */ jsx11(AuiIf, { condition: (s) => !s.message.isCopied, children: /* @__PURE__ */ jsx11(CopyIcon2, {}) })
1256
+ ] }) }),
1257
+ /* @__PURE__ */ jsx11(ActionBarPrimitive.Reload, { asChild: true, children: /* @__PURE__ */ jsx11(TooltipIconButton, { tooltip: "Refresh", children: /* @__PURE__ */ jsx11(RefreshCwIcon, {}) }) }),
1258
+ /* @__PURE__ */ jsxs7(ActionBarMorePrimitive.Root, { children: [
1259
+ /* @__PURE__ */ jsx11(ActionBarMorePrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsx11(
1260
+ TooltipIconButton,
1261
+ {
1262
+ tooltip: "More",
1263
+ className: "data-[state=open]:bg-accent",
1264
+ children: /* @__PURE__ */ jsx11(MoreHorizontalIcon, {})
1265
+ }
1266
+ ) }),
1267
+ /* @__PURE__ */ jsx11(
1268
+ ActionBarMorePrimitive.Content,
1269
+ {
1270
+ side: "bottom",
1271
+ align: "start",
1272
+ className: "aui-action-bar-more-content z-50 min-w-32 overflow-hidden rounded-xl border bg-popover p-1 text-popover-foreground shadow-md",
1273
+ children: /* @__PURE__ */ jsx11(ActionBarPrimitive.ExportMarkdown, { asChild: true, children: /* @__PURE__ */ jsxs7(ActionBarMorePrimitive.Item, { className: "aui-action-bar-more-item flex cursor-pointer select-none items-center gap-2 rounded-lg px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground", children: [
1274
+ /* @__PURE__ */ jsx11(DownloadIcon, { className: "size-4" }),
1275
+ "Export as Markdown"
1276
+ ] }) })
1277
+ }
1278
+ )
1279
+ ] })
1280
+ ]
1281
+ }
1282
+ );
1283
+ };
1284
+ var UserMessage = () => {
1285
+ return /* @__PURE__ */ jsxs7(
1286
+ MessagePrimitive2.Root,
1287
+ {
1288
+ className: "aui-user-message-root fade-in slide-in-from-bottom-1 mx-auto grid w-full max-w-(--thread-max-width) animate-in auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 py-2 duration-150 [&:where(>*)]:col-start-2",
1289
+ "data-role": "user",
1290
+ children: [
1291
+ /* @__PURE__ */ jsx11(UserMessageAttachments, {}),
1292
+ /* @__PURE__ */ jsxs7("div", { className: "aui-user-message-content-wrapper relative col-start-2 min-w-0", children: [
1293
+ /* @__PURE__ */ jsx11("div", { className: "aui-user-message-content wrap-break-word rounded-2xl bg-muted px-3 py-2 text-[13px] text-foreground", children: /* @__PURE__ */ jsx11(MessagePrimitive2.Parts, {}) }),
1294
+ /* @__PURE__ */ jsx11("div", { className: "aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2", children: /* @__PURE__ */ jsx11(UserActionBar, {}) })
1295
+ ] }),
1296
+ /* @__PURE__ */ jsx11(BranchPicker, { className: "aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end" })
1297
+ ]
1298
+ }
1299
+ );
1300
+ };
1301
+ var UserActionBar = () => {
1302
+ return /* @__PURE__ */ jsx11(
1303
+ ActionBarPrimitive.Root,
1304
+ {
1305
+ hideWhenRunning: true,
1306
+ autohide: "not-last",
1307
+ className: "aui-user-action-bar-root flex flex-col items-end",
1308
+ children: /* @__PURE__ */ jsx11(ActionBarPrimitive.Edit, { asChild: true, children: /* @__PURE__ */ jsx11(TooltipIconButton, { tooltip: "Edit", className: "aui-user-action-edit p-4", children: /* @__PURE__ */ jsx11(PencilIcon, {}) }) })
1309
+ }
1310
+ );
1311
+ };
1312
+ var EditComposer = () => {
1313
+ return /* @__PURE__ */ jsx11(MessagePrimitive2.Root, { className: "aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3", children: /* @__PURE__ */ jsxs7(ComposerPrimitive2.Root, { className: "aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-3xl bg-card", children: [
1314
+ /* @__PURE__ */ jsx11(
1315
+ ComposerPrimitive2.Input,
1316
+ {
1317
+ className: "aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none",
1318
+ autoFocus: true
1319
+ }
1320
+ ),
1321
+ /* @__PURE__ */ jsxs7("div", { className: "aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end", children: [
1322
+ /* @__PURE__ */ jsx11(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx11(Button, { variant: "ghost", size: "sm", children: "Cancel" }) }),
1323
+ /* @__PURE__ */ jsx11(ComposerPrimitive2.Send, { asChild: true, children: /* @__PURE__ */ jsx11(Button, { size: "sm", children: "Update" }) })
1324
+ ] })
1325
+ ] }) });
1326
+ };
1327
+ var BranchPicker = ({
1328
+ className,
1329
+ ...rest
1330
+ }) => {
1331
+ return /* @__PURE__ */ jsxs7(
1332
+ BranchPickerPrimitive.Root,
1333
+ {
1334
+ hideWhenSingleBranch: true,
1335
+ className: cn(
1336
+ "aui-branch-picker-root mr-2 -ml-2 inline-flex items-center text-muted-foreground text-xs",
1337
+ className
1338
+ ),
1339
+ ...rest,
1340
+ children: [
1341
+ /* @__PURE__ */ jsx11(BranchPickerPrimitive.Previous, { asChild: true, children: /* @__PURE__ */ jsx11(TooltipIconButton, { tooltip: "Previous", children: /* @__PURE__ */ jsx11(ChevronLeftIcon, {}) }) }),
1342
+ /* @__PURE__ */ jsxs7("span", { className: "aui-branch-picker-state font-medium", children: [
1343
+ /* @__PURE__ */ jsx11(BranchPickerPrimitive.Number, {}),
1344
+ " / ",
1345
+ /* @__PURE__ */ jsx11(BranchPickerPrimitive.Count, {})
1346
+ ] }),
1347
+ /* @__PURE__ */ jsx11(BranchPickerPrimitive.Next, { asChild: true, children: /* @__PURE__ */ jsx11(TooltipIconButton, { tooltip: "Next", children: /* @__PURE__ */ jsx11(ChevronRightIcon, {}) }) })
1348
+ ]
1349
+ }
1350
+ );
1351
+ };
1352
+
1353
+ // src/client/use-conversations.ts
1354
+ import { useState as useState4, useEffect as useEffect2, useRef as useRef2, useCallback as useCallback2, useMemo } from "react";
1355
+ import { useThreadRuntime } from "@assistant-ui/react";
1356
+ var CONV_INDEX_KEY = "feedback_conversations";
1357
+ var CONV_PREFIX = "feedback_conv_";
1358
+ var ACTIVE_CONV_KEY = "feedback_active_conv";
1359
+ var MAX_CONVERSATIONS = 10;
1360
+ var AUTOSAVE_DELAY = 400;
1361
+ var DEFAULT_TITLE = "New chat";
1362
+ function loadIndex() {
1363
+ try {
1364
+ const raw = localStorage.getItem(CONV_INDEX_KEY);
1365
+ return raw ? JSON.parse(raw) : [];
1366
+ } catch {
1367
+ return [];
1368
+ }
1369
+ }
1370
+ function saveIndex(conversations) {
1371
+ localStorage.setItem(CONV_INDEX_KEY, JSON.stringify(conversations));
1372
+ }
1373
+ function loadState(id) {
1374
+ try {
1375
+ const raw = localStorage.getItem(CONV_PREFIX + id);
1376
+ return raw ? JSON.parse(raw) : null;
1377
+ } catch {
1378
+ return null;
1379
+ }
1380
+ }
1381
+ function saveState(id, state) {
1382
+ localStorage.setItem(CONV_PREFIX + id, JSON.stringify(state));
1383
+ }
1384
+ function deleteMessages(id) {
1385
+ localStorage.removeItem(CONV_PREFIX + id);
1386
+ }
1387
+ function makeConversation() {
1388
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1389
+ return {
1390
+ id: crypto.randomUUID(),
1391
+ title: DEFAULT_TITLE,
1392
+ createdAt: now,
1393
+ updatedAt: now
1394
+ };
1395
+ }
1396
+ function useConversations() {
1397
+ const threadRuntime = useThreadRuntime();
1398
+ const [conversations, setConversations] = useState4([]);
1399
+ const [activeId, setActiveId] = useState4("");
1400
+ const activeIdRef = useRef2(activeId);
1401
+ const conversationsRef = useRef2(conversations);
1402
+ useEffect2(() => {
1403
+ activeIdRef.current = activeId;
1404
+ conversationsRef.current = conversations;
1405
+ });
1406
+ function saveCurrent() {
1407
+ try {
1408
+ const state = threadRuntime.exportExternalState();
1409
+ if (state && state.messages.length > 0) {
1410
+ saveState(activeIdRef.current, state);
1411
+ updateTitleIfNeeded(activeIdRef.current, state);
1412
+ }
1413
+ } catch {
1414
+ }
1415
+ }
1416
+ function restoreOrReset(id) {
1417
+ const saved = loadState(id);
1418
+ if (saved && saved.messages.length > 0) {
1419
+ try {
1420
+ threadRuntime.importExternalState(saved);
1421
+ } catch {
1422
+ threadRuntime.reset();
1423
+ }
1424
+ } else {
1425
+ threadRuntime.reset();
1426
+ }
1427
+ }
1428
+ function activate(id) {
1429
+ setActiveId(id);
1430
+ localStorage.setItem(ACTIVE_CONV_KEY, id);
1431
+ }
1432
+ const initialized = useRef2(false);
1433
+ useEffect2(() => {
1434
+ if (initialized.current) return;
1435
+ initialized.current = true;
1436
+ let index = loadIndex();
1437
+ let currentId = localStorage.getItem(ACTIVE_CONV_KEY);
1438
+ if (index.length === 0) {
1439
+ const first = makeConversation();
1440
+ index = [first];
1441
+ currentId = first.id;
1442
+ saveIndex(index);
1443
+ localStorage.setItem(ACTIVE_CONV_KEY, currentId);
1444
+ } else if (!currentId || !index.some((c) => c.id === currentId)) {
1445
+ currentId = index[0].id;
1446
+ localStorage.setItem(ACTIVE_CONV_KEY, currentId);
1447
+ }
1448
+ setConversations(index);
1449
+ setActiveId(currentId);
1450
+ const saved = loadState(currentId);
1451
+ if (saved && saved.messages.length > 0) {
1452
+ try {
1453
+ threadRuntime.importExternalState(saved);
1454
+ } catch {
1455
+ }
1456
+ }
1457
+ }, [threadRuntime]);
1458
+ const switchTo = useCallback2(
1459
+ (id) => {
1460
+ if (id === activeIdRef.current) return;
1461
+ saveCurrent();
1462
+ restoreOrReset(id);
1463
+ activate(id);
1464
+ },
1465
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- helpers use refs, only threadRuntime matters
1466
+ [threadRuntime]
1467
+ );
1468
+ const create = useCallback2(() => {
1469
+ saveCurrent();
1470
+ const newConv = makeConversation();
1471
+ let updated = [newConv, ...conversationsRef.current];
1472
+ if (updated.length > MAX_CONVERSATIONS) {
1473
+ const removed = updated.slice(MAX_CONVERSATIONS);
1474
+ for (const c of removed) deleteMessages(c.id);
1475
+ updated = updated.slice(0, MAX_CONVERSATIONS);
1476
+ }
1477
+ setConversations(updated);
1478
+ saveIndex(updated);
1479
+ threadRuntime.reset();
1480
+ activate(newConv.id);
1481
+ }, [threadRuntime]);
1482
+ const remove = useCallback2(
1483
+ (id) => {
1484
+ const current = conversationsRef.current;
1485
+ const filtered = current.filter((c) => c.id !== id);
1486
+ deleteMessages(id);
1487
+ if (filtered.length === 0) {
1488
+ const fresh = makeConversation();
1489
+ setConversations([fresh]);
1490
+ saveIndex([fresh]);
1491
+ threadRuntime.reset();
1492
+ activate(fresh.id);
1493
+ return;
1494
+ }
1495
+ if (id === activeIdRef.current) {
1496
+ const oldIndex = current.findIndex((c) => c.id === id);
1497
+ const nextIndex = Math.min(oldIndex, filtered.length - 1);
1498
+ const nextId = filtered[nextIndex].id;
1499
+ restoreOrReset(nextId);
1500
+ activate(nextId);
1501
+ }
1502
+ setConversations(filtered);
1503
+ saveIndex(filtered);
1504
+ },
1505
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- helpers use refs, only threadRuntime matters
1506
+ [threadRuntime]
1507
+ );
1508
+ const save = useCallback2(() => {
1509
+ const id = activeIdRef.current;
1510
+ if (!id) return;
1511
+ try {
1512
+ const state = threadRuntime.exportExternalState();
1513
+ if (state && state.messages.length > 0) {
1514
+ saveState(id, state);
1515
+ updateTitleIfNeeded(id, state);
1516
+ }
1517
+ } catch {
1518
+ }
1519
+ setConversations(loadIndex());
1520
+ }, [threadRuntime]);
1521
+ useEffect2(() => {
1522
+ let timeoutId = null;
1523
+ const unsubscribe = threadRuntime.subscribe(() => {
1524
+ if (timeoutId) clearTimeout(timeoutId);
1525
+ timeoutId = setTimeout(save, AUTOSAVE_DELAY);
1526
+ });
1527
+ return () => {
1528
+ unsubscribe();
1529
+ if (timeoutId) clearTimeout(timeoutId);
1530
+ save();
1531
+ };
1532
+ }, [threadRuntime, save]);
1533
+ const sorted = useMemo(
1534
+ () => [...conversations].sort(
1535
+ (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
1536
+ ),
1537
+ [conversations]
1538
+ );
1539
+ return { conversations: sorted, activeId, switchTo, create, remove, save };
1540
+ }
1541
+ function updateTitleIfNeeded(id, state) {
1542
+ const index = loadIndex();
1543
+ const conv = index.find((c) => c.id === id);
1544
+ if (!conv) return;
1545
+ if (conv.title === DEFAULT_TITLE && state.messages.length > 0) {
1546
+ const firstUserMsg = state.messages.find((m) => m.message?.role === "user");
1547
+ if (firstUserMsg?.message) {
1548
+ const msg = firstUserMsg.message;
1549
+ const textPart = msg.parts?.find(
1550
+ (p) => p.type === "text" && !!p.text
1551
+ );
1552
+ const text = textPart?.text ?? (typeof msg.content === "string" ? msg.content : "");
1553
+ if (text) {
1554
+ conv.title = text.slice(0, 40);
1555
+ }
1556
+ }
1557
+ }
1558
+ conv.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1559
+ saveIndex(index);
1560
+ }
1561
+
1562
+ // src/client/conversation-tabs.tsx
1563
+ import { Plus, X } from "lucide-react";
1564
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1565
+ function ConversationTabs({
1566
+ conversations,
1567
+ activeId,
1568
+ onSwitch,
1569
+ onCreate,
1570
+ onRemove,
1571
+ onClose
1572
+ }) {
1573
+ const canDelete = conversations.length > 1;
1574
+ return /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1.5 shrink-0", children: [
1575
+ /* @__PURE__ */ jsx12("div", { className: "flex items-center gap-1 overflow-x-auto [&::-webkit-scrollbar]:hidden min-w-0 flex-1", children: conversations.map((conv) => {
1576
+ const isActive = conv.id === activeId;
1577
+ return /* @__PURE__ */ jsxs8(
1578
+ "button",
1579
+ {
1580
+ role: "tab",
1581
+ "aria-selected": isActive,
1582
+ onClick: () => onSwitch(conv.id),
1583
+ className: `feedback-tab group relative flex items-center gap-1 px-2.5 h-8 text-[12px] whitespace-nowrap rounded-lg transition-all ${isActive ? "feedback-tab-active" : "feedback-tab-inactive"}`,
1584
+ children: [
1585
+ /* @__PURE__ */ jsx12("span", { className: "max-w-[100px] truncate", children: conv.title }),
1586
+ canDelete && /* @__PURE__ */ jsx12(
1587
+ "span",
1588
+ {
1589
+ role: "button",
1590
+ "aria-label": `Delete ${conv.title}`,
1591
+ onClick: (e) => {
1592
+ e.stopPropagation();
1593
+ onRemove(conv.id);
1594
+ },
1595
+ className: "ml-0.5 hidden h-4 w-4 shrink-0 items-center justify-center rounded-md group-hover:inline-flex hover:bg-white/10",
1596
+ children: /* @__PURE__ */ jsx12(X, { className: "h-2.5 w-2.5" })
1597
+ }
1598
+ )
1599
+ ]
1600
+ },
1601
+ conv.id
1602
+ );
1603
+ }) }),
1604
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center shrink-0 gap-1", children: [
1605
+ /* @__PURE__ */ jsx12(
1606
+ "button",
1607
+ {
1608
+ onClick: onCreate,
1609
+ className: "feedback-tab-button flex h-7 w-7 items-center justify-center rounded-lg transition-all",
1610
+ "aria-label": "New chat",
1611
+ children: /* @__PURE__ */ jsx12(Plus, { className: "h-3.5 w-3.5" })
1612
+ }
1613
+ ),
1614
+ /* @__PURE__ */ jsx12(
1615
+ "button",
1616
+ {
1617
+ onClick: onClose,
1618
+ className: "feedback-tab-button flex h-7 w-7 items-center justify-center rounded-lg transition-all",
1619
+ "aria-label": "Close feedback",
1620
+ children: /* @__PURE__ */ jsx12(X, { className: "h-3.5 w-3.5" })
1621
+ }
1622
+ )
1623
+ ] })
1624
+ ] });
1625
+ }
1626
+
1627
+ // src/client/present-options-tool-ui.tsx
1628
+ import { useState as useState5 } from "react";
1629
+ import { makeAssistantToolUI } from "@assistant-ui/react";
1630
+ import { useThreadRuntime as useThreadRuntime2 } from "@assistant-ui/react";
1631
+ import { Check } from "lucide-react";
1632
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
1633
+ var PresentOptionsToolUI = makeAssistantToolUI({
1634
+ toolName: "present_options",
1635
+ render: function PresentOptions({ args }) {
1636
+ const threadRuntime = useThreadRuntime2();
1637
+ const [selected, setSelected] = useState5(null);
1638
+ if (!args.options) return null;
1639
+ if (selected) {
1640
+ return /* @__PURE__ */ jsxs9("div", { className: "inline-flex items-center gap-1.5 rounded-full bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground", children: [
1641
+ /* @__PURE__ */ jsx13(Check, { className: "h-3.5 w-3.5" }),
1642
+ selected
1643
+ ] });
1644
+ }
1645
+ return /* @__PURE__ */ jsx13("div", { className: "flex flex-wrap gap-2 py-2", children: args.options.map((option, i) => /* @__PURE__ */ jsx13(
1646
+ "button",
1647
+ {
1648
+ onClick: () => {
1649
+ setSelected(option);
1650
+ threadRuntime.append({
1651
+ role: "user",
1652
+ content: [{ type: "text", text: option }]
1653
+ });
1654
+ },
1655
+ className: "rounded-full border border-border bg-background px-4 py-2 text-sm font-medium transition-all hover:border-foreground/20 hover:bg-muted active:scale-95",
1656
+ children: option
1657
+ },
1658
+ i
1659
+ )) });
1660
+ }
1661
+ });
1662
+
1663
+ // src/client/submit-request-tool-ui.tsx
1664
+ import { useState as useState7 } from "react";
1665
+ import { makeAssistantToolUI as makeAssistantToolUI2 } from "@assistant-ui/react";
1666
+ import { Check as Check3, Copy, ChevronRight } from "lucide-react";
1667
+
1668
+ // src/client/pipeline-tracker.tsx
1669
+ import { useState as useState6, useEffect as useEffect3, useCallback as useCallback3, useRef as useRef3 } from "react";
1670
+ import { Check as Check2, X as X2, Loader2, ExternalLink, RotateCcw, Eye, MessageSquare } from "lucide-react";
1671
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
1672
+ var STEPS = [
1673
+ { label: "Issue created", stage: "created" },
1674
+ { label: "Queued", stage: "queued" },
1675
+ { label: "Agent running", stage: "running" },
1676
+ { label: "Validating", stage: "validating" },
1677
+ { label: "Preview ready", stage: "preview_ready" },
1678
+ { label: "Deployed", stage: "deployed" }
1679
+ ];
1680
+ var STAGE_INDEX = {
1681
+ created: 0,
1682
+ queued: 1,
1683
+ running: 2,
1684
+ validating: 3,
1685
+ preview_ready: 4,
1686
+ deployed: 5,
1687
+ failed: -1,
1688
+ rejected: -1
1689
+ };
1690
+ var TERMINAL_STAGES = ["deployed", "failed", "rejected"];
1691
+ var POLL_INTERVAL_MS = 5e3;
1692
+ var PREVIEW_POLL_INTERVAL_MS = 15e3;
1693
+ var ACTIVE_PIPELINE_KEY = "feedback_active_pipeline";
1694
+ function setPipelineActive(issueNumber, stage) {
1695
+ if (TERMINAL_STAGES.includes(stage)) {
1696
+ localStorage.removeItem(ACTIVE_PIPELINE_KEY);
1697
+ } else {
1698
+ localStorage.setItem(ACTIVE_PIPELINE_KEY, JSON.stringify({ issueNumber, stage }));
1699
+ }
1700
+ window.dispatchEvent(new Event("pipeline-status"));
1701
+ }
1702
+ function getPassword() {
1703
+ return sessionStorage.getItem("feedback_password") ?? "";
1704
+ }
1705
+ function getFailedStepIndex(previousStage) {
1706
+ if (previousStage && previousStage !== "failed") {
1707
+ return STAGE_INDEX[previousStage];
1708
+ }
1709
+ return 2;
1710
+ }
1711
+ function deriveStepState(stepIndex, currentIndex, failedAtIndex, stage) {
1712
+ const isFailed = stage === "failed";
1713
+ const isDeployed = stage === "deployed";
1714
+ if (isFailed && stepIndex === failedAtIndex) return "failed";
1715
+ if (isFailed && stepIndex < failedAtIndex) return "completed";
1716
+ if (isFailed) return "future";
1717
+ if (isDeployed) return "completed";
1718
+ if (currentIndex > stepIndex) return "completed";
1719
+ if (currentIndex === stepIndex) return "active";
1720
+ return "future";
1721
+ }
1722
+ var STEP_DOT_CLASS = {
1723
+ completed: "h-[7px] w-[7px] rounded-full bg-emerald-500",
1724
+ active: "h-[7px] w-[7px] rounded-full bg-foreground animate-pulse",
1725
+ failed: "h-[7px] w-[7px] rounded-full bg-destructive",
1726
+ future: "h-[5px] w-[5px] rounded-full bg-muted-foreground/25"
1727
+ };
1728
+ function StepDot({ state }) {
1729
+ return /* @__PURE__ */ jsx14("div", { className: "flex h-4 w-4 shrink-0 items-center justify-center", children: /* @__PURE__ */ jsx14("div", { className: STEP_DOT_CLASS[state] }) });
1730
+ }
1731
+ var STEP_LABEL_CLASS = {
1732
+ completed: "text-muted-foreground",
1733
+ active: "text-foreground",
1734
+ failed: "text-destructive",
1735
+ future: "text-muted-foreground/40"
1736
+ };
1737
+ function PipelineTracker({
1738
+ issueUrl,
1739
+ statusEndpoint = "/api/feedback/status"
1740
+ }) {
1741
+ const issueNumber = parseInt(issueUrl.split("/").pop() ?? "0", 10);
1742
+ const [status, setStatus] = useState6({
1743
+ stage: "created",
1744
+ issueNumber,
1745
+ issueUrl
1746
+ });
1747
+ const [polling, setPolling] = useState6(true);
1748
+ const [actionLoading, setActionLoading] = useState6(null);
1749
+ const [showChangeInput, setShowChangeInput] = useState6(false);
1750
+ const [changeComment, setChangeComment] = useState6("");
1751
+ const [confirmReject, setConfirmReject] = useState6(false);
1752
+ const previousStageRef = useRef3(null);
1753
+ const fetchStatus = useCallback3(async () => {
1754
+ try {
1755
+ const res = await fetch(`${statusEndpoint}?issue=${issueNumber}`);
1756
+ if (!res.ok) return;
1757
+ const data = await res.json();
1758
+ setStatus(data);
1759
+ setPipelineActive(issueNumber, data.stage);
1760
+ if (TERMINAL_STAGES.includes(data.stage)) {
1761
+ setPolling(false);
1762
+ }
1763
+ } catch {
1764
+ }
1765
+ }, [statusEndpoint, issueNumber]);
1766
+ useEffect3(() => {
1767
+ if (status.stage !== "failed") {
1768
+ previousStageRef.current = status.stage;
1769
+ }
1770
+ }, [status.stage]);
1771
+ useEffect3(() => {
1772
+ setPipelineActive(issueNumber, "created");
1773
+ fetchStatus();
1774
+ if (!polling) return;
1775
+ const interval = setInterval(
1776
+ fetchStatus,
1777
+ status.stage === "preview_ready" ? PREVIEW_POLL_INTERVAL_MS : POLL_INTERVAL_MS
1778
+ );
1779
+ return () => clearInterval(interval);
1780
+ }, [fetchStatus, polling, issueNumber, status.stage]);
1781
+ async function handleAction(action, extraBody) {
1782
+ setActionLoading(action);
1783
+ try {
1784
+ const res = await fetch(`${statusEndpoint}?issue=${issueNumber}&action=${action}`, {
1785
+ method: "POST",
1786
+ headers: { "Content-Type": "application/json" },
1787
+ body: JSON.stringify({ password: getPassword(), ...extraBody })
1788
+ });
1789
+ if (!res.ok) return;
1790
+ switch (action) {
1791
+ case "retry":
1792
+ case "request_changes":
1793
+ setStatus({ stage: "created", issueNumber, issueUrl });
1794
+ setPipelineActive(issueNumber, "created");
1795
+ setPolling(true);
1796
+ setChangeComment("");
1797
+ setShowChangeInput(false);
1798
+ break;
1799
+ case "approve":
1800
+ setStatus((prev) => ({ ...prev, stage: "deployed" }));
1801
+ setPipelineActive(issueNumber, "deployed");
1802
+ setPolling(false);
1803
+ break;
1804
+ case "reject":
1805
+ setStatus((prev) => ({ ...prev, stage: "rejected" }));
1806
+ setPipelineActive(issueNumber, "rejected");
1807
+ setPolling(false);
1808
+ break;
1809
+ }
1810
+ } finally {
1811
+ setActionLoading(null);
1812
+ setConfirmReject(false);
1813
+ }
1814
+ }
1815
+ const currentIndex = STAGE_INDEX[status.stage];
1816
+ const isFailed = status.stage === "failed";
1817
+ const failedAtIndex = isFailed ? getFailedStepIndex(previousStageRef.current) : -1;
1818
+ return /* @__PURE__ */ jsxs10("div", { className: "rounded-2xl border border-border bg-card p-3 space-y-2", children: [
1819
+ /* @__PURE__ */ jsx14("div", { className: "space-y-0", children: STEPS.map((step, i) => {
1820
+ const state = deriveStepState(i, currentIndex, failedAtIndex, status.stage);
1821
+ return /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 h-6", children: [
1822
+ /* @__PURE__ */ jsx14(StepDot, { state }),
1823
+ /* @__PURE__ */ jsx14("span", { className: `text-xs ${STEP_LABEL_CLASS[state]}`, children: state === "failed" && status.failReason ? status.failReason : step.label }),
1824
+ i === 0 && /* @__PURE__ */ jsxs10("span", { className: "ml-auto text-[11px] text-muted-foreground/60", children: [
1825
+ "#",
1826
+ issueNumber
1827
+ ] })
1828
+ ] }, step.stage);
1829
+ }) }),
1830
+ status.stage === "preview_ready" && status.previewUrl && /* @__PURE__ */ jsxs10("div", { className: "space-y-2.5 pt-2 border-t border-border", children: [
1831
+ /* @__PURE__ */ jsxs10(
1832
+ "a",
1833
+ {
1834
+ href: status.previewUrl,
1835
+ target: "_blank",
1836
+ rel: "noopener noreferrer",
1837
+ className: "group flex items-center gap-2.5 rounded-lg border border-border px-3 py-2.5 transition-all hover:border-emerald-500/30 hover:bg-emerald-500/5",
1838
+ children: [
1839
+ /* @__PURE__ */ jsx14("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-emerald-500/10", children: /* @__PURE__ */ jsx14(Eye, { className: "h-3.5 w-3.5 text-emerald-400" }) }),
1840
+ /* @__PURE__ */ jsxs10("div", { className: "flex-1 min-w-0", children: [
1841
+ /* @__PURE__ */ jsx14("span", { className: "block text-xs font-medium text-foreground", children: "View preview" }),
1842
+ /* @__PURE__ */ jsx14("span", { className: "block text-[10px] text-muted-foreground truncate", children: status.previewUrl.replace(/^https?:\/\//, "") })
1843
+ ] }),
1844
+ /* @__PURE__ */ jsx14(ExternalLink, { className: "h-3 w-3 shrink-0 text-muted-foreground/50 transition-colors group-hover:text-foreground/70" })
1845
+ ]
1846
+ }
1847
+ ),
1848
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-stretch gap-1.5", children: [
1849
+ /* @__PURE__ */ jsxs10(
1850
+ "button",
1851
+ {
1852
+ onClick: () => handleAction("approve"),
1853
+ disabled: actionLoading !== null,
1854
+ className: "flex-1 inline-flex items-center justify-center gap-1.5 rounded-lg bg-emerald-500/15 px-2 py-2 text-[11px] font-medium text-emerald-400 transition-all hover:bg-emerald-500/25 disabled:opacity-50",
1855
+ children: [
1856
+ actionLoading === "approve" ? /* @__PURE__ */ jsx14(Loader2, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx14(Check2, { className: "h-3 w-3" }),
1857
+ "Approve"
1858
+ ]
1859
+ }
1860
+ ),
1861
+ /* @__PURE__ */ jsxs10(
1862
+ "button",
1863
+ {
1864
+ onClick: () => setShowChangeInput((v) => !v),
1865
+ disabled: actionLoading !== null,
1866
+ className: `flex-1 inline-flex items-center justify-center gap-1.5 rounded-lg border px-2 py-2 text-[11px] font-medium transition-all disabled:opacity-50 ${showChangeInput ? "border-foreground/20 bg-foreground/5 text-foreground" : "border-border text-muted-foreground hover:border-foreground/15 hover:text-foreground"}`,
1867
+ children: [
1868
+ /* @__PURE__ */ jsx14(MessageSquare, { className: "h-3 w-3" }),
1869
+ "Changes"
1870
+ ]
1871
+ }
1872
+ ),
1873
+ /* @__PURE__ */ jsxs10(
1874
+ "button",
1875
+ {
1876
+ onClick: () => {
1877
+ if (confirmReject) {
1878
+ handleAction("reject");
1879
+ } else {
1880
+ setConfirmReject(true);
1881
+ }
1882
+ },
1883
+ disabled: actionLoading !== null,
1884
+ className: `flex-1 inline-flex items-center justify-center gap-1.5 rounded-lg px-2 py-2 text-[11px] font-medium transition-all disabled:opacity-50 ${confirmReject ? "bg-red-500/20 text-red-400" : "border border-border text-muted-foreground hover:border-red-500/20 hover:text-red-400"}`,
1885
+ children: [
1886
+ actionLoading === "reject" ? /* @__PURE__ */ jsx14(Loader2, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx14(X2, { className: "h-3 w-3" }),
1887
+ confirmReject ? "Confirm" : "Reject"
1888
+ ]
1889
+ }
1890
+ )
1891
+ ] }),
1892
+ showChangeInput && /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
1893
+ /* @__PURE__ */ jsx14(
1894
+ "textarea",
1895
+ {
1896
+ value: changeComment,
1897
+ onChange: (e) => setChangeComment(e.target.value),
1898
+ placeholder: "Describe the changes you want...",
1899
+ className: "w-full rounded-lg border border-border bg-background px-2.5 py-2 text-xs text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:border-foreground/20 resize-none",
1900
+ rows: 3
1901
+ }
1902
+ ),
1903
+ /* @__PURE__ */ jsxs10(
1904
+ "button",
1905
+ {
1906
+ onClick: () => handleAction("request_changes", { comment: changeComment }),
1907
+ disabled: actionLoading !== null || changeComment.trim() === "",
1908
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-foreground px-3 py-1.5 text-[11px] font-medium text-background transition-colors hover:bg-foreground/90 disabled:opacity-50",
1909
+ children: [
1910
+ actionLoading === "request_changes" ? /* @__PURE__ */ jsx14(Loader2, { className: "h-3 w-3 animate-spin" }) : null,
1911
+ "Send"
1912
+ ]
1913
+ }
1914
+ )
1915
+ ] })
1916
+ ] }),
1917
+ status.stage === "rejected" && /* @__PURE__ */ jsx14("p", { className: "text-xs text-muted-foreground", children: "Request rejected" }),
1918
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 pt-1 border-t border-border", children: [
1919
+ isFailed && /* @__PURE__ */ jsxs10(
1920
+ "button",
1921
+ {
1922
+ onClick: () => handleAction("retry"),
1923
+ disabled: actionLoading !== null,
1924
+ className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50",
1925
+ children: [
1926
+ actionLoading === "retry" ? /* @__PURE__ */ jsx14(Loader2, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx14(RotateCcw, { className: "h-3 w-3" }),
1927
+ "Retry"
1928
+ ]
1929
+ }
1930
+ ),
1931
+ /* @__PURE__ */ jsxs10(
1932
+ "a",
1933
+ {
1934
+ href: issueUrl,
1935
+ target: "_blank",
1936
+ rel: "noopener noreferrer",
1937
+ className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground transition-colors hover:text-foreground",
1938
+ children: [
1939
+ /* @__PURE__ */ jsx14(ExternalLink, { className: "h-3 w-3" }),
1940
+ "View on GitHub"
1941
+ ]
1942
+ }
1943
+ )
1944
+ ] })
1945
+ ] });
1946
+ }
1947
+
1948
+ // src/client/submit-request-tool-ui.tsx
1949
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1950
+ var SubmitRequestToolUI = makeAssistantToolUI2({
1951
+ toolName: "submit_request",
1952
+ render: function SubmitRequest({ args, result, status }) {
1953
+ if (status.type === "running") {
1954
+ return /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2 py-2 text-xs text-muted-foreground", children: [
1955
+ /* @__PURE__ */ jsx15("div", { className: "h-3 w-3 animate-spin rounded-full border-[1.5px] border-current border-t-transparent" }),
1956
+ "Submitting..."
1957
+ ] });
1958
+ }
1959
+ if (status.type === "incomplete") {
1960
+ return /* @__PURE__ */ jsx15("div", { className: "rounded-xl bg-destructive/10 px-3 py-2 text-xs text-destructive", children: "An error occurred while submitting." });
1961
+ }
1962
+ if (!args.summary) return null;
1963
+ return /* @__PURE__ */ jsx15("div", { className: "my-3", children: /* @__PURE__ */ jsx15(SubmissionResult, { args, result }) });
1964
+ }
1965
+ });
1966
+ function SubmissionResult({ args, result }) {
1967
+ const [copied, setCopied] = useState7(false);
1968
+ const [promptExpanded, setPromptExpanded] = useState7(false);
1969
+ async function handleCopy(text) {
1970
+ await navigator.clipboard.writeText(text);
1971
+ setCopied(true);
1972
+ setTimeout(() => setCopied(false), 2e3);
1973
+ }
1974
+ return /* @__PURE__ */ jsxs11("div", { className: "space-y-3", children: [
1975
+ /* @__PURE__ */ jsx15("div", { className: "rounded-xl bg-muted/50 px-3 py-2.5", children: /* @__PURE__ */ jsx15("p", { className: "text-[13px] leading-relaxed", children: args.summary }) }),
1976
+ result?.github_issue_url && /* @__PURE__ */ jsx15(PipelineTracker, { issueUrl: result.github_issue_url }),
1977
+ /* @__PURE__ */ jsxs11("div", { className: "rounded-xl border border-border overflow-hidden", children: [
1978
+ /* @__PURE__ */ jsxs11(
1979
+ "button",
1980
+ {
1981
+ onClick: () => setPromptExpanded(!promptExpanded),
1982
+ className: "flex w-full items-center gap-1.5 px-3 py-2 text-[11px] font-medium text-muted-foreground transition-colors hover:text-foreground",
1983
+ children: [
1984
+ /* @__PURE__ */ jsx15(
1985
+ ChevronRight,
1986
+ {
1987
+ className: "h-3 w-3 transition-transform duration-150",
1988
+ style: { transform: promptExpanded ? "rotate(90deg)" : void 0 }
1989
+ }
1990
+ ),
1991
+ "Generated prompt",
1992
+ /* @__PURE__ */ jsxs11(
1993
+ "button",
1994
+ {
1995
+ onClick: (e) => {
1996
+ e.stopPropagation();
1997
+ handleCopy(args.generated_prompt);
1998
+ },
1999
+ className: "ml-auto inline-flex items-center gap-1 rounded-lg px-1.5 py-0.5 text-[11px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2000
+ children: [
2001
+ copied ? /* @__PURE__ */ jsx15(Check3, { className: "h-2.5 w-2.5" }) : /* @__PURE__ */ jsx15(Copy, { className: "h-2.5 w-2.5" }),
2002
+ copied ? "Copied" : "Copy"
2003
+ ]
2004
+ }
2005
+ )
2006
+ ]
2007
+ }
2008
+ ),
2009
+ promptExpanded && /* @__PURE__ */ jsx15(
2010
+ "pre",
2011
+ {
2012
+ className: "max-h-40 overflow-auto border-t border-border bg-[#0a0a0a] px-3 py-2.5 text-[11px] leading-relaxed text-zinc-400",
2013
+ style: { fontFamily: "'JetBrains Mono', monospace" },
2014
+ children: args.generated_prompt
2015
+ }
2016
+ )
2017
+ ] })
2018
+ ] });
2019
+ }
2020
+
2021
+ // src/client/feedback-panel.tsx
2022
+ import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
2023
+ var STORAGE_KEY = "feedback_password";
2024
+ function FeedbackPanel({ isOpen, onToggle, apiUrl = "/api/feedback/chat" }) {
2025
+ const [authenticated, setAuthenticated] = useState8(
2026
+ () => typeof window !== "undefined" && sessionStorage.getItem(STORAGE_KEY) !== null
2027
+ );
2028
+ const [pendingMessage, setPendingMessage] = useState8("");
2029
+ const [triggerInput, setTriggerInput] = useState8("");
2030
+ const panelRef = useRef4(null);
2031
+ const [pipelineActive, setPipelineActive2] = useState8(false);
2032
+ useEffect4(() => {
2033
+ function check() {
2034
+ setPipelineActive2(localStorage.getItem("feedback_active_pipeline") !== null);
2035
+ }
2036
+ check();
2037
+ window.addEventListener("pipeline-status", check);
2038
+ window.addEventListener("storage", check);
2039
+ return () => {
2040
+ window.removeEventListener("pipeline-status", check);
2041
+ window.removeEventListener("storage", check);
2042
+ };
2043
+ }, []);
2044
+ useEffect4(() => {
2045
+ if (!isOpen) return;
2046
+ function handleMouseDown(e) {
2047
+ if (panelRef.current && !panelRef.current.contains(e.target)) {
2048
+ onToggle();
2049
+ }
2050
+ }
2051
+ document.addEventListener("mousedown", handleMouseDown);
2052
+ return () => document.removeEventListener("mousedown", handleMouseDown);
2053
+ }, [isOpen, onToggle]);
2054
+ function handleTriggerSubmit(e) {
2055
+ e.preventDefault();
2056
+ if (!triggerInput.trim()) return;
2057
+ setPendingMessage(triggerInput.trim());
2058
+ setTriggerInput("");
2059
+ if (!isOpen) onToggle();
2060
+ }
2061
+ const clearPendingMessage = useCallback4(() => {
2062
+ setPendingMessage("");
2063
+ }, []);
2064
+ return /* @__PURE__ */ jsxs12(Fragment3, { children: [
2065
+ /* @__PURE__ */ jsx16(
2066
+ "div",
2067
+ {
2068
+ className: `feedback-trigger-bar fixed bottom-6 left-1/2 z-50 w-full -translate-x-1/2 transition-all duration-300 ${isOpen ? "pointer-events-none translate-y-4 opacity-0" : "translate-y-0 opacity-100"}`,
2069
+ children: /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
2070
+ /* @__PURE__ */ jsxs12(
2071
+ "form",
2072
+ {
2073
+ onSubmit: handleTriggerSubmit,
2074
+ className: "feedback-trigger-input flex flex-1 items-center gap-3 rounded-2xl px-5 py-1",
2075
+ children: [
2076
+ /* @__PURE__ */ jsx16(
2077
+ "input",
2078
+ {
2079
+ type: "text",
2080
+ value: triggerInput,
2081
+ onChange: (e) => setTriggerInput(e.target.value),
2082
+ placeholder: "Share an idea...",
2083
+ className: "flex-1 bg-transparent py-3 text-sm text-[#e8eaed] outline-none placeholder:text-[#8b8d93]",
2084
+ "aria-label": "Share an idea"
2085
+ }
2086
+ ),
2087
+ /* @__PURE__ */ jsx16(
2088
+ "button",
2089
+ {
2090
+ type: "submit",
2091
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-white/10 transition-colors hover:bg-white/15",
2092
+ "aria-label": "Send",
2093
+ children: /* @__PURE__ */ jsx16(ArrowUp, { className: "h-3.5 w-3.5 text-[#8b8d93]" })
2094
+ }
2095
+ )
2096
+ ]
2097
+ }
2098
+ ),
2099
+ /* @__PURE__ */ jsx16(
2100
+ "button",
2101
+ {
2102
+ type: "button",
2103
+ onClick: onToggle,
2104
+ className: "feedback-trigger-button flex h-[46px] w-[46px] shrink-0 items-center justify-center rounded-2xl",
2105
+ "aria-label": "Open panel",
2106
+ children: /* @__PURE__ */ jsx16(PanelRight, { className: "h-4 w-4" })
2107
+ }
2108
+ ),
2109
+ pipelineActive && /* @__PURE__ */ jsx16("div", { className: "flex h-[46px] w-[46px] shrink-0 items-center justify-center", title: "Pipeline running", children: /* @__PURE__ */ jsxs12("span", { className: "relative flex h-2.5 w-2.5", children: [
2110
+ /* @__PURE__ */ jsx16("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" }),
2111
+ /* @__PURE__ */ jsx16("span", { className: "relative inline-flex h-2.5 w-2.5 rounded-full bg-emerald-500" })
2112
+ ] }) })
2113
+ ] })
2114
+ }
2115
+ ),
2116
+ /* @__PURE__ */ jsx16(
2117
+ "div",
2118
+ {
2119
+ ref: panelRef,
2120
+ className: `feedback-panel fixed right-0 top-0 z-50 h-full p-3 transition-transform duration-300 ease-out ${isOpen ? "translate-x-0" : "translate-x-full"}`,
2121
+ children: /* @__PURE__ */ jsx16("div", { className: "flex h-full w-[400px] flex-col text-foreground", children: authenticated ? /* @__PURE__ */ jsx16(
2122
+ ChatContent,
2123
+ {
2124
+ isOpen,
2125
+ onClose: onToggle,
2126
+ pendingMessage,
2127
+ onPendingMessageSent: clearPendingMessage,
2128
+ apiUrl
2129
+ }
2130
+ ) : /* @__PURE__ */ jsx16("div", { className: "feedback-panel-glass flex h-full flex-col overflow-hidden", children: /* @__PURE__ */ jsx16(PasswordGate, { onAuth: () => setAuthenticated(true), onClose: onToggle, apiUrl }) }) })
2131
+ }
2132
+ )
2133
+ ] });
2134
+ }
2135
+ function ChatContent({
2136
+ isOpen,
2137
+ onClose,
2138
+ pendingMessage,
2139
+ onPendingMessageSent,
2140
+ apiUrl
2141
+ }) {
2142
+ const runtime = useChatRuntime({
2143
+ transport: new DefaultChatTransport({
2144
+ api: apiUrl,
2145
+ body: () => ({ password: sessionStorage.getItem(STORAGE_KEY) || "" })
2146
+ })
2147
+ });
2148
+ return /* @__PURE__ */ jsxs12(AssistantRuntimeProvider, { runtime, children: [
2149
+ /* @__PURE__ */ jsx16(
2150
+ ConversationManager,
2151
+ {
2152
+ isOpen,
2153
+ onClose,
2154
+ pendingMessage,
2155
+ onPendingMessageSent
2156
+ }
2157
+ ),
2158
+ /* @__PURE__ */ jsx16(PresentOptionsToolUI, {}),
2159
+ /* @__PURE__ */ jsx16(SubmitRequestToolUI, {})
2160
+ ] });
2161
+ }
2162
+ function ConversationManager({
2163
+ isOpen,
2164
+ onClose,
2165
+ pendingMessage,
2166
+ onPendingMessageSent
2167
+ }) {
2168
+ const { conversations, activeId, switchTo, create, remove, save } = useConversations();
2169
+ const threadRuntime = useThreadRuntime3();
2170
+ const onSentRef = useRef4(onPendingMessageSent);
2171
+ onSentRef.current = onPendingMessageSent;
2172
+ const prevOpenRef = useRef4(isOpen);
2173
+ useEffect4(() => {
2174
+ if (prevOpenRef.current && !isOpen) {
2175
+ save();
2176
+ }
2177
+ prevOpenRef.current = isOpen;
2178
+ }, [isOpen, save]);
2179
+ useEffect4(() => {
2180
+ if (!pendingMessage || !activeId) return;
2181
+ create();
2182
+ const timer = setTimeout(() => {
2183
+ try {
2184
+ threadRuntime.composer.setText(pendingMessage);
2185
+ } catch {
2186
+ return;
2187
+ }
2188
+ requestAnimationFrame(() => {
2189
+ try {
2190
+ threadRuntime.composer.send();
2191
+ } catch {
2192
+ }
2193
+ onSentRef.current();
2194
+ });
2195
+ }, 600);
2196
+ return () => clearTimeout(timer);
2197
+ }, [pendingMessage]);
2198
+ return /* @__PURE__ */ jsxs12("div", { className: "flex h-full flex-col gap-2", children: [
2199
+ /* @__PURE__ */ jsx16(
2200
+ ConversationTabs,
2201
+ {
2202
+ conversations,
2203
+ activeId,
2204
+ onSwitch: switchTo,
2205
+ onCreate: create,
2206
+ onRemove: remove,
2207
+ onClose
2208
+ }
2209
+ ),
2210
+ /* @__PURE__ */ jsx16("div", { className: "feedback-panel-glass flex flex-1 flex-col overflow-hidden", children: /* @__PURE__ */ jsx16(Thread, {}) })
2211
+ ] });
2212
+ }
2213
+ function PasswordGate({ onAuth, onClose, apiUrl }) {
2214
+ const [password, setPassword] = useState8("");
2215
+ const [error, setError] = useState8(null);
2216
+ const [loading, setLoading] = useState8(false);
2217
+ async function handleSubmit(e) {
2218
+ e.preventDefault();
2219
+ setError(null);
2220
+ setLoading(true);
2221
+ try {
2222
+ const res = await fetch(apiUrl, {
2223
+ method: "POST",
2224
+ headers: { "Content-Type": "application/json" },
2225
+ body: JSON.stringify({ messages: [], password })
2226
+ });
2227
+ if (res.status === 401) {
2228
+ setError("Incorrect password.");
2229
+ return;
2230
+ }
2231
+ sessionStorage.setItem(STORAGE_KEY, password);
2232
+ onAuth();
2233
+ } catch {
2234
+ setError("Connection error. Try again.");
2235
+ } finally {
2236
+ setLoading(false);
2237
+ }
2238
+ }
2239
+ return /* @__PURE__ */ jsxs12("div", { className: "relative flex h-full flex-col items-center justify-center px-8", children: [
2240
+ /* @__PURE__ */ jsx16(
2241
+ "button",
2242
+ {
2243
+ onClick: onClose,
2244
+ className: "absolute top-3 right-3 flex h-6 w-6 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2245
+ "aria-label": "Close",
2246
+ children: /* @__PURE__ */ jsx16(X3, { className: "h-3.5 w-3.5" })
2247
+ }
2248
+ ),
2249
+ /* @__PURE__ */ jsx16("div", { className: "mb-5 flex h-14 w-14 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx16(Lightbulb, { className: "h-6 w-6 text-muted-foreground" }) }),
2250
+ /* @__PURE__ */ jsx16("p", { className: "mb-8 text-center text-sm text-muted-foreground", children: "Share your ideas to improve the app" }),
2251
+ error && /* @__PURE__ */ jsxs12(
2252
+ "div",
2253
+ {
2254
+ role: "alert",
2255
+ className: "mb-4 flex w-full items-center gap-2 rounded-xl bg-destructive/10 px-4 py-3 text-sm text-destructive",
2256
+ children: [
2257
+ /* @__PURE__ */ jsx16(AlertCircle, { className: "h-4 w-4 shrink-0" }),
2258
+ error
2259
+ ]
2260
+ }
2261
+ ),
2262
+ /* @__PURE__ */ jsxs12("form", { onSubmit: handleSubmit, className: "w-full space-y-3", children: [
2263
+ /* @__PURE__ */ jsx16(
2264
+ "input",
2265
+ {
2266
+ type: "password",
2267
+ autoComplete: "off",
2268
+ placeholder: "Password",
2269
+ "aria-label": "Password",
2270
+ value: password,
2271
+ onChange: (e) => setPassword(e.target.value),
2272
+ className: "h-10 w-full rounded-xl border border-border bg-background px-4 text-sm text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring/20"
2273
+ }
2274
+ ),
2275
+ /* @__PURE__ */ jsx16(
2276
+ "button",
2277
+ {
2278
+ type: "submit",
2279
+ disabled: loading || !password,
2280
+ className: "flex h-10 w-full items-center justify-center gap-2 rounded-xl bg-primary text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50",
2281
+ children: loading ? /* @__PURE__ */ jsx16(Loader22, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxs12(Fragment3, { children: [
2282
+ "Access",
2283
+ /* @__PURE__ */ jsx16(ArrowRight, { className: "h-3.5 w-3.5" })
2284
+ ] })
2285
+ }
2286
+ )
2287
+ ] })
2288
+ ] });
2289
+ }
2290
+ export {
2291
+ FeedbackPanel,
2292
+ PipelineTracker,
2293
+ useConversations
2294
+ };