@flanksource/clicky-ui 0.2.16 → 0.2.17

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 (143) hide show
  1. package/dist/chat.cjs +16 -0
  2. package/dist/chat.cjs.map +1 -1
  3. package/dist/chat.js +17 -1
  4. package/dist/chat.js.map +1 -1
  5. package/dist/components/Combobox.cjs +19 -4
  6. package/dist/components/Combobox.cjs.map +1 -1
  7. package/dist/components/Combobox.d.ts.map +1 -1
  8. package/dist/components/Combobox.js +19 -4
  9. package/dist/components/Combobox.js.map +1 -1
  10. package/dist/components/FilterBar.cjs +40 -6
  11. package/dist/components/FilterBar.cjs.map +1 -1
  12. package/dist/components/FilterBar.d.ts +28 -0
  13. package/dist/components/FilterBar.d.ts.map +1 -1
  14. package/dist/components/FilterBar.js +40 -6
  15. package/dist/components/FilterBar.js.map +1 -1
  16. package/dist/components/JsonSchemaForm.cjs +26 -3
  17. package/dist/components/JsonSchemaForm.cjs.map +1 -1
  18. package/dist/components/JsonSchemaForm.d.ts.map +1 -1
  19. package/dist/components/JsonSchemaForm.js +26 -3
  20. package/dist/components/JsonSchemaForm.js.map +1 -1
  21. package/dist/components/json-schema-form-preferences.cjs +4 -0
  22. package/dist/components/json-schema-form-preferences.cjs.map +1 -1
  23. package/dist/components/json-schema-form-preferences.d.ts +2 -0
  24. package/dist/components/json-schema-form-preferences.d.ts.map +1 -1
  25. package/dist/components/json-schema-form-preferences.js +4 -0
  26. package/dist/components/json-schema-form-preferences.js.map +1 -1
  27. package/dist/components/json-schema-form-render.cjs +1 -1
  28. package/dist/components/json-schema-form-render.cjs.map +1 -1
  29. package/dist/components/json-schema-form-render.d.ts.map +1 -1
  30. package/dist/components/json-schema-form-render.js +2 -2
  31. package/dist/components/json-schema-form-render.js.map +1 -1
  32. package/dist/components/json-schema-form-types.d.ts +3 -2
  33. package/dist/components/json-schema-form-types.d.ts.map +1 -1
  34. package/dist/components/json-schema-form-utils.cjs +13 -0
  35. package/dist/components/json-schema-form-utils.cjs.map +1 -1
  36. package/dist/components/json-schema-form-utils.d.ts +2 -0
  37. package/dist/components/json-schema-form-utils.d.ts.map +1 -1
  38. package/dist/components/json-schema-form-utils.js +13 -0
  39. package/dist/components/json-schema-form-utils.js.map +1 -1
  40. package/dist/components/loading.cjs +19 -6
  41. package/dist/components/loading.cjs.map +1 -1
  42. package/dist/components/loading.d.ts.map +1 -1
  43. package/dist/components/loading.js +19 -6
  44. package/dist/components/loading.js.map +1 -1
  45. package/dist/data/Markdown.cjs +14 -40
  46. package/dist/data/Markdown.cjs.map +1 -1
  47. package/dist/data/Markdown.d.ts +3 -0
  48. package/dist/data/Markdown.d.ts.map +1 -1
  49. package/dist/data/Markdown.js +14 -40
  50. package/dist/data/Markdown.js.map +1 -1
  51. package/dist/data/TaskProgress.cjs +22 -0
  52. package/dist/data/TaskProgress.cjs.map +1 -1
  53. package/dist/data/TaskProgress.js +22 -0
  54. package/dist/data/TaskProgress.js.map +1 -1
  55. package/dist/data/TaskSnapshot.cjs.map +1 -1
  56. package/dist/data/TaskSnapshot.d.ts +5 -0
  57. package/dist/data/TaskSnapshot.d.ts.map +1 -1
  58. package/dist/data/TaskSnapshot.js.map +1 -1
  59. package/dist/data/Tree.cjs +32 -1
  60. package/dist/data/Tree.cjs.map +1 -1
  61. package/dist/data/Tree.d.ts +9 -1
  62. package/dist/data/Tree.d.ts.map +1 -1
  63. package/dist/data/Tree.js +32 -1
  64. package/dist/data/Tree.js.map +1 -1
  65. package/dist/data/chat/Attachment.cjs +79 -0
  66. package/dist/data/chat/Attachment.cjs.map +1 -0
  67. package/dist/data/chat/Attachment.d.ts +18 -0
  68. package/dist/data/chat/Attachment.d.ts.map +1 -0
  69. package/dist/data/chat/Attachment.js +79 -0
  70. package/dist/data/chat/Attachment.js.map +1 -0
  71. package/dist/data/chat/Chat.cjs +71 -14
  72. package/dist/data/chat/Chat.cjs.map +1 -1
  73. package/dist/data/chat/Chat.d.ts +30 -12
  74. package/dist/data/chat/Chat.d.ts.map +1 -1
  75. package/dist/data/chat/Chat.fixtures.d.ts +9 -0
  76. package/dist/data/chat/Chat.fixtures.d.ts.map +1 -1
  77. package/dist/data/chat/Chat.js +73 -16
  78. package/dist/data/chat/Chat.js.map +1 -1
  79. package/dist/data/chat/Conversation.cjs +32 -14
  80. package/dist/data/chat/Conversation.cjs.map +1 -1
  81. package/dist/data/chat/Conversation.d.ts +5 -3
  82. package/dist/data/chat/Conversation.d.ts.map +1 -1
  83. package/dist/data/chat/Conversation.js +34 -16
  84. package/dist/data/chat/Conversation.js.map +1 -1
  85. package/dist/data/chat/Message.cjs +73 -14
  86. package/dist/data/chat/Message.cjs.map +1 -1
  87. package/dist/data/chat/Message.d.ts +11 -4
  88. package/dist/data/chat/Message.d.ts.map +1 -1
  89. package/dist/data/chat/Message.js +75 -16
  90. package/dist/data/chat/Message.js.map +1 -1
  91. package/dist/data/chat/MessageActions.cjs +54 -0
  92. package/dist/data/chat/MessageActions.cjs.map +1 -0
  93. package/dist/data/chat/MessageActions.d.ts +11 -0
  94. package/dist/data/chat/MessageActions.d.ts.map +1 -0
  95. package/dist/data/chat/MessageActions.js +54 -0
  96. package/dist/data/chat/MessageActions.js.map +1 -0
  97. package/dist/data/chat/ModelSelector.cjs +43 -0
  98. package/dist/data/chat/ModelSelector.cjs.map +1 -0
  99. package/dist/data/chat/ModelSelector.d.ts +22 -0
  100. package/dist/data/chat/ModelSelector.d.ts.map +1 -0
  101. package/dist/data/chat/ModelSelector.js +43 -0
  102. package/dist/data/chat/ModelSelector.js.map +1 -0
  103. package/dist/data/chat/PromptInput.cjs +28 -15
  104. package/dist/data/chat/PromptInput.cjs.map +1 -1
  105. package/dist/data/chat/PromptInput.d.ts +14 -8
  106. package/dist/data/chat/PromptInput.d.ts.map +1 -1
  107. package/dist/data/chat/PromptInput.js +28 -15
  108. package/dist/data/chat/PromptInput.js.map +1 -1
  109. package/dist/data/chat/Reasoning.cjs +37 -0
  110. package/dist/data/chat/Reasoning.cjs.map +1 -0
  111. package/dist/data/chat/Reasoning.d.ts +11 -0
  112. package/dist/data/chat/Reasoning.d.ts.map +1 -0
  113. package/dist/data/chat/Reasoning.js +37 -0
  114. package/dist/data/chat/Reasoning.js.map +1 -0
  115. package/dist/data/chat/Suggestion.cjs +20 -0
  116. package/dist/data/chat/Suggestion.cjs.map +1 -0
  117. package/dist/data/chat/Suggestion.d.ts +10 -0
  118. package/dist/data/chat/Suggestion.d.ts.map +1 -0
  119. package/dist/data/chat/Suggestion.js +20 -0
  120. package/dist/data/chat/Suggestion.js.map +1 -0
  121. package/dist/data/chat/ToolCall.cjs +26 -3
  122. package/dist/data/chat/ToolCall.cjs.map +1 -1
  123. package/dist/data/chat/ToolCall.d.ts +5 -1
  124. package/dist/data/chat/ToolCall.d.ts.map +1 -1
  125. package/dist/data/chat/ToolCall.js +26 -3
  126. package/dist/data/chat/ToolCall.js.map +1 -1
  127. package/dist/data/chat/index.d.ts +7 -2
  128. package/dist/data/chat/index.d.ts.map +1 -1
  129. package/dist/data/chat/types.cjs +16 -0
  130. package/dist/data/chat/types.cjs.map +1 -1
  131. package/dist/data/chat/types.d.ts +29 -2
  132. package/dist/data/chat/types.d.ts.map +1 -1
  133. package/dist/data/chat/types.js +16 -0
  134. package/dist/data/chat/types.js.map +1 -1
  135. package/dist/data/version-info.cjs +3 -3
  136. package/dist/data/version-info.js +3 -3
  137. package/dist/hooks/use-task-run.cjs +33 -20
  138. package/dist/hooks/use-task-run.cjs.map +1 -1
  139. package/dist/hooks/use-task-run.d.ts.map +1 -1
  140. package/dist/hooks/use-task-run.js +33 -20
  141. package/dist/hooks/use-task-run.js.map +1 -1
  142. package/dist/styles.css +1 -1
  143. package/package.json +6 -1
