@lssm/module.ai-chat 0.0.0-canary-20251217060834 → 0.0.0-canary-20251217073102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-chat.feature.js +93 -1
- package/dist/context/context-builder.js +147 -2
- package/dist/context/file-operations.js +174 -1
- package/dist/context/index.js +5 -1
- package/dist/context/workspace-context.js +123 -2
- package/dist/core/chat-service.js +211 -2
- package/dist/core/conversation-store.js +108 -1
- package/dist/core/index.js +4 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +22 -1
- package/dist/libs/ai-providers/dist/factory.js +225 -0
- package/dist/libs/ai-providers/dist/index.js +4 -0
- package/dist/libs/ai-providers/dist/legacy.js +2 -0
- package/dist/libs/ai-providers/dist/models.js +299 -0
- package/dist/libs/ai-providers/dist/validation.js +60 -0
- package/dist/libs/design-system/dist/_virtual/rolldown_runtime.js +5 -0
- package/dist/libs/design-system/dist/components/atoms/Button.js +33 -0
- package/dist/libs/design-system/dist/components/atoms/Textarea.js +35 -0
- package/dist/libs/design-system/dist/lib/keyboard.js +193 -0
- package/dist/libs/design-system/dist/ui-kit-web/dist/ui/button.js +55 -0
- package/dist/libs/design-system/dist/ui-kit-web/dist/ui/textarea.js +16 -0
- package/dist/libs/design-system/dist/ui-kit-web/dist/ui-kit-core/dist/utils.js +13 -0
- package/dist/libs/ui-kit-web/dist/ui/avatar.js +25 -0
- package/dist/libs/ui-kit-web/dist/ui/badge.js +26 -0
- package/dist/libs/ui-kit-web/dist/ui/scroll-area.js +39 -0
- package/dist/libs/ui-kit-web/dist/ui/select.js +79 -0
- package/dist/libs/ui-kit-web/dist/ui/skeleton.js +14 -0
- package/dist/libs/ui-kit-web/dist/ui/tooltip.js +39 -0
- package/dist/libs/ui-kit-web/dist/ui/utils.js +10 -0
- package/dist/libs/ui-kit-web/dist/ui-kit-core/dist/utils.js +10 -0
- package/dist/presentation/components/ChatContainer.js +62 -1
- package/dist/presentation/components/ChatInput.d.ts +2 -2
- package/dist/presentation/components/ChatInput.js +149 -1
- package/dist/presentation/components/ChatMessage.d.ts +2 -2
- package/dist/presentation/components/ChatMessage.js +135 -1
- package/dist/presentation/components/CodePreview.d.ts +2 -2
- package/dist/presentation/components/CodePreview.js +126 -2
- package/dist/presentation/components/ContextIndicator.js +96 -1
- package/dist/presentation/components/ModelPicker.d.ts +2 -2
- package/dist/presentation/components/ModelPicker.js +197 -1
- package/dist/presentation/components/index.js +8 -1
- package/dist/presentation/hooks/index.js +4 -1
- package/dist/presentation/hooks/useChat.js +171 -1
- package/dist/presentation/hooks/useProviders.js +42 -1
- package/dist/presentation/index.d.ts +0 -1
- package/dist/presentation/index.js +12 -1
- package/dist/providers/chat-utilities.js +16 -1
- package/dist/providers/index.js +7 -1
- package/package.json +10 -10
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { __esmMin } from "../../../../_virtual/rolldown_runtime.js";
|
|
2
|
+
import { clsx } from "clsx";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
|
|
5
|
+
//#region ../../libs/design-system/dist/ui-kit-web/dist/ui-kit-core/dist/utils.js
|
|
6
|
+
function cn(...inputs) {
|
|
7
|
+
return twMerge(clsx(inputs));
|
|
8
|
+
}
|
|
9
|
+
var init_utils = __esmMin((() => {}));
|
|
10
|
+
init_utils();
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { cn, init_utils };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from "./utils.js";
|
|
4
|
+
import "react";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
|
7
|
+
|
|
8
|
+
//#region ../../libs/ui-kit-web/dist/ui/avatar.js
|
|
9
|
+
function Avatar({ className, ...props }) {
|
|
10
|
+
return /* @__PURE__ */ jsx(AvatarPrimitive.Root, {
|
|
11
|
+
"data-slot": "avatar",
|
|
12
|
+
className: cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className),
|
|
13
|
+
...props
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function AvatarFallback({ className, ...props }) {
|
|
17
|
+
return /* @__PURE__ */ jsx(AvatarPrimitive.Fallback, {
|
|
18
|
+
"data-slot": "avatar-fallback",
|
|
19
|
+
className: cn("bg-muted flex size-full items-center justify-center rounded-full", className),
|
|
20
|
+
...props
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
export { Avatar, AvatarFallback };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { cn } from "../ui-kit-core/dist/utils.js";
|
|
2
|
+
import "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
6
|
+
|
|
7
|
+
//#region ../../libs/ui-kit-web/dist/ui/badge.js
|
|
8
|
+
const badgeVariants = cva("inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-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 transition-[color,box-shadow] overflow-hidden", {
|
|
9
|
+
variants: { variant: {
|
|
10
|
+
default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
11
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
12
|
+
destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
13
|
+
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"
|
|
14
|
+
} },
|
|
15
|
+
defaultVariants: { variant: "default" }
|
|
16
|
+
});
|
|
17
|
+
function Badge({ className, variant, asChild = false, ...props }) {
|
|
18
|
+
return /* @__PURE__ */ jsx(asChild ? Slot : "span", {
|
|
19
|
+
"data-slot": "badge",
|
|
20
|
+
className: cn(badgeVariants({ variant }), className),
|
|
21
|
+
...props
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { Badge };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from "../ui-kit-core/dist/utils.js";
|
|
4
|
+
import "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
|
7
|
+
|
|
8
|
+
//#region ../../libs/ui-kit-web/dist/ui/scroll-area.js
|
|
9
|
+
function ScrollArea({ className, children, ...props }) {
|
|
10
|
+
return /* @__PURE__ */ jsxs(ScrollAreaPrimitive.Root, {
|
|
11
|
+
"data-slot": "scroll-area",
|
|
12
|
+
className: cn("relative", className),
|
|
13
|
+
...props,
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsx(ScrollAreaPrimitive.Viewport, {
|
|
16
|
+
"data-slot": "scroll-area-viewport",
|
|
17
|
+
className: "focus-visible:ring-ring/50 size-full rounded-[inherit] outline-hidden transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1",
|
|
18
|
+
children
|
|
19
|
+
}),
|
|
20
|
+
/* @__PURE__ */ jsx(ScrollBar, {}),
|
|
21
|
+
/* @__PURE__ */ jsx(ScrollAreaPrimitive.Corner, {})
|
|
22
|
+
]
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function ScrollBar({ className, orientation = "vertical", ...props }) {
|
|
26
|
+
return /* @__PURE__ */ jsx(ScrollAreaPrimitive.ScrollAreaScrollbar, {
|
|
27
|
+
"data-slot": "scroll-area-scrollbar",
|
|
28
|
+
orientation,
|
|
29
|
+
className: cn("flex touch-none p-px transition-colors select-none", orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent", orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent", className),
|
|
30
|
+
...props,
|
|
31
|
+
children: /* @__PURE__ */ jsx(ScrollAreaPrimitive.ScrollAreaThumb, {
|
|
32
|
+
"data-slot": "scroll-area-thumb",
|
|
33
|
+
className: "bg-border relative flex-1 rounded-full"
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { ScrollArea };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from "../ui-kit-core/dist/utils.js";
|
|
4
|
+
import "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
7
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
8
|
+
|
|
9
|
+
//#region ../../libs/ui-kit-web/dist/ui/select.js
|
|
10
|
+
function Select({ ...props }) {
|
|
11
|
+
return /* @__PURE__ */ jsx(SelectPrimitive.Root, {
|
|
12
|
+
"data-slot": "select",
|
|
13
|
+
...props
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function SelectValue({ ...props }) {
|
|
17
|
+
return /* @__PURE__ */ jsx(SelectPrimitive.Value, {
|
|
18
|
+
"data-slot": "select-value",
|
|
19
|
+
...props
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function SelectTrigger({ className, size = "default", children, ...props }) {
|
|
23
|
+
return /* @__PURE__ */ jsxs(SelectPrimitive.Trigger, {
|
|
24
|
+
"data-slot": "select-trigger",
|
|
25
|
+
"data-size": size,
|
|
26
|
+
className: cn("aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive border-input focus-visible:border-ring focus-visible:ring-ring/50 data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-2xs outline-hidden transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
|
|
27
|
+
...props,
|
|
28
|
+
children: [children, /* @__PURE__ */ jsx(SelectPrimitive.Icon, {
|
|
29
|
+
asChild: true,
|
|
30
|
+
children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4 opacity-50" })
|
|
31
|
+
})]
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function SelectContent({ className, children, position = "popper", ...props }) {
|
|
35
|
+
return /* @__PURE__ */ jsx(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(SelectPrimitive.Content, {
|
|
36
|
+
"data-slot": "select-content",
|
|
37
|
+
className: cn("bg-popover text-popover-foreground 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 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 relative z-50 max-h-(--radix-select-content-available-height) min-w-32 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
|
|
38
|
+
position,
|
|
39
|
+
...props,
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ jsx(SelectScrollUpButton, {}),
|
|
42
|
+
/* @__PURE__ */ jsx(SelectPrimitive.Viewport, {
|
|
43
|
+
className: cn("p-1", position === "popper" && "h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1"),
|
|
44
|
+
children
|
|
45
|
+
}),
|
|
46
|
+
/* @__PURE__ */ jsx(SelectScrollDownButton, {})
|
|
47
|
+
]
|
|
48
|
+
}) });
|
|
49
|
+
}
|
|
50
|
+
function SelectItem({ className, children, ...props }) {
|
|
51
|
+
return /* @__PURE__ */ jsxs(SelectPrimitive.Item, {
|
|
52
|
+
"data-slot": "select-item",
|
|
53
|
+
className: cn("focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-xs py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className),
|
|
54
|
+
...props,
|
|
55
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
56
|
+
className: "absolute right-2 flex size-3.5 items-center justify-center",
|
|
57
|
+
children: /* @__PURE__ */ jsx(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-4" }) })
|
|
58
|
+
}), /* @__PURE__ */ jsx(SelectPrimitive.ItemText, { children })]
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function SelectScrollUpButton({ className, ...props }) {
|
|
62
|
+
return /* @__PURE__ */ jsx(SelectPrimitive.ScrollUpButton, {
|
|
63
|
+
"data-slot": "select-scroll-up-button",
|
|
64
|
+
className: cn("flex cursor-default items-center justify-center py-1", className),
|
|
65
|
+
...props,
|
|
66
|
+
children: /* @__PURE__ */ jsx(ChevronUpIcon, { className: "size-4" })
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function SelectScrollDownButton({ className, ...props }) {
|
|
70
|
+
return /* @__PURE__ */ jsx(SelectPrimitive.ScrollDownButton, {
|
|
71
|
+
"data-slot": "select-scroll-down-button",
|
|
72
|
+
className: cn("flex cursor-default items-center justify-center py-1", className),
|
|
73
|
+
...props,
|
|
74
|
+
children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4" })
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
export { Select, SelectContent, SelectItem, SelectTrigger, SelectValue };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { cn } from "./utils.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region ../../libs/ui-kit-web/dist/ui/skeleton.js
|
|
5
|
+
function Skeleton({ className, ...props }) {
|
|
6
|
+
return /* @__PURE__ */ jsx("div", {
|
|
7
|
+
"data-slot": "skeleton",
|
|
8
|
+
className: cn("bg-accent animate-pulse rounded-md", className),
|
|
9
|
+
...props
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { Skeleton };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from "../ui-kit-core/dist/utils.js";
|
|
4
|
+
import "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
7
|
+
|
|
8
|
+
//#region ../../libs/ui-kit-web/dist/ui/tooltip.js
|
|
9
|
+
function TooltipProvider({ delayDuration = 0, ...props }) {
|
|
10
|
+
return /* @__PURE__ */ jsx(TooltipPrimitive.Provider, {
|
|
11
|
+
"data-slot": "tooltip-provider",
|
|
12
|
+
delayDuration,
|
|
13
|
+
...props
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function Tooltip({ ...props }) {
|
|
17
|
+
return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx(TooltipPrimitive.Root, {
|
|
18
|
+
"data-slot": "tooltip",
|
|
19
|
+
...props
|
|
20
|
+
}) });
|
|
21
|
+
}
|
|
22
|
+
function TooltipTrigger({ ...props }) {
|
|
23
|
+
return /* @__PURE__ */ jsx(TooltipPrimitive.Trigger, {
|
|
24
|
+
"data-slot": "tooltip-trigger",
|
|
25
|
+
...props
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function TooltipContent({ className, sideOffset = 0, children, ...props }) {
|
|
29
|
+
return /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(TooltipPrimitive.Content, {
|
|
30
|
+
"data-slot": "tooltip-content",
|
|
31
|
+
sideOffset,
|
|
32
|
+
className: cn("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", className),
|
|
33
|
+
...props,
|
|
34
|
+
children: [children, /* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { className: "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px]" })]
|
|
35
|
+
}) });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
|
@@ -1 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ScrollArea } from "../../libs/ui-kit-web/dist/ui/scroll-area.js";
|
|
4
|
+
import { cn } from "../../libs/ui-kit-web/dist/ui/utils.js";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
|
|
8
|
+
//#region src/presentation/components/ChatContainer.tsx
|
|
9
|
+
/**
|
|
10
|
+
* Container component for chat messages with scrolling
|
|
11
|
+
*/
|
|
12
|
+
function ChatContainer({ children, className, showScrollButton = true }) {
|
|
13
|
+
const scrollRef = React.useRef(null);
|
|
14
|
+
const [showScrollDown, setShowScrollDown] = React.useState(false);
|
|
15
|
+
React.useEffect(() => {
|
|
16
|
+
const container = scrollRef.current;
|
|
17
|
+
if (!container) return;
|
|
18
|
+
if (container.scrollHeight - container.scrollTop <= container.clientHeight + 100) container.scrollTop = container.scrollHeight;
|
|
19
|
+
}, [children]);
|
|
20
|
+
const handleScroll = React.useCallback((event) => {
|
|
21
|
+
const container = event.currentTarget;
|
|
22
|
+
setShowScrollDown(!(container.scrollHeight - container.scrollTop <= container.clientHeight + 100));
|
|
23
|
+
}, []);
|
|
24
|
+
const scrollToBottom = React.useCallback(() => {
|
|
25
|
+
const container = scrollRef.current;
|
|
26
|
+
if (container) container.scrollTo({
|
|
27
|
+
top: container.scrollHeight,
|
|
28
|
+
behavior: "smooth"
|
|
29
|
+
});
|
|
30
|
+
}, []);
|
|
31
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
32
|
+
className: cn("relative flex flex-1 flex-col", className),
|
|
33
|
+
children: [/* @__PURE__ */ jsx(ScrollArea, {
|
|
34
|
+
ref: scrollRef,
|
|
35
|
+
className: "flex-1",
|
|
36
|
+
onScroll: handleScroll,
|
|
37
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
38
|
+
className: "flex flex-col gap-4 p-4",
|
|
39
|
+
children
|
|
40
|
+
})
|
|
41
|
+
}), showScrollButton && showScrollDown && /* @__PURE__ */ jsxs("button", {
|
|
42
|
+
onClick: scrollToBottom,
|
|
43
|
+
className: cn("absolute bottom-4 left-1/2 -translate-x-1/2", "bg-primary text-primary-foreground", "rounded-full px-3 py-1.5 text-sm font-medium shadow-lg", "hover:bg-primary/90 transition-colors", "flex items-center gap-1.5"),
|
|
44
|
+
"aria-label": "Scroll to bottom",
|
|
45
|
+
children: [/* @__PURE__ */ jsx("svg", {
|
|
46
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
47
|
+
width: "16",
|
|
48
|
+
height: "16",
|
|
49
|
+
viewBox: "0 0 24 24",
|
|
50
|
+
fill: "none",
|
|
51
|
+
stroke: "currentColor",
|
|
52
|
+
strokeWidth: "2",
|
|
53
|
+
strokeLinecap: "round",
|
|
54
|
+
strokeLinejoin: "round",
|
|
55
|
+
children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
|
|
56
|
+
}), "New messages"]
|
|
57
|
+
})]
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { ChatContainer };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChatAttachment } from "../../core/message-types.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/presentation/components/ChatInput.d.ts
|
|
5
5
|
interface ChatInputProps {
|
|
@@ -29,6 +29,6 @@ declare function ChatInput({
|
|
|
29
29
|
className,
|
|
30
30
|
showAttachments,
|
|
31
31
|
maxAttachments
|
|
32
|
-
}: ChatInputProps):
|
|
32
|
+
}: ChatInputProps): react_jsx_runtime1.JSX.Element;
|
|
33
33
|
//#endregion
|
|
34
34
|
export { ChatInput };
|
|
@@ -1 +1,149 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../libs/ui-kit-web/dist/ui/utils.js";
|
|
4
|
+
import { Button$1 } from "../../libs/design-system/dist/components/atoms/Button.js";
|
|
5
|
+
import { Textarea$1 } from "../../libs/design-system/dist/components/atoms/Textarea.js";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Code, FileText, Loader2, Paperclip, Send, X } from "lucide-react";
|
|
9
|
+
|
|
10
|
+
//#region src/presentation/components/ChatInput.tsx
|
|
11
|
+
/**
|
|
12
|
+
* Chat input component with attachment support
|
|
13
|
+
*/
|
|
14
|
+
function ChatInput({ onSend, disabled = false, isLoading = false, placeholder = "Type a message...", className, showAttachments = true, maxAttachments = 5 }) {
|
|
15
|
+
const [content, setContent] = React.useState("");
|
|
16
|
+
const [attachments, setAttachments] = React.useState([]);
|
|
17
|
+
const textareaRef = React.useRef(null);
|
|
18
|
+
const fileInputRef = React.useRef(null);
|
|
19
|
+
const canSend = content.trim().length > 0 || attachments.length > 0;
|
|
20
|
+
const handleSubmit = React.useCallback((e) => {
|
|
21
|
+
e?.preventDefault();
|
|
22
|
+
if (!canSend || disabled || isLoading) return;
|
|
23
|
+
onSend(content.trim(), attachments.length > 0 ? attachments : void 0);
|
|
24
|
+
setContent("");
|
|
25
|
+
setAttachments([]);
|
|
26
|
+
textareaRef.current?.focus();
|
|
27
|
+
}, [
|
|
28
|
+
canSend,
|
|
29
|
+
content,
|
|
30
|
+
attachments,
|
|
31
|
+
disabled,
|
|
32
|
+
isLoading,
|
|
33
|
+
onSend
|
|
34
|
+
]);
|
|
35
|
+
const handleKeyDown = React.useCallback((e) => {
|
|
36
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
handleSubmit();
|
|
39
|
+
}
|
|
40
|
+
}, [handleSubmit]);
|
|
41
|
+
const handleFileSelect = React.useCallback(async (e) => {
|
|
42
|
+
const files = e.target.files;
|
|
43
|
+
if (!files) return;
|
|
44
|
+
const newAttachments = [];
|
|
45
|
+
for (const file of Array.from(files)) {
|
|
46
|
+
if (attachments.length + newAttachments.length >= maxAttachments) break;
|
|
47
|
+
const content$1 = await file.text();
|
|
48
|
+
const extension = file.name.split(".").pop()?.toLowerCase() ?? "";
|
|
49
|
+
const isCode = [
|
|
50
|
+
"ts",
|
|
51
|
+
"tsx",
|
|
52
|
+
"js",
|
|
53
|
+
"jsx",
|
|
54
|
+
"py",
|
|
55
|
+
"go",
|
|
56
|
+
"rs",
|
|
57
|
+
"java"
|
|
58
|
+
].includes(extension);
|
|
59
|
+
newAttachments.push({
|
|
60
|
+
id: `att_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
|
|
61
|
+
type: isCode ? "code" : "file",
|
|
62
|
+
name: file.name,
|
|
63
|
+
content: content$1,
|
|
64
|
+
mimeType: file.type,
|
|
65
|
+
size: file.size
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
setAttachments((prev) => [...prev, ...newAttachments]);
|
|
69
|
+
e.target.value = "";
|
|
70
|
+
}, [attachments.length, maxAttachments]);
|
|
71
|
+
const removeAttachment = React.useCallback((id) => {
|
|
72
|
+
setAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
73
|
+
}, []);
|
|
74
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
75
|
+
className: cn("flex flex-col gap-2", className),
|
|
76
|
+
children: [
|
|
77
|
+
attachments.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
78
|
+
className: "flex flex-wrap gap-2",
|
|
79
|
+
children: attachments.map((attachment) => /* @__PURE__ */ jsxs("div", {
|
|
80
|
+
className: cn("flex items-center gap-1.5 rounded-md px-2 py-1", "bg-muted text-muted-foreground text-sm"),
|
|
81
|
+
children: [
|
|
82
|
+
attachment.type === "code" ? /* @__PURE__ */ jsx(Code, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(FileText, { className: "h-3.5 w-3.5" }),
|
|
83
|
+
/* @__PURE__ */ jsx("span", {
|
|
84
|
+
className: "max-w-[150px] truncate",
|
|
85
|
+
children: attachment.name
|
|
86
|
+
}),
|
|
87
|
+
/* @__PURE__ */ jsx("button", {
|
|
88
|
+
type: "button",
|
|
89
|
+
onClick: () => removeAttachment(attachment.id),
|
|
90
|
+
className: "hover:text-foreground",
|
|
91
|
+
"aria-label": `Remove ${attachment.name}`,
|
|
92
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
93
|
+
})
|
|
94
|
+
]
|
|
95
|
+
}, attachment.id))
|
|
96
|
+
}),
|
|
97
|
+
/* @__PURE__ */ jsxs("form", {
|
|
98
|
+
onSubmit: handleSubmit,
|
|
99
|
+
className: "flex items-end gap-2",
|
|
100
|
+
children: [
|
|
101
|
+
showAttachments && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
|
|
102
|
+
ref: fileInputRef,
|
|
103
|
+
type: "file",
|
|
104
|
+
multiple: true,
|
|
105
|
+
accept: ".ts,.tsx,.js,.jsx,.json,.md,.txt,.py,.go,.rs,.java,.yaml,.yml",
|
|
106
|
+
onChange: handleFileSelect,
|
|
107
|
+
className: "hidden",
|
|
108
|
+
"aria-label": "Attach files"
|
|
109
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
110
|
+
type: "button",
|
|
111
|
+
variant: "ghost",
|
|
112
|
+
size: "sm",
|
|
113
|
+
onPress: () => fileInputRef.current?.click(),
|
|
114
|
+
disabled: disabled || attachments.length >= maxAttachments,
|
|
115
|
+
"aria-label": "Attach files",
|
|
116
|
+
children: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
|
|
117
|
+
})] }),
|
|
118
|
+
/* @__PURE__ */ jsx("div", {
|
|
119
|
+
className: "relative flex-1",
|
|
120
|
+
children: /* @__PURE__ */ jsx(Textarea$1, {
|
|
121
|
+
value: content,
|
|
122
|
+
onChange: (e) => setContent(e.target.value),
|
|
123
|
+
onKeyDown: handleKeyDown,
|
|
124
|
+
placeholder,
|
|
125
|
+
disabled,
|
|
126
|
+
className: cn("min-h-[44px] max-h-[200px] resize-none pr-12", "focus-visible:ring-1"),
|
|
127
|
+
rows: 1,
|
|
128
|
+
"aria-label": "Chat message"
|
|
129
|
+
})
|
|
130
|
+
}),
|
|
131
|
+
/* @__PURE__ */ jsx(Button$1, {
|
|
132
|
+
type: "submit",
|
|
133
|
+
disabled: !canSend || disabled || isLoading,
|
|
134
|
+
size: "sm",
|
|
135
|
+
"aria-label": isLoading ? "Sending..." : "Send message",
|
|
136
|
+
children: isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
|
|
137
|
+
})
|
|
138
|
+
]
|
|
139
|
+
}),
|
|
140
|
+
/* @__PURE__ */ jsx("p", {
|
|
141
|
+
className: "text-muted-foreground text-xs",
|
|
142
|
+
children: "Press Enter to send, Shift+Enter for new line"
|
|
143
|
+
})
|
|
144
|
+
]
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
//#endregion
|
|
149
|
+
export { ChatInput };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChatMessage } from "../../core/message-types.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/presentation/components/ChatMessage.d.ts
|
|
5
5
|
interface ChatMessageProps {
|
|
@@ -18,6 +18,6 @@ declare function ChatMessage$1({
|
|
|
18
18
|
className,
|
|
19
19
|
showCopy,
|
|
20
20
|
showAvatar
|
|
21
|
-
}: ChatMessageProps):
|
|
21
|
+
}: ChatMessageProps): react_jsx_runtime0.JSX.Element;
|
|
22
22
|
//#endregion
|
|
23
23
|
export { ChatMessage$1 as ChatMessage };
|
|
@@ -1 +1,135 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../libs/ui-kit-web/dist/ui/utils.js";
|
|
4
|
+
import { Avatar, AvatarFallback } from "../../libs/ui-kit-web/dist/ui/avatar.js";
|
|
5
|
+
import { Skeleton } from "../../libs/ui-kit-web/dist/ui/skeleton.js";
|
|
6
|
+
import { Button$1 } from "../../libs/design-system/dist/components/atoms/Button.js";
|
|
7
|
+
import { CodePreview } from "./CodePreview.js";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { AlertCircle, Bot, Check, Copy, User } from "lucide-react";
|
|
11
|
+
|
|
12
|
+
//#region src/presentation/components/ChatMessage.tsx
|
|
13
|
+
/**
|
|
14
|
+
* Extract code blocks from message content
|
|
15
|
+
*/
|
|
16
|
+
function extractCodeBlocks(content) {
|
|
17
|
+
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
18
|
+
const blocks = [];
|
|
19
|
+
let match;
|
|
20
|
+
while ((match = codeBlockRegex.exec(content)) !== null) blocks.push({
|
|
21
|
+
language: match[1] ?? "text",
|
|
22
|
+
code: match[2] ?? "",
|
|
23
|
+
raw: match[0]
|
|
24
|
+
});
|
|
25
|
+
return blocks;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Render message content with code blocks
|
|
29
|
+
*/
|
|
30
|
+
function MessageContent({ content }) {
|
|
31
|
+
const codeBlocks = extractCodeBlocks(content);
|
|
32
|
+
if (codeBlocks.length === 0) return /* @__PURE__ */ jsx("p", {
|
|
33
|
+
className: "whitespace-pre-wrap",
|
|
34
|
+
children: content
|
|
35
|
+
});
|
|
36
|
+
let remaining = content;
|
|
37
|
+
const parts = [];
|
|
38
|
+
let key = 0;
|
|
39
|
+
for (const block of codeBlocks) {
|
|
40
|
+
const [before, after] = remaining.split(block.raw);
|
|
41
|
+
if (before) parts.push(/* @__PURE__ */ jsx("p", {
|
|
42
|
+
className: "whitespace-pre-wrap",
|
|
43
|
+
children: before.trim()
|
|
44
|
+
}, key++));
|
|
45
|
+
parts.push(/* @__PURE__ */ jsx(CodePreview, {
|
|
46
|
+
code: block.code,
|
|
47
|
+
language: block.language,
|
|
48
|
+
className: "my-2"
|
|
49
|
+
}, key++));
|
|
50
|
+
remaining = after ?? "";
|
|
51
|
+
}
|
|
52
|
+
if (remaining.trim()) parts.push(/* @__PURE__ */ jsx("p", {
|
|
53
|
+
className: "whitespace-pre-wrap",
|
|
54
|
+
children: remaining.trim()
|
|
55
|
+
}, key++));
|
|
56
|
+
return /* @__PURE__ */ jsx(Fragment, { children: parts });
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Chat message component
|
|
60
|
+
*/
|
|
61
|
+
function ChatMessage({ message, className, showCopy = true, showAvatar = true }) {
|
|
62
|
+
const [copied, setCopied] = React.useState(false);
|
|
63
|
+
const isUser = message.role === "user";
|
|
64
|
+
const isError = message.status === "error";
|
|
65
|
+
const isStreaming = message.status === "streaming";
|
|
66
|
+
const handleCopy = React.useCallback(async () => {
|
|
67
|
+
await navigator.clipboard.writeText(message.content);
|
|
68
|
+
setCopied(true);
|
|
69
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
70
|
+
}, [message.content]);
|
|
71
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
72
|
+
className: cn("group flex gap-3", isUser && "flex-row-reverse", className),
|
|
73
|
+
children: [showAvatar && /* @__PURE__ */ jsx(Avatar, {
|
|
74
|
+
className: "h-8 w-8 shrink-0",
|
|
75
|
+
children: /* @__PURE__ */ jsx(AvatarFallback, {
|
|
76
|
+
className: cn(isUser ? "bg-primary text-primary-foreground" : "bg-muted"),
|
|
77
|
+
children: isUser ? /* @__PURE__ */ jsx(User, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Bot, { className: "h-4 w-4" })
|
|
78
|
+
})
|
|
79
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
80
|
+
className: cn("flex max-w-[80%] flex-col gap-1", isUser && "items-end"),
|
|
81
|
+
children: [
|
|
82
|
+
/* @__PURE__ */ jsx("div", {
|
|
83
|
+
className: cn("rounded-2xl px-4 py-2", isUser ? "bg-primary text-primary-foreground" : "bg-muted text-foreground", isError && "border-destructive bg-destructive/10 border"),
|
|
84
|
+
children: isError && message.error ? /* @__PURE__ */ jsxs("div", {
|
|
85
|
+
className: "flex items-start gap-2",
|
|
86
|
+
children: [/* @__PURE__ */ jsx(AlertCircle, { className: "text-destructive mt-0.5 h-4 w-4 shrink-0" }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
87
|
+
className: "text-destructive font-medium",
|
|
88
|
+
children: message.error.code
|
|
89
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
90
|
+
className: "text-muted-foreground text-sm",
|
|
91
|
+
children: message.error.message
|
|
92
|
+
})] })]
|
|
93
|
+
}) : isStreaming && !message.content ? /* @__PURE__ */ jsxs("div", {
|
|
94
|
+
className: "flex flex-col gap-2",
|
|
95
|
+
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-48" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-32" })]
|
|
96
|
+
}) : /* @__PURE__ */ jsx(MessageContent, { content: message.content })
|
|
97
|
+
}),
|
|
98
|
+
/* @__PURE__ */ jsxs("div", {
|
|
99
|
+
className: cn("flex items-center gap-2 text-xs", "text-muted-foreground opacity-0 transition-opacity", "group-hover:opacity-100"),
|
|
100
|
+
children: [
|
|
101
|
+
/* @__PURE__ */ jsx("span", { children: new Date(message.createdAt).toLocaleTimeString([], {
|
|
102
|
+
hour: "2-digit",
|
|
103
|
+
minute: "2-digit"
|
|
104
|
+
}) }),
|
|
105
|
+
message.usage && /* @__PURE__ */ jsxs("span", { children: [message.usage.inputTokens + message.usage.outputTokens, " tokens"] }),
|
|
106
|
+
showCopy && !isUser && message.content && /* @__PURE__ */ jsx(Button$1, {
|
|
107
|
+
variant: "ghost",
|
|
108
|
+
size: "sm",
|
|
109
|
+
className: "h-6 w-6 p-0",
|
|
110
|
+
onPress: handleCopy,
|
|
111
|
+
"aria-label": copied ? "Copied" : "Copy message",
|
|
112
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3" })
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
}),
|
|
116
|
+
message.reasoning && /* @__PURE__ */ jsxs("details", {
|
|
117
|
+
className: "text-muted-foreground mt-2 text-sm",
|
|
118
|
+
children: [/* @__PURE__ */ jsx("summary", {
|
|
119
|
+
className: "cursor-pointer hover:underline",
|
|
120
|
+
children: "View reasoning"
|
|
121
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
122
|
+
className: "bg-muted mt-1 rounded-md p-2",
|
|
123
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
124
|
+
className: "whitespace-pre-wrap",
|
|
125
|
+
children: message.reasoning
|
|
126
|
+
})
|
|
127
|
+
})]
|
|
128
|
+
})
|
|
129
|
+
]
|
|
130
|
+
})]
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
//#endregion
|
|
135
|
+
export { ChatMessage };
|