@flanksource/clicky-ui 0.2.15 → 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 (205) 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 +34 -11
  6. package/dist/components/Combobox.cjs.map +1 -1
  7. package/dist/components/Combobox.d.ts +8 -0
  8. package/dist/components/Combobox.d.ts.map +1 -1
  9. package/dist/components/Combobox.js +34 -11
  10. package/dist/components/Combobox.js.map +1 -1
  11. package/dist/components/DateTimePicker.cjs +3 -0
  12. package/dist/components/DateTimePicker.cjs.map +1 -1
  13. package/dist/components/DateTimePicker.d.ts +6 -2
  14. package/dist/components/DateTimePicker.d.ts.map +1 -1
  15. package/dist/components/DateTimePicker.js +3 -0
  16. package/dist/components/DateTimePicker.js.map +1 -1
  17. package/dist/components/Field.cjs +33 -0
  18. package/dist/components/Field.cjs.map +1 -0
  19. package/dist/components/Field.d.ts +27 -0
  20. package/dist/components/Field.d.ts.map +1 -0
  21. package/dist/components/Field.js +33 -0
  22. package/dist/components/Field.js.map +1 -0
  23. package/dist/components/FilterBar.cjs +81 -20
  24. package/dist/components/FilterBar.cjs.map +1 -1
  25. package/dist/components/FilterBar.d.ts +30 -0
  26. package/dist/components/FilterBar.d.ts.map +1 -1
  27. package/dist/components/FilterBar.js +83 -22
  28. package/dist/components/FilterBar.js.map +1 -1
  29. package/dist/components/JsonSchemaForm.cjs +26 -3
  30. package/dist/components/JsonSchemaForm.cjs.map +1 -1
  31. package/dist/components/JsonSchemaForm.d.ts.map +1 -1
  32. package/dist/components/JsonSchemaForm.js +26 -3
  33. package/dist/components/JsonSchemaForm.js.map +1 -1
  34. package/dist/components/MultiSelect.cjs +2 -1
  35. package/dist/components/MultiSelect.cjs.map +1 -1
  36. package/dist/components/MultiSelect.d.ts +5 -1
  37. package/dist/components/MultiSelect.d.ts.map +1 -1
  38. package/dist/components/MultiSelect.js +2 -1
  39. package/dist/components/MultiSelect.js.map +1 -1
  40. package/dist/components/json-schema-form-fields.cjs +15 -8
  41. package/dist/components/json-schema-form-fields.cjs.map +1 -1
  42. package/dist/components/json-schema-form-fields.d.ts.map +1 -1
  43. package/dist/components/json-schema-form-fields.js +15 -8
  44. package/dist/components/json-schema-form-fields.js.map +1 -1
  45. package/dist/components/json-schema-form-preferences.cjs +4 -0
  46. package/dist/components/json-schema-form-preferences.cjs.map +1 -1
  47. package/dist/components/json-schema-form-preferences.d.ts +2 -0
  48. package/dist/components/json-schema-form-preferences.d.ts.map +1 -1
  49. package/dist/components/json-schema-form-preferences.js +4 -0
  50. package/dist/components/json-schema-form-preferences.js.map +1 -1
  51. package/dist/components/json-schema-form-render.cjs +1 -1
  52. package/dist/components/json-schema-form-render.cjs.map +1 -1
  53. package/dist/components/json-schema-form-render.d.ts.map +1 -1
  54. package/dist/components/json-schema-form-render.js +2 -2
  55. package/dist/components/json-schema-form-render.js.map +1 -1
  56. package/dist/components/json-schema-form-types.d.ts +4 -2
  57. package/dist/components/json-schema-form-types.d.ts.map +1 -1
  58. package/dist/components/json-schema-form-utils.cjs +13 -0
  59. package/dist/components/json-schema-form-utils.cjs.map +1 -1
  60. package/dist/components/json-schema-form-utils.d.ts +2 -0
  61. package/dist/components/json-schema-form-utils.d.ts.map +1 -1
  62. package/dist/components/json-schema-form-utils.js +13 -0
  63. package/dist/components/json-schema-form-utils.js.map +1 -1
  64. package/dist/components/loading.cjs +21 -8
  65. package/dist/components/loading.cjs.map +1 -1
  66. package/dist/components/loading.d.ts +6 -4
  67. package/dist/components/loading.d.ts.map +1 -1
  68. package/dist/components/loading.js +21 -8
  69. package/dist/components/loading.js.map +1 -1
  70. package/dist/components/use-form.cjs +27 -0
  71. package/dist/components/use-form.cjs.map +1 -0
  72. package/dist/components/use-form.d.ts +30 -0
  73. package/dist/components/use-form.d.ts.map +1 -0
  74. package/dist/components/use-form.js +27 -0
  75. package/dist/components/use-form.js.map +1 -0
  76. package/dist/components.cjs +7 -0
  77. package/dist/components.cjs.map +1 -1
  78. package/dist/components.d.ts +4 -1
  79. package/dist/components.d.ts.map +1 -1
  80. package/dist/components.js +8 -1
  81. package/dist/components.js.map +1 -1
  82. package/dist/data/Markdown.cjs +14 -40
  83. package/dist/data/Markdown.cjs.map +1 -1
  84. package/dist/data/Markdown.d.ts +3 -0
  85. package/dist/data/Markdown.d.ts.map +1 -1
  86. package/dist/data/Markdown.js +14 -40
  87. package/dist/data/Markdown.js.map +1 -1
  88. package/dist/data/TaskProgress.cjs +22 -0
  89. package/dist/data/TaskProgress.cjs.map +1 -1
  90. package/dist/data/TaskProgress.js +22 -0
  91. package/dist/data/TaskProgress.js.map +1 -1
  92. package/dist/data/TaskSnapshot.cjs.map +1 -1
  93. package/dist/data/TaskSnapshot.d.ts +5 -0
  94. package/dist/data/TaskSnapshot.d.ts.map +1 -1
  95. package/dist/data/TaskSnapshot.js.map +1 -1
  96. package/dist/data/Tree.cjs +32 -1
  97. package/dist/data/Tree.cjs.map +1 -1
  98. package/dist/data/Tree.d.ts +9 -1
  99. package/dist/data/Tree.d.ts.map +1 -1
  100. package/dist/data/Tree.js +32 -1
  101. package/dist/data/Tree.js.map +1 -1
  102. package/dist/data/chat/Attachment.cjs +79 -0
  103. package/dist/data/chat/Attachment.cjs.map +1 -0
  104. package/dist/data/chat/Attachment.d.ts +18 -0
  105. package/dist/data/chat/Attachment.d.ts.map +1 -0
  106. package/dist/data/chat/Attachment.js +79 -0
  107. package/dist/data/chat/Attachment.js.map +1 -0
  108. package/dist/data/chat/Chat.cjs +71 -14
  109. package/dist/data/chat/Chat.cjs.map +1 -1
  110. package/dist/data/chat/Chat.d.ts +30 -12
  111. package/dist/data/chat/Chat.d.ts.map +1 -1
  112. package/dist/data/chat/Chat.fixtures.d.ts +9 -0
  113. package/dist/data/chat/Chat.fixtures.d.ts.map +1 -1
  114. package/dist/data/chat/Chat.js +73 -16
  115. package/dist/data/chat/Chat.js.map +1 -1
  116. package/dist/data/chat/Conversation.cjs +32 -14
  117. package/dist/data/chat/Conversation.cjs.map +1 -1
  118. package/dist/data/chat/Conversation.d.ts +5 -3
  119. package/dist/data/chat/Conversation.d.ts.map +1 -1
  120. package/dist/data/chat/Conversation.js +34 -16
  121. package/dist/data/chat/Conversation.js.map +1 -1
  122. package/dist/data/chat/Message.cjs +73 -14
  123. package/dist/data/chat/Message.cjs.map +1 -1
  124. package/dist/data/chat/Message.d.ts +11 -4
  125. package/dist/data/chat/Message.d.ts.map +1 -1
  126. package/dist/data/chat/Message.js +75 -16
  127. package/dist/data/chat/Message.js.map +1 -1
  128. package/dist/data/chat/MessageActions.cjs +54 -0
  129. package/dist/data/chat/MessageActions.cjs.map +1 -0
  130. package/dist/data/chat/MessageActions.d.ts +11 -0
  131. package/dist/data/chat/MessageActions.d.ts.map +1 -0
  132. package/dist/data/chat/MessageActions.js +54 -0
  133. package/dist/data/chat/MessageActions.js.map +1 -0
  134. package/dist/data/chat/ModelSelector.cjs +43 -0
  135. package/dist/data/chat/ModelSelector.cjs.map +1 -0
  136. package/dist/data/chat/ModelSelector.d.ts +22 -0
  137. package/dist/data/chat/ModelSelector.d.ts.map +1 -0
  138. package/dist/data/chat/ModelSelector.js +43 -0
  139. package/dist/data/chat/ModelSelector.js.map +1 -0
  140. package/dist/data/chat/PromptInput.cjs +28 -15
  141. package/dist/data/chat/PromptInput.cjs.map +1 -1
  142. package/dist/data/chat/PromptInput.d.ts +14 -8
  143. package/dist/data/chat/PromptInput.d.ts.map +1 -1
  144. package/dist/data/chat/PromptInput.js +28 -15
  145. package/dist/data/chat/PromptInput.js.map +1 -1
  146. package/dist/data/chat/Reasoning.cjs +37 -0
  147. package/dist/data/chat/Reasoning.cjs.map +1 -0
  148. package/dist/data/chat/Reasoning.d.ts +11 -0
  149. package/dist/data/chat/Reasoning.d.ts.map +1 -0
  150. package/dist/data/chat/Reasoning.js +37 -0
  151. package/dist/data/chat/Reasoning.js.map +1 -0
  152. package/dist/data/chat/Suggestion.cjs +20 -0
  153. package/dist/data/chat/Suggestion.cjs.map +1 -0
  154. package/dist/data/chat/Suggestion.d.ts +10 -0
  155. package/dist/data/chat/Suggestion.d.ts.map +1 -0
  156. package/dist/data/chat/Suggestion.js +20 -0
  157. package/dist/data/chat/Suggestion.js.map +1 -0
  158. package/dist/data/chat/ToolCall.cjs +26 -3
  159. package/dist/data/chat/ToolCall.cjs.map +1 -1
  160. package/dist/data/chat/ToolCall.d.ts +5 -1
  161. package/dist/data/chat/ToolCall.d.ts.map +1 -1
  162. package/dist/data/chat/ToolCall.js +26 -3
  163. package/dist/data/chat/ToolCall.js.map +1 -1
  164. package/dist/data/chat/index.d.ts +7 -2
  165. package/dist/data/chat/index.d.ts.map +1 -1
  166. package/dist/data/chat/types.cjs +16 -0
  167. package/dist/data/chat/types.cjs.map +1 -1
  168. package/dist/data/chat/types.d.ts +29 -2
  169. package/dist/data/chat/types.d.ts.map +1 -1
  170. package/dist/data/chat/types.js +16 -0
  171. package/dist/data/chat/types.js.map +1 -1
  172. package/dist/data/version-info.cjs +3 -3
  173. package/dist/data/version-info.js +3 -3
  174. package/dist/hooks/use-task-run.cjs +33 -20
  175. package/dist/hooks/use-task-run.cjs.map +1 -1
  176. package/dist/hooks/use-task-run.d.ts.map +1 -1
  177. package/dist/hooks/use-task-run.js +33 -20
  178. package/dist/hooks/use-task-run.js.map +1 -1
  179. package/dist/index.cjs +7 -0
  180. package/dist/index.cjs.map +1 -1
  181. package/dist/index.js +8 -1
  182. package/dist/index.js.map +1 -1
  183. package/dist/overlay/Modal.cjs +27 -23
  184. package/dist/overlay/Modal.cjs.map +1 -1
  185. package/dist/overlay/Modal.d.ts +7 -1
  186. package/dist/overlay/Modal.d.ts.map +1 -1
  187. package/dist/overlay/Modal.js +27 -23
  188. package/dist/overlay/Modal.js.map +1 -1
  189. package/dist/rpc/FilterForm.cjs +4 -4
  190. package/dist/rpc/FilterForm.cjs.map +1 -1
  191. package/dist/rpc/FilterForm.js +4 -4
  192. package/dist/rpc/FilterForm.js.map +1 -1
  193. package/dist/rpc/OperationCatalog.cjs +17 -21
  194. package/dist/rpc/OperationCatalog.cjs.map +1 -1
  195. package/dist/rpc/OperationCatalog.d.ts +3 -3
  196. package/dist/rpc/OperationCatalog.d.ts.map +1 -1
  197. package/dist/rpc/OperationCatalog.js +18 -22
  198. package/dist/rpc/OperationCatalog.js.map +1 -1
  199. package/dist/rpc/formMetadata.cjs +5 -3
  200. package/dist/rpc/formMetadata.cjs.map +1 -1
  201. package/dist/rpc/formMetadata.d.ts.map +1 -1
  202. package/dist/rpc/formMetadata.js +5 -3
  203. package/dist/rpc/formMetadata.js.map +1 -1
  204. package/dist/styles.css +1 -1
  205. package/package.json +6 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.cjs","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":["useMemo","DefaultChatTransport","useChat","cn","jsx","Conversation","PromptInput"],"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,oBAAoBA,MAAAA,QAAkC,MAAM;AAChE,QAAI,WAAW;AACb,aAAO;AAAA,IACT;AACA,WAAO,IAAIC,GAAAA,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,IAASC,QAAAA,QAAmB;AAAA,IACjE,WAAW;AAAA,IACX,GAAI,kBAAkB,EAAE,UAAU,oBAAoB,CAAA;AAAA,EAAC,CACxD;AAED,yCACG,OAAA,EAAI,WAAWC,MAAAA,GAAG,wBAAwB,SAAS,GAClD,UAAA;AAAA,IAAAC,2BAAAA,IAACC,aAAAA,cAAA,EAAa,UAAoB,WAAA,CAAwB;AAAA,IAC1DD,2BAAAA,IAAC,OAAA,EAAI,WAAU,YACb,UAAAA,2BAAAA;AAAAA,MAACE,YAAAA;AAAAA,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.cjs","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":["useState","useEffect","useRef","useMemo","DefaultChatTransport","useChat","lastAssistantMessageIsCompleteWithApprovalResponses","jsxs","jsx","ModelSelector","EffortSelector","Suggestions","cn","Conversation","PromptInput"],"mappings":"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"}