@@ -1,14 +1,25 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useMemo } from "react";
2
+ import { useState, useEffect, useRef, useMemo } from "react";
3
3
  import { useChat } from "@ai-sdk/react";
4
- import { DefaultChatTransport } from "ai";
4
+ import { DefaultChatTransport, lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
5
5
  import { cn } from "../../lib/utils.js";
6
6
  import { Conversation } from "./Conversation.js";
7
7
  import { PromptInput } from "./PromptInput.js";
8
+ import { Suggestions } from "./Suggestion.js";
9
+ import { ModelSelector, EffortSelector } from "./ModelSelector.js";
10
+ const DEFAULT_EFFORTS = ["low", "medium", "high"];
8
11
  function Chat({
9
12
  api = "/api/chat",
10
- model,
11
- reasoningEffort,
13
+ models: modelsProp,
14
+ modelsApi = "/api/chat/models",
15
+ defaultModel,
16
+ reasoningEfforts = DEFAULT_EFFORTS,
17
+ defaultReasoningEffort = "",
18
+ onModelChange,
19
+ suggestions,
20
+ enableAttachments = false,
21
+ toolApproval,
22
+ threadId,
12
23
  body,
13
24
  transport,
14
25
  initialMessages,
@@ -16,28 +27,74 @@ function Chat({
16
27
  emptyState,
17
28
  className
18
29
  }) {
19
- const resolvedTransport = useMemo(() => {
20
- if (transport) {
21
- return transport;
22
- }
23
- return new DefaultChatTransport({
24
- api,
25
- body: { ...body, ...model ? { model } : {}, ...reasoningEffort ? { reasoningEffort } : {} }
26
- });
27
- }, [transport, api, body, model, reasoningEffort]);
28
- const { messages, sendMessage, status, stop } = useChat({
30
+ const [models, setModels] = useState(modelsProp ?? []);
31
+ const [model, setModel] = useState(defaultModel);
32
+ const [effort, setEffort] = useState(defaultReasoningEffort);
33
+ useEffect(() => {
34
+ if (modelsProp || !modelsApi) return;
35
+ let cancelled = false;
36
+ fetch(modelsApi).then((r) => r.ok ? r.json() : Promise.reject(new Error(`models ${r.status}`))).then((data) => {
37
+ if (cancelled) return;
38
+ setModels(data);
39
+ setModel((m) => {
40
+ var _a;
41
+ return m ?? ((_a = data.find((d) => d.configured !== false)) == null ? void 0 : _a.id);
42
+ });
43
+ }).catch((err) => console.warn("clicky-ui: failed to load chat models", err));
44
+ return () => {
45
+ cancelled = true;
46
+ };
47
+ }, [modelsProp, modelsApi]);
48
+ const selectedModel = models.find((m) => m.id === model);
49
+ const showEffort = !selectedModel || selectedModel.reasoning;
50
+ const bodyRef = useRef({});
51
+ bodyRef.current = {
52
+ ...body,
53
+ ...model ? { model } : {},
54
+ ...effort ? { reasoningEffort: effort } : {},
55
+ ...toolApproval ? { toolApproval } : {},
56
+ ...threadId ? { threadId } : {}
57
+ };
58
+ const resolvedTransport = useMemo(
59
+ () => transport ?? new DefaultChatTransport({ api, body: () => bodyRef.current }),
60
+ [transport, api]
61
+ );
62
+ const { messages, sendMessage, regenerate, addToolApprovalResponse, status, stop } = useChat({
29
63
  transport: resolvedTransport,
64
+ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
30
65
  ...initialMessages ? { messages: initialMessages } : {}
31
66
  });
67
+ const onModelSelect = (id) => {
68
+ setModel(id);
69
+ onModelChange == null ? void 0 : onModelChange(id);
70
+ };
71
+ const toolbar = models.length > 0 || showEffort ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
72
+ /* @__PURE__ */ jsx(ModelSelector, { models, value: model, onChange: onModelSelect }),
73
+ showEffort && /* @__PURE__ */ jsx(EffortSelector, { efforts: reasoningEfforts, value: effort, onChange: setEffort })
74
+ ] }) : void 0;
75
+ const empty = messages.length === 0 && (emptyState || (suggestions == null ? void 0 : suggestions.length)) ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
76
+ emptyState,
77
+ suggestions && suggestions.length > 0 && /* @__PURE__ */ jsx(Suggestions, { suggestions, onSelect: (text) => void sendMessage({ text }) })
78
+ ] }) : void 0;
32
79
  return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full flex-col", className), children: [
33
- /* @__PURE__ */ jsx(Conversation, { messages, emptyState }),
80
+ /* @__PURE__ */ jsx(
81
+ Conversation,
82
+ {
83
+ messages,
84
+ emptyState: empty,
85
+ onRegenerate: (messageId) => void regenerate({ messageId }),
86
+ onApprove: (id, approved, reason) => void addToolApprovalResponse(reason ? { id, approved, reason } : { id, approved })
87
+ }
88
+ ),
34
89
  /* @__PURE__ */ jsx("div", { className: "p-4 pt-0", children: /* @__PURE__ */ jsx(
35
90
  PromptInput,
36
91
  {
37
92
  status,
38
93
  onStop: () => void stop(),
39
94
  placeholder,
40
- onSubmit: (text) => void sendMessage({ text })
95
+ enableAttachments,
96
+ toolbar,
97
+ onSubmit: (text, files) => void sendMessage({ text, files })
41
98
  }
42
99
  ) })
