@kognitivedev/ui 0.2.11

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 (258) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/CHANGELOG.md +19 -0
  3. package/README.md +264 -0
  4. package/dist/__tests__/context-provider.test.d.ts +1 -0
  5. package/dist/__tests__/context-provider.test.js +38 -0
  6. package/dist/__tests__/event-emitter.test.d.ts +1 -0
  7. package/dist/__tests__/event-emitter.test.js +62 -0
  8. package/dist/__tests__/keyboard-shortcuts.test.d.ts +1 -0
  9. package/dist/__tests__/keyboard-shortcuts.test.js +36 -0
  10. package/dist/__tests__/kognitive-runtime.test.d.ts +1 -0
  11. package/dist/__tests__/kognitive-runtime.test.js +58 -0
  12. package/dist/__tests__/kognitive-transport.test.d.ts +1 -0
  13. package/dist/__tests__/kognitive-transport.test.js +96 -0
  14. package/dist/__tests__/make-tool-ui.test.d.ts +1 -0
  15. package/dist/__tests__/make-tool-ui.test.js +50 -0
  16. package/dist/__tests__/message-helpers.test.d.ts +1 -0
  17. package/dist/__tests__/message-helpers.test.js +80 -0
  18. package/dist/__tests__/thread-manager.test.d.ts +1 -0
  19. package/dist/__tests__/thread-manager.test.js +84 -0
  20. package/dist/__tests__/tool-ui-registry.test.d.ts +1 -0
  21. package/dist/__tests__/tool-ui-registry.test.js +68 -0
  22. package/dist/__tests__/toolkit.test.d.ts +1 -0
  23. package/dist/__tests__/toolkit.test.js +33 -0
  24. package/dist/components/attachment-preview.d.ts +8 -0
  25. package/dist/components/attachment-preview.js +10 -0
  26. package/dist/components/composer.d.ts +6 -0
  27. package/dist/components/composer.js +10 -0
  28. package/dist/components/error-banner.d.ts +6 -0
  29. package/dist/components/error-banner.js +8 -0
  30. package/dist/components/markdown-content.d.ts +13 -0
  31. package/dist/components/markdown-content.js +31 -0
  32. package/dist/components/message.d.ts +7 -0
  33. package/dist/components/message.js +28 -0
  34. package/dist/components/status-indicator.d.ts +6 -0
  35. package/dist/components/status-indicator.js +16 -0
  36. package/dist/components/suggestions.d.ts +4 -0
  37. package/dist/components/suggestions.js +12 -0
  38. package/dist/components/thread-list.d.ts +4 -0
  39. package/dist/components/thread-list.js +20 -0
  40. package/dist/components/thread.d.ts +15 -0
  41. package/dist/components/thread.js +19 -0
  42. package/dist/components/tool-approval.d.ts +7 -0
  43. package/dist/components/tool-approval.js +12 -0
  44. package/dist/components/tool-invocation.d.ts +5 -0
  45. package/dist/components/tool-invocation.js +33 -0
  46. package/dist/context/kognitive-context.d.ts +2 -0
  47. package/dist/context/kognitive-context.js +6 -0
  48. package/dist/context/types.d.ts +44 -0
  49. package/dist/context/types.js +2 -0
  50. package/dist/context/use-kognitive.d.ts +1 -0
  51. package/dist/context/use-kognitive.js +7 -0
  52. package/dist/hooks/use-auto-scroll.d.ts +2 -0
  53. package/dist/hooks/use-auto-scroll.js +18 -0
  54. package/dist/hooks/use-copy-to-clipboard.d.ts +4 -0
  55. package/dist/hooks/use-copy-to-clipboard.js +13 -0
  56. package/dist/hooks/use-keyboard-shortcuts.d.ts +8 -0
  57. package/dist/hooks/use-keyboard-shortcuts.js +24 -0
  58. package/dist/hooks/use-kognitive-chat.d.ts +38 -0
  59. package/dist/hooks/use-kognitive-chat.js +35 -0
  60. package/dist/hooks/use-kognitive-event.d.ts +12 -0
  61. package/dist/hooks/use-kognitive-event.js +47 -0
  62. package/dist/hooks/use-streaming-status.d.ts +2 -0
  63. package/dist/hooks/use-streaming-status.js +8 -0
  64. package/dist/hooks/use-thread-manager.d.ts +20 -0
  65. package/dist/hooks/use-thread-manager.js +83 -0
  66. package/dist/index.d.ts +41 -0
  67. package/dist/index.js +92 -0
  68. package/dist/kognitive-ui.d.ts +48 -0
  69. package/dist/kognitive-ui.js +130 -0
  70. package/dist/primitives/action-bar/action-bar-copy.d.ts +6 -0
  71. package/dist/primitives/action-bar/action-bar-copy.js +16 -0
  72. package/dist/primitives/action-bar/action-bar-edit.d.ts +6 -0
  73. package/dist/primitives/action-bar/action-bar-edit.js +11 -0
  74. package/dist/primitives/action-bar/action-bar-feedback.d.ts +10 -0
  75. package/dist/primitives/action-bar/action-bar-feedback.js +30 -0
  76. package/dist/primitives/action-bar/action-bar-retry.d.ts +6 -0
  77. package/dist/primitives/action-bar/action-bar-retry.js +25 -0
  78. package/dist/primitives/action-bar/action-bar-root.d.ts +6 -0
  79. package/dist/primitives/action-bar/action-bar-root.js +7 -0
  80. package/dist/primitives/action-bar/action-bar-stop.d.ts +6 -0
  81. package/dist/primitives/action-bar/action-bar-stop.js +11 -0
  82. package/dist/primitives/action-bar/index.d.ts +14 -0
  83. package/dist/primitives/action-bar/index.js +17 -0
  84. package/dist/primitives/composer/composer-attachment-trigger.d.ts +7 -0
  85. package/dist/primitives/composer/composer-attachment-trigger.js +33 -0
  86. package/dist/primitives/composer/composer-attachments.d.ts +7 -0
  87. package/dist/primitives/composer/composer-attachments.js +16 -0
  88. package/dist/primitives/composer/composer-input.d.ts +6 -0
  89. package/dist/primitives/composer/composer-input.js +19 -0
  90. package/dist/primitives/composer/composer-root.d.ts +6 -0
  91. package/dist/primitives/composer/composer-root.js +91 -0
  92. package/dist/primitives/composer/composer-send.d.ts +6 -0
  93. package/dist/primitives/composer/composer-send.js +10 -0
  94. package/dist/primitives/composer/edit-composer-root.d.ts +11 -0
  95. package/dist/primitives/composer/edit-composer-root.js +24 -0
  96. package/dist/primitives/composer/index.d.ts +15 -0
  97. package/dist/primitives/composer/index.js +19 -0
  98. package/dist/primitives/composer/use-composer.d.ts +12 -0
  99. package/dist/primitives/composer/use-composer.js +6 -0
  100. package/dist/primitives/message/index.d.ts +9 -0
  101. package/dist/primitives/message/index.js +13 -0
  102. package/dist/primitives/message/message-content.d.ts +25 -0
  103. package/dist/primitives/message/message-content.js +70 -0
  104. package/dist/primitives/message/message-role.d.ts +6 -0
  105. package/dist/primitives/message/message-role.js +11 -0
  106. package/dist/primitives/message/message-root.d.ts +9 -0
  107. package/dist/primitives/message/message-root.js +38 -0
  108. package/dist/primitives/message/use-message.d.ts +10 -0
  109. package/dist/primitives/message/use-message.js +6 -0
  110. package/dist/primitives/thread/index.d.ts +17 -0
  111. package/dist/primitives/thread/index.js +21 -0
  112. package/dist/primitives/thread/thread-empty.d.ts +5 -0
  113. package/dist/primitives/thread/thread-empty.js +11 -0
  114. package/dist/primitives/thread/thread-error.d.ts +6 -0
  115. package/dist/primitives/thread/thread-error.js +11 -0
  116. package/dist/primitives/thread/thread-loading.d.ts +5 -0
  117. package/dist/primitives/thread/thread-loading.js +11 -0
  118. package/dist/primitives/thread/thread-messages.d.ts +6 -0
  119. package/dist/primitives/thread/thread-messages.js +9 -0
  120. package/dist/primitives/thread/thread-root.d.ts +6 -0
  121. package/dist/primitives/thread/thread-root.js +12 -0
  122. package/dist/primitives/thread/thread-scroll-to-bottom.d.ts +6 -0
  123. package/dist/primitives/thread/thread-scroll-to-bottom.js +14 -0
  124. package/dist/primitives/thread/thread-suggestions.d.ts +6 -0
  125. package/dist/primitives/thread/thread-suggestions.js +14 -0
  126. package/dist/primitives/thread/use-thread.d.ts +9 -0
  127. package/dist/primitives/thread/use-thread.js +6 -0
  128. package/dist/primitives/thread-list/index.d.ts +11 -0
  129. package/dist/primitives/thread-list/index.js +15 -0
  130. package/dist/primitives/thread-list/thread-list-item.d.ts +9 -0
  131. package/dist/primitives/thread-list/thread-list-item.js +13 -0
  132. package/dist/primitives/thread-list/thread-list-items.d.ts +6 -0
  133. package/dist/primitives/thread-list/thread-list-items.js +9 -0
  134. package/dist/primitives/thread-list/thread-list-new.d.ts +6 -0
  135. package/dist/primitives/thread-list/thread-list-new.js +13 -0
  136. package/dist/primitives/thread-list/thread-list-root.d.ts +6 -0
  137. package/dist/primitives/thread-list/thread-list-root.js +16 -0
  138. package/dist/primitives/thread-list/use-thread-list.d.ts +9 -0
  139. package/dist/primitives/thread-list/use-thread-list.js +6 -0
  140. package/dist/primitives/tool-ui/index.d.ts +7 -0
  141. package/dist/primitives/tool-ui/index.js +11 -0
  142. package/dist/primitives/tool-ui/tool-ui-fallback.d.ts +5 -0
  143. package/dist/primitives/tool-ui/tool-ui-fallback.js +20 -0
  144. package/dist/primitives/tool-ui/tool-ui-root.d.ts +5 -0
  145. package/dist/primitives/tool-ui/tool-ui-root.js +20 -0
  146. package/dist/primitives/tool-ui/use-tool-ui.d.ts +2 -0
  147. package/dist/primitives/tool-ui/use-tool-ui.js +8 -0
  148. package/dist/runtime/kognitive-runtime.d.ts +30 -0
  149. package/dist/runtime/kognitive-runtime.js +32 -0
  150. package/dist/runtime/kognitive-transport.d.ts +26 -0
  151. package/dist/runtime/kognitive-transport.js +42 -0
  152. package/dist/runtime/thread-manager.d.ts +17 -0
  153. package/dist/runtime/thread-manager.js +62 -0
  154. package/dist/runtime/types.d.ts +58 -0
  155. package/dist/runtime/types.js +2 -0
  156. package/dist/tool-ui/make-tool-ui.d.ts +59 -0
  157. package/dist/tool-ui/make-tool-ui.js +10 -0
  158. package/dist/tool-ui/registry.d.ts +9 -0
  159. package/dist/tool-ui/registry.js +26 -0
  160. package/dist/tool-ui/tool-ui-context.d.ts +2 -0
  161. package/dist/tool-ui/tool-ui-context.js +6 -0
  162. package/dist/tool-ui/toolkit.d.ts +31 -0
  163. package/dist/tool-ui/toolkit.js +33 -0
  164. package/dist/tool-ui/types.d.ts +19 -0
  165. package/dist/tool-ui/types.js +2 -0
  166. package/dist/utils/cn.d.ts +2 -0
  167. package/dist/utils/cn.js +8 -0
  168. package/dist/utils/create-context.d.ts +1 -0
  169. package/dist/utils/create-context.js +16 -0
  170. package/dist/utils/message-helpers.d.ts +6 -0
  171. package/dist/utils/message-helpers.js +46 -0
  172. package/package.json +56 -0
  173. package/src/__tests__/context-provider.test.ts +43 -0
  174. package/src/__tests__/event-emitter.test.ts +69 -0
  175. package/src/__tests__/keyboard-shortcuts.test.ts +55 -0
  176. package/src/__tests__/kognitive-runtime.test.ts +62 -0
  177. package/src/__tests__/kognitive-transport.test.ts +113 -0
  178. package/src/__tests__/make-tool-ui.test.ts +60 -0
  179. package/src/__tests__/message-helpers.test.ts +101 -0
  180. package/src/__tests__/thread-manager.test.ts +118 -0
  181. package/src/__tests__/tool-ui-registry.test.ts +80 -0
  182. package/src/__tests__/toolkit.test.ts +37 -0
  183. package/src/components/attachment-preview.tsx +46 -0
  184. package/src/components/composer.tsx +59 -0
  185. package/src/components/error-banner.tsx +33 -0
  186. package/src/components/markdown-content.tsx +64 -0
  187. package/src/components/message.tsx +145 -0
  188. package/src/components/status-indicator.tsx +26 -0
  189. package/src/components/suggestions.tsx +27 -0
  190. package/src/components/thread-list.tsx +69 -0
  191. package/src/components/thread.tsx +89 -0
  192. package/src/components/tool-approval.tsx +54 -0
  193. package/src/components/tool-invocation.tsx +94 -0
  194. package/src/context/kognitive-context.tsx +8 -0
  195. package/src/context/types.ts +43 -0
  196. package/src/context/use-kognitive.ts +5 -0
  197. package/src/hooks/use-auto-scroll.ts +19 -0
  198. package/src/hooks/use-copy-to-clipboard.ts +16 -0
  199. package/src/hooks/use-keyboard-shortcuts.ts +34 -0
  200. package/src/hooks/use-kognitive-chat.ts +73 -0
  201. package/src/hooks/use-kognitive-event.ts +56 -0
  202. package/src/hooks/use-streaming-status.ts +7 -0
  203. package/src/hooks/use-thread-manager.ts +114 -0
  204. package/src/index.ts +56 -0
  205. package/src/kognitive-ui.tsx +216 -0
  206. package/src/primitives/action-bar/action-bar-copy.tsx +30 -0
  207. package/src/primitives/action-bar/action-bar-edit.tsx +24 -0
  208. package/src/primitives/action-bar/action-bar-feedback.tsx +59 -0
  209. package/src/primitives/action-bar/action-bar-retry.tsx +38 -0
  210. package/src/primitives/action-bar/action-bar-root.tsx +14 -0
  211. package/src/primitives/action-bar/action-bar-stop.tsx +24 -0
  212. package/src/primitives/action-bar/index.ts +15 -0
  213. package/src/primitives/composer/composer-attachment-trigger.tsx +70 -0
  214. package/src/primitives/composer/composer-attachments.tsx +36 -0
  215. package/src/primitives/composer/composer-input.tsx +46 -0
  216. package/src/primitives/composer/composer-root.tsx +130 -0
  217. package/src/primitives/composer/composer-send.tsx +23 -0
  218. package/src/primitives/composer/edit-composer-root.tsx +52 -0
  219. package/src/primitives/composer/index.ts +17 -0
  220. package/src/primitives/composer/use-composer.ts +19 -0
  221. package/src/primitives/message/index.ts +11 -0
  222. package/src/primitives/message/message-content.tsx +117 -0
  223. package/src/primitives/message/message-role.tsx +13 -0
  224. package/src/primitives/message/message-root.tsx +64 -0
  225. package/src/primitives/message/use-message.ts +17 -0
  226. package/src/primitives/thread/index.ts +19 -0
  227. package/src/primitives/thread/thread-empty.tsx +12 -0
  228. package/src/primitives/thread/thread-error.tsx +18 -0
  229. package/src/primitives/thread/thread-loading.tsx +12 -0
  230. package/src/primitives/thread/thread-messages.tsx +12 -0
  231. package/src/primitives/thread/thread-root.tsx +28 -0
  232. package/src/primitives/thread/thread-scroll-to-bottom.tsx +26 -0
  233. package/src/primitives/thread/thread-suggestions.tsx +31 -0
  234. package/src/primitives/thread/use-thread.ts +16 -0
  235. package/src/primitives/thread-list/index.ts +13 -0
  236. package/src/primitives/thread-list/thread-list-item.tsx +37 -0
  237. package/src/primitives/thread-list/thread-list-items.tsx +19 -0
  238. package/src/primitives/thread-list/thread-list-new.tsx +26 -0
  239. package/src/primitives/thread-list/thread-list-root.tsx +29 -0
  240. package/src/primitives/thread-list/use-thread-list.ts +16 -0
  241. package/src/primitives/tool-ui/index.ts +9 -0
  242. package/src/primitives/tool-ui/tool-ui-fallback.tsx +63 -0
  243. package/src/primitives/tool-ui/tool-ui-root.tsx +26 -0
  244. package/src/primitives/tool-ui/use-tool-ui.ts +7 -0
  245. package/src/runtime/kognitive-runtime.ts +56 -0
  246. package/src/runtime/kognitive-transport.ts +56 -0
  247. package/src/runtime/thread-manager.ts +92 -0
  248. package/src/runtime/types.ts +63 -0
  249. package/src/tool-ui/make-tool-ui.ts +71 -0
  250. package/src/tool-ui/registry.ts +27 -0
  251. package/src/tool-ui/tool-ui-context.tsx +8 -0
  252. package/src/tool-ui/toolkit.ts +40 -0
  253. package/src/tool-ui/types.ts +29 -0
  254. package/src/utils/cn.ts +6 -0
  255. package/src/utils/create-context.ts +18 -0
  256. package/src/utils/message-helpers.ts +42 -0
  257. package/tsconfig.json +15 -0
  258. package/vitest.config.ts +8 -0
