@contractspec/module.ai-chat 1.44.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.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -0
  3. package/dist/ai-chat.feature.d.ts +12 -0
  4. package/dist/ai-chat.feature.d.ts.map +1 -0
  5. package/dist/ai-chat.feature.js +95 -0
  6. package/dist/ai-chat.feature.js.map +1 -0
  7. package/dist/ai-chat.operations.d.ts +243 -0
  8. package/dist/ai-chat.operations.d.ts.map +1 -0
  9. package/dist/ai-chat.operations.js +174 -0
  10. package/dist/ai-chat.operations.js.map +1 -0
  11. package/dist/context/context-builder.d.ts +57 -0
  12. package/dist/context/context-builder.d.ts.map +1 -0
  13. package/dist/context/context-builder.js +148 -0
  14. package/dist/context/context-builder.js.map +1 -0
  15. package/dist/context/file-operations.d.ts +100 -0
  16. package/dist/context/file-operations.d.ts.map +1 -0
  17. package/dist/context/file-operations.js +175 -0
  18. package/dist/context/file-operations.js.map +1 -0
  19. package/dist/context/index.d.ts +4 -0
  20. package/dist/context/index.js +5 -0
  21. package/dist/context/workspace-context.d.ts +117 -0
  22. package/dist/context/workspace-context.d.ts.map +1 -0
  23. package/dist/context/workspace-context.js +124 -0
  24. package/dist/context/workspace-context.js.map +1 -0
  25. package/dist/core/chat-service.d.ts +73 -0
  26. package/dist/core/chat-service.d.ts.map +1 -0
  27. package/dist/core/chat-service.js +227 -0
  28. package/dist/core/chat-service.js.map +1 -0
  29. package/dist/core/conversation-store.d.ts +74 -0
  30. package/dist/core/conversation-store.d.ts.map +1 -0
  31. package/dist/core/conversation-store.js +109 -0
  32. package/dist/core/conversation-store.js.map +1 -0
  33. package/dist/core/index.d.ts +4 -0
  34. package/dist/core/index.js +4 -0
  35. package/dist/core/message-types.d.ts +150 -0
  36. package/dist/core/message-types.d.ts.map +1 -0
  37. package/dist/events.d.ts +115 -0
  38. package/dist/events.d.ts.map +1 -0
  39. package/dist/events.js +100 -0
  40. package/dist/events.js.map +1 -0
  41. package/dist/index.d.ts +21 -0
  42. package/dist/index.js +23 -0
  43. package/dist/libs/schema/dist/EnumType.js +2 -0
  44. package/dist/libs/schema/dist/FieldType.js +50 -0
  45. package/dist/libs/schema/dist/FieldType.js.map +1 -0
  46. package/dist/libs/schema/dist/GraphQLSchemaType.js +1 -0
  47. package/dist/libs/schema/dist/JsonSchemaType.js +1 -0
  48. package/dist/libs/schema/dist/ScalarTypeEnum.js +237 -0
  49. package/dist/libs/schema/dist/ScalarTypeEnum.js.map +1 -0
  50. package/dist/libs/schema/dist/SchemaModel.js +40 -0
  51. package/dist/libs/schema/dist/SchemaModel.js.map +1 -0
  52. package/dist/libs/schema/dist/ZodSchemaType.js +1 -0
  53. package/dist/libs/schema/dist/entity/defineEntity.js +1 -0
  54. package/dist/libs/schema/dist/entity/index.js +2 -0
  55. package/dist/libs/schema/dist/entity/types.js +1 -0
  56. package/dist/libs/schema/dist/index.js +9 -0
  57. package/dist/presentation/components/ChatContainer.d.ts +21 -0
  58. package/dist/presentation/components/ChatContainer.d.ts.map +1 -0
  59. package/dist/presentation/components/ChatContainer.js +63 -0
  60. package/dist/presentation/components/ChatContainer.js.map +1 -0
  61. package/dist/presentation/components/ChatInput.d.ts +35 -0
  62. package/dist/presentation/components/ChatInput.d.ts.map +1 -0
  63. package/dist/presentation/components/ChatInput.js +149 -0
  64. package/dist/presentation/components/ChatInput.js.map +1 -0
  65. package/dist/presentation/components/ChatMessage.d.ts +24 -0
  66. package/dist/presentation/components/ChatMessage.d.ts.map +1 -0
  67. package/dist/presentation/components/ChatMessage.js +136 -0
  68. package/dist/presentation/components/ChatMessage.js.map +1 -0
  69. package/dist/presentation/components/CodePreview.d.ts +40 -0
  70. package/dist/presentation/components/CodePreview.d.ts.map +1 -0
  71. package/dist/presentation/components/CodePreview.js +127 -0
  72. package/dist/presentation/components/CodePreview.js.map +1 -0
  73. package/dist/presentation/components/ContextIndicator.d.ts +26 -0
  74. package/dist/presentation/components/ContextIndicator.d.ts.map +1 -0
  75. package/dist/presentation/components/ContextIndicator.js +97 -0
  76. package/dist/presentation/components/ContextIndicator.js.map +1 -0
  77. package/dist/presentation/components/ModelPicker.d.ts +39 -0
  78. package/dist/presentation/components/ModelPicker.d.ts.map +1 -0
  79. package/dist/presentation/components/ModelPicker.js +202 -0
  80. package/dist/presentation/components/ModelPicker.js.map +1 -0
  81. package/dist/presentation/components/index.d.ts +7 -0
  82. package/dist/presentation/components/index.js +8 -0
  83. package/dist/presentation/hooks/index.d.ts +3 -0
  84. package/dist/presentation/hooks/index.js +4 -0
  85. package/dist/presentation/hooks/useChat.d.ts +67 -0
  86. package/dist/presentation/hooks/useChat.d.ts.map +1 -0
  87. package/dist/presentation/hooks/useChat.js +172 -0
  88. package/dist/presentation/hooks/useChat.js.map +1 -0
  89. package/dist/presentation/hooks/useProviders.d.ts +38 -0
  90. package/dist/presentation/hooks/useProviders.d.ts.map +1 -0
  91. package/dist/presentation/hooks/useProviders.js +41 -0
  92. package/dist/presentation/hooks/useProviders.js.map +1 -0
  93. package/dist/presentation/index.d.ts +11 -0
  94. package/dist/presentation/index.js +12 -0
  95. package/dist/providers/chat-utilities.d.ts +15 -0
  96. package/dist/providers/chat-utilities.d.ts.map +1 -0
  97. package/dist/providers/chat-utilities.js +17 -0
  98. package/dist/providers/chat-utilities.js.map +1 -0
  99. package/dist/providers/index.d.ts +3 -0
  100. package/dist/providers/index.js +4 -0
  101. package/dist/schema.d.ts +222 -0
  102. package/dist/schema.d.ts.map +1 -0
  103. package/dist/schema.js +102 -0
  104. package/dist/schema.js.map +1 -0
  105. package/package.json +80 -0
