@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 +1 @@
1
- {"version":3,"file":"Tree.js","sources":["../../src/data/Tree.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"./Icon\";\nimport { UiClose, UiCollapseAll, UiExpandAll, UiSearch } from \"../icons\";\nimport { TreeNode, type TreeNodeProps } from \"./TreeNode\";\n\nexport type TreeProps<T> = Omit<\n TreeNodeProps<T>,\n \"node\" | \"depth\" | \"expandAll\" | \"forcedOpenKeys\"\n> & {\n /** Root nodes to render. */\n roots: T[];\n /** Empty-state content when no roots are provided. */\n empty?: ReactNode;\n /** Classes applied to the tree root. */\n className?: string;\n /**\n * Render the Expand all / Collapse all toolbar above the tree. Enabled by\n * default. Set to false to suppress when the tree is tiny or the controls\n * are owned externally.\n */\n showControls?: boolean;\n /**\n * Externally controlled expand-all state. When provided together with\n * `onExpandAllChange`, the tree is controlled; otherwise it manages the\n * toolbar state internally.\n */\n expandAll?: boolean | null;\n /** Called when the toolbar expand-all state changes. */\n onExpandAllChange?: (next: boolean | null) => void;\n /** Classes applied to the search/control toolbar. */\n toolbarClassName?: string;\n /**\n * Override the text the filter matches against for a given node.\n * Defaults to a recursive walk of the node's own fields (excluding\n * its `children` and any secondary children), which works for\n * plain-shape trees but leaks text from nested domain objects —\n * e.g. a SQL Table's Field rows are marked secondary, yet the\n * parent record still carries `record.fields[].field` strings that\n * the default walker would collect. Callers that need precise\n * control over search behaviour should return only the text they\n * want surfaced (the node's own display label / id / type).\n */\n getSearchText?: (node: T) => string;\n};\n\n// Edge counting, search-text collection and filtering all walk the tree through\n// the synchronous `getChildren` only. A lazy node (one declared via\n// `hasMoreChildren` whose children have not been fetched yet) therefore\n// contributes no edges and no search text until the operator expands it — so it\n// is invisible to \"expand all\" and to the filter while still collapsed. This is\n// intentional: we never trigger network loads from bulk/search operations.\nfunction countTreeEdges<T>(roots: T[], getChildren: TreeProps<T>[\"getChildren\"]) {\n let total = 0;\n const stack = [...roots];\n\n while (stack.length > 0) {\n const node = stack.pop()!;\n const children = getChildren(node) ?? [];\n total += children.length;\n stack.push(...children);\n }\n\n return total;\n}\n\nfunction collectTreeSearchText(\n value: unknown,\n excluded = new Set<unknown>(),\n seen = new Set<unknown>(),\n): string {\n if (value == null || excluded.has(value)) return \"\";\n\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\"\n ) {\n return String(value);\n }\n\n if (typeof value !== \"object\") return \"\";\n if (seen.has(value)) return \"\";\n seen.add(value);\n\n if (Array.isArray(value)) {\n return value\n .map((entry) => collectTreeSearchText(entry, excluded, seen))\n .filter(Boolean)\n .join(\" \");\n }\n\n const preferredKeys = [\"label\", \"name\", \"title\", \"text\", \"plain\", \"id\", \"content\"];\n const record = value as Record<string, unknown>;\n const chunks: string[] = [];\n\n for (const key of preferredKeys) {\n if (!(key in record)) continue;\n const text = collectTreeSearchText(record[key], excluded, seen);\n if (text) chunks.push(text);\n }\n\n for (const [key, entry] of Object.entries(record)) {\n if (key === \"children\" || preferredKeys.includes(key)) continue;\n const text = collectTreeSearchText(entry, excluded, seen);\n if (text) chunks.push(text);\n }\n\n return chunks.join(\" \");\n}\n\nfunction nodeSearchText<T>(node: T, children: ReturnType<TreeProps<T>[\"getChildren\"]>) {\n const excluded = new Set<unknown>();\n if (children) excluded.add(children);\n return collectTreeSearchText(node, excluded).trim().toLowerCase();\n}\n\nfunction filterTreeRoots<T>(\n roots: T[],\n getChildren: TreeProps<T>[\"getChildren\"],\n getKey: TreeProps<T>[\"getKey\"],\n query: string,\n isSecondary?: (node: T) => boolean,\n getSearchText?: (node: T) => string,\n) {\n const normalizedQuery = query.trim().toLowerCase();\n if (!normalizedQuery) {\n return {\n roots,\n filteredChildren: null as Map<string | number, T[]> | null,\n forcedOpenKeys: null as Set<string | number> | null,\n };\n }\n\n const filteredChildren = new Map<string | number, T[]>();\n const forcedOpenKeys = new Set<string | number>();\n\n function matchText(node: T, children: T[]): string {\n // Honour the caller-supplied text extractor first — it's the\n // only way to get precise control over what the filter sees when\n // the node carries domain data whose fields would otherwise leak\n // into the default walker.\n if (getSearchText) return getSearchText(node).trim().toLowerCase();\n // Match-text skips secondary children so e.g. a Table's search\n // text only draws from the Table itself, not from its Field\n // rows. The Table is primary, so its own text still counts.\n const searchChildren = children.filter((c) => !isSecondary?.(c));\n return nodeSearchText(node, searchChildren);\n }\n\n function visit(node: T): boolean {\n // A secondary node never drives a filter match — it's\n // effectively invisible to search. Still rendered when the\n // caller opens the parent manually. This is what lets a 28k-row\n // field tree stay searchable at the record level without \"field\n // name contains 'x'\" matches spamming the results.\n if (isSecondary?.(node)) return false;\n\n const key = getKey(node);\n const children = getChildren(node) ?? [];\n const childrenAllSecondary =\n children.length > 0 && children.every((child) => isSecondary?.(child));\n const matches = matchText(node, children).includes(normalizedQuery);\n const visibleChildren: T[] = [];\n\n for (const child of children) {\n if (visit(child)) visibleChildren.push(child);\n }\n\n if (matches) {\n if (children.length > 0) {\n filteredChildren.set(key, children);\n if (!childrenAllSecondary) forcedOpenKeys.add(key);\n }\n return true;\n }\n\n if (visibleChildren.length > 0) {\n filteredChildren.set(key, visibleChildren);\n forcedOpenKeys.add(key);\n return true;\n }\n\n return false;\n }\n\n return {\n roots: roots.filter((root) => visit(root)),\n filteredChildren,\n forcedOpenKeys,\n };\n}\n\nexport function Tree<T>({\n roots,\n empty,\n className,\n showControls = true,\n expandAll: controlledExpandAll,\n onExpandAllChange,\n toolbarClassName,\n getSearchText,\n ...nodeProps\n}: TreeProps<T>) {\n const [internalExpandAll, setInternalExpandAll] = useState<boolean | null>(null);\n const [filterQuery, setFilterQuery] = useState(\"\");\n const basePaddingPx = nodeProps.basePaddingPx ?? 8;\n const isControlled = onExpandAllChange !== undefined;\n const expandAll = isControlled ? (controlledExpandAll ?? null) : internalExpandAll;\n const totalEdges = useMemo(\n () => countTreeEdges(roots, nodeProps.getChildren),\n [roots, nodeProps.getChildren],\n );\n const showFilter = totalEdges > 20;\n const activeFilter = showFilter ? filterQuery : \"\";\n const filteredTree = useMemo(\n () =>\n filterTreeRoots(\n roots,\n nodeProps.getChildren,\n nodeProps.getKey,\n activeFilter,\n nodeProps.isSecondary,\n getSearchText,\n ),\n [\n roots,\n nodeProps.getChildren,\n nodeProps.getKey,\n activeFilter,\n nodeProps.isSecondary,\n getSearchText,\n ],\n );\n const treeRoots = filteredTree.roots;\n const filteredChildren = filteredTree.filteredChildren;\n const forcedOpenKeys = filteredTree.forcedOpenKeys;\n const effectiveGetChildren = filteredChildren\n ? (node: T) => filteredChildren.get(nodeProps.getKey(node)) ?? []\n : nodeProps.getChildren;\n const effectiveExpandAll = expandAll;\n const effectiveEmpty = activeFilter.trim() ? (\n <div className=\"px-3 py-4 text-sm text-muted-foreground\">No matching tree nodes.</div>\n ) : (\n empty\n );\n const showControlButtons = showControls && roots.length > 0;\n const showVirtualRow = showFilter || showControlButtons;\n const setExpandAll = (next: boolean | null) => {\n if (isControlled) onExpandAllChange?.(next);\n else setInternalExpandAll(next);\n };\n\n if (treeRoots.length === 0 && !showVirtualRow) return <>{effectiveEmpty ?? null}</>;\n\n return (\n <div className={cn(\"flex flex-col min-h-0\", className)}>\n <div role=\"tree\" className=\"min-h-0 flex-1 overflow-auto\">\n {showVirtualRow && (\n <div\n role=\"presentation\"\n className={cn(\n \"sticky top-0 z-10 flex items-center gap-1.5 border-b border-border/70 bg-background/95 py-1 pr-2 text-sm backdrop-blur supports-[backdrop-filter]:bg-background/80\",\n toolbarClassName,\n )}\n style={{ paddingLeft: `${basePaddingPx}px` }}\n >\n {showFilter ? (\n <Icon icon={UiSearch} className=\"w-3 shrink-0 text-xs text-muted-foreground\" />\n ) : (\n <span className=\"w-3 shrink-0\" aria-hidden />\n )}\n\n {showFilter ? (\n <label className=\"flex min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background px-2 py-1 text-xs text-foreground shadow-sm\">\n <input\n type=\"search\"\n value={filterQuery}\n onChange={(event) => setFilterQuery(event.target.value)}\n placeholder=\"Filter tree\"\n className=\"h-5 w-full border-0 bg-transparent p-0 text-xs outline-none placeholder:text-muted-foreground\"\n aria-label=\"Filter tree nodes\"\n />\n {filterQuery && (\n <button\n type=\"button\"\n onClick={() => setFilterQuery(\"\")}\n className=\"inline-flex items-center rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground\"\n aria-label=\"Clear tree filter\"\n title=\"Clear tree filter\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n </label>\n ) : (\n <span className=\"flex-1\" />\n )}\n\n {showControlButtons && (\n <div className=\"ml-auto flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => setExpandAll(true)}\n aria-pressed={effectiveExpandAll === true}\n aria-label=\"Expand all\"\n title=\"Expand all\"\n disabled={activeFilter.trim().length > 0}\n className={cn(\n \"inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n effectiveExpandAll === true && \"bg-accent text-accent-foreground\",\n )}\n >\n <Icon icon={UiExpandAll} className=\"text-sm\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setExpandAll(false)}\n aria-pressed={effectiveExpandAll === false}\n aria-label=\"Collapse all\"\n title=\"Collapse all\"\n disabled={activeFilter.trim().length > 0}\n className={cn(\n \"inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n effectiveExpandAll === false && \"bg-accent text-accent-foreground\",\n )}\n >\n <Icon icon={UiCollapseAll} className=\"text-sm\" />\n </button>\n </div>\n )}\n </div>\n )}\n\n {treeRoots.length === 0 ? (\n (effectiveEmpty ?? null)\n ) : (\n <>\n {treeRoots.map((root) => (\n <TreeNode<T>\n {...nodeProps}\n key={nodeProps.getKey(root)}\n node={root}\n expandAll={effectiveExpandAll}\n forcedOpenKeys={forcedOpenKeys}\n getChildren={effectiveGetChildren}\n />\n ))}\n </>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Tree.js","sources":["../../src/data/Tree.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"./Icon\";\nimport { UiClose, UiCollapseAll, UiExpandAll, UiSearch } from \"../icons\";\nimport { TreeNode, type TreeNodeProps } from \"./TreeNode\";\n\nexport type TreeProps<T> = Omit<\n TreeNodeProps<T>,\n \"node\" | \"depth\" | \"expandAll\" | \"forcedOpenKeys\"\n> & {\n /** Root nodes to render. */\n roots: T[];\n /** Empty-state content when no roots are provided. */\n empty?: ReactNode;\n /** Classes applied to the tree root. */\n className?: string;\n /**\n * Render the Expand all / Collapse all toolbar above the tree. Enabled by\n * default. Set to false to suppress when the tree is tiny or the controls\n * are owned externally.\n */\n showControls?: boolean;\n /**\n * Externally controlled expand-all state. When provided together with\n * `onExpandAllChange`, the tree is controlled; otherwise it manages the\n * toolbar state internally.\n */\n expandAll?: boolean | null;\n /** Called when the toolbar expand-all state changes. */\n onExpandAllChange?: (next: boolean | null) => void;\n /** Classes applied to the search/control toolbar. */\n toolbarClassName?: string;\n /**\n * Override the text the filter matches against for a given node.\n * Defaults to a recursive walk of the node's own fields (excluding\n * its `children` and any secondary children), which works for\n * plain-shape trees but leaks text from nested domain objects —\n * e.g. a SQL Table's Field rows are marked secondary, yet the\n * parent record still carries `record.fields[].field` strings that\n * the default walker would collect. Callers that need precise\n * control over search behaviour should return only the text they\n * want surfaced (the node's own display label / id / type).\n */\n getSearchText?: (node: T) => string;\n /**\n * Force-open the ancestors of the `selected` node so it is always revealed,\n * even when its branch was collapsed (a deep-link, a programmatic selection\n * from outside the tree, or simply a folder the user never opened). While a\n * node is selected its ancestors cannot be collapsed. Off by default; other\n * nodes keep their normal expand state. Requires `selected` to be set.\n */\n revealSelected?: boolean;\n};\n\n// ancestorPathKeys returns the keys of every ancestor on the path from a root\n// down to `target` (excluding target itself — opening the ancestors is what\n// makes target render). Matches by getKey so it is robust to object identity\n// across roots rebuilds. Returns null when target is not found.\nfunction ancestorPathKeys<T>(\n roots: T[],\n getChildren: TreeProps<T>[\"getChildren\"],\n getKey: TreeProps<T>[\"getKey\"],\n target: T,\n): Set<string | number> | null {\n const targetKey = getKey(target);\n const path: (string | number)[] = [];\n const visit = (node: T): boolean => {\n if (getKey(node) === targetKey) return true;\n for (const child of getChildren(node) ?? []) {\n if (visit(child)) {\n path.push(getKey(node));\n return true;\n }\n }\n return false;\n };\n for (const root of roots) {\n if (visit(root)) return path.length ? new Set(path) : null;\n }\n return null;\n}\n\n// mergeKeySets unions two optional key sets, preserving the null fast-path when\n// both are empty so consumers without a filter or reveal pass null downstream.\nfunction mergeKeySets(\n a: Set<string | number> | null,\n b: Set<string | number> | null,\n): Set<string | number> | null {\n if (!a) return b;\n if (!b) return a;\n return new Set([...a, ...b]);\n}\n\n// Edge counting, search-text collection and filtering all walk the tree through\n// the synchronous `getChildren` only. A lazy node (one declared via\n// `hasMoreChildren` whose children have not been fetched yet) therefore\n// contributes no edges and no search text until the operator expands it — so it\n// is invisible to \"expand all\" and to the filter while still collapsed. This is\n// intentional: we never trigger network loads from bulk/search operations.\nfunction countTreeEdges<T>(roots: T[], getChildren: TreeProps<T>[\"getChildren\"]) {\n let total = 0;\n const stack = [...roots];\n\n while (stack.length > 0) {\n const node = stack.pop()!;\n const children = getChildren(node) ?? [];\n total += children.length;\n stack.push(...children);\n }\n\n return total;\n}\n\nfunction collectTreeSearchText(\n value: unknown,\n excluded = new Set<unknown>(),\n seen = new Set<unknown>(),\n): string {\n if (value == null || excluded.has(value)) return \"\";\n\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\"\n ) {\n return String(value);\n }\n\n if (typeof value !== \"object\") return \"\";\n if (seen.has(value)) return \"\";\n seen.add(value);\n\n if (Array.isArray(value)) {\n return value\n .map((entry) => collectTreeSearchText(entry, excluded, seen))\n .filter(Boolean)\n .join(\" \");\n }\n\n const preferredKeys = [\"label\", \"name\", \"title\", \"text\", \"plain\", \"id\", \"content\"];\n const record = value as Record<string, unknown>;\n const chunks: string[] = [];\n\n for (const key of preferredKeys) {\n if (!(key in record)) continue;\n const text = collectTreeSearchText(record[key], excluded, seen);\n if (text) chunks.push(text);\n }\n\n for (const [key, entry] of Object.entries(record)) {\n if (key === \"children\" || preferredKeys.includes(key)) continue;\n const text = collectTreeSearchText(entry, excluded, seen);\n if (text) chunks.push(text);\n }\n\n return chunks.join(\" \");\n}\n\nfunction nodeSearchText<T>(node: T, children: ReturnType<TreeProps<T>[\"getChildren\"]>) {\n const excluded = new Set<unknown>();\n if (children) excluded.add(children);\n return collectTreeSearchText(node, excluded).trim().toLowerCase();\n}\n\nfunction filterTreeRoots<T>(\n roots: T[],\n getChildren: TreeProps<T>[\"getChildren\"],\n getKey: TreeProps<T>[\"getKey\"],\n query: string,\n isSecondary?: (node: T) => boolean,\n getSearchText?: (node: T) => string,\n) {\n const normalizedQuery = query.trim().toLowerCase();\n if (!normalizedQuery) {\n return {\n roots,\n filteredChildren: null as Map<string | number, T[]> | null,\n forcedOpenKeys: null as Set<string | number> | null,\n };\n }\n\n const filteredChildren = new Map<string | number, T[]>();\n const forcedOpenKeys = new Set<string | number>();\n\n function matchText(node: T, children: T[]): string {\n // Honour the caller-supplied text extractor first — it's the\n // only way to get precise control over what the filter sees when\n // the node carries domain data whose fields would otherwise leak\n // into the default walker.\n if (getSearchText) return getSearchText(node).trim().toLowerCase();\n // Match-text skips secondary children so e.g. a Table's search\n // text only draws from the Table itself, not from its Field\n // rows. The Table is primary, so its own text still counts.\n const searchChildren = children.filter((c) => !isSecondary?.(c));\n return nodeSearchText(node, searchChildren);\n }\n\n function visit(node: T): boolean {\n // A secondary node never drives a filter match — it's\n // effectively invisible to search. Still rendered when the\n // caller opens the parent manually. This is what lets a 28k-row\n // field tree stay searchable at the record level without \"field\n // name contains 'x'\" matches spamming the results.\n if (isSecondary?.(node)) return false;\n\n const key = getKey(node);\n const children = getChildren(node) ?? [];\n const childrenAllSecondary =\n children.length > 0 && children.every((child) => isSecondary?.(child));\n const matches = matchText(node, children).includes(normalizedQuery);\n const visibleChildren: T[] = [];\n\n for (const child of children) {\n if (visit(child)) visibleChildren.push(child);\n }\n\n if (matches) {\n if (children.length > 0) {\n filteredChildren.set(key, children);\n if (!childrenAllSecondary) forcedOpenKeys.add(key);\n }\n return true;\n }\n\n if (visibleChildren.length > 0) {\n filteredChildren.set(key, visibleChildren);\n forcedOpenKeys.add(key);\n return true;\n }\n\n return false;\n }\n\n return {\n roots: roots.filter((root) => visit(root)),\n filteredChildren,\n forcedOpenKeys,\n };\n}\n\nexport function Tree<T>({\n roots,\n empty,\n className,\n showControls = true,\n expandAll: controlledExpandAll,\n onExpandAllChange,\n toolbarClassName,\n getSearchText,\n revealSelected = false,\n ...nodeProps\n}: TreeProps<T>) {\n const [internalExpandAll, setInternalExpandAll] = useState<boolean | null>(null);\n const [filterQuery, setFilterQuery] = useState(\"\");\n const basePaddingPx = nodeProps.basePaddingPx ?? 8;\n const isControlled = onExpandAllChange !== undefined;\n const expandAll = isControlled ? (controlledExpandAll ?? null) : internalExpandAll;\n const totalEdges = useMemo(\n () => countTreeEdges(roots, nodeProps.getChildren),\n [roots, nodeProps.getChildren],\n );\n const showFilter = totalEdges > 20;\n const activeFilter = showFilter ? filterQuery : \"\";\n const filteredTree = useMemo(\n () =>\n filterTreeRoots(\n roots,\n nodeProps.getChildren,\n nodeProps.getKey,\n activeFilter,\n nodeProps.isSecondary,\n getSearchText,\n ),\n [\n roots,\n nodeProps.getChildren,\n nodeProps.getKey,\n activeFilter,\n nodeProps.isSecondary,\n getSearchText,\n ],\n );\n const treeRoots = filteredTree.roots;\n const filteredChildren = filteredTree.filteredChildren;\n // Force-open the selected node's ancestors when revealSelected is set, unioned\n // with the filter's own forced-open keys. Recomputed when the selection moves\n // so the tree follows it without a remount.\n const revealKeys = useMemo(\n () =>\n revealSelected && nodeProps.selected\n ? ancestorPathKeys(roots, nodeProps.getChildren, nodeProps.getKey, nodeProps.selected)\n : null,\n [revealSelected, roots, nodeProps.getChildren, nodeProps.getKey, nodeProps.selected],\n );\n const forcedOpenKeys = useMemo(\n () => mergeKeySets(filteredTree.forcedOpenKeys, revealKeys),\n [filteredTree.forcedOpenKeys, revealKeys],\n );\n const effectiveGetChildren = filteredChildren\n ? (node: T) => filteredChildren.get(nodeProps.getKey(node)) ?? []\n : nodeProps.getChildren;\n const effectiveExpandAll = expandAll;\n const effectiveEmpty = activeFilter.trim() ? (\n <div className=\"px-3 py-4 text-sm text-muted-foreground\">No matching tree nodes.</div>\n ) : (\n empty\n );\n const showControlButtons = showControls && roots.length > 0;\n const showVirtualRow = showFilter || showControlButtons;\n const setExpandAll = (next: boolean | null) => {\n if (isControlled) onExpandAllChange?.(next);\n else setInternalExpandAll(next);\n };\n\n if (treeRoots.length === 0 && !showVirtualRow) return <>{effectiveEmpty ?? null}</>;\n\n return (\n <div className={cn(\"flex flex-col min-h-0\", className)}>\n <div role=\"tree\" className=\"min-h-0 flex-1 overflow-auto\">\n {showVirtualRow && (\n <div\n role=\"presentation\"\n className={cn(\n \"sticky top-0 z-10 flex items-center gap-1.5 border-b border-border/70 bg-background/95 py-1 pr-2 text-sm backdrop-blur supports-[backdrop-filter]:bg-background/80\",\n toolbarClassName,\n )}\n style={{ paddingLeft: `${basePaddingPx}px` }}\n >\n {showFilter ? (\n <Icon icon={UiSearch} className=\"w-3 shrink-0 text-xs text-muted-foreground\" />\n ) : (\n <span className=\"w-3 shrink-0\" aria-hidden />\n )}\n\n {showFilter ? (\n <label className=\"flex min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background px-2 py-1 text-xs text-foreground shadow-sm\">\n <input\n type=\"search\"\n value={filterQuery}\n onChange={(event) => setFilterQuery(event.target.value)}\n placeholder=\"Filter tree\"\n className=\"h-5 w-full border-0 bg-transparent p-0 text-xs outline-none placeholder:text-muted-foreground\"\n aria-label=\"Filter tree nodes\"\n />\n {filterQuery && (\n <button\n type=\"button\"\n onClick={() => setFilterQuery(\"\")}\n className=\"inline-flex items-center rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground\"\n aria-label=\"Clear tree filter\"\n title=\"Clear tree filter\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n </label>\n ) : (\n <span className=\"flex-1\" />\n )}\n\n {showControlButtons && (\n <div className=\"ml-auto flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => setExpandAll(true)}\n aria-pressed={effectiveExpandAll === true}\n aria-label=\"Expand all\"\n title=\"Expand all\"\n disabled={activeFilter.trim().length > 0}\n className={cn(\n \"inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n effectiveExpandAll === true && \"bg-accent text-accent-foreground\",\n )}\n >\n <Icon icon={UiExpandAll} className=\"text-sm\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setExpandAll(false)}\n aria-pressed={effectiveExpandAll === false}\n aria-label=\"Collapse all\"\n title=\"Collapse all\"\n disabled={activeFilter.trim().length > 0}\n className={cn(\n \"inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n effectiveExpandAll === false && \"bg-accent text-accent-foreground\",\n )}\n >\n <Icon icon={UiCollapseAll} className=\"text-sm\" />\n </button>\n </div>\n )}\n </div>\n )}\n\n {treeRoots.length === 0 ? (\n (effectiveEmpty ?? null)\n ) : (\n <>\n {treeRoots.map((root) => (\n <TreeNode<T>\n {...nodeProps}\n key={nodeProps.getKey(root)}\n node={root}\n expandAll={effectiveExpandAll}\n forcedOpenKeys={forcedOpenKeys}\n getChildren={effectiveGetChildren}\n />\n ))}\n </>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const react = require("react");
5
+ const ai = require("ai");
6
+ const utils = require("../../lib/utils.cjs");
7
+ const Icon = require("../Icon.cjs");
8
+ const button = require("../../components/button.cjs");
9
+ const UiAdd = require("../../icons/components/UiAdd.cjs");
10
+ const UiFile = require("../../icons/components/UiFile.cjs");
11
+ const UiClose = require("../../icons/components/UiClose.cjs");
12
+ const MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
13
+ function AttachmentButton({ onAdd, disabled, className }) {
14
+ const inputRef = react.useRef(null);
15
+ const onChange = async (e) => {
16
+ const list = e.target.files;
17
+ if (!list || list.length === 0) return;
18
+ const tooBig = Array.from(list).filter((f) => f.size > MAX_ATTACHMENT_BYTES);
19
+ if (tooBig.length > 0) {
20
+ console.warn(`clicky-ui: ${tooBig.length} attachment(s) exceed ${MAX_ATTACHMENT_BYTES} bytes and were skipped`);
21
+ }
22
+ const ok = Array.from(list).filter((f) => f.size <= MAX_ATTACHMENT_BYTES);
23
+ if (ok.length > 0) {
24
+ const dt = new DataTransfer();
25
+ ok.forEach((f) => dt.items.add(f));
26
+ onAdd(await ai.convertFileListToFileUIParts(dt.files));
27
+ }
28
+ e.target.value = "";
29
+ };
30
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
31
+ /* @__PURE__ */ jsxRuntime.jsx("input", { ref: inputRef, type: "file", multiple: true, hidden: true, onChange }),
32
+ /* @__PURE__ */ jsxRuntime.jsx(
33
+ button.Button,
34
+ {
35
+ type: "button",
36
+ size: "icon",
37
+ variant: "ghost",
38
+ "aria-label": "Attach files",
39
+ disabled,
40
+ onClick: () => {
41
+ var _a;
42
+ return (_a = inputRef.current) == null ? void 0 : _a.click();
43
+ },
44
+ className,
45
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiAdd.UiAdd, className: "size-4" })
46
+ }
47
+ )
48
+ ] });
49
+ }
50
+ function AttachmentList({ files, onRemove, className }) {
51
+ if (files.length === 0) return null;
52
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: utils.cn("flex flex-wrap gap-2 px-1", className), children: files.map((file, i) => {
53
+ var _a;
54
+ return /* @__PURE__ */ jsxRuntime.jsxs(
55
+ "div",
56
+ {
57
+ className: "flex items-center gap-1.5 rounded-md border border-border bg-background py-1 pl-2 pr-1 text-xs",
58
+ children: [
59
+ ((_a = file.mediaType) == null ? void 0 : _a.startsWith("image/")) && file.url ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: file.url, alt: file.filename ?? "", className: "size-6 rounded object-cover" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiFile.UiFile, className: "size-3.5 text-muted-foreground" }),
60
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-32 truncate", children: file.filename ?? file.mediaType ?? "file" }),
61
+ /* @__PURE__ */ jsxRuntime.jsx(
62
+ "button",
63
+ {
64
+ type: "button",
65
+ "aria-label": "Remove attachment",
66
+ onClick: () => onRemove(i),
67
+ className: "rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground",
68
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiClose.UiClose, className: "size-3" })
69
+ }
70
+ )
71
+ ]
72
+ },
73
+ `${file.filename ?? "file"}-${i}`
74
+ );
75
+ }) });
76
+ }
77
+ exports.AttachmentButton = AttachmentButton;
78
+ exports.AttachmentList = AttachmentList;
79
+ //# sourceMappingURL=Attachment.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Attachment.cjs","sources":["../../../src/data/chat/Attachment.tsx"],"sourcesContent":["import { useRef } from \"react\";\nimport { convertFileListToFileUIParts } from \"ai\";\nimport { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { UiAdd, UiClose, UiFile } from \"../../icons\";\nimport type { FileUIPart } from \"./types\";\n\n/** Files larger than this are rejected client-side: attachments are inlined as\n * data URLs in the request body, so large files would bloat every turn. */\nconst MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nexport type AttachmentButtonProps = {\n /** Called with newly selected files converted to FileUIParts. */\n onAdd: (parts: FileUIPart[]) => void;\n disabled?: boolean;\n className?: string;\n};\n\n/** A paperclip-style button that opens the file picker and emits the chosen\n * files as FileUIParts (data-URL encoded). */\nexport function AttachmentButton({ onAdd, disabled, className }: AttachmentButtonProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n const onChange = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const list = e.target.files;\n if (!list || list.length === 0) return;\n const tooBig = Array.from(list).filter((f) => f.size > MAX_ATTACHMENT_BYTES);\n if (tooBig.length > 0) {\n console.warn(`clicky-ui: ${tooBig.length} attachment(s) exceed ${MAX_ATTACHMENT_BYTES} bytes and were skipped`);\n }\n const ok = Array.from(list).filter((f) => f.size <= MAX_ATTACHMENT_BYTES);\n if (ok.length > 0) {\n const dt = new DataTransfer();\n ok.forEach((f) => dt.items.add(f));\n onAdd(await convertFileListToFileUIParts(dt.files));\n }\n e.target.value = \"\";\n };\n\n return (\n <>\n <input ref={inputRef} type=\"file\" multiple hidden onChange={onChange} />\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n aria-label=\"Attach files\"\n disabled={disabled}\n onClick={() => inputRef.current?.click()}\n className={className}\n >\n <Icon icon={UiAdd} className=\"size-4\" />\n </Button>\n </>\n );\n}\n\nexport type AttachmentListProps = {\n files: FileUIPart[];\n onRemove: (index: number) => void;\n className?: string;\n};\n\n/** Chips/thumbnails for the pending attachments, each removable. */\nexport function AttachmentList({ files, onRemove, className }: AttachmentListProps) {\n if (files.length === 0) return null;\n return (\n <div className={cn(\"flex flex-wrap gap-2 px-1\", className)}>\n {files.map((file, i) => (\n <div\n key={`${file.filename ?? \"file\"}-${i}`}\n className=\"flex items-center gap-1.5 rounded-md border border-border bg-background py-1 pl-2 pr-1 text-xs\"\n >\n {file.mediaType?.startsWith(\"image/\") && file.url ? (\n <img src={file.url} alt={file.filename ?? \"\"} className=\"size-6 rounded object-cover\" />\n ) : (\n <Icon icon={UiFile} className=\"size-3.5 text-muted-foreground\" />\n )}\n <span className=\"max-w-32 truncate\">{file.filename ?? file.mediaType ?? \"file\"}</span>\n <button\n type=\"button\"\n aria-label=\"Remove attachment\"\n onClick={() => onRemove(i)}\n className=\"rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground\"\n >\n <Icon icon={UiClose} className=\"size-3\" />\n </button>\n </div>\n ))}\n </div>\n );\n}\n"],"names":["useRef","convertFileListToFileUIParts","jsxs","Fragment","jsx","Button","Icon","UiAdd","cn","UiFile","UiClose"],"mappings":";;;;;;;;;;;AAUA,MAAM,uBAAuB,IAAI,OAAO;AAWjC,SAAS,iBAAiB,EAAE,OAAO,UAAU,aAAoC;AACtF,QAAM,WAAWA,MAAAA,OAAyB,IAAI;AAE9C,QAAM,WAAW,OAAO,MAA2C;AACjE,UAAM,OAAO,EAAE,OAAO;AACtB,QAAI,CAAC,QAAQ,KAAK,WAAW,EAAG;AAChC,UAAM,SAAS,MAAM,KAAK,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,oBAAoB;AAC3E,QAAI,OAAO,SAAS,GAAG;AACrB,cAAQ,KAAK,cAAc,OAAO,MAAM,yBAAyB,oBAAoB,yBAAyB;AAAA,IAChH;AACA,UAAM,KAAK,MAAM,KAAK,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,oBAAoB;AACxE,QAAI,GAAG,SAAS,GAAG;AACjB,YAAM,KAAK,IAAI,aAAA;AACf,SAAG,QAAQ,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,CAAC;AACjC,YAAM,MAAMC,GAAAA,6BAA6B,GAAG,KAAK,CAAC;AAAA,IACpD;AACA,MAAE,OAAO,QAAQ;AAAA,EACnB;AAEA,SACEC,2BAAAA,KAAAC,qBAAA,EACE,UAAA;AAAA,IAAAC,2BAAAA,IAAC,SAAA,EAAM,KAAK,UAAU,MAAK,QAAO,UAAQ,MAAC,QAAM,MAAC,SAAA,CAAoB;AAAA,IACtEA,2BAAAA;AAAAA,MAACC,OAAAA;AAAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,cAAW;AAAA,QACX;AAAA,QACA,SAAS,MAAA;;AAAM,gCAAS,YAAT,mBAAkB;AAAA;AAAA,QACjC;AAAA,QAEA,UAAAD,2BAAAA,IAACE,WAAA,EAAK,MAAMC,MAAAA,OAAO,WAAU,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EACxC,GACF;AAEJ;AASO,SAAS,eAAe,EAAE,OAAO,UAAU,aAAkC;AAClF,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SACEH,2BAAAA,IAAC,OAAA,EAAI,WAAWI,MAAAA,GAAG,6BAA6B,SAAS,GACtD,UAAA,MAAM,IAAI,CAAC,MAAM,MAAA;;AAChBN,sCAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QAEC,WAAU;AAAA,QAET,UAAA;AAAA,YAAA,UAAK,cAAL,mBAAgB,WAAW,cAAa,KAAK,MAC5CE,2BAAAA,IAAC,OAAA,EAAI,KAAK,KAAK,KAAK,KAAK,KAAK,YAAY,IAAI,WAAU,8BAAA,CAA8B,mCAErFE,KAAAA,MAAA,EAAK,MAAMG,OAAAA,QAAQ,WAAU,iCAAA,CAAiC;AAAA,UAEjEL,2BAAAA,IAAC,UAAK,WAAU,qBAAqB,eAAK,YAAY,KAAK,aAAa,OAAA,CAAO;AAAA,UAC/EA,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,cAAW;AAAA,cACX,SAAS,MAAM,SAAS,CAAC;AAAA,cACzB,WAAU;AAAA,cAEV,UAAAA,2BAAAA,IAACE,WAAA,EAAK,MAAMI,QAAAA,SAAS,WAAU,SAAA,CAAS;AAAA,YAAA;AAAA,UAAA;AAAA,QAC1C;AAAA,MAAA;AAAA,MAhBK,GAAG,KAAK,YAAY,MAAM,IAAI,CAAC;AAAA,IAAA;AAAA,GAkBvC,GACH;AAEJ;;;"}
@@ -0,0 +1,18 @@
1
+ import { FileUIPart } from './types';
2
+ export type AttachmentButtonProps = {
3
+ /** Called with newly selected files converted to FileUIParts. */
4
+ onAdd: (parts: FileUIPart[]) => void;
5
+ disabled?: boolean;
6
+ className?: string;
7
+ };
8
+ /** A paperclip-style button that opens the file picker and emits the chosen
9
+ * files as FileUIParts (data-URL encoded). */
10
+ export declare function AttachmentButton({ onAdd, disabled, className }: AttachmentButtonProps): import("react/jsx-runtime").JSX.Element;
11
+ export type AttachmentListProps = {
12
+ files: FileUIPart[];
13
+ onRemove: (index: number) => void;
14
+ className?: string;
15
+ };
16
+ /** Chips/thumbnails for the pending attachments, each removable. */
17
+ export declare function AttachmentList({ files, onRemove, className }: AttachmentListProps): import("react/jsx-runtime").JSX.Element | null;
18
+ //# sourceMappingURL=Attachment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Attachment.d.ts","sourceRoot":"","sources":["../../../src/data/chat/Attachment.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAM1C,MAAM,MAAM,qBAAqB,GAAG;IAClC,iEAAiE;IACjE,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;+CAC+C;AAC/C,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,qBAAqB,2CAmCrF;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,mBAAmB,kDA2BjF"}
@@ -0,0 +1,79 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import { useRef } from "react";
3
+ import { convertFileListToFileUIParts } from "ai";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { Icon } from "../Icon.js";
6
+ import { Button } from "../../components/button.js";
7
+ import { UiAdd } from "../../icons/components/UiAdd.js";
8
+ import { UiFile } from "../../icons/components/UiFile.js";
9
+ import { UiClose } from "../../icons/components/UiClose.js";
10
+ const MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
11
+ function AttachmentButton({ onAdd, disabled, className }) {
12
+ const inputRef = useRef(null);
13
+ const onChange = async (e) => {
14
+ const list = e.target.files;
15
+ if (!list || list.length === 0) return;
16
+ const tooBig = Array.from(list).filter((f) => f.size > MAX_ATTACHMENT_BYTES);
17
+ if (tooBig.length > 0) {
18
+ console.warn(`clicky-ui: ${tooBig.length} attachment(s) exceed ${MAX_ATTACHMENT_BYTES} bytes and were skipped`);
19
+ }
20
+ const ok = Array.from(list).filter((f) => f.size <= MAX_ATTACHMENT_BYTES);
21
+ if (ok.length > 0) {
22
+ const dt = new DataTransfer();
23
+ ok.forEach((f) => dt.items.add(f));
24
+ onAdd(await convertFileListToFileUIParts(dt.files));
25
+ }
26
+ e.target.value = "";
27
+ };
28
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
29
+ /* @__PURE__ */ jsx("input", { ref: inputRef, type: "file", multiple: true, hidden: true, onChange }),
30
+ /* @__PURE__ */ jsx(
31
+ Button,
32
+ {
33
+ type: "button",
34
+ size: "icon",
35
+ variant: "ghost",
36
+ "aria-label": "Attach files",
37
+ disabled,
38
+ onClick: () => {
39
+ var _a;
40
+ return (_a = inputRef.current) == null ? void 0 : _a.click();
41
+ },
42
+ className,
43
+ children: /* @__PURE__ */ jsx(Icon, { icon: UiAdd, className: "size-4" })
44
+ }
45
+ )
46
+ ] });
47
+ }
48
+ function AttachmentList({ files, onRemove, className }) {
49
+ if (files.length === 0) return null;
50
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap gap-2 px-1", className), children: files.map((file, i) => {
51
+ var _a;
52
+ return /* @__PURE__ */ jsxs(
53
+ "div",
54
+ {
55
+ className: "flex items-center gap-1.5 rounded-md border border-border bg-background py-1 pl-2 pr-1 text-xs",
56
+ children: [
57
+ ((_a = file.mediaType) == null ? void 0 : _a.startsWith("image/")) && file.url ? /* @__PURE__ */ jsx("img", { src: file.url, alt: file.filename ?? "", className: "size-6 rounded object-cover" }) : /* @__PURE__ */ jsx(Icon, { icon: UiFile, className: "size-3.5 text-muted-foreground" }),
58
+ /* @__PURE__ */ jsx("span", { className: "max-w-32 truncate", children: file.filename ?? file.mediaType ?? "file" }),
59
+ /* @__PURE__ */ jsx(
60
+ "button",
61
+ {
62
+ type: "button",
63
+ "aria-label": "Remove attachment",
64
+ onClick: () => onRemove(i),
65
+ className: "rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground",
66
+ children: /* @__PURE__ */ jsx(Icon, { icon: UiClose, className: "size-3" })
67
+ }
68
+ )
69
+ ]
70
+ },
71
+ `${file.filename ?? "file"}-${i}`
72
+ );
73
+ }) });
74
+ }
75
+ export {
76
+ AttachmentButton,
77
+ AttachmentList
78
+ };
79
+ //# sourceMappingURL=Attachment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Attachment.js","sources":["../../../src/data/chat/Attachment.tsx"],"sourcesContent":["import { useRef } from \"react\";\nimport { convertFileListToFileUIParts } from \"ai\";\nimport { cn } from \"../../lib/utils\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { UiAdd, UiClose, UiFile } from \"../../icons\";\nimport type { FileUIPart } from \"./types\";\n\n/** Files larger than this are rejected client-side: attachments are inlined as\n * data URLs in the request body, so large files would bloat every turn. */\nconst MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nexport type AttachmentButtonProps = {\n /** Called with newly selected files converted to FileUIParts. */\n onAdd: (parts: FileUIPart[]) => void;\n disabled?: boolean;\n className?: string;\n};\n\n/** A paperclip-style button that opens the file picker and emits the chosen\n * files as FileUIParts (data-URL encoded). */\nexport function AttachmentButton({ onAdd, disabled, className }: AttachmentButtonProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n const onChange = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const list = e.target.files;\n if (!list || list.length === 0) return;\n const tooBig = Array.from(list).filter((f) => f.size > MAX_ATTACHMENT_BYTES);\n if (tooBig.length > 0) {\n console.warn(`clicky-ui: ${tooBig.length} attachment(s) exceed ${MAX_ATTACHMENT_BYTES} bytes and were skipped`);\n }\n const ok = Array.from(list).filter((f) => f.size <= MAX_ATTACHMENT_BYTES);\n if (ok.length > 0) {\n const dt = new DataTransfer();\n ok.forEach((f) => dt.items.add(f));\n onAdd(await convertFileListToFileUIParts(dt.files));\n }\n e.target.value = \"\";\n };\n\n return (\n <>\n <input ref={inputRef} type=\"file\" multiple hidden onChange={onChange} />\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n aria-label=\"Attach files\"\n disabled={disabled}\n onClick={() => inputRef.current?.click()}\n className={className}\n >\n <Icon icon={UiAdd} className=\"size-4\" />\n </Button>\n </>\n );\n}\n\nexport type AttachmentListProps = {\n files: FileUIPart[];\n onRemove: (index: number) => void;\n className?: string;\n};\n\n/** Chips/thumbnails for the pending attachments, each removable. */\nexport function AttachmentList({ files, onRemove, className }: AttachmentListProps) {\n if (files.length === 0) return null;\n return (\n <div className={cn(\"flex flex-wrap gap-2 px-1\", className)}>\n {files.map((file, i) => (\n <div\n key={`${file.filename ?? \"file\"}-${i}`}\n className=\"flex items-center gap-1.5 rounded-md border border-border bg-background py-1 pl-2 pr-1 text-xs\"\n >\n {file.mediaType?.startsWith(\"image/\") && file.url ? (\n <img src={file.url} alt={file.filename ?? \"\"} className=\"size-6 rounded object-cover\" />\n ) : (\n <Icon icon={UiFile} className=\"size-3.5 text-muted-foreground\" />\n )}\n <span className=\"max-w-32 truncate\">{file.filename ?? file.mediaType ?? \"file\"}</span>\n <button\n type=\"button\"\n aria-label=\"Remove attachment\"\n onClick={() => onRemove(i)}\n className=\"rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground\"\n >\n <Icon icon={UiClose} className=\"size-3\" />\n </button>\n </div>\n ))}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAUA,MAAM,uBAAuB,IAAI,OAAO;AAWjC,SAAS,iBAAiB,EAAE,OAAO,UAAU,aAAoC;AACtF,QAAM,WAAW,OAAyB,IAAI;AAE9C,QAAM,WAAW,OAAO,MAA2C;AACjE,UAAM,OAAO,EAAE,OAAO;AACtB,QAAI,CAAC,QAAQ,KAAK,WAAW,EAAG;AAChC,UAAM,SAAS,MAAM,KAAK,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,oBAAoB;AAC3E,QAAI,OAAO,SAAS,GAAG;AACrB,cAAQ,KAAK,cAAc,OAAO,MAAM,yBAAyB,oBAAoB,yBAAyB;AAAA,IAChH;AACA,UAAM,KAAK,MAAM,KAAK,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,oBAAoB;AACxE,QAAI,GAAG,SAAS,GAAG;AACjB,YAAM,KAAK,IAAI,aAAA;AACf,SAAG,QAAQ,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,CAAC;AACjC,YAAM,MAAM,6BAA6B,GAAG,KAAK,CAAC;AAAA,IACpD;AACA,MAAE,OAAO,QAAQ;AAAA,EACnB;AAEA,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAM,KAAK,UAAU,MAAK,QAAO,UAAQ,MAAC,QAAM,MAAC,SAAA,CAAoB;AAAA,IACtE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,cAAW;AAAA,QACX;AAAA,QACA,SAAS,MAAA;;AAAM,gCAAS,YAAT,mBAAkB;AAAA;AAAA,QACjC;AAAA,QAEA,UAAA,oBAAC,MAAA,EAAK,MAAM,OAAO,WAAU,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EACxC,GACF;AAEJ;AASO,SAAS,eAAe,EAAE,OAAO,UAAU,aAAkC;AAClF,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SACE,oBAAC,OAAA,EAAI,WAAW,GAAG,6BAA6B,SAAS,GACtD,UAAA,MAAM,IAAI,CAAC,MAAM,MAAA;;AAChB;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,WAAU;AAAA,QAET,UAAA;AAAA,YAAA,UAAK,cAAL,mBAAgB,WAAW,cAAa,KAAK,MAC5C,oBAAC,OAAA,EAAI,KAAK,KAAK,KAAK,KAAK,KAAK,YAAY,IAAI,WAAU,8BAAA,CAA8B,wBAErF,MAAA,EAAK,MAAM,QAAQ,WAAU,iCAAA,CAAiC;AAAA,UAEjE,oBAAC,UAAK,WAAU,qBAAqB,eAAK,YAAY,KAAK,aAAa,OAAA,CAAO;AAAA,UAC/E;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,cAAW;AAAA,cACX,SAAS,MAAM,SAAS,CAAC;AAAA,cACzB,WAAU;AAAA,cAEV,UAAA,oBAAC,MAAA,EAAK,MAAM,SAAS,WAAU,SAAA,CAAS;AAAA,YAAA;AAAA,UAAA;AAAA,QAC1C;AAAA,MAAA;AAAA,MAhBK,GAAG,KAAK,YAAY,MAAM,IAAI,CAAC;AAAA,IAAA;AAAA,GAkBvC,GACH;AAEJ;"}
@@ -7,10 +7,21 @@ const ai = require("ai");
7
7
  const utils = require("../../lib/utils.cjs");