43
100
  ] });
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.js","sources":["../../../src/data/chat/Chat.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport, type ChatTransport, type UIMessage } from \"ai\";\nimport { cn } from \"../../lib/utils\";\nimport { Conversation } from \"./Conversation\";\nimport { PromptInput } from \"./PromptInput\";\n\nexport type ChatProps = {\n /** Endpoint that speaks the AI SDK v6 UI Message Stream protocol.\n * Ignored when `transport` is supplied. Defaults to \"/api/chat\". */\n api?: string;\n /** Selected `provider/model` id, forwarded in the request body. */\n model?: string;\n /** Reasoning effort (`low` | `medium` | `high`), forwarded in the request body. */\n reasoningEffort?: string;\n /** Extra fields merged into every request body (e.g. tool scoping). */\n body?: Record<string, unknown>;\n /** Pre-built transport. Supply a mock in stories/tests; otherwise a\n * DefaultChatTransport is constructed from `api`/`body`. */\n transport?: ChatTransport<UIMessage>;\n /** Initial messages to seed the conversation. */\n initialMessages?: UIMessage[];\n placeholder?: string;\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** Self-contained AI chat: a streaming conversation log over a prompt input,\n * driven by the AI SDK `useChat` hook against a v6-compatible `/api/chat`. The\n * backend owns model selection and tool execution; `model`, `reasoningEffort`\n * and `body` are forwarded in the request body for it to honor. */\nexport function Chat({\n api = \"/api/chat\",\n model,\n reasoningEffort,\n body,\n transport,\n initialMessages,\n placeholder,\n emptyState,\n className,\n}: ChatProps) {\n const resolvedTransport = useMemo<ChatTransport<UIMessage>>(() => {\n if (transport) {\n return transport;\n }\n return new DefaultChatTransport({\n api,\n body: { ...body, ...(model ? { model } : {}), ...(reasoningEffort ? { reasoningEffort } : {}) },\n });\n }, [transport, api, body, model, reasoningEffort]);\n\n const { messages, sendMessage, status, stop } = useChat<UIMessage>({\n transport: resolvedTransport,\n ...(initialMessages ? { messages: initialMessages } : {}),\n });\n\n return (\n <div className={cn(\"flex h-full flex-col\", className)}>\n <Conversation messages={messages} emptyState={emptyState} />\n <div className=\"p-4 pt-0\">\n <PromptInput\n status={status}\n onStop={() => void stop()}\n placeholder={placeholder}\n onSubmit={(text) => void sendMessage({ text })}\n />\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;AA+BO,SAAS,KAAK;AAAA,EACnB,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAc;AACZ,QAAM,oBAAoB,QAAkC,MAAM;AAChE,QAAI,WAAW;AACb,aAAO;AAAA,IACT;AACA,WAAO,IAAI,qBAAqB;AAAA,MAC9B;AAAA,MACA,MAAM,EAAE,GAAG,MAAM,GAAI,QAAQ,EAAE,UAAU,CAAA,GAAK,GAAI,kBAAkB,EAAE,gBAAA,IAAoB,CAAA,EAAC;AAAA,IAAG,CAC/F;AAAA,EACH,GAAG,CAAC,WAAW,KAAK,MAAM,OAAO,eAAe,CAAC;AAEjD,QAAM,EAAE,UAAU,aAAa,QAAQ,KAAA,IAAS,QAAmB;AAAA,IACjE,WAAW;AAAA,IACX,GAAI,kBAAkB,EAAE,UAAU,oBAAoB,CAAA;AAAA,EAAC,CACxD;AAED,8BACG,OAAA,EAAI,WAAW,GAAG,wBAAwB,SAAS,GAClD,UAAA;AAAA,IAAA,oBAAC,cAAA,EAAa,UAAoB,WAAA,CAAwB;AAAA,IAC1D,oBAAC,OAAA,EAAI,WAAU,YACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,QAAQ,MAAM,KAAK,KAAA;AAAA,QACnB;AAAA,QACA,UAAU,CAAC,SAAS,KAAK,YAAY,EAAE,MAAM;AAAA,MAAA;AAAA,IAAA,EAC/C,CACF;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"Chat.js","sources":["../../../src/data/chat/Chat.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useChat } from \"@ai-sdk/react\";\nimport {\n DefaultChatTransport,\n lastAssistantMessageIsCompleteWithApprovalResponses,\n type ChatTransport,\n type UIMessage,\n} from \"ai\";\nimport { cn } from \"../../lib/utils\";\nimport { Conversation } from \"./Conversation\";\nimport { PromptInput } from \"./PromptInput\";\nimport { Suggestions } from \"./Suggestion\";\nimport { ModelSelector, EffortSelector } from \"./ModelSelector\";\nimport type { ChatModel, Suggestion } from \"./types\";\n\nexport type ChatProps = {\n /** Endpoint that speaks the AI SDK v6 UI Message Stream protocol.\n * Ignored when `transport` is supplied. Defaults to \"/api/chat\". */\n api?: string;\n /** Model menu. When omitted and `modelsApi` is set, it is fetched. */\n models?: ChatModel[];\n /** Endpoint returning the model menu (ModelInfo[]). Defaults to\n * \"/api/chat/models\". Set to null to disable fetching. */\n modelsApi?: string | null;\n /** Initially selected model id; otherwise the first configured model. */\n defaultModel?: string;\n /** Reasoning-effort options for capable models. */\n reasoningEfforts?: string[];\n /** Initially selected reasoning effort (\"\" = none). */\n defaultReasoningEffort?: string;\n /** Notified when the user changes the model. */\n onModelChange?: (id: string) => void;\n /** Suggested prompts shown on the empty state. */\n suggestions?: Suggestion[];\n /** Enables file/image attachments. */\n enableAttachments?: boolean;\n /** \"manual\" forwards a tool-approval policy hint to the backend. */\n toolApproval?: \"auto\" | \"manual\";\n /** Thread id to persist this conversation under (forwarded in the body). */\n threadId?: string;\n /** Extra fields merged into every request body. */\n body?: Record<string, unknown>;\n /** Pre-built transport (e.g. a mock for stories/tests). */\n transport?: ChatTransport<UIMessage>;\n /** Initial messages to seed the conversation. */\n initialMessages?: UIMessage[];\n placeholder?: string;\n emptyState?: React.ReactNode;\n className?: string;\n};\n\nconst DEFAULT_EFFORTS = [\"low\", \"medium\", \"high\"];\n\n/** Self-contained AI chat over a v6-compatible `/api/chat`: a streaming\n * conversation with model/effort selectors, suggestions, attachments,\n * per-message copy/regenerate, and human-in-the-loop tool approvals. The\n * backend owns model selection and tool execution; the selected model,\n * effort, approval policy and thread id are forwarded in the request body. */\nexport function Chat({\n api = \"/api/chat\",\n models: modelsProp,\n modelsApi = \"/api/chat/models\",\n defaultModel,\n reasoningEfforts = DEFAULT_EFFORTS,\n defaultReasoningEffort = \"\",\n onModelChange,\n suggestions,\n enableAttachments = false,\n toolApproval,\n threadId,\n body,\n transport,\n initialMessages,\n placeholder,\n emptyState,\n className,\n}: ChatProps) {\n const [models, setModels] = useState<ChatModel[]>(modelsProp ?? []);\n const [model, setModel] = useState<string | undefined>(defaultModel);\n const [effort, setEffort] = useState(defaultReasoningEffort);\n\n // Fetch the model menu unless one was supplied or fetching is disabled.\n useEffect(() => {\n if (modelsProp || !modelsApi) return;\n let cancelled = false;\n fetch(modelsApi)\n .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`models ${r.status}`))))\n .then((data: ChatModel[]) => {\n if (cancelled) return;\n setModels(data);\n setModel((m) => m ?? data.find((d) => d.configured !== false)?.id);\n })\n .catch((err) => console.warn(\"clicky-ui: failed to load chat models\", err));\n return () => {\n cancelled = true;\n };\n }, [modelsProp, modelsApi]);\n\n const selectedModel = models.find((m) => m.id === model);\n const showEffort = !selectedModel || selectedModel.reasoning;\n\n // A function body keeps the transport stable while always sending the latest\n // model/effort/policy selections.\n const bodyRef = useRef<Record<string, unknown>>({});\n bodyRef.current = {\n ...body,\n ...(model ? { model } : {}),\n ...(effort ? { reasoningEffort: effort } : {}),\n ...(toolApproval ? { toolApproval } : {}),\n ...(threadId ? { threadId } : {}),\n };\n\n const resolvedTransport = useMemo<ChatTransport<UIMessage>>(\n () => transport ?? new DefaultChatTransport({ api, body: () => bodyRef.current }),\n [transport, api],\n );\n\n const { messages, sendMessage, regenerate, addToolApprovalResponse, status, stop } =\n useChat<UIMessage>({\n transport: resolvedTransport,\n sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,\n ...(initialMessages ? { messages: initialMessages } : {}),\n });\n\n const onModelSelect = (id: string) => {\n setModel(id);\n onModelChange?.(id);\n };\n\n const toolbar = models.length > 0 || showEffort ? (\n <div className=\"flex items-center gap-2\">\n <ModelSelector models={models} value={model} onChange={onModelSelect} />\n {showEffort && (\n <EffortSelector efforts={reasoningEfforts} value={effort} onChange={setEffort} />\n )}\n </div>\n ) : undefined;\n\n const empty = (messages.length === 0 && (emptyState || suggestions?.length)) ? (\n <div className=\"flex flex-col items-center gap-4\">\n {emptyState}\n {suggestions && suggestions.length > 0 && (\n <Suggestions suggestions={suggestions} onSelect={(text) => void sendMessage({ text })} />\n )}\n </div>\n ) : undefined;\n\n return (\n <div className={cn(\"flex h-full flex-col\", className)}>\n <Conversation\n messages={messages}\n emptyState={empty}\n onRegenerate={(messageId) => void regenerate({ messageId })}\n onApprove={(id, approved, reason) =>\n void addToolApprovalResponse(reason ? { id, approved, reason } : { id, approved })\n }\n />\n <div className=\"p-4 pt-0\">\n <PromptInput\n status={status}\n onStop={() => void stop()}\n placeholder={placeholder}\n enableAttachments={enableAttachments}\n toolbar={toolbar}\n onSubmit={(text, files) => void sendMessage({ text, files })}\n />\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -3,32 +3,50 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const react = require("react");
5
5
  const utils = require("../../lib/utils.cjs");