@@ -0,0 +1,130 @@
1
+ import React, { useState, useCallback, type ReactNode, type FormEvent, type DragEvent, type ClipboardEvent } from "react";
2
+ import type { FileUIPart } from "ai";
3
+ import { useKognitiveContext } from "../../context/kognitive-context";
4
+ import { ComposerContextProvider } from "./use-composer";
5
+
6
+ async function fileToFileUIPart(file: File): Promise<FileUIPart> {
7
+ const buffer = await file.arrayBuffer();
8
+ const base64 = btoa(
9
+ new Uint8Array(buffer).reduce(
10
+ (data, byte) => data + String.fromCharCode(byte),
11
+ "",
12
+ ),
13
+ );
14
+ return {
15
+ type: "file",
16
+ mediaType: file.type || "application/octet-stream",
17
+ url: `data:${file.type || "application/octet-stream"};base64,${base64}`,
18
+ filename: file.name,
19
+ };
20
+ }
21
+
22
+ export interface ComposerRootProps {
23
+ className?: string;
24
+ children: ReactNode;
25
+ }
26
+
27
+ export function ComposerRoot({ className, children }: ComposerRootProps) {
28
+ const { send, isStreaming } = useKognitiveContext();
29
+ const [input, setInput] = useState("");
30
+ const [files, setFiles] = useState<FileUIPart[]>([]);
31
+ const [isDragOver, setIsDragOver] = useState(false);
32
+
33
+ const addFile = useCallback((file: FileUIPart) => {
34
+ setFiles((prev) => [...prev, file]);
35
+ }, []);
36
+
37
+ const removeFile = useCallback((index: number) => {
38
+ setFiles((prev) => prev.filter((_, i) => i !== index));
39
+ }, []);
40
+
41
+ const clearFiles = useCallback(() => {
42
+ setFiles([]);
43
+ }, []);
44
+
45
+ const submit = useCallback(() => {
46
+ const text = input.trim();
47
+ if (!text && files.length === 0) return;
48
+ send(text, files.length > 0 ? { files } : undefined);
49
+ setInput("");
50
+ setFiles([]);
51
+ }, [input, files, send]);
52
+
53
+ const handleSubmit = useCallback(
54
+ (e: FormEvent) => {
55
+ e.preventDefault();
56
+ submit();
57
+ },
58
+ [submit],
59
+ );
60
+
61
+ // ── Drag-and-drop ──
62
+ const handleDragOver = useCallback((e: DragEvent) => {
63
+ e.preventDefault();
64
+ setIsDragOver(true);
65
+ }, []);
66
+
67
+ const handleDragLeave = useCallback((e: DragEvent) => {
68
+ e.preventDefault();
69
+ setIsDragOver(false);
70
+ }, []);
71
+
72
+ const handleDrop = useCallback(
73
+ async (e: DragEvent) => {
74
+ e.preventDefault();
75
+ setIsDragOver(false);
76
+ const droppedFiles = e.dataTransfer?.files;
77
+ if (!droppedFiles) return;
78
+ for (const file of Array.from(droppedFiles)) {
79
+ const part = await fileToFileUIPart(file);
80
+ addFile(part);
81
+ }
82
+ },
83
+ [addFile],
84
+ );
85
+
86
+ // ── Paste upload ──
87
+ const handlePaste = useCallback(
88
+ async (e: ClipboardEvent) => {
89
+ const items = e.clipboardData?.items;
90
+ if (!items) return;
91
+ for (const item of Array.from(items)) {
92
+ if (item.kind === "file") {
93
+ const file = item.getAsFile();
94
+ if (file) {
95
+ const part = await fileToFileUIPart(file);
96
+ addFile(part);
97
+ }
98
+ }
99
+ }
100
+ },
101
+ [addFile],
102
+ );
103
+
104
+ return (
105
+ <ComposerContextProvider
106
+ value={{
107
+ input,
108
+ setInput,
109
+ files,
110
+ addFile,
111
+ removeFile,
112
+ clearFiles,
113
+ submit,
114
+ isDisabled: isStreaming,
115
+ }}
116
+ >
117
+ <form
118
+ className={className}
119
+ onSubmit={handleSubmit}
120
+ onDragOver={handleDragOver}
121
+ onDragLeave={handleDragLeave}
122
+ onDrop={handleDrop}
123
+ onPaste={handlePaste}
124
+ data-drag-over={isDragOver || undefined}
125
+ >
126
+ {children}
127
+ </form>
128
+ </ComposerContextProvider>
129
+ );
130
+ }
@@ -0,0 +1,23 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { useComposer } from "./use-composer";
3
+
4
+ export interface ComposerSendProps {
5
+ className?: string;
6
+ children?: ReactNode;
7
+ }
8
+
9
+ export function ComposerSend({ className, children }: ComposerSendProps) {
10
+ const { input, files, isDisabled } = useComposer();
11
+ const isEmpty = !input.trim() && files.length === 0;
12
+
13
+ return (
14
+ <button
15
+ type="submit"
16
+ className={className}
17
+ disabled={isEmpty || isDisabled}
18
+ aria-label="Send message"
19
+ >
20
+ {children ?? "Send"}
21
+ </button>
22
+ );
23
+ }
@@ -0,0 +1,52 @@
1
+ import React, { useState, useCallback, type ReactNode, type FormEvent } from "react";
2
+ import { useMessage } from "../message/use-message";
3
+ import { getMessageText } from "../../utils/message-helpers";
4
+
5
+ export interface EditComposerRootProps {
6
+ className?: string;
7
+ children?: ReactNode | ((props: {
8
+ value: string;
9
+ setValue: (v: string) => void;
10
+ submit: () => void;
11
+ cancel: () => void;
12
+ }) => ReactNode);
13
+ }
14
+
15
+ export function EditComposerRoot({ className, children }: EditComposerRootProps) {
16
+ const { message, isEditing, submitEdit, cancelEdit } = useMessage();
17
+ const [value, setValue] = useState(() => getMessageText(message));
18
+
19
+ const handleSubmit = useCallback(
20
+ (e: FormEvent) => {
21
+ e.preventDefault();
22
+ const text = value.trim();
23
+ if (text) submitEdit(text);
24
+ },
25
+ [value, submitEdit],
26
+ );
27
+
28
+ if (!isEditing) return null;
29
+
30
+ if (typeof children === "function") {
31
+ return (
32
+ <form className={className} onSubmit={handleSubmit}>
33
+ {children({ value, setValue, submit: () => { const t = value.trim(); if (t) submitEdit(t); }, cancel: cancelEdit })}
34
+ </form>
35
+ );
36
+ }
37
+
38
+ return (
39
+ <form className={className} onSubmit={handleSubmit}>
40
+ <textarea
41
+ value={value}
42
+ onChange={(e) => setValue(e.target.value)}
43
+ rows={3}
44
+ aria-label="Edit message"
45
+ />
46
+ <div>
47
+ <button type="submit">Save & Send</button>
48
+ <button type="button" onClick={cancelEdit}>Cancel</button>
49
+ </div>
50
+ </form>
51
+ );
52
+ }
@@ -0,0 +1,17 @@
1
+ import { ComposerRoot } from "./composer-root";
2
+ import { ComposerInput } from "./composer-input";
3
+ import { ComposerSend } from "./composer-send";
4
+ import { ComposerAttachments } from "./composer-attachments";
5
+ import { ComposerAttachmentTrigger } from "./composer-attachment-trigger";
6
+ import { EditComposerRoot } from "./edit-composer-root";
7
+
8
+ export const ComposerPrimitive = {
9
+ Root: ComposerRoot,
10
+ Input: ComposerInput,
11
+ Send: ComposerSend,
12
+ Attachments: ComposerAttachments,
13
+ AttachmentTrigger: ComposerAttachmentTrigger,
14
+ EditRoot: EditComposerRoot,
15
+ };
16
+
17
+ export { useComposer } from "./use-composer";
@@ -0,0 +1,19 @@
1
+ import { createContext } from "../../utils/create-context";
2
+ import type { FileUIPart } from "ai";
3
+
4
+ export interface ComposerContextValue {
5
+ input: string;
6
+ setInput: (value: string) => void;
7
+ files: FileUIPart[];
8
+ addFile: (file: FileUIPart) => void;
9
+ removeFile: (index: number) => void;
10
+ clearFiles: () => void;
11
+ submit: () => void;
12
+ isDisabled: boolean;
13
+ }
14
+
15
+ export const [
16
+ ComposerContextProvider,
17
+ useComposer,
18
+ ComposerContext,
19
+ ] = createContext<ComposerContextValue>("Composer");
@@ -0,0 +1,11 @@
1
+ import { MessageRoot } from "./message-root";
2
+ import { MessageContent } from "./message-content";
3
+ import { MessageRole } from "./message-role";
4
+
5
+ export const MessagePrimitive = {
6
+ Root: MessageRoot,
7
+ Content: MessageContent,
8
+ Role: MessageRole,
9
+ };
10
+
11
+ export { useMessage } from "./use-message";
@@ -0,0 +1,117 @@
1
+ import React, { type ComponentType } from "react";
2
+ import type { FileUIPart } from "ai";
3
+ import { useMessage } from "./use-message";
4
+ import { useToolUIRegistry } from "../../tool-ui/tool-ui-context";
5
+ import type { ToolUIRenderProps, ToolInvocationState } from "../../tool-ui/types";
6
+
7
+ export interface TextComponentProps {
8
+ text: string;
9
+ }
10
+
11
+ export interface ToolInvocationComponentProps {
12
+ toolCallId: string;
13
+ toolName: string;
14
+ input: any;
15
+ output: any;
16
+ state: ToolInvocationState;
17
+ }
18
+
19
+ export interface FileComponentProps {
20
+ file: FileUIPart;
21
+ }
22
+
23
+ export interface MessageContentComponents {
24
+ Text?: ComponentType<TextComponentProps>;
25
+ ToolInvocation?: ComponentType<ToolInvocationComponentProps>;
26
+ File?: ComponentType<FileComponentProps>;
27
+ }
28
+
29
+ export interface MessageContentProps {
30
+ components?: MessageContentComponents;
31
+ }
32
+
33
+ function DefaultText({ text }: TextComponentProps) {
34
+ return <p>{text}</p>;
35
+ }
36
+
37
+ function DefaultFile({ file }: FileComponentProps) {
38
+ if (file.mediaType.startsWith("image/")) {
39
+ return <img src={file.url} alt={file.filename ?? "attachment"} />;
40
+ }
41
+ return <div data-file-type={file.mediaType}>File: {file.filename ?? file.mediaType}</div>;
42
+ }
43
+
44
+ function isToolPart(part: { type: string }): boolean {
45
+ return part.type.startsWith("tool-") || part.type === "dynamic-tool";
46
+ }
47
+
48
+ function getToolName(part: any): string {
49
+ if (part.type === "dynamic-tool") return part.toolName;
50
+ // For typed tool parts, type is "tool-{name}"
51
+ return part.type.replace(/^tool-/, "");
52
+ }
53
+
54
+ export function MessageContent({ components }: MessageContentProps) {
55
+ const { message } = useMessage();
56
+ let registry: ReturnType<typeof useToolUIRegistry> | undefined;
57
+
58
+ try {
59
+ registry = useToolUIRegistry();
60
+ } catch {
61
+ // ToolUIRegistry is optional
62
+ }
63
+
64
+ const TextComponent = components?.Text ?? DefaultText;
65
+ const ToolInvocationComponent = components?.ToolInvocation;
66
+ const FileComponent = components?.File ?? DefaultFile;
67
+
68
+ return (
69
+ <>
70
+ {message.parts.map((part, i) => {
71
+ if (part.type === "text") {
72
+ return <TextComponent key={i} text={part.text} />;
73
+ }
74
+
75
+ if (isToolPart(part)) {
76
+ const toolPart = part as any;
77
+ const toolName = getToolName(toolPart);
78
+ const renderProps: ToolUIRenderProps = {
79
+ toolCallId: toolPart.toolCallId,
80
+ toolName,
81
+ input: toolPart.input,
82
+ output: toolPart.output,
83
+ state: toolPart.state,
84
+ };
85
+
86
+ // Check registry first
87
+ const RegisteredUI = registry?.get(toolName);
88
+ if (RegisteredUI) {
89
+ return <RegisteredUI key={i} {...renderProps} />;
90
+ }
91
+
92
+ // Then check component override
93
+ if (ToolInvocationComponent) {
94
+ return <ToolInvocationComponent key={i} {...renderProps} />;
95
+ }
96
+
97
+ // Default: simple JSON display
98
+ return (
99
+ <div key={i} data-tool={toolName} data-tool-state={toolPart.state}>
100
+ <strong>{toolName}</strong>
101
+ <pre>{JSON.stringify(toolPart.input, null, 2)}</pre>
102
+ {toolPart.output !== undefined && (
103
+ <pre>{JSON.stringify(toolPart.output, null, 2)}</pre>
104
+ )}
105
+ </div>
106
+ );
107
+ }
108
+
109
+ if (part.type === "file") {
110
+ return <FileComponent key={i} file={part} />;
111
+ }
112
+
113
+ return null;
114
+ })}
115
+ </>
116
+ );
117
+ }
@@ -0,0 +1,13 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { useMessage } from "./use-message";
3
+
4
+ export interface MessageRoleProps {
5
+ match: "user" | "assistant" | "system";
6
+ children: ReactNode;
7
+ }
8
+
9
+ export function MessageRole({ match, children }: MessageRoleProps) {
10
+ const { message } = useMessage();
11
+ if (message.role !== match) return null;
12
+ return <>{children}</>;
13
+ }
@@ -0,0 +1,64 @@
1
+ import React, { useState, useCallback, type ReactNode } from "react";
2
+ import type { UIMessage } from "ai";
3
+ import { MessageContextProvider } from "./use-message";
4
+ import { useKognitiveContext } from "../../context/kognitive-context";
5
+ import { getMessageText } from "../../utils/message-helpers";
6
+
7
+ export interface MessageRootProps {
8
+ message?: UIMessage;
9
+ index?: number;
10
+ className?: string;
11
+ children: ReactNode;
12
+ }
13
+
14
+ export function MessageRoot({ message, index, className, children }: MessageRootProps) {
15
+ if (!message) {
16
+ throw new Error("MessagePrimitive.Root requires a `message` prop or must be used inside ThreadPrimitive.Messages");
17
+ }
18
+
19
+ const { messages, setMessages, send } = useKognitiveContext();
20
+ const [isEditing, setIsEditing] = useState(false);
21
+
22
+ const startEdit = useCallback(() => {
23
+ if (message.role === "user") setIsEditing(true);
24
+ }, [message.role]);
25
+
26
+ const cancelEdit = useCallback(() => {
27
+ setIsEditing(false);
28
+ }, []);
29
+
30
+ const submitEdit = useCallback(
31
+ (newText: string) => {
32
+ setIsEditing(false);
33
+ // Trim conversation to before this message and re-send
34
+ const msgIndex = messages.findIndex((m) => m.id === message.id);
35
+ if (msgIndex >= 0) {
36
+ setMessages(messages.slice(0, msgIndex));
37
+ send(newText);
38
+ }
39
+ },
40
+ [messages, message.id, setMessages, send],
41
+ );
42
+
43
+ return (
44
+ <MessageContextProvider
45
+ value={{
46
+ message,
47
+ index: index ?? 0,
48
+ isEditing,
49
+ startEdit,
50
+ cancelEdit,
51
+ submitEdit,
52
+ }}
53
+ >
54
+ <div
55
+ className={className}
56
+ data-role={message.role}
57
+ data-message-id={message.id}
58
+ data-editing={isEditing || undefined}
59
+ >
60
+ {children}
61
+ </div>
62
+ </MessageContextProvider>
63
+ );
64
+ }
@@ -0,0 +1,17 @@
1
+ import { createContext } from "../../utils/create-context";
2
+ import type { UIMessage } from "ai";
3
+
4
+ export interface MessageContextValue {
5
+ message: UIMessage;
6
+ index: number;
7
+ isEditing: boolean;
8
+ startEdit: () => void;
9
+ cancelEdit: () => void;
10
+ submitEdit: (newText: string) => void;
11
+ }
12
+
13
+ export const [
14
+ MessageContextProvider,
15
+ useMessage,
16
+ MessageContext,
17
+ ] = createContext<MessageContextValue>("Message");
@@ -0,0 +1,19 @@
1
+ import { ThreadRoot } from "./thread-root";
2
+ import { ThreadMessages } from "./thread-messages";
3
+ import { ThreadEmpty } from "./thread-empty";
4
+ import { ThreadLoading } from "./thread-loading";
5
+ import { ThreadScrollToBottom } from "./thread-scroll-to-bottom";
6
+ import { ThreadError } from "./thread-error";
7
+ import { ThreadSuggestions } from "./thread-suggestions";
8
+
9
+ export const ThreadPrimitive = {
10
+ Root: ThreadRoot,
11
+ Messages: ThreadMessages,
12
+ Empty: ThreadEmpty,
13
+ Loading: ThreadLoading,
14
+ ScrollToBottom: ThreadScrollToBottom,
15
+ Error: ThreadError,
16
+ Suggestions: ThreadSuggestions,
17
+ };
18
+
19
+ export { useThread } from "./use-thread";
@@ -0,0 +1,12 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { useThread } from "./use-thread";
3
+
4
+ export interface ThreadEmptyProps {
5
+ children: ReactNode;
6
+ }
7
+
8
+ export function ThreadEmpty({ children }: ThreadEmptyProps) {
9
+ const { messages } = useThread();
10
+ if (messages.length > 0) return null;
11
+ return <>{children}</>;
12
+ }
@@ -0,0 +1,18 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { useKognitiveContext } from "../../context/kognitive-context";
3
+
4
+ export interface ThreadErrorProps {
5
+ children?: ReactNode | ((error: Error) => ReactNode);
6
+ className?: string;
7
+ }
8
+
9
+ export function ThreadError({ children, className }: ThreadErrorProps) {
10
+ const { error } = useKognitiveContext();
11
+ if (!error) return null;
12
+
13
+ return (
14
+ <div className={className} role="alert" data-error>
15
+ {typeof children === "function" ? children(error) : children ?? error.message}
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,12 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { useThread } from "./use-thread";
3
+
4
+ export interface ThreadLoadingProps {
5
+ children: ReactNode;
6
+ }
7
+
8
+ export function ThreadLoading({ children }: ThreadLoadingProps) {
9
+ const { isStreaming } = useThread();
10
+ if (!isStreaming) return null;
11
+ return <>{children}</>;
12
+ }
@@ -0,0 +1,12 @@
1
+ import React, { type ReactNode } from "react";
2
+ import type { UIMessage } from "ai";
3
+ import { useThread } from "./use-thread";
4
+
5
+ export interface ThreadMessagesProps {
6
+ children: (message: UIMessage, index: number) => ReactNode;
7
+ }
8
+
9
+ export function ThreadMessages({ children }: ThreadMessagesProps) {
10
+ const { messages } = useThread();
11
+ return <>{messages.map((message, index) => children(message, index))}</>;
12
+ }
@@ -0,0 +1,28 @@
1
+ import React, { useRef, type ReactNode } from "react";
2
+ import { useKognitiveContext } from "../../context/kognitive-context";
3
+ import { ThreadContextProvider } from "./use-thread";
4
+
5
+ export interface ThreadRootProps {
6
+ className?: string;
7
+ children: ReactNode;
8
+ }
9
+
10
+ export function ThreadRoot({ className, children }: ThreadRootProps) {
11
+ const { messages, status, isStreaming, send, stop } = useKognitiveContext();
12
+ const containerRef = useRef<HTMLDivElement>(null);
13
+
14
+ return (
15
+ <ThreadContextProvider
16
+ value={{ messages, status, isStreaming, send, stop }}
17
+ >
18
+ <div
19
+ ref={containerRef}
20
+ className={className}
21
+ data-status={status}
22
+ data-streaming={isStreaming || undefined}
23
+ >
24
+ {children}
25
+ </div>
26
+ </ThreadContextProvider>
27
+ );
28
+ }
@@ -0,0 +1,26 @@
1
+ import React, { useCallback, type ReactNode } from "react";
2
+
3
+ export interface ThreadScrollToBottomProps {
4
+ className?: string;
5
+ children?: ReactNode;
6
+ }
7
+
8
+ export function ThreadScrollToBottom({ className, children }: ThreadScrollToBottomProps) {
9
+ const scrollToBottom = useCallback(() => {
10
+ const container = document.querySelector("[data-kognitive-thread-root]");
11
+ if (container) {
12
+ container.scrollTop = container.scrollHeight;
13
+ }
14
+ }, []);
15
+
16
+ return (
17
+ <button
18
+ type="button"
19
+ className={className}
20
+ onClick={scrollToBottom}
21
+ aria-label="Scroll to bottom"
22
+ >
23
+ {children ?? "↓"}
24
+ </button>
25
+ );
26
+ }
@@ -0,0 +1,31 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { useKognitiveContext } from "../../context/kognitive-context";
3
+
4
+ export interface ThreadSuggestionsProps {
5
+ className?: string;
6
+ children?: (suggestions: string[], send: (text: string) => void) => ReactNode;
7
+ }
8
+
9
+ export function ThreadSuggestions({ className, children }: ThreadSuggestionsProps) {
10
+ const { suggestions, send, isStreaming } = useKognitiveContext();
11
+ if (suggestions.length === 0 || isStreaming) return null;
12
+
13
+ if (children) {
14
+ return <>{children(suggestions, send)}</>;
15
+ }
16
+
17
+ return (
18
+ <div className={className} data-suggestions>
19
+ {suggestions.map((suggestion, i) => (
20
+ <button
21
+ key={i}
22
+ type="button"
23
+ onClick={() => send(suggestion)}
24
+ data-suggestion
25
+ >
26
+ {suggestion}
27
+ </button>
28
+ ))}
29
+ </div>
30
+ );
31
+ }
@@ -0,0 +1,16 @@
1
+ import { createContext } from "../../utils/create-context";
2
+ import type { UIMessage, ChatStatus } from "ai";
3
+
4
+ export interface ThreadContextValue {
5
+ messages: UIMessage[];
6
+ status: ChatStatus;
7
+ isStreaming: boolean;
8
+ send: (text: string) => void;
9
+ stop: () => void;
10
+ }
11
+
12
+ export const [
13
+ ThreadContextProvider,
14
+ useThread,
15
+ ThreadContext,
16
+ ] = createContext<ThreadContextValue>("Thread");
@@ -0,0 +1,13 @@
1
+ import { ThreadListRoot } from "./thread-list-root";
2
+ import { ThreadListItems } from "./thread-list-items";
3
+ import { ThreadListItem } from "./thread-list-item";
4
+ import { ThreadListNew } from "./thread-list-new";
5
+
6
+ export const ThreadListPrimitive = {
7
+ Root: ThreadListRoot,
8
+ Items: ThreadListItems,
9
+ Item: ThreadListItem,
10
+ New: ThreadListNew,
11
+ };
12
+
13
+ export { useThreadList } from "./use-thread-list";