8
8
  const Conversation = require("./Conversation.cjs");
9
9
  const PromptInput = require("./PromptInput.cjs");
10
+ const Suggestion = require("./Suggestion.cjs");
11
+ const ModelSelector = require("./ModelSelector.cjs");
12
+ const DEFAULT_EFFORTS = ["low", "medium", "high"];
10
13
  function Chat({
11
14
  api = "/api/chat",
12
- model,
13
- reasoningEffort,
15
+ models: modelsProp,
16
+ modelsApi = "/api/chat/models",
17
+ defaultModel,
18
+ reasoningEfforts = DEFAULT_EFFORTS,
19
+ defaultReasoningEffort = "",
20
+ onModelChange,
21
+ suggestions,
22
+ enableAttachments = false,
23
+ toolApproval,
24
+ threadId,
14
25
  body,
15
26
  transport,
16
27
  initialMessages,
@@ -18,28 +29,74 @@ function Chat({
18
29
  emptyState,
19
30
  className
20
31
  }) {
21
- const resolvedTransport = react.useMemo(() => {
22
- if (transport) {
23
- return transport;
24
- }
25
- return new ai.DefaultChatTransport({
26
- api,
27
- body: { ...body, ...model ? { model } : {}, ...reasoningEffort ? { reasoningEffort } : {} }
28
- });
29
- }, [transport, api, body, model, reasoningEffort]);
30
- const { messages, sendMessage, status, stop } = react$1.useChat({
32
+ const [models, setModels] = react.useState(modelsProp ?? []);
33
+ const [model, setModel] = react.useState(defaultModel);
34
+ const [effort, setEffort] = react.useState(defaultReasoningEffort);
35
+ react.useEffect(() => {
36
+ if (modelsProp || !modelsApi) return;
37
+ let cancelled = false;
38
+ fetch(modelsApi).then((r) => r.ok ? r.json() : Promise.reject(new Error(`models ${r.status}`))).then((data) => {
39
+ if (cancelled) return;
40
+ setModels(data);
41
+ setModel((m) => {
42
+ var _a;
43
+ return m ?? ((_a = data.find((d) => d.configured !== false)) == null ? void 0 : _a.id);
44
+ });
45
+ }).catch((err) => console.warn("clicky-ui: failed to load chat models", err));
46
+ return () => {
47
+ cancelled = true;
48
+ };
49
+ }, [modelsProp, modelsApi]);
50
+ const selectedModel = models.find((m) => m.id === model);
51
+ const showEffort = !selectedModel || selectedModel.reasoning;
52
+ const bodyRef = react.useRef({});
53
+ bodyRef.current = {
54
+ ...body,
55
+ ...model ? { model } : {},
56
+ ...effort ? { reasoningEffort: effort } : {},
57
+ ...toolApproval ? { toolApproval } : {},
58
+ ...threadId ? { threadId } : {}
59
+ };
60
+ const resolvedTransport = react.useMemo(
61
+ () => transport ?? new ai.DefaultChatTransport({ api, body: () => bodyRef.current }),
62
+ [transport, api]
63
+ );
64
+ const { messages, sendMessage, regenerate, addToolApprovalResponse, status, stop } = react$1.useChat({
31
65
  transport: resolvedTransport,
66
+ sendAutomaticallyWhen: ai.lastAssistantMessageIsCompleteWithApprovalResponses,
32
67
  ...initialMessages ? { messages: initialMessages } : {}
33
68
  });
69
+ const onModelSelect = (id) => {
70
+ setModel(id);
71
+ onModelChange == null ? void 0 : onModelChange(id);
72
+ };
73
+ const toolbar = models.length > 0 || showEffort ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
74
+ /* @__PURE__ */ jsxRuntime.jsx(ModelSelector.ModelSelector, { models, value: model, onChange: onModelSelect }),
75
+ showEffort && /* @__PURE__ */ jsxRuntime.jsx(ModelSelector.EffortSelector, { efforts: reasoningEfforts, value: effort, onChange: setEffort })
76
+ ] }) : void 0;
77
+ const empty = messages.length === 0 && (emptyState || (suggestions == null ? void 0 : suggestions.length)) ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-4", children: [
78
+ emptyState,
79
+ suggestions && suggestions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Suggestion.Suggestions, { suggestions, onSelect: (text) => void sendMessage({ text }) })
80
+ ] }) : void 0;
34
81
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("flex h-full flex-col", className), children: [
35
- /* @__PURE__ */ jsxRuntime.jsx(Conversation.Conversation, { messages, emptyState }),
82
+ /* @__PURE__ */ jsxRuntime.jsx(
83
+ Conversation.Conversation,
84
+ {
85
+ messages,
86
+ emptyState: empty,
87
+ onRegenerate: (messageId) => void regenerate({ messageId }),
88
+ onApprove: (id, approved, reason) => void addToolApprovalResponse(reason ? { id, approved, reason } : { id, approved })
89
+ }
90
+ ),
36
91
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 pt-0", children: /* @__PURE__ */ jsxRuntime.jsx(
37
92
  PromptInput.PromptInput,
38
93
  {
39
94
  status,
40
95
  onStop: () => void stop(),
41
96
  placeholder,
42
- onSubmit: (text) => void sendMessage({ text })
97
+ enableAttachments,
98
+ toolbar,
99
+ onSubmit: (text, files) => void sendMessage({ text, files })
43
100
  }
44
101
  ) })
45
102
  ] });
@@ -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"}