6
+ const Icon = require("../Icon.cjs");
6
7
  const Message = require("./Message.cjs");
7
- function Conversation({ messages, emptyState, className }) {
8
+ const UiArrowDown = require("../../icons/components/UiArrowDown.cjs");
9
+ function Conversation({ messages, emptyState, className, ...actions }) {
8
10
  const scrollRef = react.useRef(null);
9
- const pinnedRef = react.useRef(true);
11
+ const [pinned, setPinned] = react.useState(true);
10
12
  const onScroll = () => {
11
13
  const el = scrollRef.current;
12
14
  if (!el) return;
13
15
  const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
14
- pinnedRef.current = distance < 32;
16
+ setPinned(distance < 32);
15
17
  };
16
18
  react.useEffect(() => {
17
19
  const el = scrollRef.current;
18
- if (el && pinnedRef.current) {
20
+ if (el && pinned) {
19
21
  el.scrollTop = el.scrollHeight;
20
22
  }
21
23
  });
22
- return /* @__PURE__ */ jsxRuntime.jsx(
23
- "div",
24
- {
25
- ref: scrollRef,
26
- onScroll,
27
- role: "log",
28
- className: utils.cn("relative flex-1 overflow-y-auto", className),
29
- children: messages.length === 0 && emptyState ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-full flex-col items-center justify-center gap-3 p-8 text-center", children: emptyState }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-8 p-4", children: messages.map((message) => /* @__PURE__ */ jsxRuntime.jsx(Message.Message, { message }, message.id)) })
30
- }
31
- );
24
+ const scrollToBottom = () => {
25
+ const el = scrollRef.current;
26
+ if (el) el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
27
+ };
28
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
29
+ /* @__PURE__ */ jsxRuntime.jsx(
30
+ "div",
31
+ {
32
+ ref: scrollRef,
33
+ onScroll,
34
+ role: "log",
35
+ className: utils.cn("flex-1 overflow-y-auto", className),
36
+ children: messages.length === 0 && emptyState ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-full flex-col items-center justify-center gap-3 p-8 text-center", children: emptyState }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-8 p-4", children: messages.map((message) => /* @__PURE__ */ jsxRuntime.jsx(Message.Message, { message, ...actions }, message.id)) })
37
+ }
38
+ ),
39
+ !pinned && messages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
40
+ "button",
41
+ {
42
+ type: "button",
43
+ "aria-label": "Scroll to latest",
44
+ onClick: scrollToBottom,
45
+ className: "absolute bottom-3 left-1/2 flex size-8 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-md hover:text-foreground",
46
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiArrowDown.UiArrowDown, className: "size-4" })
47
+ }
48
+ )
49
+ ] });
32
50
  }
33
51
  exports.Conversation = Conversation;
34
52
  //# sourceMappingURL=Conversation.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Conversation.cjs","sources":["../../../src/data/chat/Conversation.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Message } from \"./Message\";\nimport type { UIMessage } from \"./types\";\n\nexport type ConversationProps = {\n messages: UIMessage[];\n /** Shown when there are no messages yet. */\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** A scrollable message log that sticks to the bottom as new content streams\n * in, unless the user has scrolled up. */\nexport function Conversation({ messages, emptyState, className }: ConversationProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n const pinnedRef = useRef(true);\n\n const onScroll = () => {\n const el = scrollRef.current;\n if (!el) return;\n const distance = el.scrollHeight - el.scrollTop - el.clientHeight;\n pinnedRef.current = distance < 32;\n };\n\n useEffect(() => {\n const el = scrollRef.current;\n if (el && pinnedRef.current) {\n el.scrollTop = el.scrollHeight;\n }\n });\n\n return (\n <div\n ref={scrollRef}\n onScroll={onScroll}\n role=\"log\"\n className={cn(\"relative flex-1 overflow-y-auto\", className)}\n >\n {messages.length === 0 && emptyState ? (\n <div className=\"flex size-full flex-col items-center justify-center gap-3 p-8 text-center\">\n {emptyState}\n </div>\n ) : (\n <div className=\"flex flex-col gap-8 p-4\">\n {messages.map((message) => (\n <Message key={message.id} message={message} />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":["useRef","useEffect","jsx","cn","Message"],"mappings":";;;;;;AAcO,SAAS,aAAa,EAAE,UAAU,YAAY,aAAgC;AACnF,QAAM,YAAYA,MAAAA,OAAuB,IAAI;AAC7C,QAAM,YAAYA,MAAAA,OAAO,IAAI;AAE7B,QAAM,WAAW,MAAM;AACrB,UAAM,KAAK,UAAU;AACrB,QAAI,CAAC,GAAI;AACT,UAAM,WAAW,GAAG,eAAe,GAAG,YAAY,GAAG;AACrD,cAAU,UAAU,WAAW;AAAA,EACjC;AAEAC,QAAAA,UAAU,MAAM;AACd,UAAM,KAAK,UAAU;AACrB,QAAI,MAAM,UAAU,SAAS;AAC3B,SAAG,YAAY,GAAG;AAAA,IACpB;AAAA,EACF,CAAC;AAED,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK;AAAA,MACL;AAAA,MACA,MAAK;AAAA,MACL,WAAWC,MAAAA,GAAG,mCAAmC,SAAS;AAAA,MAEzD,UAAA,SAAS,WAAW,KAAK,aACxBD,2BAAAA,IAAC,SAAI,WAAU,6EACZ,UAAA,WAAA,CACH,IAEAA,2BAAAA,IAAC,OAAA,EAAI,WAAU,2BACZ,UAAA,SAAS,IAAI,CAAC,YACbA,+BAACE,QAAAA,WAAyB,QAAA,GAAZ,QAAQ,EAAsB,CAC7C,EAAA,CACH;AAAA,IAAA;AAAA,EAAA;AAIR;;"}
