@lssm/module.ai-chat 0.0.0-canary-20251217060834 → 0.0.0-canary-20251217072406

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/ai-chat.feature.js +93 -1
  2. package/dist/context/context-builder.js +147 -2
  3. package/dist/context/file-operations.js +174 -1
  4. package/dist/context/index.js +5 -1
  5. package/dist/context/workspace-context.js +123 -2
  6. package/dist/core/chat-service.js +211 -2
  7. package/dist/core/conversation-store.js +108 -1
  8. package/dist/core/index.js +4 -1
  9. package/dist/index.d.ts +0 -2
  10. package/dist/index.js +22 -1
  11. package/dist/libs/ai-providers/dist/factory.js +225 -0
  12. package/dist/libs/ai-providers/dist/index.js +4 -0
  13. package/dist/libs/ai-providers/dist/legacy.js +2 -0
  14. package/dist/libs/ai-providers/dist/models.js +299 -0
  15. package/dist/libs/ai-providers/dist/validation.js +60 -0
  16. package/dist/libs/design-system/dist/_virtual/rolldown_runtime.js +5 -0
  17. package/dist/libs/design-system/dist/components/atoms/Button.js +33 -0
  18. package/dist/libs/design-system/dist/components/atoms/Textarea.js +35 -0
  19. package/dist/libs/design-system/dist/lib/keyboard.js +193 -0
  20. package/dist/libs/design-system/dist/ui-kit-web/dist/ui/button.js +55 -0
  21. package/dist/libs/design-system/dist/ui-kit-web/dist/ui/textarea.js +16 -0
  22. package/dist/libs/design-system/dist/ui-kit-web/dist/ui-kit-core/dist/utils.js +13 -0
  23. package/dist/libs/ui-kit-web/dist/ui/avatar.js +25 -0
  24. package/dist/libs/ui-kit-web/dist/ui/badge.js +26 -0
  25. package/dist/libs/ui-kit-web/dist/ui/scroll-area.js +39 -0
  26. package/dist/libs/ui-kit-web/dist/ui/select.js +79 -0
  27. package/dist/libs/ui-kit-web/dist/ui/skeleton.js +14 -0
  28. package/dist/libs/ui-kit-web/dist/ui/tooltip.js +39 -0
  29. package/dist/libs/ui-kit-web/dist/ui/utils.js +10 -0
  30. package/dist/libs/ui-kit-web/dist/ui-kit-core/dist/utils.js +10 -0
  31. package/dist/presentation/components/ChatContainer.js +62 -1
  32. package/dist/presentation/components/ChatInput.js +149 -1
  33. package/dist/presentation/components/ChatMessage.js +135 -1
  34. package/dist/presentation/components/CodePreview.js +126 -2
  35. package/dist/presentation/components/ContextIndicator.js +96 -1
  36. package/dist/presentation/components/ModelPicker.d.ts +1 -1
  37. package/dist/presentation/components/ModelPicker.js +197 -1
  38. package/dist/presentation/components/index.js +8 -1
  39. package/dist/presentation/hooks/index.js +4 -1
  40. package/dist/presentation/hooks/useChat.js +171 -1
  41. package/dist/presentation/hooks/useProviders.js +42 -1
  42. package/dist/presentation/index.d.ts +0 -1
  43. package/dist/presentation/index.js +12 -1
  44. package/dist/providers/chat-utilities.js +16 -1
  45. package/dist/providers/index.js +7 -1
  46. 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 };
