@shapesos/clay 0.6.0 → 0.8.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.
- package/dist/button.cjs +604 -0
- package/dist/button.cjs.map +1 -0
- package/dist/button.d.cts +53 -0
- package/dist/button.d.ts +53 -0
- package/dist/button.js +17 -0
- package/dist/chat.cjs +117 -71
- package/dist/chat.cjs.map +1 -1
- package/dist/chat.d.cts +5 -0
- package/dist/chat.d.ts +5 -0
- package/dist/chat.js +2 -2
- package/dist/chunk-FFABDVB3.js +149 -0
- package/dist/chunk-FFABDVB3.js.map +1 -0
- package/dist/{chunk-J6VPAHO3.js → chunk-KYTNUZO6.js} +90 -46
- package/dist/chunk-KYTNUZO6.js.map +1 -0
- package/dist/chunk-N4FMZU56.js +71 -0
- package/dist/chunk-N4FMZU56.js.map +1 -0
- package/dist/{chunk-WYGH3FKT.js → chunk-QUJYTIGD.js} +2 -2
- package/dist/chunk-QUJYTIGD.js.map +1 -0
- package/dist/index.cjs +357 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +18 -3
- package/dist/utils.cjs +73 -3
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +26 -1
- package/dist/utils.d.ts +26 -1
- package/dist/utils.js +8 -3
- package/package.json +6 -1
- package/dist/chunk-DRBRT57F.js +0 -1
- package/dist/chunk-J6VPAHO3.js.map +0 -1
- package/dist/chunk-WYGH3FKT.js.map +0 -1
- /package/dist/{chunk-DRBRT57F.js.map → button.js.map} +0 -0
|
@@ -10,16 +10,71 @@ import {
|
|
|
10
10
|
} from "./chunk-JF3P66JF.js";
|
|
11
11
|
import {
|
|
12
12
|
markdownToPlainText
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-QUJYTIGD.js";
|
|
14
14
|
|
|
15
15
|
// src/components/chat/chat-message-content/chat-message-content.tsx
|
|
16
|
+
import { useMemo } from "react";
|
|
16
17
|
import ReactMarkdown from "react-markdown";
|
|
17
18
|
import remarkBreaks from "remark-breaks";
|
|
18
19
|
import remarkGfm from "remark-gfm";
|
|
19
20
|
|
|
20
|
-
// src/components/chat/
|
|
21
|
+
// src/components/chat/scrollable-table/scrollable-table.tsx
|
|
22
|
+
import { useContext as useContext2 } from "react";
|
|
23
|
+
|
|
24
|
+
// src/components/chat/chat-context/chat-context.ts
|
|
25
|
+
import { createContext, useContext } from "react";
|
|
26
|
+
var ChatContext = createContext(null);
|
|
27
|
+
function useChatContext() {
|
|
28
|
+
const context = useContext(ChatContext);
|
|
29
|
+
if (!context) {
|
|
30
|
+
throw new Error("useChatContext must be used within a Chat.Root component");
|
|
31
|
+
}
|
|
32
|
+
return context;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/components/chat/scrollable-table/scrollable-table-styles.ts
|
|
21
36
|
import styled from "styled-components";
|
|
22
|
-
var
|
|
37
|
+
var TableContainer = styled.div`
|
|
38
|
+
position: relative;
|
|
39
|
+
margin-block: 8px 16px;
|
|
40
|
+
`;
|
|
41
|
+
var TableScroll = styled.div`
|
|
42
|
+
width: 100%;
|
|
43
|
+
overflow-x: auto;
|
|
44
|
+
overflow-y: hidden;
|
|
45
|
+
`;
|
|
46
|
+
var TableActionsWrapper = styled.div`
|
|
47
|
+
display: flex;
|
|
48
|
+
justify-content: flex-start;
|
|
49
|
+
margin-block: 8px 0;
|
|
50
|
+
`;
|
|
51
|
+
var TableActionsDivider = styled.div`
|
|
52
|
+
height: 1px;
|
|
53
|
+
background: ${colors["brown-40"]};
|
|
54
|
+
margin-block: 8px 0;
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
// src/components/chat/scrollable-table/scrollable-table.tsx
|
|
58
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
59
|
+
function ScrollableTable({ node: _node, messageId, counter, ...props }) {
|
|
60
|
+
const currentIndex = counter ? counter.value : 0;
|
|
61
|
+
if (counter) {
|
|
62
|
+
counter.value += 1;
|
|
63
|
+
}
|
|
64
|
+
const context = useContext2(ChatContext);
|
|
65
|
+
const TableActions = context?.TableActions;
|
|
66
|
+
return /* @__PURE__ */ jsxs(TableContainer, { children: [
|
|
67
|
+
/* @__PURE__ */ jsx(TableScroll, { children: /* @__PURE__ */ jsx("table", { ...props }) }),
|
|
68
|
+
TableActions && messageId && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
69
|
+
/* @__PURE__ */ jsx(TableActionsWrapper, { children: /* @__PURE__ */ jsx(TableActions, { messageId, tableIndex: currentIndex }) }),
|
|
70
|
+
/* @__PURE__ */ jsx(TableActionsDivider, {})
|
|
71
|
+
] })
|
|
72
|
+
] });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/components/chat/chat-message-content/chat-message-content-styles.ts
|
|
76
|
+
import styled2 from "styled-components";
|
|
77
|
+
var ContentWrapper = styled2.div`
|
|
23
78
|
${typographyMixin(typographyTypes.GEIST_BODY_S_REGULAR)};
|
|
24
79
|
color: ${colors["brown-100"]};
|
|
25
80
|
word-break: break-word;
|
|
@@ -135,23 +190,23 @@ var ContentWrapper = styled.div`
|
|
|
135
190
|
color: ${colors["brown-80"]};
|
|
136
191
|
}
|
|
137
192
|
`;
|
|
138
|
-
var TableScroll = styled.div`
|
|
139
|
-
width: 100%;
|
|
140
|
-
overflow-x: auto;
|
|
141
|
-
overflow-y: hidden;
|
|
142
|
-
margin-block: 8px 16px;
|
|
143
|
-
`;
|
|
144
193
|
|
|
145
194
|
// src/components/chat/chat-message-content/chat-message-content.tsx
|
|
146
|
-
import { jsx } from "react/jsx-runtime";
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
|
|
195
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
196
|
+
function ChatMessageContent({ content, messageId }) {
|
|
197
|
+
const counter = { value: 0 };
|
|
198
|
+
const sanitizedContent = content.split("\n").filter((line) => {
|
|
199
|
+
const t = line.trim();
|
|
200
|
+
return !(t.startsWith("<!-- table-title:") && t.endsWith("-->"));
|
|
201
|
+
}).join("\n");
|
|
202
|
+
const markdownComponents = useMemo(
|
|
203
|
+
() => ({
|
|
204
|
+
table: ((tableProps) => /* @__PURE__ */ jsx2(ScrollableTable, { ...tableProps, messageId, counter }))
|
|
205
|
+
}),
|
|
206
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- counter is intentionally a fresh object each render for sequential table indexing
|
|
207
|
+
[messageId]
|
|
208
|
+
);
|
|
209
|
+
return /* @__PURE__ */ jsx2(ContentWrapper, { children: /* @__PURE__ */ jsx2(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkBreaks], components: markdownComponents, children: sanitizedContent }) });
|
|
155
210
|
}
|
|
156
211
|
|
|
157
212
|
// src/components/chat/chat-message-actions/chat-message-actions.tsx
|
|
@@ -229,17 +284,6 @@ var IconThumbDown = createReactComponent("outline", "thumb-down", "ThumbDown", _
|
|
|
229
284
|
var __iconNode4 = [["path", { "d": "M7 11v8a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-7a1 1 0 0 1 1 -1h3a4 4 0 0 0 4 -4v-1a2 2 0 0 1 4 0v5h3a2 2 0 0 1 2 2l-1 5a2 3 0 0 1 -2 2h-7a3 3 0 0 1 -3 -3", "key": "svg-0" }]];
|
|
230
285
|
var IconThumbUp = createReactComponent("outline", "thumb-up", "ThumbUp", __iconNode4);
|
|
231
286
|
|
|
232
|
-
// src/components/chat/chat-context/chat-context.ts
|
|
233
|
-
import { createContext, useContext } from "react";
|
|
234
|
-
var ChatContext = createContext(null);
|
|
235
|
-
function useChatContext() {
|
|
236
|
-
const context = useContext(ChatContext);
|
|
237
|
-
if (!context) {
|
|
238
|
-
throw new Error("useChatContext must be used within a Chat.Root component");
|
|
239
|
-
}
|
|
240
|
-
return context;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
287
|
// src/components/chat/hooks/use-copy-to-clipboard.ts
|
|
244
288
|
import { useState, useCallback, useRef, useEffect } from "react";
|
|
245
289
|
|
|
@@ -283,16 +327,16 @@ var MESSAGE_ROLE = {
|
|
|
283
327
|
};
|
|
284
328
|
|
|
285
329
|
// src/components/chat/chat-message-actions/chat-message-actions-styles.ts
|
|
286
|
-
import
|
|
330
|
+
import styled3 from "styled-components";
|
|
287
331
|
var FADE_DURATION_MS = 150;
|
|
288
332
|
var ANIMATE_DURATION_MS = 200;
|
|
289
|
-
var ActionsContainer =
|
|
333
|
+
var ActionsContainer = styled3.div`
|
|
290
334
|
display: flex;
|
|
291
335
|
opacity: ${({ $role }) => $role === MESSAGE_ROLE.ASSISTANT ? 1 : 0};
|
|
292
336
|
pointer-events: ${({ $role }) => $role === MESSAGE_ROLE.ASSISTANT ? "auto" : "none"};
|
|
293
337
|
transition: opacity ${FADE_DURATION_MS}ms ease;
|
|
294
338
|
`;
|
|
295
|
-
var AnimatedAction =
|
|
339
|
+
var AnimatedAction = styled3.div`
|
|
296
340
|
display: flex;
|
|
297
341
|
overflow: hidden;
|
|
298
342
|
max-width: ${({ $visible }) => $visible ? "40px" : "0"};
|
|
@@ -301,7 +345,7 @@ var AnimatedAction = styled2.div`
|
|
|
301
345
|
`;
|
|
302
346
|
|
|
303
347
|
// src/components/chat/chat-message-actions/chat-message-actions.tsx
|
|
304
|
-
import { jsx as
|
|
348
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
305
349
|
function ChatMessageActions({ className, messageId, content, role, isHelpful }) {
|
|
306
350
|
const { onCopyMessage, onThumbUpClick, onThumbDownClick } = useChatContext();
|
|
307
351
|
const { isCopied, copy } = useCopyToClipboard();
|
|
@@ -317,8 +361,8 @@ function ChatMessageActions({ className, messageId, content, role, isHelpful })
|
|
|
317
361
|
}, [messageId, isHelpful, onThumbDownClick]);
|
|
318
362
|
const isAssistant = role === MESSAGE_ROLE.ASSISTANT;
|
|
319
363
|
const hasFeedback = isAssistant && (onThumbUpClick || onThumbDownClick);
|
|
320
|
-
return /* @__PURE__ */
|
|
321
|
-
/* @__PURE__ */
|
|
364
|
+
return /* @__PURE__ */ jsxs2(ActionsContainer, { $role: role, className, children: [
|
|
365
|
+
/* @__PURE__ */ jsx3(
|
|
322
366
|
IconButton,
|
|
323
367
|
{
|
|
324
368
|
icon: isCopied ? IconCheck : IconCopy,
|
|
@@ -326,7 +370,7 @@ function ChatMessageActions({ className, messageId, content, role, isHelpful })
|
|
|
326
370
|
"aria-label": isCopied ? "Copied" : "Copy message"
|
|
327
371
|
}
|
|
328
372
|
),
|
|
329
|
-
hasFeedback && /* @__PURE__ */
|
|
373
|
+
hasFeedback && /* @__PURE__ */ jsx3(AnimatedAction, { $visible: isHelpful !== false, children: /* @__PURE__ */ jsx3(
|
|
330
374
|
IconButton,
|
|
331
375
|
{
|
|
332
376
|
icon: IconThumbUp,
|
|
@@ -335,7 +379,7 @@ function ChatMessageActions({ className, messageId, content, role, isHelpful })
|
|
|
335
379
|
"aria-label": "Good response"
|
|
336
380
|
}
|
|
337
381
|
) }),
|
|
338
|
-
hasFeedback && /* @__PURE__ */
|
|
382
|
+
hasFeedback && /* @__PURE__ */ jsx3(AnimatedAction, { $visible: isHelpful !== true, children: /* @__PURE__ */ jsx3(
|
|
339
383
|
IconButton,
|
|
340
384
|
{
|
|
341
385
|
icon: IconThumbDown,
|
|
@@ -348,8 +392,8 @@ function ChatMessageActions({ className, messageId, content, role, isHelpful })
|
|
|
348
392
|
}
|
|
349
393
|
|
|
350
394
|
// src/components/chat/chat-message/chat-message-styles.ts
|
|
351
|
-
import
|
|
352
|
-
var MessageRow =
|
|
395
|
+
import styled4 from "styled-components";
|
|
396
|
+
var MessageRow = styled4.div`
|
|
353
397
|
display: flex;
|
|
354
398
|
justify-content: ${({ $role }) => $role === MESSAGE_ROLE.USER ? "flex-end" : "flex-start"};
|
|
355
399
|
|
|
@@ -358,7 +402,7 @@ var MessageRow = styled3.div`
|
|
|
358
402
|
pointer-events: auto;
|
|
359
403
|
}
|
|
360
404
|
`;
|
|
361
|
-
var MessageContainer =
|
|
405
|
+
var MessageContainer = styled4.div`
|
|
362
406
|
display: flex;
|
|
363
407
|
flex-direction: column;
|
|
364
408
|
align-items: ${({ $role }) => $role === MESSAGE_ROLE.USER ? "flex-end" : "flex-start"};
|
|
@@ -366,7 +410,7 @@ var MessageContainer = styled3.div`
|
|
|
366
410
|
width: ${({ $role }) => $role === MESSAGE_ROLE.USER ? "auto" : "100%"};
|
|
367
411
|
gap: 12px;
|
|
368
412
|
`;
|
|
369
|
-
var MessageBubble =
|
|
413
|
+
var MessageBubble = styled4.div`
|
|
370
414
|
${typographyMixin(typographyTypes.GEIST_BODY_S_REGULAR)};
|
|
371
415
|
color: ${colors["brown-100"]};
|
|
372
416
|
background: ${({ $role }) => $role === MESSAGE_ROLE.USER ? colors.white : "transparent"};
|
|
@@ -377,11 +421,11 @@ var MessageBubble = styled3.div`
|
|
|
377
421
|
`;
|
|
378
422
|
|
|
379
423
|
// src/components/chat/chat-message/chat-message.tsx
|
|
380
|
-
import { jsx as
|
|
424
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
381
425
|
function ChatMessage({ message }) {
|
|
382
|
-
return /* @__PURE__ */
|
|
383
|
-
/* @__PURE__ */
|
|
384
|
-
/* @__PURE__ */
|
|
426
|
+
return /* @__PURE__ */ jsx4(MessageRow, { $role: message.role, role: "article", "aria-label": `${message.role} message`, children: /* @__PURE__ */ jsxs3(MessageContainer, { $role: message.role, children: [
|
|
427
|
+
/* @__PURE__ */ jsx4(MessageBubble, { $role: message.role, children: /* @__PURE__ */ jsx4(ChatMessageContent, { content: message.content, messageId: message.id }) }),
|
|
428
|
+
/* @__PURE__ */ jsx4(
|
|
385
429
|
ChatMessageActions,
|
|
386
430
|
{
|
|
387
431
|
messageId: message.id,
|
|
@@ -416,4 +460,4 @@ export {
|
|
|
416
460
|
* See the LICENSE file in the root directory of this source tree.
|
|
417
461
|
*)
|
|
418
462
|
*/
|
|
419
|
-
//# sourceMappingURL=chunk-
|
|
463
|
+
//# sourceMappingURL=chunk-KYTNUZO6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/chat/chat-message-content/chat-message-content.tsx","../src/components/chat/scrollable-table/scrollable-table.tsx","../src/components/chat/chat-context/chat-context.ts","../src/components/chat/scrollable-table/scrollable-table-styles.ts","../src/components/chat/chat-message-content/chat-message-content-styles.ts","../src/components/chat/chat-message-actions/chat-message-actions.tsx","../node_modules/@tabler/icons-react/src/defaultAttributes.ts","../node_modules/@tabler/icons-react/src/createReactComponent.ts","../node_modules/@tabler/icons-react/src/icons/IconCheck.ts","../node_modules/@tabler/icons-react/src/icons/IconCopy.ts","../node_modules/@tabler/icons-react/src/icons/IconThumbDown.ts","../node_modules/@tabler/icons-react/src/icons/IconThumbUp.ts","../src/components/chat/hooks/use-copy-to-clipboard.ts","../src/utils/clipboard.ts","../src/components/chat/constants.ts","../src/components/chat/chat-message-actions/chat-message-actions-styles.ts","../src/components/chat/chat-message/chat-message-styles.ts","../src/components/chat/chat-message/chat-message.tsx"],"sourcesContent":["import { type ReactNode, useMemo } from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport remarkBreaks from \"remark-breaks\";\nimport remarkGfm from \"remark-gfm\";\n\nimport { ScrollableTable, type TableCounter } from \"../scrollable-table/scrollable-table\";\nimport { ContentWrapper } from \"./chat-message-content-styles\";\n\n/** Props for the ChatMessageContent component. */\ninterface ChatMessageContentProps {\n /** The markdown string to render. */\n content: string;\n /** The ID of the message this content belongs to. Used for table action callbacks. */\n messageId?: string;\n}\n\n/** Renders markdown content with GFM support (tables, strikethrough) and line breaks. */\nexport function ChatMessageContent({ content, messageId }: ChatMessageContentProps): ReactNode {\n const counter: TableCounter = { value: 0 };\n\n const sanitizedContent = content\n .split(\"\\n\")\n .filter((line) => {\n const t = line.trim();\n return !(t.startsWith(\"<!-- table-title:\") && t.endsWith(\"-->\"));\n })\n .join(\"\\n\");\n\n const markdownComponents = useMemo(\n () => ({\n table: ((tableProps: { node?: unknown; children?: ReactNode }) => (\n <ScrollableTable {...tableProps} messageId={messageId} counter={counter} />\n )) as React.ComponentType<unknown>,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps -- counter is intentionally a fresh object each render for sequential table indexing\n [messageId]\n );\n\n return (\n <ContentWrapper>\n <ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]} components={markdownComponents}>\n {sanitizedContent}\n </ReactMarkdown>\n </ContentWrapper>\n );\n}\n","import { type ReactNode, useContext } from \"react\";\n\nimport { ChatContext } from \"../chat-context/chat-context\";\nimport { TableActionsWrapper, TableActionsDivider, TableContainer, TableScroll } from \"./scrollable-table-styles\";\n\n/** Mutable counter used to assign sequential table indices during a single render pass. */\nexport interface TableCounter {\n /** The current value; incremented each time a table component is rendered. */\n value: number;\n}\n\n/** Props for the ScrollableTable component. */\ninterface ScrollableTableProps {\n /** Unused node prop from react-markdown. */\n node?: unknown;\n /** Table children elements. */\n children?: ReactNode;\n /** The ID of the parent message. */\n messageId?: string;\n /** Mutable counter for tracking table indices. */\n counter?: TableCounter;\n}\n\n/** Renders a table inside a horizontally scrollable wrapper with optional actions below. */\nexport function ScrollableTable({ node: _node, messageId, counter, ...props }: ScrollableTableProps): ReactNode {\n const currentIndex = counter ? counter.value : 0;\n if (counter) {\n // eslint-disable-next-line react-hooks/immutability -- Counter is an intentionally mutable shared object for sequential table indexing\n counter.value += 1;\n }\n\n const context = useContext(ChatContext);\n const TableActions = context?.TableActions;\n\n return (\n <TableContainer>\n <TableScroll>\n <table {...props} />\n </TableScroll>\n {TableActions && messageId && (\n <>\n <TableActionsWrapper>\n <TableActions messageId={messageId} tableIndex={currentIndex} />\n </TableActionsWrapper>\n <TableActionsDivider />\n </>\n )}\n </TableContainer>\n );\n}\n","import { createContext, useContext } from \"react\";\nimport type { ChatContextValue } from \"../types\";\n\nexport const ChatContext = createContext<ChatContextValue | null>(null);\n\nexport function useChatContext(): ChatContextValue {\n const context = useContext(ChatContext);\n if (!context) {\n throw new Error(\"useChatContext must be used within a Chat.Root component\");\n }\n return context;\n}\n","import styled from \"styled-components\";\n\nimport { colors } from \"@/tokens/colors\";\n\nexport const TableContainer = styled.div`\n position: relative;\n margin-block: 8px 16px;\n`;\n\nexport const TableScroll = styled.div`\n width: 100%;\n overflow-x: auto;\n overflow-y: hidden;\n`;\n\nexport const TableActionsWrapper = styled.div`\n display: flex;\n justify-content: flex-start;\n margin-block: 8px 0;\n`;\n\nexport const TableActionsDivider = styled.div`\n height: 1px;\n background: ${colors[\"brown-40\"]};\n margin-block: 8px 0;\n`;\n","import styled from \"styled-components\";\n\nimport { colors } from \"../../../tokens/colors\";\nimport { typographyMixin, typographyTypes } from \"../../../tokens/typography\";\n\nexport const ContentWrapper = styled.div`\n ${typographyMixin(typographyTypes.GEIST_BODY_S_REGULAR)};\n color: ${colors[\"brown-100\"]};\n word-break: break-word;\n\n & h1 {\n ${typographyMixin(typographyTypes.GEIST_HEADING_S_BOLD)};\n margin-block: 20px;\n }\n\n & h2 {\n ${typographyMixin(typographyTypes.GEIST_BODY_L_SEMI_BOLD)};\n margin-block: 12px;\n }\n\n & h3,\n & h4,\n & h5,\n & h6 {\n ${typographyMixin(typographyTypes.GEIST_BODY_M_SEMI_BOLD)};\n margin-block: 8px;\n }\n\n & strong {\n ${typographyMixin(typographyTypes.GEIST_BODY_S_SEMI_BOLD)};\n }\n\n & p {\n margin-block: 0px;\n }\n\n & p + p {\n margin-block: 8px 0px;\n }\n\n & ul,\n & ol {\n margin-block: 16px;\n padding-left: 20px;\n }\n\n & li + li {\n margin-block: 4px;\n }\n\n & ol {\n list-style-type: decimal;\n }\n\n & ol ol {\n list-style-type: lower-alpha;\n }\n\n & ol ol ol {\n list-style-type: lower-roman;\n }\n\n & a {\n color: inherit;\n text-decoration-line: underline;\n text-decoration-style: dotted;\n text-decoration-thickness: auto;\n text-underline-offset: auto;\n text-underline-position: from-font;\n transition: color 75ms ease-in-out;\n\n &:hover {\n color: ${colors[\"blue-600\"]};\n }\n }\n\n & hr {\n border: none;\n border-top: 1px solid ${colors[\"brown-40\"]};\n margin-block: 20px;\n }\n\n & table {\n margin: 0;\n width: 100%;\n border-collapse: collapse;\n }\n\n & th,\n & td {\n border: none;\n border-bottom: 1px solid ${colors[\"brown-40\"]};\n text-align: left;\n }\n\n & th {\n ${typographyMixin(typographyTypes.GEIST_LABEL_CAPTION_MEDIUM)};\n padding: 8px;\n }\n\n & td {\n ${typographyMixin(typographyTypes.GEIST_BODY_XS_MEDIUM)};\n padding: 16px 8px;\n min-width: 150px;\n width: 1%;\n }\n\n & td strong {\n font-weight: inherit;\n font-size: inherit;\n line-height: inherit;\n }\n\n & blockquote {\n margin-block: 4px;\n margin-inline: 0px;\n padding-left: 16px;\n border-left: 4px solid ${colors[\"brown-40\"]};\n color: ${colors[\"brown-80\"]};\n }\n`;\n","import { useCallback } from \"react\";\nimport { IconCopy, IconCheck, IconThumbUp, IconThumbDown } from \"@tabler/icons-react\";\n\nimport { IconButton } from \"../../icon-button/icon-button\";\nimport { useChatContext } from \"../chat-context/chat-context\";\nimport { useCopyToClipboard } from \"../hooks/use-copy-to-clipboard\";\nimport { MESSAGE_ROLE } from \"../constants\";\nimport type { MessageRole } from \"../types\";\nimport { ActionsContainer, AnimatedAction } from \"./chat-message-actions-styles\";\n\ninterface ChatMessageActionsProps {\n className?: string;\n messageId: string;\n content: string;\n role: MessageRole;\n isHelpful?: boolean | null;\n}\n\nexport function ChatMessageActions({ className, messageId, content, role, isHelpful }: ChatMessageActionsProps) {\n const { onCopyMessage, onThumbUpClick, onThumbDownClick } = useChatContext();\n const { isCopied, copy } = useCopyToClipboard();\n\n const handleCopy = useCallback(() => {\n copy(content);\n onCopyMessage?.(messageId);\n }, [content, messageId, copy, onCopyMessage]);\n\n const handleThumbUp = useCallback(() => {\n onThumbUpClick?.(messageId, isHelpful === true ? null : true);\n }, [messageId, isHelpful, onThumbUpClick]);\n\n const handleThumbDown = useCallback(() => {\n onThumbDownClick?.(messageId, isHelpful === false ? null : false);\n }, [messageId, isHelpful, onThumbDownClick]);\n\n const isAssistant = role === MESSAGE_ROLE.ASSISTANT;\n const hasFeedback = isAssistant && (onThumbUpClick || onThumbDownClick);\n\n return (\n <ActionsContainer $role={role} className={className}>\n <IconButton\n icon={isCopied ? IconCheck : IconCopy}\n onClick={handleCopy}\n aria-label={isCopied ? \"Copied\" : \"Copy message\"}\n />\n\n {hasFeedback && (\n <AnimatedAction $visible={isHelpful !== false}>\n <IconButton\n icon={IconThumbUp}\n onClick={handleThumbUp}\n isSelected={isHelpful === true}\n aria-label=\"Good response\"\n />\n </AnimatedAction>\n )}\n\n {hasFeedback && (\n <AnimatedAction $visible={isHelpful !== true}>\n <IconButton\n icon={IconThumbDown}\n onClick={handleThumbDown}\n isSelected={isHelpful === false}\n aria-label=\"Bad response\"\n />\n </AnimatedAction>\n )}\n </ActionsContainer>\n );\n}\n","export default {\n outline: {\n xmlns: 'http://www.w3.org/2000/svg',\n width: 24,\n height: 24,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n },\n filled: {\n xmlns: 'http://www.w3.org/2000/svg',\n width: 24,\n height: 24,\n viewBox: '0 0 24 24',\n fill: 'currentColor',\n stroke: 'none',\n },\n};\n","import { forwardRef, createElement } from 'react';\nimport defaultAttributes from './defaultAttributes';\nimport type { IconNode, IconProps, Icon } from './types';\n\nconst createReactComponent = (\n type: 'outline' | 'filled',\n iconName: string,\n iconNamePascal: string,\n iconNode: IconNode,\n) => {\n const Component = forwardRef<SVGSVGElement, IconProps>(\n (\n { color = 'currentColor', size = 24, stroke = 2, title, className, children, ...rest }: IconProps,\n ref,\n ) =>\n createElement(\n 'svg',\n {\n ref,\n ...defaultAttributes[type],\n width: size,\n height: size,\n className: [`tabler-icon`, `tabler-icon-${iconName}`, className].join(' '),\n ...(type === 'filled'\n ? {\n fill: color,\n }\n : {\n strokeWidth: stroke,\n stroke: color,\n }),\n ...rest,\n },\n [\n title && createElement('title', { key: 'svg-title' }, title),\n ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),\n ...(Array.isArray(children) ? children : [children]),\n ],\n ),\n );\n\n Component.displayName = `${iconNamePascal}`;\n\n return Component;\n};\n\nexport default createReactComponent;\n","import createReactComponent from '../createReactComponent';\nimport { IconNode } from '../types';\n\nexport const __iconNode: IconNode = [[\"path\",{\"d\":\"M5 12l5 5l10 -10\",\"key\":\"svg-0\"}]]\n\nconst IconCheck = createReactComponent('outline', 'check', 'Check', __iconNode);\n\nexport default IconCheck;","import createReactComponent from '../createReactComponent';\nimport { IconNode } from '../types';\n\nexport const __iconNode: IconNode = [[\"path\",{\"d\":\"M7 9.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667l0 -8.666\",\"key\":\"svg-0\"}],[\"path\",{\"d\":\"M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1\",\"key\":\"svg-1\"}]]\n\nconst IconCopy = createReactComponent('outline', 'copy', 'Copy', __iconNode);\n\nexport default IconCopy;","import createReactComponent from '../createReactComponent';\nimport { IconNode } from '../types';\n\nexport const __iconNode: IconNode = [[\"path\",{\"d\":\"M7 13v-8a1 1 0 0 0 -1 -1h-2a1 1 0 0 0 -1 1v7a1 1 0 0 0 1 1h3a4 4 0 0 1 4 4v1a2 2 0 0 0 4 0v-5h3a2 2 0 0 0 2 -2l-1 -5a2 3 0 0 0 -2 -2h-7a3 3 0 0 0 -3 3\",\"key\":\"svg-0\"}]]\n\nconst IconThumbDown = createReactComponent('outline', 'thumb-down', 'ThumbDown', __iconNode);\n\nexport default IconThumbDown;","import createReactComponent from '../createReactComponent';\nimport { IconNode } from '../types';\n\nexport const __iconNode: IconNode = [[\"path\",{\"d\":\"M7 11v8a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-7a1 1 0 0 1 1 -1h3a4 4 0 0 0 4 -4v-1a2 2 0 0 1 4 0v5h3a2 2 0 0 1 2 2l-1 5a2 3 0 0 1 -2 2h-7a3 3 0 0 1 -3 -3\",\"key\":\"svg-0\"}]]\n\nconst IconThumbUp = createReactComponent('outline', 'thumb-up', 'ThumbUp', __iconNode);\n\nexport default IconThumbUp;","import { useState, useCallback, useRef, useEffect } from \"react\";\n\nimport { copyToClipboard } from \"@/utils/clipboard\";\nimport { markdownToPlainText } from \"@/utils/markdown-to-plain-text\";\n\nconst RESET_DELAY_MS = 2000;\n\ninterface UseCopyToClipboardResult {\n isCopied: boolean;\n copy: (text: string) => void;\n}\n\nexport function useCopyToClipboard(): UseCopyToClipboardResult {\n const [isCopied, setIsCopied] = useState(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n };\n }, []);\n\n const copy = useCallback((text: string) => {\n copyToClipboard(markdownToPlainText(text), {\n onSuccess: () => {\n setIsCopied(true);\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n timeoutRef.current = setTimeout(() => {\n setIsCopied(false);\n timeoutRef.current = null;\n }, RESET_DELAY_MS);\n },\n });\n }, []);\n\n return { isCopied, copy };\n}\n","/** Options for the {@link copyToClipboard} function. */\ninterface CopyToClipboardOptions {\n /** Called after the text has been successfully written to the clipboard. */\n onSuccess?: () => void;\n /** Called if the clipboard write fails (e.g., permission denied). */\n onFailure?: (error: unknown) => void;\n}\n\n/**\n * Copies text to the system clipboard using the Clipboard API.\n * @param text - The string to copy.\n * @param options - Optional success/failure callbacks.\n */\nexport function copyToClipboard(text: string, options?: CopyToClipboardOptions): void {\n navigator.clipboard.writeText(text).then(\n () => options?.onSuccess?.(),\n (error) => options?.onFailure?.(error)\n );\n}\n","export const MESSAGE_ROLE = {\n USER: \"user\",\n ASSISTANT: \"assistant\",\n} as const;\n","import styled from \"styled-components\";\n\nimport { MESSAGE_ROLE } from \"../constants\";\nimport type { MessageRole } from \"../types\";\n\nconst FADE_DURATION_MS = 150;\nconst ANIMATE_DURATION_MS = 200;\n\nexport const ActionsContainer = styled.div<{ $role: MessageRole }>`\n display: flex;\n opacity: ${({ $role }) => ($role === MESSAGE_ROLE.ASSISTANT ? 1 : 0)};\n pointer-events: ${({ $role }) => ($role === MESSAGE_ROLE.ASSISTANT ? \"auto\" : \"none\")};\n transition: opacity ${FADE_DURATION_MS}ms ease;\n`;\n\nexport const AnimatedAction = styled.div<{ $visible: boolean }>`\n display: flex;\n overflow: hidden;\n max-width: ${({ $visible }) => ($visible ? \"40px\" : \"0\")};\n opacity: ${({ $visible }) => ($visible ? 1 : 0)};\n transition: max-width ${ANIMATE_DURATION_MS}ms ease, opacity ${ANIMATE_DURATION_MS}ms ease;\n`;\n","import styled from \"styled-components\";\n\nimport { colors } from \"../../../tokens/colors\";\nimport { typographyMixin, typographyTypes } from \"../../../tokens/typography\";\nimport { ActionsContainer } from \"../chat-message-actions/chat-message-actions-styles\";\nimport { MESSAGE_ROLE } from \"../constants\";\nimport type { MessageRole } from \"../types\";\n\nexport const MessageRow = styled.div<{ $role: MessageRole }>`\n display: flex;\n justify-content: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? \"flex-end\" : \"flex-start\")};\n\n &:hover ${ActionsContainer} {\n opacity: 1;\n pointer-events: auto;\n }\n`;\n\nexport const MessageContainer = styled.div<{ $role: MessageRole }>`\n display: flex;\n flex-direction: column;\n align-items: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? \"flex-end\" : \"flex-start\")};\n max-width: 90%;\n width: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? \"auto\" : \"100%\")};\n gap: 12px;\n`;\n\nexport const MessageBubble = styled.div<{ $role: MessageRole }>`\n ${typographyMixin(typographyTypes.GEIST_BODY_S_REGULAR)};\n color: ${colors[\"brown-100\"]};\n background: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? colors.white : \"transparent\")};\n padding: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? \"12px 16px\" : \"0\")};\n border-radius: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? \"16px\" : \"0\")};\n max-width: 100%;\n min-width: 0;\n`;\n","import type { ChatMessage as ChatMessageType } from \"../types\";\nimport { ChatMessageContent } from \"../chat-message-content/chat-message-content\";\nimport { ChatMessageActions } from \"../chat-message-actions/chat-message-actions\";\nimport { MessageRow, MessageBubble, MessageContainer } from \"./chat-message-styles\";\n\ninterface ChatMessageProps {\n message: ChatMessageType;\n}\n\nexport function ChatMessage({ message }: ChatMessageProps) {\n return (\n <MessageRow $role={message.role} role=\"article\" aria-label={`${message.role} message`}>\n <MessageContainer $role={message.role}>\n <MessageBubble $role={message.role}>\n <ChatMessageContent content={message.content} messageId={message.id} />\n </MessageBubble>\n <ChatMessageActions\n messageId={message.id}\n content={message.content}\n role={message.role}\n isHelpful={message.isHelpful}\n />\n </MessageContainer>\n </MessageRow>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAAA,SAAyB,eAAe;AACxC,OAAO,mBAAmB;AAC1B,OAAO,kBAAkB;AACzB,OAAO,eAAe;;;ACHtB,SAAyB,cAAAA,mBAAkB;;;ACA3C,SAAS,eAAe,kBAAkB;AAGnC,IAAM,cAAc,cAAuC,IAAI;AAE/D,SAAS,iBAAmC;AACjD,QAAM,UAAU,WAAW,WAAW;AACtC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AACA,SAAO;AACT;;;ACXA,OAAO,YAAY;AAIZ,IAAM,iBAAiB,OAAO;AAAA;AAAA;AAAA;AAK9B,IAAM,cAAc,OAAO;AAAA;AAAA;AAAA;AAAA;AAM3B,IAAM,sBAAsB,OAAO;AAAA;AAAA;AAAA;AAAA;AAMnC,IAAM,sBAAsB,OAAO;AAAA;AAAA,gBAE1B,OAAO,UAAU,CAAC;AAAA;AAAA;;;AFc1B,SAGA,UAHA,KAGA,YAHA;AAbD,SAAS,gBAAgB,EAAE,MAAM,OAAO,WAAW,SAAS,GAAG,MAAM,GAAoC;AAC9G,QAAM,eAAe,UAAU,QAAQ,QAAQ;AAC/C,MAAI,SAAS;AAEX,YAAQ,SAAS;AAAA,EACnB;AAEA,QAAM,UAAUC,YAAW,WAAW;AACtC,QAAM,eAAe,SAAS;AAE9B,SACE,qBAAC,kBACC;AAAA,wBAAC,eACC,8BAAC,WAAO,GAAG,OAAO,GACpB;AAAA,IACC,gBAAgB,aACf,iCACE;AAAA,0BAAC,uBACC,8BAAC,gBAAa,WAAsB,YAAY,cAAc,GAChE;AAAA,MACA,oBAAC,uBAAoB;AAAA,OACvB;AAAA,KAEJ;AAEJ;;;AGjDA,OAAOC,aAAY;AAKZ,IAAM,iBAAiBC,QAAO;AAAA,IACjC,gBAAgB,gBAAgB,oBAAoB,CAAC;AAAA,WAC9C,OAAO,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA,MAIxB,gBAAgB,gBAAgB,oBAAoB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,MAKrD,gBAAgB,gBAAgB,sBAAsB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQvD,gBAAgB,gBAAgB,sBAAsB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,MAKvD,gBAAgB,gBAAgB,sBAAsB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eA2C9C,OAAO,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAML,OAAO,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAaf,OAAO,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,MAK3C,gBAAgB,gBAAgB,0BAA0B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,MAK3D,gBAAgB,gBAAgB,oBAAoB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAgB9B,OAAO,UAAU,CAAC;AAAA,aAClC,OAAO,UAAU,CAAC;AAAA;AAAA;;;AJvFvB,gBAAAC,YAAA;AAdD,SAAS,mBAAmB,EAAE,SAAS,UAAU,GAAuC;AAC7F,QAAM,UAAwB,EAAE,OAAO,EAAE;AAEzC,QAAM,mBAAmB,QACtB,MAAM,IAAI,EACV,OAAO,CAAC,SAAS;AAChB,UAAM,IAAI,KAAK,KAAK;AACpB,WAAO,EAAE,EAAE,WAAW,mBAAmB,KAAK,EAAE,SAAS,KAAK;AAAA,EAChE,CAAC,EACA,KAAK,IAAI;AAEZ,QAAM,qBAAqB;AAAA,IACzB,OAAO;AAAA,MACL,QAAQ,CAAC,eACP,gBAAAA,KAAC,mBAAiB,GAAG,YAAY,WAAsB,SAAkB;AAAA,IAE7E;AAAA;AAAA,IAEA,CAAC,SAAS;AAAA,EACZ;AAEA,SACE,gBAAAA,KAAC,kBACC,0BAAAA,KAAC,iBAAc,eAAe,CAAC,WAAW,YAAY,GAAG,YAAY,oBAClE,4BACH,GACF;AAEJ;;;AK7CA,SAAS,eAAAC,oBAAmB;A;;;;;ACA5B,IAAA,oBAAe;EACb,SAAS;IACP,OAAO;IACP,OAAO;IACP,QAAQ;IACR,SAAS;IACT,MAAM;IACN,QAAQ;IACR,aAAa;IACb,eAAe;IACf,gBAAgB;EAAA;EAElB,QAAQ;IACN,OAAO;IACP,OAAO;IACP,QAAQ;IACR,SAAS;IACT,MAAM;IACN,QAAQ;EAAA;AAEZ;;;AChBA,IAAM,uBAAuB,CAC3B,MACA,UACA,gBACA,aACG;AACH,QAAM,YAAY;IAChB,CACE,EAAE,QAAQ,gBAAgB,OAAO,IAAI,SAAS,GAAG,OAAO,WAAW,UAAU,GAAG,KAAA,GAChF,QAEA;MACE;MACA;QACE;QACA,GAAG,kBAAkB,IAAI;QACzB,OAAO;QACP,QAAQ;QACR,WAAW,CAAC,eAAe,eAAe,QAAQ,IAAI,SAAS,EAAE,KAAK,GAAG;QACzE,GAAI,SAAS,WACT;UACE,MAAM;QAAA,IAER;UACE,aAAa;UACb,QAAQ;QAAA;QAEd,GAAG;MAAA;MAEL;QACE,SAAS,cAAc,SAAS,EAAE,KAAK,YAAA,GAAe,KAAK;QAC3D,GAAG,SAAS,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,cAAc,KAAK,KAAK,CAAC;QAC3D,GAAI,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAQ;MAAA;IACpD;EACF;AAGJ,YAAU,cAAc,GAAG,cAAc;AAEzC,SAAO;AACT;;;ACzCO,IAAM,aAAuB,CAAC,CAAC,QAAO,EAAC,KAAI,oBAAmB,OAAM,QAAA,CAAQ,CAAC;AAEpF,IAAM,YAAY,qBAAqB,WAAW,SAAS,SAAS,UAAU;;;ACFvE,IAAMC,cAAuB,CAAC,CAAC,QAAO,EAAC,KAAI,oKAAmK,OAAM,QAAA,CAAQ,GAAE,CAAC,QAAO,EAAC,KAAI,iGAAgG,OAAM,QAAA,CAAQ,CAAC;AAEjW,IAAM,WAAW,qBAAqB,WAAW,QAAQ,QAAQA,WAAU;;;ACFpE,IAAMC,cAAuB,CAAC,CAAC,QAAO,EAAC,KAAI,0JAAyJ,OAAM,QAAA,CAAQ,CAAC;AAE1N,IAAM,gBAAgB,qBAAqB,WAAW,cAAc,aAAaA,WAAU;;;ACFpF,IAAMC,cAAuB,CAAC,CAAC,QAAO,EAAC,KAAI,0JAAyJ,OAAM,QAAA,CAAQ,CAAC;AAE1N,IAAM,cAAc,qBAAqB,WAAW,YAAY,WAAWA,WAAU;;;ACLrF,SAAS,UAAU,aAAa,QAAQ,iBAAiB;;;ACalD,SAAS,gBAAgB,MAAc,SAAwC;AACpF,YAAU,UAAU,UAAU,IAAI,EAAE;AAAA,IAClC,MAAM,SAAS,YAAY;AAAA,IAC3B,CAAC,UAAU,SAAS,YAAY,KAAK;AAAA,EACvC;AACF;;;ADbA,IAAM,iBAAiB;AAOhB,SAAS,qBAA+C;AAC7D,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,aAAa,OAA6C,IAAI;AAEpE,YAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,WAAW,QAAS,cAAa,WAAW,OAAO;AAAA,IACzD;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,OAAO,YAAY,CAAC,SAAiB;AACzC,oBAAgB,oBAAoB,IAAI,GAAG;AAAA,MACzC,WAAW,MAAM;AACf,oBAAY,IAAI;AAChB,YAAI,WAAW,QAAS,cAAa,WAAW,OAAO;AACvD,mBAAW,UAAU,WAAW,MAAM;AACpC,sBAAY,KAAK;AACjB,qBAAW,UAAU;AAAA,QACvB,GAAG,cAAc;AAAA,MACnB;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,UAAU,KAAK;AAC1B;;;AEpCO,IAAM,eAAe;AAAA,EAC1B,MAAM;AAAA,EACN,WAAW;AACb;;;ACHA,OAAOC,aAAY;AAKnB,IAAM,mBAAmB;AACzB,IAAM,sBAAsB;AAErB,IAAM,mBAAmBC,QAAO;AAAA;AAAA,aAE1B,CAAC,EAAE,MAAM,MAAO,UAAU,aAAa,YAAY,IAAI,CAAE;AAAA,oBAClD,CAAC,EAAE,MAAM,MAAO,UAAU,aAAa,YAAY,SAAS,MAAO;AAAA,wBAC/D,gBAAgB;AAAA;AAGjC,IAAM,iBAAiBA,QAAO;AAAA;AAAA;AAAA,eAGtB,CAAC,EAAE,SAAS,MAAO,WAAW,SAAS,GAAI;AAAA,aAC7C,CAAC,EAAE,SAAS,MAAO,WAAW,IAAI,CAAE;AAAA,0BACvB,mBAAmB,oBAAoB,mBAAmB;AAAA;;;AVmBhF,SACE,OAAAC,MADF,QAAAC,aAAA;AArBG,SAAS,mBAAmB,EAAE,WAAW,WAAW,SAAS,MAAM,UAAU,GAA4B;AAC9G,QAAM,EAAE,eAAe,gBAAgB,iBAAiB,IAAI,eAAe;AAC3E,QAAM,EAAE,UAAU,KAAK,IAAI,mBAAmB;AAE9C,QAAM,aAAaC,aAAY,MAAM;AACnC,SAAK,OAAO;AACZ,oBAAgB,SAAS;AAAA,EAC3B,GAAG,CAAC,SAAS,WAAW,MAAM,aAAa,CAAC;AAE5C,QAAM,gBAAgBA,aAAY,MAAM;AACtC,qBAAiB,WAAW,cAAc,OAAO,OAAO,IAAI;AAAA,EAC9D,GAAG,CAAC,WAAW,WAAW,cAAc,CAAC;AAEzC,QAAM,kBAAkBA,aAAY,MAAM;AACxC,uBAAmB,WAAW,cAAc,QAAQ,OAAO,KAAK;AAAA,EAClE,GAAG,CAAC,WAAW,WAAW,gBAAgB,CAAC;AAE3C,QAAM,cAAc,SAAS,aAAa;AAC1C,QAAM,cAAc,gBAAgB,kBAAkB;AAEtD,SACE,gBAAAD,MAAC,oBAAiB,OAAO,MAAM,WAC7B;AAAA,oBAAAD;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,WAAW,YAAY;AAAA,QAC7B,SAAS;AAAA,QACT,cAAY,WAAW,WAAW;AAAA;AAAA,IACpC;AAAA,IAEC,eACC,gBAAAA,KAAC,kBAAe,UAAU,cAAc,OACtC,0BAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,cAAc;AAAA,QAC1B,cAAW;AAAA;AAAA,IACb,GACF;AAAA,IAGD,eACC,gBAAAA,KAAC,kBAAe,UAAU,cAAc,MACtC,0BAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,cAAc;AAAA,QAC1B,cAAW;AAAA;AAAA,IACb,GACF;AAAA,KAEJ;AAEJ;;;AWrEA,OAAOG,aAAY;AAQZ,IAAM,aAAaC,QAAO;AAAA;AAAA,qBAEZ,CAAC,EAAE,MAAM,MAAO,UAAU,aAAa,OAAO,aAAa,YAAa;AAAA;AAAA,YAEjF,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAMrB,IAAM,mBAAmBA,QAAO;AAAA;AAAA;AAAA,iBAGtB,CAAC,EAAE,MAAM,MAAO,UAAU,aAAa,OAAO,aAAa,YAAa;AAAA;AAAA,WAE9E,CAAC,EAAE,MAAM,MAAO,UAAU,aAAa,OAAO,SAAS,MAAO;AAAA;AAAA;AAIlE,IAAM,gBAAgBA,QAAO;AAAA,IAChC,gBAAgB,gBAAgB,oBAAoB,CAAC;AAAA,WAC9C,OAAO,WAAW,CAAC;AAAA,gBACd,CAAC,EAAE,MAAM,MAAO,UAAU,aAAa,OAAO,OAAO,QAAQ,aAAc;AAAA,aAC9E,CAAC,EAAE,MAAM,MAAO,UAAU,aAAa,OAAO,cAAc,GAAI;AAAA,mBAC1D,CAAC,EAAE,MAAM,MAAO,UAAU,aAAa,OAAO,SAAS,GAAI;AAAA;AAAA;AAAA;;;ACpBxE,SAEI,OAAAC,MAFJ,QAAAC,aAAA;AAHC,SAAS,YAAY,EAAE,QAAQ,GAAqB;AACzD,SACE,gBAAAD,KAAC,cAAW,OAAO,QAAQ,MAAM,MAAK,WAAU,cAAY,GAAG,QAAQ,IAAI,YACzE,0BAAAC,MAAC,oBAAiB,OAAO,QAAQ,MAC/B;AAAA,oBAAAD,KAAC,iBAAc,OAAO,QAAQ,MAC5B,0BAAAA,KAAC,sBAAmB,SAAS,QAAQ,SAAS,WAAW,QAAQ,IAAI,GACvE;AAAA,IACA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,QAAQ;AAAA,QACnB,SAAS,QAAQ;AAAA,QACjB,MAAM,QAAQ;AAAA,QACd,WAAW,QAAQ;AAAA;AAAA,IACrB;AAAA,KACF,GACF;AAEJ;","names":["useContext","useContext","styled","styled","jsx","useCallback","__iconNode","__iconNode","__iconNode","styled","styled","jsx","jsxs","useCallback","styled","styled","jsx","jsxs"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/utils/parse-markdown-table.ts
|
|
2
|
+
function parseCells(line) {
|
|
3
|
+
const trimmed = line.trim();
|
|
4
|
+
const stripped = trimmed.startsWith("|") ? trimmed.slice(1) : trimmed;
|
|
5
|
+
const withoutTrailing = stripped.endsWith("|") ? stripped.slice(0, -1) : stripped;
|
|
6
|
+
return withoutTrailing.split("|").map((cell) => cell.trim());
|
|
7
|
+
}
|
|
8
|
+
function isSeparatorRow(line) {
|
|
9
|
+
const cells = parseCells(line);
|
|
10
|
+
return cells.length > 0 && cells.every((cell) => /^:?-{1,}:?$/.test(cell));
|
|
11
|
+
}
|
|
12
|
+
function isTableRow(line) {
|
|
13
|
+
return line.includes("|");
|
|
14
|
+
}
|
|
15
|
+
var TABLE_TITLE_PREFIX = "<!-- table-title:";
|
|
16
|
+
var TABLE_TITLE_SUFFIX = "-->";
|
|
17
|
+
function extractTableTitle(line) {
|
|
18
|
+
const trimmed = line.trim();
|
|
19
|
+
if (!trimmed.startsWith(TABLE_TITLE_PREFIX) || !trimmed.endsWith(TABLE_TITLE_SUFFIX)) {
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
return trimmed.slice(TABLE_TITLE_PREFIX.length, -TABLE_TITLE_SUFFIX.length).trim() || void 0;
|
|
23
|
+
}
|
|
24
|
+
function parseMarkdownTable(markdown, tableIndex = 0) {
|
|
25
|
+
const lines = markdown.split("\n");
|
|
26
|
+
let tableCount = 0;
|
|
27
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
28
|
+
const headerLine = lines[i];
|
|
29
|
+
const separatorLine = lines[i + 1];
|
|
30
|
+
if (!isTableRow(headerLine) || !isSeparatorRow(separatorLine)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (tableCount === tableIndex) {
|
|
34
|
+
const columns = parseCells(headerLine);
|
|
35
|
+
const rows = [];
|
|
36
|
+
const title = i > 0 ? extractTableTitle(lines[i - 1]) : void 0;
|
|
37
|
+
for (let j = i + 2; j < lines.length; j++) {
|
|
38
|
+
if (!isTableRow(lines[j]) || isSeparatorRow(lines[j])) {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
const trimmedLine = lines[j].trim();
|
|
42
|
+
if (trimmedLine === "") {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
rows.push(parseCells(lines[j]));
|
|
46
|
+
}
|
|
47
|
+
return { columns, rows, ...title ? { title } : {} };
|
|
48
|
+
}
|
|
49
|
+
tableCount++;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/utils/convert-table-to-csv.ts
|
|
55
|
+
function escapeField(field) {
|
|
56
|
+
if (field.includes(",") || field.includes('"') || field.includes("\n") || field.includes("\r")) {
|
|
57
|
+
return `"${field.replace(/"/g, '""')}"`;
|
|
58
|
+
}
|
|
59
|
+
return field;
|
|
60
|
+
}
|
|
61
|
+
function convertTableToCSV(table) {
|
|
62
|
+
const headerRow = table.columns.map(escapeField).join(",");
|
|
63
|
+
const dataRows = table.rows.map((row) => row.map(escapeField).join(","));
|
|
64
|
+
return [headerRow, ...dataRows].join("\r\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
parseMarkdownTable,
|
|
69
|
+
convertTableToCSV
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=chunk-N4FMZU56.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/parse-markdown-table.ts","../src/utils/convert-table-to-csv.ts"],"sourcesContent":["/** Structured representation of a parsed markdown table. */\nexport interface MarkdownTable {\n /** Column header labels. */\n columns: string[];\n /** Data rows, each an array of cell values matching the column order. */\n rows: string[][];\n /** Optional title extracted from a `<!-- table-title: ... -->` comment above the table. */\n title?: string;\n}\n\n/**\n * Parses cells from a markdown table row, trimming whitespace and stripping leading/trailing pipes.\n * @param line - A single line of a markdown table.\n * @returns An array of trimmed cell values.\n */\nfunction parseCells(line: string): string[] {\n const trimmed = line.trim();\n const stripped = trimmed.startsWith(\"|\") ? trimmed.slice(1) : trimmed;\n const withoutTrailing = stripped.endsWith(\"|\") ? stripped.slice(0, -1) : stripped;\n return withoutTrailing.split(\"|\").map((cell) => cell.trim());\n}\n\n/**\n * Tests whether a line is a GFM table separator row (e.g. `| --- | --- |`).\n * @param line - A single line to test.\n * @returns `true` if the line matches the separator pattern.\n */\nfunction isSeparatorRow(line: string): boolean {\n const cells = parseCells(line);\n return cells.length > 0 && cells.every((cell) => /^:?-{1,}:?$/.test(cell));\n}\n\n/**\n * Tests whether a line looks like a markdown table row (contains at least one pipe).\n * @param line - A single line to test.\n * @returns `true` if the line contains a pipe character.\n */\nfunction isTableRow(line: string): boolean {\n return line.includes(\"|\");\n}\n\nconst TABLE_TITLE_PREFIX = \"<!-- table-title:\";\nconst TABLE_TITLE_SUFFIX = \"-->\";\n\n/**\n * Extracts a table title from a `<!-- table-title: ... -->` HTML comment.\n * Uses string methods instead of regex to avoid ReDoS on untrusted input.\n * @param line - A single line to test.\n * @returns The title string, or `undefined` if the line is not a title comment.\n */\nfunction extractTableTitle(line: string): string | undefined {\n const trimmed = line.trim();\n if (!trimmed.startsWith(TABLE_TITLE_PREFIX) || !trimmed.endsWith(TABLE_TITLE_SUFFIX)) {\n return undefined;\n }\n return trimmed.slice(TABLE_TITLE_PREFIX.length, -TABLE_TITLE_SUFFIX.length).trim() || undefined;\n}\n\n/**\n * Extracts the Nth GFM markdown table from a string.\n * @param markdown - The markdown content to parse.\n * @param tableIndex - Zero-based index of the table to extract.\n * @returns The parsed table, or `null` if no table exists at the given index.\n */\nexport function parseMarkdownTable(markdown: string, tableIndex = 0): MarkdownTable | null {\n const lines = markdown.split(\"\\n\");\n let tableCount = 0;\n\n for (let i = 0; i < lines.length - 1; i++) {\n const headerLine = lines[i];\n const separatorLine = lines[i + 1];\n\n if (!isTableRow(headerLine) || !isSeparatorRow(separatorLine)) {\n continue;\n }\n\n if (tableCount === tableIndex) {\n const columns = parseCells(headerLine);\n const rows: string[][] = [];\n const title = i > 0 ? extractTableTitle(lines[i - 1]) : undefined;\n\n for (let j = i + 2; j < lines.length; j++) {\n if (!isTableRow(lines[j]) || isSeparatorRow(lines[j])) {\n break;\n }\n const trimmedLine = lines[j].trim();\n if (trimmedLine === \"\") {\n break;\n }\n rows.push(parseCells(lines[j]));\n }\n\n return { columns, rows, ...(title ? { title } : {}) };\n }\n\n tableCount++;\n }\n\n return null;\n}\n","import type { MarkdownTable } from \"./parse-markdown-table\";\n\n/**\n * Escapes and optionally quotes a CSV field per RFC 4180.\n * Fields containing commas, double quotes, or newlines are wrapped in double quotes.\n * Existing double quotes are escaped by doubling them.\n * @param field - The raw field value.\n * @returns The escaped/quoted field string.\n */\nfunction escapeField(field: string): string {\n if (field.includes(\",\") || field.includes('\"') || field.includes(\"\\n\") || field.includes(\"\\r\")) {\n return `\"${field.replace(/\"/g, '\"\"')}\"`;\n }\n return field;\n}\n\n/**\n * Converts a parsed markdown table to a CSV string (RFC 4180).\n * Fields containing commas, double quotes, or newlines are quoted.\n * @param table - The structured table data to convert.\n * @returns A CSV-formatted string with CRLF line endings.\n */\nexport function convertTableToCSV(table: MarkdownTable): string {\n const headerRow = table.columns.map(escapeField).join(\",\");\n const dataRows = table.rows.map((row) => row.map(escapeField).join(\",\"));\n return [headerRow, ...dataRows].join(\"\\r\\n\");\n}\n"],"mappings":";AAeA,SAAS,WAAW,MAAwB;AAC1C,QAAM,UAAU,KAAK,KAAK;AAC1B,QAAM,WAAW,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI;AAC9D,QAAM,kBAAkB,SAAS,SAAS,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI;AACzE,SAAO,gBAAgB,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;AAC7D;AAOA,SAAS,eAAe,MAAuB;AAC7C,QAAM,QAAQ,WAAW,IAAI;AAC7B,SAAO,MAAM,SAAS,KAAK,MAAM,MAAM,CAAC,SAAS,cAAc,KAAK,IAAI,CAAC;AAC3E;AAOA,SAAS,WAAW,MAAuB;AACzC,SAAO,KAAK,SAAS,GAAG;AAC1B;AAEA,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAQ3B,SAAS,kBAAkB,MAAkC;AAC3D,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,QAAQ,WAAW,kBAAkB,KAAK,CAAC,QAAQ,SAAS,kBAAkB,GAAG;AACpF,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,MAAM,mBAAmB,QAAQ,CAAC,mBAAmB,MAAM,EAAE,KAAK,KAAK;AACxF;AAQO,SAAS,mBAAmB,UAAkB,aAAa,GAAyB;AACzF,QAAM,QAAQ,SAAS,MAAM,IAAI;AACjC,MAAI,aAAa;AAEjB,WAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;AACzC,UAAM,aAAa,MAAM,CAAC;AAC1B,UAAM,gBAAgB,MAAM,IAAI,CAAC;AAEjC,QAAI,CAAC,WAAW,UAAU,KAAK,CAAC,eAAe,aAAa,GAAG;AAC7D;AAAA,IACF;AAEA,QAAI,eAAe,YAAY;AAC7B,YAAM,UAAU,WAAW,UAAU;AACrC,YAAM,OAAmB,CAAC;AAC1B,YAAM,QAAQ,IAAI,IAAI,kBAAkB,MAAM,IAAI,CAAC,CAAC,IAAI;AAExD,eAAS,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACzC,YAAI,CAAC,WAAW,MAAM,CAAC,CAAC,KAAK,eAAe,MAAM,CAAC,CAAC,GAAG;AACrD;AAAA,QACF;AACA,cAAM,cAAc,MAAM,CAAC,EAAE,KAAK;AAClC,YAAI,gBAAgB,IAAI;AACtB;AAAA,QACF;AACA,aAAK,KAAK,WAAW,MAAM,CAAC,CAAC,CAAC;AAAA,MAChC;AAEA,aAAO,EAAE,SAAS,MAAM,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,EAAG;AAAA,IACtD;AAEA;AAAA,EACF;AAEA,SAAO;AACT;;;AC1FA,SAAS,YAAY,OAAuB;AAC1C,MAAI,MAAM,SAAS,GAAG,KAAK,MAAM,SAAS,GAAG,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,SAAS,IAAI,GAAG;AAC9F,WAAO,IAAI,MAAM,QAAQ,MAAM,IAAI,CAAC;AAAA,EACtC;AACA,SAAO;AACT;AAQO,SAAS,kBAAkB,OAA8B;AAC9D,QAAM,YAAY,MAAM,QAAQ,IAAI,WAAW,EAAE,KAAK,GAAG;AACzD,QAAM,WAAW,MAAM,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI,WAAW,EAAE,KAAK,GAAG,CAAC;AACvE,SAAO,CAAC,WAAW,GAAG,QAAQ,EAAE,KAAK,MAAM;AAC7C;","names":[]}
|
|
@@ -5,7 +5,7 @@ var PLACEHOLDER_REGEX = new RegExp(`${PLACEHOLDER_PREFIX}(\\d+)${PLACEHOLDER_SUF
|
|
|
5
5
|
function markdownToPlainText(markdown) {
|
|
6
6
|
if (!markdown) return "";
|
|
7
7
|
const inlineCodeSnippets = [];
|
|
8
|
-
let result = markdown.replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, (_match, code) => {
|
|
8
|
+
let result = markdown.replace(/^<!--[\s\S]*?--!?>\n?/gm, "").replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, (_match, code) => {
|
|
9
9
|
inlineCodeSnippets.push(code);
|
|
10
10
|
return `${PLACEHOLDER_PREFIX}${inlineCodeSnippets.length - 1}${PLACEHOLDER_SUFFIX}`;
|
|
11
11
|
}).replace(/^#{1,6}\s+/gm, "").replace(/^(?:---|\*\*\*|___)\s*$/gm, "").replace(/\*{3}(.+?)\*{3}/g, "$1").replace(/\*{2}(.+?)\*{2}/g, "$1").replace(/_{2}(.+?)_{2}/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/_(.+?)_/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^>\s?/gm, "").replace(/^\|[-:\s|]+\|\s*$/gm, "").replace(/^\|\s?/gm, "").replace(/\s?\|$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
@@ -16,4 +16,4 @@ function markdownToPlainText(markdown) {
|
|
|
16
16
|
export {
|
|
17
17
|
markdownToPlainText
|
|
18
18
|
};
|
|
19
|
-
//# sourceMappingURL=chunk-
|
|
19
|
+
//# sourceMappingURL=chunk-QUJYTIGD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/markdown-to-plain-text.ts"],"sourcesContent":["const PLACEHOLDER_PREFIX = \"\\u200B\\u200BIC\";\nconst PLACEHOLDER_SUFFIX = \"\\u200B\\u200B\";\nconst PLACEHOLDER_REGEX = new RegExp(`${PLACEHOLDER_PREFIX}(\\\\d+)${PLACEHOLDER_SUFFIX}`, \"g\");\n\n/**\n * Converts a Markdown string to plain text by stripping all formatting syntax.\n *\n * Handles headings, bold/italic/strikethrough, links, images, blockquotes, code blocks,\n * inline code, tables, and horizontal rules. Inline code content is preserved literally\n * (formatting inside backticks is not stripped).\n *\n * @param markdown - The Markdown source string.\n * @returns A plain-text string with all Markdown syntax removed.\n */\nexport function markdownToPlainText(markdown: string): string {\n if (!markdown) return \"\";\n\n // Protect inline code content from bold/italic stripping by replacing with placeholders\n const inlineCodeSnippets: string[] = [];\n let result = markdown\n // HTML comments (e.g. <!-- table-title: ... -->), including multiline and --!> variants\n .replace(/^<!--[\\s\\S]*?--!?>\\n?/gm, \"\")\n // Code blocks (must come first)\n .replace(/```[\\s\\S]*?```/g, \"\")\n // Inline code → placeholder\n .replace(/`([^`]+)`/g, (_match, code: string) => {\n inlineCodeSnippets.push(code);\n return `${PLACEHOLDER_PREFIX}${inlineCodeSnippets.length - 1}${PLACEHOLDER_SUFFIX}`;\n })\n // Remove headings markers, keep text\n .replace(/^#{1,6}\\s+/gm, \"\")\n // Remove horizontal rules\n .replace(/^(?:---|\\*\\*\\*|___)\\s*$/gm, \"\")\n // Bold + italic\n .replace(/\\*{3}(.+?)\\*{3}/g, \"$1\")\n // Bold\n .replace(/\\*{2}(.+?)\\*{2}/g, \"$1\")\n .replace(/_{2}(.+?)_{2}/g, \"$1\")\n // Italic\n .replace(/\\*(.+?)\\*/g, \"$1\")\n .replace(/_(.+?)_/g, \"$1\")\n // Strikethrough\n .replace(/~~(.+?)~~/g, \"$1\")\n // Images:  → alt (must come before links)\n .replace(/!\\[([^\\]]*)\\]\\([^)]+\\)/g, \"$1\")\n // Links: [text](url) → text\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n // Blockquote markers\n .replace(/^>\\s?/gm, \"\")\n // Table separator rows (|---|---|)\n .replace(/^\\|[-:\\s|]+\\|\\s*$/gm, \"\")\n // Table rows: | a | b | → a | b\n .replace(/^\\|\\s?/gm, \"\")\n .replace(/\\s?\\|$/gm, \"\")\n // Collapse multiple blank lines\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n\n // Restore inline code content\n result = result.replace(PLACEHOLDER_REGEX, (_match, index: string) => inlineCodeSnippets[Number(index)]);\n\n return result;\n}\n"],"mappings":";AAAA,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB,IAAI,OAAO,GAAG,kBAAkB,SAAS,kBAAkB,IAAI,GAAG;AAYrF,SAAS,oBAAoB,UAA0B;AAC5D,MAAI,CAAC,SAAU,QAAO;AAGtB,QAAM,qBAA+B,CAAC;AACtC,MAAI,SAAS,SAEV,QAAQ,2BAA2B,EAAE,EAErC,QAAQ,mBAAmB,EAAE,EAE7B,QAAQ,cAAc,CAAC,QAAQ,SAAiB;AAC/C,uBAAmB,KAAK,IAAI;AAC5B,WAAO,GAAG,kBAAkB,GAAG,mBAAmB,SAAS,CAAC,GAAG,kBAAkB;AAAA,EACnF,CAAC,EAEA,QAAQ,gBAAgB,EAAE,EAE1B,QAAQ,6BAA6B,EAAE,EAEvC,QAAQ,oBAAoB,IAAI,EAEhC,QAAQ,oBAAoB,IAAI,EAChC,QAAQ,kBAAkB,IAAI,EAE9B,QAAQ,cAAc,IAAI,EAC1B,QAAQ,YAAY,IAAI,EAExB,QAAQ,cAAc,IAAI,EAE1B,QAAQ,2BAA2B,IAAI,EAEvC,QAAQ,0BAA0B,IAAI,EAEtC,QAAQ,WAAW,EAAE,EAErB,QAAQ,uBAAuB,EAAE,EAEjC,QAAQ,YAAY,EAAE,EACtB,QAAQ,YAAY,EAAE,EAEtB,QAAQ,WAAW,MAAM,EACzB,KAAK;AAGR,WAAS,OAAO,QAAQ,mBAAmB,CAAC,QAAQ,UAAkB,mBAAmB,OAAO,KAAK,CAAC,CAAC;AAEvG,SAAO;AACT;","names":[]}
|