1
+ {"version":3,"file":"Conversation.cjs","sources":["../../../src/data/chat/Conversation.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { UiArrowDown } from \"../../icons\";\nimport { Message, type MessageActionHandlers } from \"./Message\";\nimport type { UIMessage } from \"./types\";\n\nexport type ConversationProps = MessageActionHandlers & {\n messages: UIMessage[];\n /** Shown when there are no messages yet. */\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** A scrollable message log that sticks to the bottom as new content streams\n * in, unless the user has scrolled up — in which case a jump-to-bottom button\n * appears. */\nexport function Conversation({ messages, emptyState, className, ...actions }: ConversationProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n const [pinned, setPinned] = useState(true);\n\n const onScroll = () => {\n const el = scrollRef.current;\n if (!el) return;\n const distance = el.scrollHeight - el.scrollTop - el.clientHeight;\n setPinned(distance < 32);\n };\n\n useEffect(() => {\n const el = scrollRef.current;\n if (el && pinned) {\n el.scrollTop = el.scrollHeight;\n }\n });\n\n const scrollToBottom = () => {\n const el = scrollRef.current;\n if (el) el.scrollTo({ top: el.scrollHeight, behavior: \"smooth\" });\n };\n\n return (\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\n <div\n ref={scrollRef}\n onScroll={onScroll}\n role=\"log\"\n className={cn(\"flex-1 overflow-y-auto\", className)}\n >\n {messages.length === 0 && emptyState ? (\n <div className=\"flex size-full flex-col items-center justify-center gap-3 p-8 text-center\">\n {emptyState}\n </div>\n ) : (\n <div className=\"flex flex-col gap-8 p-4\">\n {messages.map((message) => (\n <Message key={message.id} message={message} {...actions} />\n ))}\n </div>\n )}\n </div>\n\n {!pinned && messages.length > 0 && (\n <button\n type=\"button\"\n aria-label=\"Scroll to latest\"\n onClick={scrollToBottom}\n className=\"absolute bottom-3 left-1/2 flex size-8 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-md hover:text-foreground\"\n >\n <Icon icon={UiArrowDown} className=\"size-4\" />\n </button>\n )}\n </div>\n );\n}\n"],"names":["useRef","useState","useEffect","jsxs","jsx","cn","Message","Icon","UiArrowDown"],"mappings":";;;;;;;;AAiBO,SAAS,aAAa,EAAE,UAAU,YAAY,WAAW,GAAG,WAA8B;AAC/F,QAAM,YAAYA,MAAAA,OAAuB,IAAI;AAC7C,QAAM,CAAC,QAAQ,SAAS,IAAIC,MAAAA,SAAS,IAAI;AAEzC,QAAM,WAAW,MAAM;AACrB,UAAM,KAAK,UAAU;AACrB,QAAI,CAAC,GAAI;AACT,UAAM,WAAW,GAAG,eAAe,GAAG,YAAY,GAAG;AACrD,cAAU,WAAW,EAAE;AAAA,EACzB;AAEAC,QAAAA,UAAU,MAAM;AACd,UAAM,KAAK,UAAU;AACrB,QAAI,MAAM,QAAQ;AAChB,SAAG,YAAY,GAAG;AAAA,IACpB;AAAA,EACF,CAAC;AAED,QAAM,iBAAiB,MAAM;AAC3B,UAAM,KAAK,UAAU;AACrB,QAAI,OAAO,SAAS,EAAE,KAAK,GAAG,cAAc,UAAU,UAAU;AAAA,EAClE;AAEA,SACEC,2BAAAA,KAAC,OAAA,EAAI,WAAU,yCACb,UAAA;AAAA,IAAAC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL;AAAA,QACA,MAAK;AAAA,QACL,WAAWC,MAAAA,GAAG,0BAA0B,SAAS;AAAA,QAEhD,UAAA,SAAS,WAAW,KAAK,aACxBD,2BAAAA,IAAC,OAAA,EAAI,WAAU,6EACZ,UAAA,WAAA,CACH,IAEAA,2BAAAA,IAAC,OAAA,EAAI,WAAU,2BACZ,UAAA,SAAS,IAAI,CAAC,YACbA,2BAAAA,IAACE,QAAAA,SAAA,EAAyB,SAAmB,GAAG,QAAA,GAAlC,QAAQ,EAAmC,CAC1D,EAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,IAIH,CAAC,UAAU,SAAS,SAAS,KAC5BF,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,SAAS;AAAA,QACT,WAAU;AAAA,QAEV,UAAAA,2BAAAA,IAACG,WAAA,EAAK,MAAMC,YAAAA,aAAa,WAAU,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAC9C,GAEJ;AAEJ;;"}
@@ -1,11 +1,13 @@
1
+ import { MessageActionHandlers } from './Message';
1
2
  import { UIMessage } from './types';
2
- export type ConversationProps = {
3
+ export type ConversationProps = MessageActionHandlers & {
3
4
  messages: UIMessage[];
4
5
  /** Shown when there are no messages yet. */
5
6
  emptyState?: React.ReactNode;
6
7
  className?: string;
7
8
  };
8
9
  /** A scrollable message log that sticks to the bottom as new content streams
9
- * in, unless the user has scrolled up. */
10
- export declare function Conversation({ messages, emptyState, className }: ConversationProps): import("react/jsx-runtime").JSX.Element;
10
+ * in, unless the user has scrolled up — in which case a jump-to-bottom button
11
+ * appears. */
12
+ export declare function Conversation({ messages, emptyState, className, ...actions }: ConversationProps): import("react/jsx-runtime").JSX.Element;
11
13
  //# sourceMappingURL=Conversation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Conversation.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Conversation.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;2CAC2C;AAC3C,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CAsClF"}
1
+ {"version":3,"file":"Conversation.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Conversation.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAW,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG;IACtD,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;eAEe;AACf,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,OAAO,EAAE,EAAE,iBAAiB,2CAwD9F"}
@@ -1,32 +1,50 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import { useRef, useEffect } from "react";
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useRef, useState, useEffect } from "react";
3
3
  import { cn } from "../../lib/utils.js";
4
+ import { Icon } from "../Icon.js";
4
5
  import { Message } from "./Message.js";