@@ -0,0 +1,10 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ //#region ../../libs/ui-kit-web/dist/ui/utils.js
5
+ function cn(...inputs) {
6
+ return twMerge(clsx(inputs));
7
+ }
8
+
9
+ //#endregion
10
+ export { cn };
@@ -0,0 +1,10 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ //#region ../../libs/ui-kit-web/dist/ui-kit-core/dist/utils.js
5
+ function cn(...inputs) {
6
+ return twMerge(clsx(inputs));
7
+ }
8
+
9
+ //#endregion
10
+ export { cn };
@@ -1 +1,62 @@
1
- "use client";import*as e from"react";import{ScrollArea as t}from"@lssm/lib.ui-kit-web/ui/scroll-area";import{cn as n}from"@lssm/lib.ui-kit-web/ui/utils";import{jsx as r,jsxs as i}from"react/jsx-runtime";function a({children:a,className:o,showScrollButton:s=!0}){let c=e.useRef(null),[l,u]=e.useState(!1);e.useEffect(()=>{let e=c.current;e&&e.scrollHeight-e.scrollTop<=e.clientHeight+100&&(e.scrollTop=e.scrollHeight)},[a]);let d=e.useCallback(e=>{let t=e.currentTarget;u(!(t.scrollHeight-t.scrollTop<=t.clientHeight+100))},[]),f=e.useCallback(()=>{let e=c.current;e&&e.scrollTo({top:e.scrollHeight,behavior:`smooth`})},[]);return i(`div`,{className:n(`relative flex flex-1 flex-col`,o),children:[r(t,{ref:c,className:`flex-1`,onScroll:d,children:r(`div`,{className:`flex flex-col gap-4 p-4`,children:a})}),s&&l&&i(`button`,{onClick:f,className:n(`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`),"aria-label":`Scroll to bottom`,children:[r(`svg`,{xmlns:`http://www.w3.org/2000/svg`,width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:r(`path`,{d:`m6 9 6 6 6-6`})}),`New messages`]})]})}export{a as ChatContainer};
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 +1,149 @@
1
- "use client";import*as e from"react";import{cn as t}from"@lssm/lib.ui-kit-web/ui/utils";import{Fragment as n,jsx as r,jsxs as i}from"react/jsx-runtime";import{Code as a,FileText as o,Loader2 as s,Paperclip as c,Send as l,X as u}from"lucide-react";import{Button as d,Textarea as f}from"@lssm/lib.design-system";function p({onSend:p,disabled:m=!1,isLoading:h=!1,placeholder:g=`Type a message...`,className:_,showAttachments:v=!0,maxAttachments:y=5}){let[b,x]=e.useState(``),[S,C]=e.useState([]),w=e.useRef(null),T=e.useRef(null),E=b.trim().length>0||S.length>0,D=e.useCallback(e=>{e?.preventDefault(),!(!E||m||h)&&(p(b.trim(),S.length>0?S:void 0),x(``),C([]),w.current?.focus())},[E,b,S,m,h,p]),O=e.useCallback(e=>{e.key===`Enter`&&!e.shiftKey&&(e.preventDefault(),D())},[D]),k=e.useCallback(async e=>{let t=e.target.files;if(!t)return;let n=[];for(let e of Array.from(t)){if(S.length+n.length>=y)break;let t=await e.text(),r=e.name.split(`.`).pop()?.toLowerCase()??``,i=[`ts`,`tsx`,`js`,`jsx`,`py`,`go`,`rs`,`java`].includes(r);n.push({id:`att_${Date.now()}_${Math.random().toString(36).slice(2,9)}`,type:i?`code`:`file`,name:e.name,content:t,mimeType:e.type,size:e.size})}C(e=>[...e,...n]),e.target.value=``},[S.length,y]),A=e.useCallback(e=>{C(t=>t.filter(t=>t.id!==e))},[]);return i(`div`,{className:t(`flex flex-col gap-2`,_),children:[S.length>0&&r(`div`,{className:`flex flex-wrap gap-2`,children:S.map(e=>i(`div`,{className:t(`flex items-center gap-1.5 rounded-md px-2 py-1`,`bg-muted text-muted-foreground text-sm`),children:[e.type===`code`?r(a,{className:`h-3.5 w-3.5`}):r(o,{className:`h-3.5 w-3.5`}),r(`span`,{className:`max-w-[150px] truncate`,children:e.name}),r(`button`,{type:`button`,onClick:()=>A(e.id),className:`hover:text-foreground`,"aria-label":`Remove ${e.name}`,children:r(u,{className:`h-3.5 w-3.5`})})]},e.id))}),i(`form`,{onSubmit:D,className:`flex items-end gap-2`,children:[v&&i(n,{children:[r(`input`,{ref:T,type:`file`,multiple:!0,accept:`.ts,.tsx,.js,.jsx,.json,.md,.txt,.py,.go,.rs,.java,.yaml,.yml`,onChange:k,className:`hidden`,"aria-label":`Attach files`}),r(d,{type:`button`,variant:`ghost`,size:`sm`,onPress:()=>T.current?.click(),disabled:m||S.length>=y,"aria-label":`Attach files`,children:r(c,{className:`h-4 w-4`})})]}),r(`div`,{className:`relative flex-1`,children:r(f,{value:b,onChange:e=>x(e.target.value),onKeyDown:O,placeholder:g,disabled:m,className:t(`min-h-[44px] max-h-[200px] resize-none pr-12`,`focus-visible:ring-1`),rows:1,"aria-label":`Chat message`})}),r(d,{type:`submit`,disabled:!E||m||h,size:`sm`,"aria-label":h?`Sending...`:`Send message`,children:h?r(s,{className:`h-4 w-4 animate-spin`}):r(l,{className:`h-4 w-4`})})]}),r(`p`,{className:`text-muted-foreground text-xs`,children:`Press Enter to send, Shift+Enter for new line`})]})}export{p as ChatInput};
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 +1,135 @@
1
- "use client";import{CodePreview as e}from"./CodePreview.js";import*as t from"react";import{cn as n}from"@lssm/lib.ui-kit-web/ui/utils";import{Fragment as r,jsx as i,jsxs as a}from"react/jsx-runtime";import{Avatar as o,AvatarFallback as s}from"@lssm/lib.ui-kit-web/ui/avatar";import{Skeleton as c}from"@lssm/lib.ui-kit-web/ui/skeleton";import{AlertCircle as l,Bot as u,Check as d,Copy as f,User as p}from"lucide-react";import{Button as m}from"@lssm/lib.design-system";function h(e){let t=/```(\w+)?\n([\s\S]*?)```/g,n=[],r;for(;(r=t.exec(e))!==null;)n.push({language:r[1]??`text`,code:r[2]??``,raw:r[0]});return n}function g({content:t}){let n=h(t);if(n.length===0)return i(`p`,{className:`whitespace-pre-wrap`,children:t});let a=t,o=[],s=0;for(let t of n){let[n,r]=a.split(t.raw);n&&o.push(i(`p`,{className:`whitespace-pre-wrap`,children:n.trim()},s++)),o.push(i(e,{code:t.code,language:t.language,className:`my-2`},s++)),a=r??``}return a.trim()&&o.push(i(`p`,{className:`whitespace-pre-wrap`,children:a.trim()},s++)),i(r,{children:o})}function _({message:e,className:r,showCopy:h=!0,showAvatar:_=!0}){let[v,y]=t.useState(!1),b=e.role===`user`,x=e.status===`error`,S=e.status===`streaming`,C=t.useCallback(async()=>{await navigator.clipboard.writeText(e.content),y(!0),setTimeout(()=>y(!1),2e3)},[e.content]);return a(`div`,{className:n(`group flex gap-3`,b&&`flex-row-reverse`,r),children:[_&&i(o,{className:`h-8 w-8 shrink-0`,children:i(s,{className:n(b?`bg-primary text-primary-foreground`:`bg-muted`),children:i(b?p:u,{className:`h-4 w-4`})})}),a(`div`,{className:n(`flex max-w-[80%] flex-col gap-1`,b&&`items-end`),children:[i(`div`,{className:n(`rounded-2xl px-4 py-2`,b?`bg-primary text-primary-foreground`:`bg-muted text-foreground`,x&&`border-destructive bg-destructive/10 border`),children:x&&e.error?a(`div`,{className:`flex items-start gap-2`,children:[i(l,{className:`text-destructive mt-0.5 h-4 w-4 shrink-0`}),a(`div`,{children:[i(`p`,{className:`text-destructive font-medium`,children:e.error.code}),i(`p`,{className:`text-muted-foreground text-sm`,children:e.error.message})]})]}):S&&!e.content?a(`div`,{className:`flex flex-col gap-2`,children:[i(c,{className:`h-4 w-48`}),i(c,{className:`h-4 w-32`})]}):i(g,{content:e.content})}),a(`div`,{className:n(`flex items-center gap-2 text-xs`,`text-muted-foreground opacity-0 transition-opacity`,`group-hover:opacity-100`),children:[i(`span`,{children:new Date(e.createdAt).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`})}),e.usage&&a(`span`,{children:[e.usage.inputTokens+e.usage.outputTokens,` tokens`]}),h&&!b&&e.content&&i(m,{variant:`ghost`,size:`sm`,className:`h-6 w-6 p-0`,onPress:C,"aria-label":v?`Copied`:`Copy message`,children:i(v?d:f,{className:`h-3 w-3`})})]}),e.reasoning&&a(`details`,{className:`text-muted-foreground mt-2 text-sm`,children:[i(`summary`,{className:`cursor-pointer hover:underline`,children:`View reasoning`}),i(`div`,{className:`bg-muted mt-1 rounded-md p-2`,children:i(`p`,{className:`whitespace-pre-wrap`,children:e.reasoning})})]})]})]})}export{_ as ChatMessage};
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 };