@@ -1,16 +1,33 @@
1
1
  import { ChatTransport, UIMessage } from 'ai';
2
+ import { ChatModel, Suggestion } from './types';
2
3
  export type ChatProps = {
3
4
  /** Endpoint that speaks the AI SDK v6 UI Message Stream protocol.
4
5
  * Ignored when `transport` is supplied. Defaults to "/api/chat". */
5
6
  api?: string;
6
- /** Selected `provider/model` id, forwarded in the request body. */
7
- model?: string;
8
- /** Reasoning effort (`low` | `medium` | `high`), forwarded in the request body. */
9
- reasoningEffort?: string;
10
- /** Extra fields merged into every request body (e.g. tool scoping). */
7
+ /** Model menu. When omitted and `modelsApi` is set, it is fetched. */
8
+ models?: ChatModel[];
9
+ /** Endpoint returning the model menu (ModelInfo[]). Defaults to
10
+ * "/api/chat/models". Set to null to disable fetching. */
11
+ modelsApi?: string | null;
12
+ /** Initially selected model id; otherwise the first configured model. */
13
+ defaultModel?: string;
14
+ /** Reasoning-effort options for capable models. */
15
+ reasoningEfforts?: string[];
16
+ /** Initially selected reasoning effort ("" = none). */
17
+ defaultReasoningEffort?: string;
18
+ /** Notified when the user changes the model. */
19
+ onModelChange?: (id: string) => void;
20
+ /** Suggested prompts shown on the empty state. */
21
+ suggestions?: Suggestion[];
22
+ /** Enables file/image attachments. */
23
+ enableAttachments?: boolean;
24
+ /** "manual" forwards a tool-approval policy hint to the backend. */
25
+ toolApproval?: "auto" | "manual";
26
+ /** Thread id to persist this conversation under (forwarded in the body). */
27
+ threadId?: string;
28
+ /** Extra fields merged into every request body. */
11
29
  body?: Record<string, unknown>;
12
- /** Pre-built transport. Supply a mock in stories/tests; otherwise a
13
- * DefaultChatTransport is constructed from `api`/`body`. */
30
+ /** Pre-built transport (e.g. a mock for stories/tests). */
14
31
  transport?: ChatTransport<UIMessage>;
15
32
  /** Initial messages to seed the conversation. */
16
33
  initialMessages?: UIMessage[];
@@ -18,9 +35,10 @@ export type ChatProps = {
18
35
  emptyState?: React.ReactNode;
19
36
  className?: string;
20
37
  };
21
- /** Self-contained AI chat: a streaming conversation log over a prompt input,
22
- * driven by the AI SDK `useChat` hook against a v6-compatible `/api/chat`. The
23
- * backend owns model selection and tool execution; `model`, `reasoningEffort`
24
- * and `body` are forwarded in the request body for it to honor. */
25
- export declare function Chat({ api, model, reasoningEffort, body, transport, initialMessages, placeholder, emptyState, className, }: ChatProps): import("react/jsx-runtime").JSX.Element;
38
+ /** Self-contained AI chat over a v6-compatible `/api/chat`: a streaming
39
+ * conversation with model/effort selectors, suggestions, attachments,
40
+ * per-message copy/regenerate, and human-in-the-loop tool approvals. The
41
+ * backend owns model selection and tool execution; the selected model,
42
+ * effort, approval policy and thread id are forwarded in the request body. */
43
+ export declare function Chat({ api, models: modelsProp, modelsApi, defaultModel, reasoningEfforts, defaultReasoningEffort, onModelChange, suggestions, enableAttachments, toolApproval, threadId, body, transport, initialMessages, placeholder, emptyState, className, }: ChatProps): import("react/jsx-runtime").JSX.Element;
26
44
  //# sourceMappingURL=Chat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Chat.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,IAAI,CAAC;AAK9E,MAAM,MAAM,SAAS,GAAG;IACtB;yEACqE;IACrE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B;iEAC6D;IAC7D,SAAS,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACrC,iDAAiD;IACjD,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;oEAGoE;AACpE,wBAAgB,IAAI,CAAC,EACnB,GAAiB,EACjB,KAAK,EACL,eAAe,EACf,IAAI,EACJ,SAAS,EACT,eAAe,EACf,WAAW,EACX,UAAU,EACV,SAAS,GACV,EAAE,SAAS,2CA6BX"}
1
+ {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Chat.tsx"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,IAAI,CAAC;AAMZ,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErD,MAAM,MAAM,SAAS,GAAG;IACtB;yEACqE;IACrE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB;+DAC2D;IAC3D,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,uDAAuD;IACvD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gDAAgD;IAChD,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,kDAAkD;IAClD,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,sCAAsC;IACtC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,2DAA2D;IAC3D,SAAS,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACrC,iDAAiD;IACjD,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAIF;;;;+EAI+E;AAC/E,wBAAgB,IAAI,CAAC,EACnB,GAAiB,EACjB,MAAM,EAAE,UAAU,EAClB,SAA8B,EAC9B,YAAY,EACZ,gBAAkC,EAClC,sBAA2B,EAC3B,aAAa,EACb,WAAW,EACX,iBAAyB,EACzB,YAAY,EACZ,QAAQ,EACR,IAAI,EACJ,SAAS,EACT,eAAe,EACf,WAAW,EACX,UAAU,EACV,SAAS,GACV,EAAE,SAAS,2CA6FX"}
@@ -1,6 +1,15 @@
1
1
  import { ChatTransport, UIMessage } from 'ai';
2
+ import { ChatModel } from './types';
3
+ /** A sample model menu for stories driving the model selector. */
4
+ export declare const MOCK_MODELS: ChatModel[];
2
5
  /** A mock ChatTransport that replies with a markdown text turn, then a tool
3
6
  * round-trip turn on the next submission. Use in stories/tests to drive the
4
7
  * Chat UI without a backend. */
5
8
  export declare function mockChatTransport(delayMs?: number): ChatTransport<UIMessage>;
9
+ /** A mock transport whose first turn requests tool approval, and whose resume
10
+ * (after the user approves/denies) completes the tool and replies. */
11
+ export declare function mockApprovalTransport(delayMs?: number): ChatTransport<UIMessage>;
12
+ /** A mock transport that streams a reasoning ("thinking") block before its
13
+ * answer. */
14
+ export declare function mockReasoningTransport(delayMs?: number): ChatTransport<UIMessage>;
6
15
  //# sourceMappingURL=Chat.fixtures.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.fixtures.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Chat.fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAkB,MAAM,IAAI,CAAC;AAyDnE;;iCAEiC;AACjC,wBAAgB,iBAAiB,CAAC,OAAO,SAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAWzE"}
1
+ {"version":3,"file":"Chat.fixtures.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Chat.fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAkB,MAAM,IAAI,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,kEAAkE;AAClE,eAAO,MAAM,WAAW,EAAE,SAAS,EAIlC,CAAC;AAwFF;;iCAEiC;AACjC,wBAAgB,iBAAiB,CAAC,OAAO,SAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAWzE;AAED;uEACuE;AACvE,wBAAgB,qBAAqB,CAAC,OAAO,SAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAW7E;AAED;cACc;AACd,wBAAgB,sBAAsB,CAAC,OAAO,SAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAS9E"}
@@ -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