5
- function Conversation({ messages, emptyState, className }) {
6
+ import { UiArrowDown } from "../../icons/components/UiArrowDown.js";
7
+ function Conversation({ messages, emptyState, className, ...actions }) {
6
8
  const scrollRef = useRef(null);
7
- const pinnedRef = useRef(true);
9
+ const [pinned, setPinned] = useState(true);
8
10
  const onScroll = () => {
9
11
  const el = scrollRef.current;
10
12
  if (!el) return;
11
13
  const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
12
- pinnedRef.current = distance < 32;
14
+ setPinned(distance < 32);
13
15
  };
14
16
  useEffect(() => {
15
17
  const el = scrollRef.current;
16
- if (el && pinnedRef.current) {
18
+ if (el && pinned) {
17
19
  el.scrollTop = el.scrollHeight;
18
20
  }
19
21
  });
20
- return /* @__PURE__ */ jsx(
21
- "div",
22
- {
23
- ref: scrollRef,
24
- onScroll,
25
- role: "log",
26
- className: cn("relative flex-1 overflow-y-auto", className),
27
- children: messages.length === 0 && emptyState ? /* @__PURE__ */ jsx("div", { className: "flex size-full flex-col items-center justify-center gap-3 p-8 text-center", children: emptyState }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-8 p-4", children: messages.map((message) => /* @__PURE__ */ jsx(Message, { message }, message.id)) })
28
- }
29
- );
22
+ const scrollToBottom = () => {
23
+ const el = scrollRef.current;
24
+ if (el) el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
25
+ };
26
+ return /* @__PURE__ */ jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
27
+ /* @__PURE__ */ jsx(
28
+ "div",
29
+ {
30
+ ref: scrollRef,
31
+ onScroll,
32
+ role: "log",
33
+ className: cn("flex-1 overflow-y-auto", className),
34
+ children: messages.length === 0 && emptyState ? /* @__PURE__ */ jsx("div", { className: "flex size-full flex-col items-center justify-center gap-3 p-8 text-center", children: emptyState }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-8 p-4", children: messages.map((message) => /* @__PURE__ */ jsx(Message, { message, ...actions }, message.id)) })
35
+ }
36
+ ),
37
+ !pinned && messages.length > 0 && /* @__PURE__ */ jsx(
38
+ "button",
39
+ {
40
+ type: "button",
41
+ "aria-label": "Scroll to latest",
42
+ onClick: scrollToBottom,
43
+ className: "absolute bottom-3 left-1/2 flex size-8 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-md hover:text-foreground",
44
+ children: /* @__PURE__ */ jsx(Icon, { icon: UiArrowDown, className: "size-4" })
45
+ }
46
+ )
47
+ ] });
30
48
  }
31
49
  export {
32
50
  Conversation
@@ -1 +1 @@
1
- {"version":3,"file":"Conversation.js","sources":["../../../src/data/chat/Conversation.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Message } from \"./Message\";\nimport type { UIMessage } from \"./types\";\n\nexport type ConversationProps = {\n messages: UIMessage[];\n /** Shown when there are no messages yet. */\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** A scrollable message log that sticks to the bottom as new content streams\n * in, unless the user has scrolled up. */\nexport function Conversation({ messages, emptyState, className }: ConversationProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n const pinnedRef = useRef(true);\n\n const onScroll = () => {\n const el = scrollRef.current;\n if (!el) return;\n const distance = el.scrollHeight - el.scrollTop - el.clientHeight;\n pinnedRef.current = distance < 32;\n };\n\n useEffect(() => {\n const el = scrollRef.current;\n if (el && pinnedRef.current) {\n el.scrollTop = el.scrollHeight;\n }\n });\n\n return (\n <div\n ref={scrollRef}\n onScroll={onScroll}\n role=\"log\"\n className={cn(\"relative flex-1 overflow-y-auto\", className)}\n >\n {messages.length === 0 && emptyState ? (\n <div className=\"flex size-full flex-col items-center justify-center gap-3 p-8 text-center\">\n {emptyState}\n </div>\n ) : (\n <div className=\"flex flex-col gap-8 p-4\">\n {messages.map((message) => (\n <Message key={message.id} message={message} />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;AAcO,SAAS,aAAa,EAAE,UAAU,YAAY,aAAgC;AACnF,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,YAAY,OAAO,IAAI;AAE7B,QAAM,WAAW,MAAM;AACrB,UAAM,KAAK,UAAU;AACrB,QAAI,CAAC,GAAI;AACT,UAAM,WAAW,GAAG,eAAe,GAAG,YAAY,GAAG;AACrD,cAAU,UAAU,WAAW;AAAA,EACjC;AAEA,YAAU,MAAM;AACd,UAAM,KAAK,UAAU;AACrB,QAAI,MAAM,UAAU,SAAS;AAC3B,SAAG,YAAY,GAAG;AAAA,IACpB;AAAA,EACF,CAAC;AAED,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK;AAAA,MACL;AAAA,MACA,MAAK;AAAA,MACL,WAAW,GAAG,mCAAmC,SAAS;AAAA,MAEzD,UAAA,SAAS,WAAW,KAAK,aACxB,oBAAC,SAAI,WAAU,6EACZ,UAAA,WAAA,CACH,IAEA,oBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA,SAAS,IAAI,CAAC,YACb,oBAAC,WAAyB,QAAA,GAAZ,QAAQ,EAAsB,CAC7C,EAAA,CACH;AAAA,IAAA;AAAA,EAAA;AAIR;"}
1
+ {"version":3,"file":"Conversation.js","sources":["../../../src/data/chat/Conversation.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { UiArrowDown } from \"../../icons\";\nimport { Message, type MessageActionHandlers } from \"./Message\";\nimport type { UIMessage } from \"./types\";\n\nexport type ConversationProps = MessageActionHandlers & {\n messages: UIMessage[];\n /** Shown when there are no messages yet. */\n emptyState?: React.ReactNode;\n className?: string;\n};\n\n/** A scrollable message log that sticks to the bottom as new content streams\n * in, unless the user has scrolled up — in which case a jump-to-bottom button\n * appears. */\nexport function Conversation({ messages, emptyState, className, ...actions }: ConversationProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n const [pinned, setPinned] = useState(true);\n\n const onScroll = () => {\n const el = scrollRef.current;\n if (!el) return;\n const distance = el.scrollHeight - el.scrollTop - el.clientHeight;\n setPinned(distance < 32);\n };\n\n useEffect(() => {\n const el = scrollRef.current;\n if (el && pinned) {\n el.scrollTop = el.scrollHeight;\n }\n });\n\n const scrollToBottom = () => {\n const el = scrollRef.current;\n if (el) el.scrollTo({ top: el.scrollHeight, behavior: \"smooth\" });\n };\n\n return (\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\n <div\n ref={scrollRef}\n onScroll={onScroll}\n role=\"log\"\n className={cn(\"flex-1 overflow-y-auto\", className)}\n >\n {messages.length === 0 && emptyState ? (\n <div className=\"flex size-full flex-col items-center justify-center gap-3 p-8 text-center\">\n {emptyState}\n </div>\n ) : (\n <div className=\"flex flex-col gap-8 p-4\">\n {messages.map((message) => (\n <Message key={message.id} message={message} {...actions} />\n ))}\n </div>\n )}\n </div>\n\n {!pinned && messages.length > 0 && (\n <button\n type=\"button\"\n aria-label=\"Scroll to latest\"\n onClick={scrollToBottom}\n className=\"absolute bottom-3 left-1/2 flex size-8 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-md hover:text-foreground\"\n >\n <Icon icon={UiArrowDown} className=\"size-4\" />\n </button>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;AAiBO,SAAS,aAAa,EAAE,UAAU,YAAY,WAAW,GAAG,WAA8B;AAC/F,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,IAAI;AAEzC,QAAM,WAAW,MAAM;AACrB,UAAM,KAAK,UAAU;AACrB,QAAI,CAAC,GAAI;AACT,UAAM,WAAW,GAAG,eAAe,GAAG,YAAY,GAAG;AACrD,cAAU,WAAW,EAAE;AAAA,EACzB;AAEA,YAAU,MAAM;AACd,UAAM,KAAK,UAAU;AACrB,QAAI,MAAM,QAAQ;AAChB,SAAG,YAAY,GAAG;AAAA,IACpB;AAAA,EACF,CAAC;AAED,QAAM,iBAAiB,MAAM;AAC3B,UAAM,KAAK,UAAU;AACrB,QAAI,OAAO,SAAS,EAAE,KAAK,GAAG,cAAc,UAAU,UAAU;AAAA,EAClE;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,yCACb,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL;AAAA,QACA,MAAK;AAAA,QACL,WAAW,GAAG,0BAA0B,SAAS;AAAA,QAEhD,UAAA,SAAS,WAAW,KAAK,aACxB,oBAAC,OAAA,EAAI,WAAU,6EACZ,UAAA,WAAA,CACH,IAEA,oBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA,SAAS,IAAI,CAAC,YACb,oBAAC,SAAA,EAAyB,SAAmB,GAAG,QAAA,GAAlC,QAAQ,EAAmC,CAC1D,EAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,IAIH,CAAC,UAAU,SAAS,SAAS,KAC5B;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,SAAS;AAAA,QACT,WAAU;AAAA,QAEV,UAAA,oBAAC,MAAA,EAAK,MAAM,aAAa,WAAU,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAC9C,GAEJ;AAEJ;"}
@@ -2,12 +2,17 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const utils = require("../../lib/utils.cjs");
5
+ const Icon = require("../Icon.cjs");
5
6
  const Markdown = require("../Markdown.cjs");
6
7
  const ToolCall = require("./ToolCall.cjs");
8
+ const MessageActions = require("./MessageActions.cjs");
9
+ const Reasoning = require("./Reasoning.cjs");
7
10
  const types = require("./types.cjs");
8
- function Message({ message, className }) {
11
+ const UiFile = require("../../icons/components/UiFile.cjs");
12
+ function Message({ message, className, onRegenerate, onApprove }) {
9
13
  const isUser = message.role === "user";
10
- return /* @__PURE__ */ jsxRuntime.jsx(
14
+ const text = message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
15
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11
16
  "div",
12
17
  {
13
18
  className: utils.cn(
@@ -15,30 +20,84 @@ function Message({ message, className }) {
15
20
  isUser ? "ml-auto items-end" : "items-start",
16
21
  className
17
22
  ),
18
- children: /* @__PURE__ */ jsxRuntime.jsx(
19
- "div",
20
- {
21
- className: utils.cn(
22
- "flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
23
- isUser && "rounded-lg bg-secondary px-4 py-3 text-secondary-foreground"
24
- ),
25
- children: message.parts.map((part, i) => /* @__PURE__ */ jsxRuntime.jsx(MessagePart, { part, isUser }, `${message.id}-${i}`))
26
- }
27
- )
23
+ children: [
24
+ /* @__PURE__ */ jsxRuntime.jsx(
25
+ "div",
26
+ {
27
+ className: utils.cn(
28
+ "flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
29
+ isUser && "rounded-lg bg-secondary px-4 py-3 text-secondary-foreground"
30
+ ),
31
+ children: message.parts.map((part, i) => /* @__PURE__ */ jsxRuntime.jsx(
32
+ MessagePart,
33
+ {
34
+ part,
35
+ isUser,
36
+ onApprove
37
+ },
38
+ `${message.id}-${i}`
39
+ ))
40
+ }
41
+ ),
42
+ !isUser && text && /* @__PURE__ */ jsxRuntime.jsx(
43
+ MessageActions.MessageActions,
44
+ {
45
+ text,
46
+ onRegenerate: onRegenerate ? () => onRegenerate(message.id) : void 0
47
+ }
48
+ )
49
+ ]
28
50
  }
29
51
  );
30
52
  }
31
- function MessagePart({ part, isUser }) {
53
+ function MessagePart({
54
+ part,
55
+ isUser,
56
+ onApprove
57
+ }) {
32
58
  if (part.type === "text") {
33
59
  if (isUser) {
34
60
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-pre-wrap break-words", children: part.text });
35
61
  }
36
62
  return /* @__PURE__ */ jsxRuntime.jsx(Markdown.Markdown, { text: part.text });
37
63
  }
64
+ if (types.isReasoningPart(part)) {
65
+ return /* @__PURE__ */ jsxRuntime.jsx(Reasoning.Reasoning, { text: part.text });
66
+ }
67
+ if (types.isFilePart(part)) {
68
+ return /* @__PURE__ */ jsxRuntime.jsx(FilePart, { part });
69
+ }
38
70
  if (types.isDynamicToolPart(part) || types.isTypedToolPart(part)) {
39
- return /* @__PURE__ */ jsxRuntime.jsx(ToolCall.ToolCall, { part });
71
+ return /* @__PURE__ */ jsxRuntime.jsx(ToolCall.ToolCall, { part, onApprove });
40
72
  }
41
73
  return null;
42
74
  }
75
+ function FilePart({ part }) {
76
+ var _a;
77
+ const isImage = (_a = part.mediaType) == null ? void 0 : _a.startsWith("image/");
78
+ if (isImage && part.url) {
79
+ return /* @__PURE__ */ jsxRuntime.jsx(
80
+ "img",
81
+ {
82
+ src: part.url,
83
+ alt: part.filename ?? "attachment",
84
+ className: "max-h-48 max-w-full rounded-md border border-border"
85
+ }
86
+ );
87
+ }
88
+ return /* @__PURE__ */ jsxRuntime.jsxs(
89
+ "a",
90
+ {
91
+ href: part.url,
92
+ target: "_blank",
93
+ rel: "noopener noreferrer",
94
+ className: "flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:text-foreground",
95
+ children: [
96
+ /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiFile.UiFile, className: "size-3.5 shrink-0" }),
97
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: part.filename ?? part.mediaType ?? "file" })
98
+ ]
99
+ }
100
+ );
101
+ }
43
102
  exports.Message = Message;
44
103
  //# sourceMappingURL=Message.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Message.cjs","sources":["../../../src/data/chat/Message.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Markdown } from \"../Markdown\";\nimport { ToolCall } from \"./ToolCall\";\nimport type { UIMessage } from \"./types\";\nimport { isDynamicToolPart, isTypedToolPart, type AnyToolPart } from \"./types\";\n\nexport type MessageProps = {\n message: UIMessage;\n className?: string;\n};\n\n/** Renders one chat message. User messages are right-aligned bubbles; assistant\n * messages render text parts as markdown and tool parts as collapsible tool\n * calls, in document order. */\nexport function Message({ message, className }: MessageProps) {\n const isUser = message.role === \"user\";\n return (\n <div\n className={cn(\n \"group flex w-full max-w-[95%] flex-col gap-2\",\n isUser ? \"ml-auto items-end\" : \"items-start\",\n className,\n )}\n >\n <div\n className={cn(\n \"flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm\",\n isUser && \"rounded-lg bg-secondary px-4 py-3 text-secondary-foreground\",\n )}\n >\n {message.parts.map((part, i) => (\n <MessagePart key={`${message.id}-${i}`} part={part} isUser={isUser} />\n ))}\n </div>\n </div>\n );\n}\n\nfunction MessagePart({ part, isUser }: { part: UIMessage[\"parts\"][number]; isUser: boolean }) {\n if (part.type === \"text\") {\n if (isUser) {\n return <span className=\"whitespace-pre-wrap break-words\">{part.text}</span>;\n }\n return <Markdown text={part.text} />;\n }\n if (isDynamicToolPart(part) || isTypedToolPart(part)) {\n return <ToolCall part={part as AnyToolPart} />;\n }\n return null;\n}\n"],"names":["jsx","cn","Markdown","isDynamicToolPart","isTypedToolPart","ToolCall"],"mappings":";;;;;;;AAcO,SAAS,QAAQ,EAAE,SAAS,aAA2B;AAC5D,QAAM,SAAS,QAAQ,SAAS;AAChC,SACEA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,MAAAA;AAAAA,QACT;AAAA,QACA,SAAS,sBAAsB;AAAA,QAC/B;AAAA,MAAA;AAAA,MAGF,UAAAD,2BAAAA;AAAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAWC,MAAAA;AAAAA,YACT;AAAA,YACA,UAAU;AAAA,UAAA;AAAA,UAGX,kBAAQ,MAAM,IAAI,CAAC,MAAM,MACxBD,2BAAAA,IAAC,aAAA,EAAuC,MAAY,OAAA,GAAlC,GAAG,QAAQ,EAAE,IAAI,CAAC,EAAgC,CACrE;AAAA,QAAA;AAAA,MAAA;AAAA,IACH;AAAA,EAAA;AAGN;AAEA,SAAS,YAAY,EAAE,MAAM,UAAiE;AAC5F,MAAI,KAAK,SAAS,QAAQ;AACxB,QAAI,QAAQ;AACV,aAAOA,2BAAAA,IAAC,QAAA,EAAK,WAAU,mCAAmC,eAAK,MAAK;AAAA,IACtE;AACA,WAAOA,2BAAAA,IAACE,SAAAA,UAAA,EAAS,MAAM,KAAK,KAAA,CAAM;AAAA,EACpC;AACA,MAAIC,MAAAA,kBAAkB,IAAI,KAAKC,MAAAA,gBAAgB,IAAI,GAAG;AACpD,WAAOJ,+BAACK,SAAAA,YAAS,MAA2B;AAAA,EAC9C;AACA,SAAO;AACT;;"}
1
+ {"version":3,"file":"Message.cjs","sources":["../../../src/data/chat/Message.tsx"],"sourcesContent":["import { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { UiFile } from \"../../icons\";\nimport { Markdown } from \"../Markdown\";\nimport { ToolCall } from \"./ToolCall\";\nimport { MessageActions } from \"./MessageActions\";\nimport { Reasoning } from \"./Reasoning\";\nimport type { UIMessage, FileUIPart } from \"./types\";\nimport {\n isDynamicToolPart,\n isTypedToolPart,\n isReasoningPart,\n isFilePart,\n type AnyToolPart,\n} from \"./types\";\n\n/** Callbacks the conversation threads down to each message. */\nexport type MessageActionHandlers = {\n /** Re-generate the assistant message with the given id. */\n onRegenerate?: ((messageId: string) => void) | undefined;\n /** Respond to a tool approval request. */\n onApprove?: ((approvalId: string, approved: boolean, reason?: string) => void) | undefined;\n};\n\nexport type MessageProps = MessageActionHandlers & {\n message: UIMessage;\n className?: string;\n};\n\n/** Renders one chat message. User messages are right-aligned bubbles; assistant\n * messages render text as markdown, reasoning and tool parts inline, file parts\n * as thumbnails/chips, and a hover action row (copy / regenerate). */\nexport function Message({ message, className, onRegenerate, onApprove }: MessageProps) {\n const isUser = message.role === \"user\";\n const text = message.parts\n .filter((p) => p.type === \"text\")\n .map((p) => (p as { text: string }).text)\n .join(\"\");\n\n return (\n <div\n className={cn(\n \"group flex w-full max-w-[95%] flex-col gap-2\",\n isUser ? \"ml-auto items-end\" : \"items-start\",\n className,\n )}\n >\n <div\n className={cn(\n \"flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm\",\n isUser && \"rounded-lg bg-secondary px-4 py-3 text-secondary-foreground\",\n )}\n >\n {message.parts.map((part, i) => (\n <MessagePart\n key={`${message.id}-${i}`}\n part={part}\n isUser={isUser}\n onApprove={onApprove}\n />\n ))}\n </div>\n\n {!isUser && text && (\n <MessageActions\n text={text}\n onRegenerate={onRegenerate ? () => onRegenerate(message.id) : undefined}\n />\n )}\n </div>\n );\n}\n\nfunction MessagePart({\n part,\n isUser,\n onApprove,\n}: {\n part: UIMessage[\"parts\"][number];\n isUser: boolean;\n onApprove: MessageActionHandlers[\"onApprove\"];\n}) {\n if (part.type === \"text\") {\n if (isUser) {\n return <span className=\"whitespace-pre-wrap break-words\">{part.text}</span>;\n }\n return <Markdown text={part.text} />;\n }\n if (isReasoningPart(part)) {\n return <Reasoning text={part.text} />;\n }\n if (isFilePart(part)) {\n return <FilePart part={part} />;\n }\n if (isDynamicToolPart(part) || isTypedToolPart(part)) {\n return <ToolCall part={part as AnyToolPart} onApprove={onApprove} />;\n }\n return null;\n}\n\n/** Renders an attachment: images inline as a thumbnail, everything else as a\n * labelled file chip. */\nfunction FilePart({ part }: { part: FileUIPart }) {\n const isImage = part.mediaType?.startsWith(\"image/\");\n if (isImage && part.url) {\n return (\n <img\n src={part.url}\n alt={part.filename ?? \"attachment\"}\n className=\"max-h-48 max-w-full rounded-md border border-border\"\n />\n );\n }\n return (\n <a\n href={part.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiFile} className=\"size-3.5 shrink-0\" />\n <span className=\"truncate\">{part.filename ?? part.mediaType ?? \"file\"}</span>\n </a>\n );\n}\n"],"names":["jsxs","cn","jsx","MessageActions","Markdown","isReasoningPart","Reasoning","isFilePart","isDynamicToolPart","isTypedToolPart","ToolCall","Icon","UiFile"],"mappings":";;;;;;;;;;;AAgCO,SAAS,QAAQ,EAAE,SAAS,WAAW,cAAc,aAA2B;AACrF,QAAM,SAAS,QAAQ,SAAS;AAChC,QAAM,OAAO,QAAQ,MAClB,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAC/B,IAAI,CAAC,MAAO,EAAuB,IAAI,EACvC,KAAK,EAAE;AAEV,SACEA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,MAAAA;AAAAA,QACT;AAAA,QACA,SAAS,sBAAsB;AAAA,QAC/B;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAAC,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAWD,MAAAA;AAAAA,cACT;AAAA,cACA,UAAU;AAAA,YAAA;AAAA,YAGX,UAAA,QAAQ,MAAM,IAAI,CAAC,MAAM,MACxBC,2BAAAA;AAAAA,cAAC;AAAA,cAAA;AAAA,gBAEC;AAAA,gBACA;AAAA,gBACA;AAAA,cAAA;AAAA,cAHK,GAAG,QAAQ,EAAE,IAAI,CAAC;AAAA,YAAA,CAK1B;AAAA,UAAA;AAAA,QAAA;AAAA,QAGF,CAAC,UAAU,QACVA,2BAAAA;AAAAA,UAACC,eAAAA;AAAAA,UAAA;AAAA,YACC;AAAA,YACA,cAAc,eAAe,MAAM,aAAa,QAAQ,EAAE,IAAI;AAAA,UAAA;AAAA,QAAA;AAAA,MAChE;AAAA,IAAA;AAAA,EAAA;AAIR;AAEA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,KAAK,SAAS,QAAQ;AACxB,QAAI,QAAQ;AACV,aAAOD,2BAAAA,IAAC,QAAA,EAAK,WAAU,mCAAmC,eAAK,MAAK;AAAA,IACtE;AACA,WAAOA,2BAAAA,IAACE,SAAAA,UAAA,EAAS,MAAM,KAAK,KAAA,CAAM;AAAA,EACpC;AACA,MAAIC,MAAAA,gBAAgB,IAAI,GAAG;AACzB,WAAOH,2BAAAA,IAACI,UAAAA,WAAA,EAAU,MAAM,KAAK,KAAA,CAAM;AAAA,EACrC;AACA,MAAIC,MAAAA,WAAW,IAAI,GAAG;AACpB,WAAOL,+BAAC,YAAS,MAAY;AAAA,EAC/B;AACA,MAAIM,MAAAA,kBAAkB,IAAI,KAAKC,MAAAA,gBAAgB,IAAI,GAAG;AACpD,WAAOP,2BAAAA,IAACQ,SAAAA,UAAA,EAAS,MAA2B,UAAA,CAAsB;AAAA,EACpE;AACA,SAAO;AACT;AAIA,SAAS,SAAS,EAAE,QAA8B;;AAChD,QAAM,WAAU,UAAK,cAAL,mBAAgB,WAAW;AAC3C,MAAI,WAAW,KAAK,KAAK;AACvB,WACER,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK,KAAK;AAAA,QACV,KAAK,KAAK,YAAY;AAAA,QACtB,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EAGhB;AACA,SACEF,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,KAAK;AAAA,MACX,QAAO;AAAA,MACP,KAAI;AAAA,MACJ,WAAU;AAAA,MAEV,UAAA;AAAA,QAAAE,2BAAAA,IAACS,KAAAA,MAAA,EAAK,MAAMC,OAAAA,QAAQ,WAAU,qBAAoB;AAAA,QAClDV,2BAAAA,IAAC,UAAK,WAAU,YAAY,eAAK,YAAY,KAAK,aAAa,OAAA,CAAO;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG5E;;"}
@@ -1,10 +1,17 @@
1
1
  import { UIMessage } from './types';
2
- export type MessageProps = {
2
+ /** Callbacks the conversation threads down to each message. */
3
+ export type MessageActionHandlers = {
4
+ /** Re-generate the assistant message with the given id. */
5
+ onRegenerate?: ((messageId: string) => void) | undefined;
6
+ /** Respond to a tool approval request. */
7
+ onApprove?: ((approvalId: string, approved: boolean, reason?: string) => void) | undefined;
8
+ };
9
+ export type MessageProps = MessageActionHandlers & {
3
10
  message: UIMessage;
4
11
  className?: string;
5
12
  };
6
13
  /** Renders one chat message. User messages are right-aligned bubbles; assistant
7
- * messages render text parts as markdown and tool parts as collapsible tool
8
- * calls, in document order. */
9
- export declare function Message({ message, className }: MessageProps): import("react/jsx-runtime").JSX.Element;
14
+ * messages render text as markdown, reasoning and tool parts inline, file parts
15
+ * as thumbnails/chips, and a hover action row (copy / regenerate). */
16
+ export declare function Message({ message, className, onRegenerate, onApprove }: MessageProps): import("react/jsx-runtime").JSX.Element;
10
17
  //# sourceMappingURL=Message.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Message.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Message.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGzC,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;gCAEgC;AAChC,wBAAgB,OAAO,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,YAAY,2CAsB3D"}
1
+ {"version":3,"file":"Message.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Message.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,SAAS,CAAC;AASrD,+DAA+D;AAC/D,MAAM,MAAM,qBAAqB,GAAG;IAClC,2DAA2D;IAC3D,YAAY,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACzD,0CAA0C;IAC1C,SAAS,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC5F,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,qBAAqB,GAAG;IACjD,OAAO,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;uEAEuE;AACvE,wBAAgB,OAAO,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,YAAY,2CAuCpF"}