@@ -0,0 +1,2 @@
1
+ import "./defineEntity.js";
2
+ import "./types.js";
@@ -0,0 +1 @@
1
+ import "zod";
@@ -0,0 +1,9 @@
1
+ import "./EnumType.js";
2
+ import { FieldType } from "./FieldType.js";
3
+ import "./GraphQLSchemaType.js";
4
+ import "./JsonSchemaType.js";
5
+ import { ScalarTypeEnum } from "./ScalarTypeEnum.js";
6
+ import { SchemaModel, defineSchemaModel } from "./SchemaModel.js";
7
+ import "./ZodSchemaType.js";
8
+ import "./entity/defineEntity.js";
9
+ import "./entity/index.js";
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+
4
+ //#region src/presentation/components/ChatContainer.d.ts
5
+ interface ChatContainerProps {
6
+ children: React.ReactNode;
7
+ className?: string;
8
+ /** Show scroll-to-bottom button when scrolled up */
9
+ showScrollButton?: boolean;
10
+ }
11
+ /**
12
+ * Container component for chat messages with scrolling
13
+ */
14
+ declare function ChatContainer({
15
+ children,
16
+ className,
17
+ showScrollButton
18
+ }: ChatContainerProps): react_jsx_runtime0.JSX.Element;
19
+ //#endregion
20
+ export { ChatContainer };
21
+ //# sourceMappingURL=ChatContainer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatContainer.d.ts","names":[],"sources":["../../../src/presentation/components/ChatContainer.tsx"],"sourcesContent":[],"mappings":";;;;UAMiB,kBAAA;YACL,KAAA,CAAM;;EADD;EAUD,gBAAa,CAAA,EAAA,OAAA;;;;;AAIR,iBAJL,aAAA,CAIK;EAAA,QAAA;EAAA,SAAA;EAAA;AAAA,CAAA,EAAlB,kBAAkB,CAAA,EAAA,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -0,0 +1,63 @@
1
+ 'use client';
2
+
3
+ import * as React from "react";
4
+ import { ScrollArea } from "@contractspec/lib.ui-kit-web/ui/scroll-area";
5
+ import { cn } from "@contractspec/lib.ui-kit-web/ui/utils";
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 };
63
+ //# sourceMappingURL=ChatContainer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatContainer.js","names":[],"sources":["../../../src/presentation/components/ChatContainer.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { ScrollArea } from '@contractspec/lib.ui-kit-web/ui/scroll-area';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\n\nexport interface ChatContainerProps {\n children: React.ReactNode;\n className?: string;\n /** Show scroll-to-bottom button when scrolled up */\n showScrollButton?: boolean;\n}\n\n/**\n * Container component for chat messages with scrolling\n */\nexport function ChatContainer({\n children,\n className,\n showScrollButton = true,\n}: ChatContainerProps) {\n const scrollRef = React.useRef<HTMLDivElement>(null);\n const [showScrollDown, setShowScrollDown] = React.useState(false);\n\n // Auto-scroll to bottom when children change\n React.useEffect(() => {\n const container = scrollRef.current;\n if (!container) return;\n\n // Check if user has scrolled up\n const isAtBottom =\n container.scrollHeight - container.scrollTop <=\n container.clientHeight + 100;\n\n if (isAtBottom) {\n container.scrollTop = container.scrollHeight;\n }\n }, [children]);\n\n // Track scroll position for scroll-to-bottom button\n const handleScroll = React.useCallback(\n (event: React.UIEvent<HTMLDivElement>) => {\n const container = event.currentTarget;\n const isAtBottom =\n container.scrollHeight - container.scrollTop <=\n container.clientHeight + 100;\n setShowScrollDown(!isAtBottom);\n },\n []\n );\n\n const scrollToBottom = React.useCallback(() => {\n const container = scrollRef.current;\n if (container) {\n container.scrollTo({\n top: container.scrollHeight,\n behavior: 'smooth',\n });\n }\n }, []);\n\n return (\n <div className={cn('relative flex flex-1 flex-col', className)}>\n <ScrollArea ref={scrollRef} className=\"flex-1\" onScroll={handleScroll}>\n <div className=\"flex flex-col gap-4 p-4\">{children}</div>\n </ScrollArea>\n\n {showScrollButton && showScrollDown && (\n <button\n onClick={scrollToBottom}\n className={cn(\n 'absolute bottom-4 left-1/2 -translate-x-1/2',\n 'bg-primary text-primary-foreground',\n 'rounded-full px-3 py-1.5 text-sm font-medium shadow-lg',\n 'hover:bg-primary/90 transition-colors',\n 'flex items-center gap-1.5'\n )}\n aria-label=\"Scroll to bottom\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n New messages\n </button>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAgBA,SAAgB,cAAc,EAC5B,UACA,WACA,mBAAmB,QACE;CACrB,MAAM,YAAY,MAAM,OAAuB,KAAK;CACpD,MAAM,CAAC,gBAAgB,qBAAqB,MAAM,SAAS,MAAM;AAGjE,OAAM,gBAAgB;EACpB,MAAM,YAAY,UAAU;AAC5B,MAAI,CAAC,UAAW;AAOhB,MAHE,UAAU,eAAe,UAAU,aACnC,UAAU,eAAe,IAGzB,WAAU,YAAY,UAAU;IAEjC,CAAC,SAAS,CAAC;CAGd,MAAM,eAAe,MAAM,aACxB,UAAyC;EACxC,MAAM,YAAY,MAAM;AAIxB,oBAAkB,EAFhB,UAAU,eAAe,UAAU,aACnC,UAAU,eAAe,KACG;IAEhC,EAAE,CACH;CAED,MAAM,iBAAiB,MAAM,kBAAkB;EAC7C,MAAM,YAAY,UAAU;AAC5B,MAAI,UACF,WAAU,SAAS;GACjB,KAAK,UAAU;GACf,UAAU;GACX,CAAC;IAEH,EAAE,CAAC;AAEN,QACE,qBAAC;EAAI,WAAW,GAAG,iCAAiC,UAAU;aAC5D,oBAAC;GAAW,KAAK;GAAW,WAAU;GAAS,UAAU;aACvD,oBAAC;IAAI,WAAU;IAA2B;KAAe;IAC9C,EAEZ,oBAAoB,kBACnB,qBAAC;GACC,SAAS;GACT,WAAW,GACT,+CACA,sCACA,0DACA,yCACA,4BACD;GACD,cAAW;cAEX,oBAAC;IACC,OAAM;IACN,OAAM;IACN,QAAO;IACP,SAAQ;IACR,MAAK;IACL,QAAO;IACP,aAAY;IACZ,eAAc;IACd,gBAAe;cAEf,oBAAC,UAAK,GAAE,iBAAiB;KACrB;IAEC;GAEP"}
@@ -0,0 +1,35 @@
1
+ import { ChatAttachment } from "../../core/message-types.js";
2
+ import * as react_jsx_runtime2 from "react/jsx-runtime";
3
+
4
+ //#region src/presentation/components/ChatInput.d.ts
5
+ interface ChatInputProps {
6
+ /** Called when a message is sent */
7
+ onSend: (content: string, attachments?: ChatAttachment[]) => void;
8
+ /** Whether input is disabled (e.g., during streaming) */
9
+ disabled?: boolean;
10
+ /** Whether currently loading/streaming */
11
+ isLoading?: boolean;
12
+ /** Placeholder text */
13
+ placeholder?: string;
14
+ /** Additional class name */
15
+ className?: string;
16
+ /** Show attachment button */
17
+ showAttachments?: boolean;
18
+ /** Max attachments allowed */
19
+ maxAttachments?: number;
20
+ }
21
+ /**
22
+ * Chat input component with attachment support
23
+ */
24
+ declare function ChatInput({
25
+ onSend,
26
+ disabled,
27
+ isLoading,
28
+ placeholder,
29
+ className,
30
+ showAttachments,
31
+ maxAttachments
32
+ }: ChatInputProps): react_jsx_runtime2.JSX.Element;
33
+ //#endregion
34
+ export { ChatInput };
35
+ //# sourceMappingURL=ChatInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatInput.d.ts","names":[],"sources":["../../../src/presentation/components/ChatInput.tsx"],"sourcesContent":[],"mappings":";;;;UASiB,cAAA;;0CAEyB;EAFzB;EAoBD,QAAA,CAAA,EAAA,OAAS;EACvB;EACA,SAAA,CAAA,EAAA,OAAA;EACA;EACA,WAAA,CAAA,EAAA,MAAA;EACA;EACA,SAAA,CAAA,EAAA,MAAA;EACA;EACC,eAAA,CAAA,EAAA,OAAA;EAAc;EAAA,cAAA,CAAA,EAAA,MAAA;;;;;iBARD,SAAA;;;;;;;;GAQb,iBAAc,kBAAA,CAAA,GAAA,CAAA"}
@@ -0,0 +1,149 @@
1
+ 'use client';
2
+
3
+ import * as React from "react";
4
+ import { cn } from "@contractspec/lib.ui-kit-web/ui/utils";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ import { Code, FileText, Loader2, Paperclip, Send, X } from "lucide-react";
7
+ import { Button, Textarea } from "@contractspec/lib.design-system";
8
+
9
+ //#region src/presentation/components/ChatInput.tsx
10
+ /**
11
+ * Chat input component with attachment support
12
+ */
13
+ function ChatInput({ onSend, disabled = false, isLoading = false, placeholder = "Type a message...", className, showAttachments = true, maxAttachments = 5 }) {
14
+ const [content, setContent] = React.useState("");
15
+ const [attachments, setAttachments] = React.useState([]);
16
+ const textareaRef = React.useRef(null);
17
+ const fileInputRef = React.useRef(null);
18
+ const canSend = content.trim().length > 0 || attachments.length > 0;
19
+ const handleSubmit = React.useCallback((e) => {
20
+ e?.preventDefault();
21
+ if (!canSend || disabled || isLoading) return;
22
+ onSend(content.trim(), attachments.length > 0 ? attachments : void 0);
23
+ setContent("");
24
+ setAttachments([]);
25
+ textareaRef.current?.focus();
26
+ }, [
27
+ canSend,
28
+ content,
29
+ attachments,
30
+ disabled,
31
+ isLoading,
32
+ onSend
33
+ ]);
34
+ const handleKeyDown = React.useCallback((e) => {
35
+ if (e.key === "Enter" && !e.shiftKey) {
36
+ e.preventDefault();
37
+ handleSubmit();
38
+ }
39
+ }, [handleSubmit]);
40
+ const handleFileSelect = React.useCallback(async (e) => {
41
+ const files = e.target.files;
42
+ if (!files) return;
43
+ const newAttachments = [];
44
+ for (const file of Array.from(files)) {
45
+ if (attachments.length + newAttachments.length >= maxAttachments) break;
46
+ const content$1 = await file.text();
47
+ const extension = file.name.split(".").pop()?.toLowerCase() ?? "";
48
+ const isCode = [
49
+ "ts",
50
+ "tsx",
51
+ "js",
52
+ "jsx",
53
+ "py",
54
+ "go",
55
+ "rs",
56
+ "java"
57
+ ].includes(extension);
58
+ newAttachments.push({
59
+ id: `att_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
60
+ type: isCode ? "code" : "file",
61
+ name: file.name,
62
+ content: content$1,
63
+ mimeType: file.type,
64
+ size: file.size
65
+ });
66
+ }
67
+ setAttachments((prev) => [...prev, ...newAttachments]);
68
+ e.target.value = "";
69
+ }, [attachments.length, maxAttachments]);
70
+ const removeAttachment = React.useCallback((id) => {
71
+ setAttachments((prev) => prev.filter((a) => a.id !== id));
72
+ }, []);
73
+ return /* @__PURE__ */ jsxs("div", {
74
+ className: cn("flex flex-col gap-2", className),
75
+ children: [
76
+ attachments.length > 0 && /* @__PURE__ */ jsx("div", {
77
+ className: "flex flex-wrap gap-2",
78
+ children: attachments.map((attachment) => /* @__PURE__ */ jsxs("div", {
79
+ className: cn("flex items-center gap-1.5 rounded-md px-2 py-1", "bg-muted text-muted-foreground text-sm"),
80
+ children: [
81
+ attachment.type === "code" ? /* @__PURE__ */ jsx(Code, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(FileText, { className: "h-3.5 w-3.5" }),
82
+ /* @__PURE__ */ jsx("span", {
83
+ className: "max-w-[150px] truncate",
84
+ children: attachment.name
85
+ }),
86
+ /* @__PURE__ */ jsx("button", {
87
+ type: "button",
88
+ onClick: () => removeAttachment(attachment.id),
89
+ className: "hover:text-foreground",
90
+ "aria-label": `Remove ${attachment.name}`,
91
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
92
+ })
93
+ ]
94
+ }, attachment.id))
95
+ }),
96
+ /* @__PURE__ */ jsxs("form", {
97
+ onSubmit: handleSubmit,
98
+ className: "flex items-end gap-2",
99
+ children: [
100
+ showAttachments && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
101
+ ref: fileInputRef,
102
+ type: "file",
103
+ multiple: true,
104
+ accept: ".ts,.tsx,.js,.jsx,.json,.md,.txt,.py,.go,.rs,.java,.yaml,.yml",
105
+ onChange: handleFileSelect,
106
+ className: "hidden",
107
+ "aria-label": "Attach files"
108
+ }), /* @__PURE__ */ jsx(Button, {
109
+ type: "button",
110
+ variant: "ghost",
111
+ size: "sm",
112
+ onPress: () => fileInputRef.current?.click(),
113
+ disabled: disabled || attachments.length >= maxAttachments,
114
+ "aria-label": "Attach files",
115
+ children: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
116
+ })] }),
117
+ /* @__PURE__ */ jsx("div", {
118
+ className: "relative flex-1",
119
+ children: /* @__PURE__ */ jsx(Textarea, {
120
+ value: content,
121
+ onChange: (e) => setContent(e.target.value),
122
+ onKeyDown: handleKeyDown,
123
+ placeholder,
124
+ disabled,
125
+ className: cn("max-h-[200px] min-h-[44px] resize-none pr-12", "focus-visible:ring-1"),
126
+ rows: 1,
127
+ "aria-label": "Chat message"
128
+ })
129
+ }),
130
+ /* @__PURE__ */ jsx(Button, {
131
+ type: "submit",
132
+ disabled: !canSend || disabled || isLoading,
133
+ size: "sm",
134
+ "aria-label": isLoading ? "Sending..." : "Send message",
135
+ children: isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
136
+ })
137
+ ]
138
+ }),
139
+ /* @__PURE__ */ jsx("p", {
140
+ className: "text-muted-foreground text-xs",
141
+ children: "Press Enter to send, Shift+Enter for new line"
142
+ })
143
+ ]
144
+ });
145
+ }
146
+
147
+ //#endregion
148
+ export { ChatInput };
149
+ //# sourceMappingURL=ChatInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatInput.js","names":["newAttachments: ChatAttachment[]","content"],"sources":["../../../src/presentation/components/ChatInput.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Textarea } from '@contractspec/lib.design-system';\nimport { Button } from '@contractspec/lib.design-system';\nimport { Send, Paperclip, X, Loader2, FileText, Code } from 'lucide-react';\nimport type { ChatAttachment } from '../../core/message-types';\n\nexport interface ChatInputProps {\n /** Called when a message is sent */\n onSend: (content: string, attachments?: ChatAttachment[]) => void;\n /** Whether input is disabled (e.g., during streaming) */\n disabled?: boolean;\n /** Whether currently loading/streaming */\n isLoading?: boolean;\n /** Placeholder text */\n placeholder?: string;\n /** Additional class name */\n className?: string;\n /** Show attachment button */\n showAttachments?: boolean;\n /** Max attachments allowed */\n maxAttachments?: number;\n}\n\n/**\n * Chat input component with attachment support\n */\nexport function ChatInput({\n onSend,\n disabled = false,\n isLoading = false,\n placeholder = 'Type a message...',\n className,\n showAttachments = true,\n maxAttachments = 5,\n}: ChatInputProps) {\n const [content, setContent] = React.useState('');\n const [attachments, setAttachments] = React.useState<ChatAttachment[]>([]);\n const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n const fileInputRef = React.useRef<HTMLInputElement>(null);\n\n const canSend = content.trim().length > 0 || attachments.length > 0;\n\n const handleSubmit = React.useCallback(\n (e?: React.FormEvent) => {\n e?.preventDefault();\n if (!canSend || disabled || isLoading) return;\n\n onSend(content.trim(), attachments.length > 0 ? attachments : undefined);\n setContent('');\n setAttachments([]);\n\n // Focus back on textarea\n textareaRef.current?.focus();\n },\n [canSend, content, attachments, disabled, isLoading, onSend]\n );\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n // Submit on Enter (without Shift)\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n },\n [handleSubmit]\n );\n\n const handleFileSelect = React.useCallback(\n async (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = e.target.files;\n if (!files) return;\n\n const newAttachments: ChatAttachment[] = [];\n\n for (const file of Array.from(files)) {\n if (attachments.length + newAttachments.length >= maxAttachments) break;\n\n const content = await file.text();\n const extension = file.name.split('.').pop()?.toLowerCase() ?? '';\n const isCode = [\n 'ts',\n 'tsx',\n 'js',\n 'jsx',\n 'py',\n 'go',\n 'rs',\n 'java',\n ].includes(extension);\n\n newAttachments.push({\n id: `att_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,\n type: isCode ? 'code' : 'file',\n name: file.name,\n content,\n mimeType: file.type,\n size: file.size,\n });\n }\n\n setAttachments((prev) => [...prev, ...newAttachments]);\n\n // Reset file input\n e.target.value = '';\n },\n [attachments.length, maxAttachments]\n );\n\n const removeAttachment = React.useCallback((id: string) => {\n setAttachments((prev) => prev.filter((a) => a.id !== id));\n }, []);\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n {/* Attachments preview */}\n {attachments.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {attachments.map((attachment) => (\n <div\n key={attachment.id}\n className={cn(\n 'flex items-center gap-1.5 rounded-md px-2 py-1',\n 'bg-muted text-muted-foreground text-sm'\n )}\n >\n {attachment.type === 'code' ? (\n <Code className=\"h-3.5 w-3.5\" />\n ) : (\n <FileText className=\"h-3.5 w-3.5\" />\n )}\n <span className=\"max-w-[150px] truncate\">{attachment.name}</span>\n <button\n type=\"button\"\n onClick={() => removeAttachment(attachment.id)}\n className=\"hover:text-foreground\"\n aria-label={`Remove ${attachment.name}`}\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n ))}\n </div>\n )}\n\n {/* Input form */}\n <form onSubmit={handleSubmit} className=\"flex items-end gap-2\">\n {/* Attachment button */}\n {showAttachments && (\n <>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept=\".ts,.tsx,.js,.jsx,.json,.md,.txt,.py,.go,.rs,.java,.yaml,.yml\"\n onChange={handleFileSelect}\n className=\"hidden\"\n aria-label=\"Attach files\"\n />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onPress={() => fileInputRef.current?.click()}\n disabled={disabled || attachments.length >= maxAttachments}\n aria-label=\"Attach files\"\n >\n <Paperclip className=\"h-4 w-4\" />\n </Button>\n </>\n )}\n\n {/* Text input */}\n <div className=\"relative flex-1\">\n <Textarea\n value={content}\n onChange={(e) => setContent(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled}\n className={cn(\n 'max-h-[200px] min-h-[44px] resize-none pr-12',\n 'focus-visible:ring-1'\n )}\n rows={1}\n aria-label=\"Chat message\"\n />\n </div>\n\n {/* Send button */}\n <Button\n type=\"submit\"\n disabled={!canSend || disabled || isLoading}\n size=\"sm\"\n aria-label={isLoading ? 'Sending...' : 'Send message'}\n >\n {isLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Send className=\"h-4 w-4\" />\n )}\n </Button>\n </form>\n\n {/* Helper text */}\n <p className=\"text-muted-foreground text-xs\">\n Press Enter to send, Shift+Enter for new line\n </p>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { ChatMessage as ChatMessage$1 } from "../../core/message-types.js";
2
+ import * as react_jsx_runtime1 from "react/jsx-runtime";
3
+
4
+ //#region src/presentation/components/ChatMessage.d.ts
5
+ interface ChatMessageProps {
6
+ message: ChatMessage$1;
7
+ className?: string;
8
+ /** Show copy button */
9
+ showCopy?: boolean;
10
+ /** Show avatar */
11
+ showAvatar?: boolean;
12
+ }
13
+ /**
14
+ * Chat message component
15
+ */
16
+ declare function ChatMessage({
17
+ message,
18
+ className,
19
+ showCopy,
20
+ showAvatar
21
+ }: ChatMessageProps): react_jsx_runtime1.JSX.Element;
22
+ //#endregion
23
+ export { ChatMessage };
24
+ //# sourceMappingURL=ChatMessage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatMessage.d.ts","names":[],"sources":["../../../src/presentation/components/ChatMessage.tsx"],"sourcesContent":[],"mappings":";;;;UAWiB,gBAAA;WACN;;EADM;EA+ED,QAAA,CAAA,EAAA,OAAW;EACzB;EACA,UAAA,CAAA,EAAA,OAAA;;;;;AAGiB,iBALH,WAAA,CAKG;EAAA,OAAA;EAAA,SAAA;EAAA,QAAA;EAAA;AAAA,CAAA,EAAhB,gBAAgB,CAAA,EAAA,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -0,0 +1,136 @@
1
+ 'use client';
2
+
3
+ import { CodePreview } from "./CodePreview.js";
4
+ import * as React from "react";
5
+ import { cn } from "@contractspec/lib.ui-kit-web/ui/utils";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { Avatar, AvatarFallback } from "@contractspec/lib.ui-kit-web/ui/avatar";
8
+ import { Skeleton } from "@contractspec/lib.ui-kit-web/ui/skeleton";
9
+ import { AlertCircle, Bot, Check, Copy, User } from "lucide-react";
10
+ import { Button } from "@contractspec/lib.design-system";
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, {
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 };
136
+ //# sourceMappingURL=ChatMessage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatMessage.js","names":["blocks: { language: string; code: string; raw: string }[]","parts: React.ReactNode[]"],"sources":["../../../src/presentation/components/ChatMessage.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Avatar, AvatarFallback } from '@contractspec/lib.ui-kit-web/ui/avatar';\nimport { Skeleton } from '@contractspec/lib.ui-kit-web/ui/skeleton';\nimport { Bot, User, AlertCircle, Copy, Check } from 'lucide-react';\nimport { Button } from '@contractspec/lib.design-system';\nimport type { ChatMessage as ChatMessageType } from '../../core/message-types';\nimport { CodePreview } from './CodePreview';\n\nexport interface ChatMessageProps {\n message: ChatMessageType;\n className?: string;\n /** Show copy button */\n showCopy?: boolean;\n /** Show avatar */\n showAvatar?: boolean;\n}\n\n/**\n * Extract code blocks from message content\n */\nfunction extractCodeBlocks(\n content: string\n): { language: string; code: string; raw: string }[] {\n const codeBlockRegex = /```(\\w+)?\\n([\\s\\S]*?)```/g;\n const blocks: { language: string; code: string; raw: string }[] = [];\n let match;\n\n while ((match = codeBlockRegex.exec(content)) !== null) {\n blocks.push({\n language: match[1] ?? 'text',\n code: match[2] ?? '',\n raw: match[0],\n });\n }\n\n return blocks;\n}\n\n/**\n * Render message content with code blocks\n */\nfunction MessageContent({ content }: { content: string }) {\n const codeBlocks = extractCodeBlocks(content);\n\n if (codeBlocks.length === 0) {\n return <p className=\"whitespace-pre-wrap\">{content}</p>;\n }\n\n // Split content by code blocks and render\n let remaining = content;\n const parts: React.ReactNode[] = [];\n let key = 0;\n\n for (const block of codeBlocks) {\n const [before, after] = remaining.split(block.raw);\n if (before) {\n parts.push(\n <p key={key++} className=\"whitespace-pre-wrap\">\n {before.trim()}\n </p>\n );\n }\n parts.push(\n <CodePreview\n key={key++}\n code={block.code}\n language={block.language}\n className=\"my-2\"\n />\n );\n remaining = after ?? '';\n }\n\n if (remaining.trim()) {\n parts.push(\n <p key={key++} className=\"whitespace-pre-wrap\">\n {remaining.trim()}\n </p>\n );\n }\n\n return <>{parts}</>;\n}\n\n/**\n * Chat message component\n */\nexport function ChatMessage({\n message,\n className,\n showCopy = true,\n showAvatar = true,\n}: ChatMessageProps) {\n const [copied, setCopied] = React.useState(false);\n\n const isUser = message.role === 'user';\n const isError = message.status === 'error';\n const isStreaming = message.status === 'streaming';\n\n const handleCopy = React.useCallback(async () => {\n await navigator.clipboard.writeText(message.content);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }, [message.content]);\n\n return (\n <div\n className={cn(\n 'group flex gap-3',\n isUser && 'flex-row-reverse',\n className\n )}\n >\n {showAvatar && (\n <Avatar className=\"h-8 w-8 shrink-0\">\n <AvatarFallback\n className={cn(\n isUser ? 'bg-primary text-primary-foreground' : 'bg-muted'\n )}\n >\n {isUser ? (\n <User className=\"h-4 w-4\" />\n ) : (\n <Bot className=\"h-4 w-4\" />\n )}\n </AvatarFallback>\n </Avatar>\n )}\n\n <div\n className={cn('flex max-w-[80%] flex-col gap-1', isUser && 'items-end')}\n >\n <div\n className={cn(\n 'rounded-2xl px-4 py-2',\n isUser\n ? 'bg-primary text-primary-foreground'\n : 'bg-muted text-foreground',\n isError && 'border-destructive bg-destructive/10 border'\n )}\n >\n {isError && message.error ? (\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"text-destructive mt-0.5 h-4 w-4 shrink-0\" />\n <div>\n <p className=\"text-destructive font-medium\">\n {message.error.code}\n </p>\n <p className=\"text-muted-foreground text-sm\">\n {message.error.message}\n </p>\n </div>\n </div>\n ) : isStreaming && !message.content ? (\n <div className=\"flex flex-col gap-2\">\n <Skeleton className=\"h-4 w-48\" />\n <Skeleton className=\"h-4 w-32\" />\n </div>\n ) : (\n <MessageContent content={message.content} />\n )}\n </div>\n\n {/* Message meta */}\n <div\n className={cn(\n 'flex items-center gap-2 text-xs',\n 'text-muted-foreground opacity-0 transition-opacity',\n 'group-hover:opacity-100'\n )}\n >\n <span>\n {new Date(message.createdAt).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })}\n </span>\n\n {message.usage && (\n <span>\n {message.usage.inputTokens + message.usage.outputTokens} tokens\n </span>\n )}\n\n {showCopy && !isUser && message.content && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-6 w-6 p-0\"\n onPress={handleCopy}\n aria-label={copied ? 'Copied' : 'Copy message'}\n >\n {copied ? (\n <Check className=\"h-3 w-3\" />\n ) : (\n <Copy className=\"h-3 w-3\" />\n )}\n </Button>\n )}\n </div>\n\n {/* Reasoning (for models that support it) */}\n {message.reasoning && (\n <details className=\"text-muted-foreground mt-2 text-sm\">\n <summary className=\"cursor-pointer hover:underline\">\n View reasoning\n </summary>\n <div className=\"bg-muted mt-1 rounded-md p-2\">\n <p className=\"whitespace-pre-wrap\">{message.reasoning}</p>\n </div>\n </details>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/presentation/components/CodePreview.d.ts
4
+ interface CodePreviewProps {
5
+ /** Code content */
6
+ code: string;
7
+ /** Programming language */
8
+ language?: string;
9
+ /** File name */
10
+ filename?: string;
11
+ /** Additional class name */
12
+ className?: string;
13
+ /** Show copy button */
14
+ showCopy?: boolean;
15
+ /** Show execute button (for applicable languages) */
16
+ showExecute?: boolean;
17
+ /** Called when execute is clicked */
18
+ onExecute?: (code: string) => void;
19
+ /** Show download button */
20
+ showDownload?: boolean;
21
+ /** Max height before scroll */
22
+ maxHeight?: number;
23
+ }
24
+ /**
25
+ * Code preview component with syntax highlighting placeholder
26
+ */
27
+ declare function CodePreview({
28
+ code,
29
+ language,
30
+ filename,
31
+ className,
32
+ showCopy,
33
+ showExecute,
34
+ onExecute,
35
+ showDownload,
36
+ maxHeight
37
+ }: CodePreviewProps): react_jsx_runtime0.JSX.Element;
38
+ //#endregion
39
+ export { CodePreview };
40
+ //# sourceMappingURL=CodePreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CodePreview.d.ts","names":[],"sources":["../../../src/presentation/components/CodePreview.tsx"],"sourcesContent":[],"mappings":";;;UAOiB,gBAAA;;;EAAA;EAgDD,QAAA,CAAA,EAAA,MAAW;EACzB;EACA,QAAA,CAAA,EAAA,MAAA;EACA;EACA,SAAA,CAAA,EAAA,MAAA;EACA;EACA,QAAA,CAAA,EAAA,OAAA;EACA;EACA,WAAA,CAAA,EAAA,OAAA;EACA;EACC,SAAA,CAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAAgB;EAAA,YAAA,CAAA,EAAA,OAAA;;;;;;;iBAVH,WAAA;;;;;;;;;;GAUb,mBAAgB,kBAAA,CAAA,GAAA,CAAA"}