@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,80 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { ToolUIRegistry } from "../tool-ui/registry";
3
+ import { makeToolUI } from "../tool-ui/make-tool-ui";
4
+
5
+ describe("ToolUIRegistry", () => {
6
+ it("registers and retrieves a tool UI component", () => {
7
+ const registry = new ToolUIRegistry();
8
+ const render = () => null;
9
+ registry.register({ toolName: "test_tool", component: render });
10
+
11
+ expect(registry.has("test_tool")).toBe(true);
12
+ expect(registry.get("test_tool")).toBe(render);
13
+ });
14
+
15
+ it("returns undefined for unregistered tools", () => {
16
+ const registry = new ToolUIRegistry();
17
+ expect(registry.has("nonexistent")).toBe(false);
18
+ expect(registry.get("nonexistent")).toBeUndefined();
19
+ });
20
+
21
+ it("registers multiple tools via registerAll", () => {
22
+ const registry = new ToolUIRegistry();
23
+ const renderA = () => null;
24
+ const renderB = () => null;
25
+ registry.registerAll([
26
+ { toolName: "tool_a", component: renderA },
27
+ { toolName: "tool_b", component: renderB },
28
+ ]);
29
+
30
+ expect(registry.has("tool_a")).toBe(true);
31
+ expect(registry.has("tool_b")).toBe(true);
32
+ expect(registry.get("tool_a")).toBe(renderA);
33
+ expect(registry.get("tool_b")).toBe(renderB);
34
+ });
35
+
36
+ it("lists registered tool names", () => {
37
+ const registry = new ToolUIRegistry();
38
+ registry.register({ toolName: "alpha", component: () => null });
39
+ registry.register({ toolName: "beta", component: () => null });
40
+
41
+ const names = registry.names();
42
+ expect(names).toContain("alpha");
43
+ expect(names).toContain("beta");
44
+ expect(names).toHaveLength(2);
45
+ });
46
+
47
+ it("overwrites a registration for the same tool name", () => {
48
+ const registry = new ToolUIRegistry();
49
+ const first = () => null;
50
+ const second = () => null;
51
+
52
+ registry.register({ toolName: "tool", component: first });
53
+ expect(registry.get("tool")).toBe(first);
54
+
55
+ registry.register({ toolName: "tool", component: second });
56
+ expect(registry.get("tool")).toBe(second);
57
+ });
58
+ });
59
+
60
+ describe("makeToolUI", () => {
61
+ it("creates a registration from a tool object", () => {
62
+ const registration = makeToolUI({
63
+ tool: { id: "get_weather" },
64
+ render: () => null,
65
+ });
66
+
67
+ expect(registration.toolName).toBe("get_weather");
68
+ expect(typeof registration.component).toBe("function");
69
+ });
70
+
71
+ it("creates a registration from a tool name string", () => {
72
+ const registration = makeToolUI({
73
+ toolName: "search_db",
74
+ render: () => null,
75
+ });
76
+
77
+ expect(registration.toolName).toBe("search_db");
78
+ expect(typeof registration.component).toBe("function");
79
+ });
80
+ });
@@ -0,0 +1,37 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { toolkit } from "../tool-ui/toolkit";
3
+ import { ToolUIRegistry } from "../tool-ui/registry";
4
+
5
+ describe("toolkit", () => {
6
+ it("creates registrations from a map of tool names to render functions", () => {
7
+ const registrations = toolkit({
8
+ weather: { render: () => null },
9
+ search: { render: () => null },
10
+ });
11
+
12
+ expect(registrations).toHaveLength(2);
13
+ expect(registrations[0].toolName).toBe("weather");
14
+ expect(registrations[1].toolName).toBe("search");
15
+ });
16
+
17
+ it("creates an empty array from an empty map", () => {
18
+ const registrations = toolkit({});
19
+ expect(registrations).toHaveLength(0);
20
+ });
21
+
22
+ it("integrates with ToolUIRegistry.registerAll", () => {
23
+ const registrations = toolkit({
24
+ tool_a: { render: () => null },
25
+ tool_b: { render: () => null },
26
+ tool_c: { render: () => null },
27
+ });
28
+
29
+ const registry = new ToolUIRegistry();
30
+ registry.registerAll(registrations);
31
+
32
+ expect(registry.has("tool_a")).toBe(true);
33
+ expect(registry.has("tool_b")).toBe(true);
34
+ expect(registry.has("tool_c")).toBe(true);
35
+ expect(registry.names()).toHaveLength(3);
36
+ });
37
+ });
@@ -0,0 +1,46 @@
1
+ import React from "react";
2
+ import { cn } from "../utils/cn";
3
+
4
+ export interface AttachmentPreviewProps {
5
+ url: string;
6
+ mediaType: string;
7
+ filename?: string;
8
+ className?: string;
9
+ onRemove?: () => void;
10
+ }
11
+
12
+ export function AttachmentPreview({ url, mediaType, filename, className, onRemove }: AttachmentPreviewProps) {
13
+ const isImage = mediaType.startsWith("image/");
14
+
15
+ return (
16
+ <div
17
+ className={cn(
18
+ "relative inline-flex items-center gap-2 rounded-lg border border-zinc-200 p-2 dark:border-zinc-700",
19
+ className,
20
+ )}
21
+ data-mime-type={mediaType}
22
+ >
23
+ {isImage ? (
24
+ <img
25
+ src={url}
26
+ alt={filename ?? "Attachment"}
27
+ className="h-16 w-16 rounded object-cover"
28
+ />
29
+ ) : (
30
+ <div className="flex h-16 w-16 items-center justify-center rounded bg-zinc-100 text-xs dark:bg-zinc-800">
31
+ {filename ?? mediaType.split("/")[1]?.toUpperCase() ?? "FILE"}
32
+ </div>
33
+ )}
34
+ {onRemove && (
35
+ <button
36
+ type="button"
37
+ onClick={onRemove}
38
+ className="absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-zinc-500 text-xs text-white hover:bg-zinc-600"
39
+ aria-label="Remove attachment"
40
+ >
41
+ x
42
+ </button>
43
+ )}
44
+ </div>
45
+ );
46
+ }
@@ -0,0 +1,59 @@
1
+ import React from "react";
2
+ import { cn } from "../utils/cn";
3
+ import { ComposerPrimitive } from "../primitives/composer";
4
+ import { AttachmentPreview } from "./attachment-preview";
5
+
6
+ export interface ComposerProps {
7
+ className?: string;
8
+ placeholder?: string;
9
+ allowAttachments?: boolean;
10
+ }
11
+
12
+ export function Composer({
13
+ className,
14
+ placeholder = "Send a message...",
15
+ allowAttachments = false,
16
+ }: ComposerProps) {
17
+ return (
18
+ <ComposerPrimitive.Root
19
+ className={cn(
20
+ "border-t border-zinc-200 p-4 dark:border-zinc-700",
21
+ className,
22
+ )}
23
+ >
24
+ {/* Pending attachments */}
25
+ <ComposerPrimitive.Attachments className="mb-2 flex flex-wrap gap-2">
26
+ {(file, index, remove) => (
27
+ <AttachmentPreview
28
+ key={index}
29
+ url={file.url}
30
+ mediaType={file.mediaType}
31
+ filename={file.filename}
32
+ onRemove={remove}
33
+ />
34
+ )}
35
+ </ComposerPrimitive.Attachments>
36
+
37
+ <div className="flex items-end gap-2">
38
+ {allowAttachments && (
39
+ <ComposerPrimitive.AttachmentTrigger
40
+ className="flex h-10 w-10 items-center justify-center rounded-lg text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-zinc-300"
41
+ accept="image/*,.pdf,.txt,.md,.json,.csv"
42
+ >
43
+ +
44
+ </ComposerPrimitive.AttachmentTrigger>
45
+ )}
46
+
47
+ <ComposerPrimitive.Input
48
+ placeholder={placeholder}
49
+ className="flex-1 resize-none rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm outline-none placeholder:text-zinc-400 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 dark:border-zinc-700 dark:bg-zinc-900 dark:placeholder:text-zinc-500 dark:focus:border-blue-400"
50
+ rows={1}
51
+ />
52
+
53
+ <ComposerPrimitive.Send className="flex h-10 items-center rounded-lg bg-blue-600 px-4 text-sm font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-600">
54
+ Send
55
+ </ComposerPrimitive.Send>
56
+ </div>
57
+ </ComposerPrimitive.Root>
58
+ );
59
+ }
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import { cn } from "../utils/cn";
3
+
4
+ export interface ErrorBannerProps {
5
+ message: string;
6
+ className?: string;
7
+ onDismiss?: () => void;
8
+ }
9
+
10
+ export function ErrorBanner({ message, className, onDismiss }: ErrorBannerProps) {
11
+ return (
12
+ <div
13
+ className={cn(
14
+ "flex items-center gap-2 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-800 dark:bg-red-950/30 dark:text-red-400",
15
+ className,
16
+ )}
17
+ role="alert"
18
+ >
19
+ <span className="shrink-0">&#x26A0;</span>
20
+ <span className="flex-1">{message}</span>
21
+ {onDismiss && (
22
+ <button
23
+ type="button"
24
+ onClick={onDismiss}
25
+ className="shrink-0 text-red-500 hover:text-red-700 dark:hover:text-red-300"
26
+ aria-label="Dismiss error"
27
+ >
28
+ x
29
+ </button>
30
+ )}
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,64 @@
1
+ import React from "react";
2
+ import { cn } from "../utils/cn";
3
+
4
+ export interface MarkdownContentProps {
5
+ text: string;
6
+ className?: string;
7
+ }
8
+
9
+ /**
10
+ * Simple markdown-like text renderer.
11
+ *
12
+ * For full markdown support, override the Text component in MessagePrimitive.Content
13
+ * with your preferred markdown library (e.g., react-markdown).
14
+ *
15
+ * This component provides basic paragraph splitting and code block detection.
16
+ */
17
+ export function MarkdownContent({ text, className }: MarkdownContentProps) {
18
+ const paragraphs = text.split("\n\n").filter(Boolean);
19
+
20
+ return (
21
+ <div className={cn("space-y-2", className)}>
22
+ {paragraphs.map((paragraph, i) => {
23
+ // Code block
24
+ if (paragraph.startsWith("```")) {
25
+ const lines = paragraph.split("\n");
26
+ const lang = lines[0].replace("```", "").trim();
27
+ const code = lines.slice(1, lines[lines.length - 1] === "```" ? -1 : undefined).join("\n");
28
+ return (
29
+ <pre
30
+ key={i}
31
+ className="overflow-x-auto rounded-lg bg-zinc-900 p-3 text-sm text-zinc-100"
32
+ data-language={lang || undefined}
33
+ >
34
+ <code>{code}</code>
35
+ </pre>
36
+ );
37
+ }
38
+
39
+ // Inline code
40
+ const parts = paragraph.split(/(`[^`]+`)/g);
41
+ if (parts.length > 1) {
42
+ return (
43
+ <p key={i}>
44
+ {parts.map((part, j) =>
45
+ part.startsWith("`") && part.endsWith("`") ? (
46
+ <code
47
+ key={j}
48
+ className="rounded bg-zinc-100 px-1 py-0.5 text-sm dark:bg-zinc-800"
49
+ >
50
+ {part.slice(1, -1)}
51
+ </code>
52
+ ) : (
53
+ <span key={j}>{part}</span>
54
+ ),
55
+ )}
56
+ </p>
57
+ );
58
+ }
59
+
60
+ return <p key={i}>{paragraph}</p>;
61
+ })}
62
+ </div>
63
+ );
64
+ }
@@ -0,0 +1,145 @@
1
+ import React from "react";
2
+ import type { UIMessage } from "ai";
3
+ import { cn } from "../utils/cn";
4
+ import { MessagePrimitive } from "../primitives/message";
5
+ import { ActionBarPrimitive } from "../primitives/action-bar";
6
+ import { ComposerPrimitive } from "../primitives/composer";
7
+ import { MarkdownContent } from "./markdown-content";
8
+ import { ToolInvocation } from "./tool-invocation";
9
+ import { useMessage } from "../primitives/message/use-message";
10
+
11
+ export interface MessageProps {
12
+ message: UIMessage;
13
+ index?: number;
14
+ className?: string;
15
+ }
16
+
17
+ function MessageInner({ className }: { className?: string }) {
18
+ const { message, isEditing } = useMessage();
19
+ const isUser = message.role === "user";
20
+
21
+ return (
22
+ <div
23
+ className={cn(
24
+ "flex gap-3 py-4",
25
+ isUser ? "flex-row-reverse" : "flex-row",
26
+ className,
27
+ )}
28
+ >
29
+ {/* Avatar */}
30
+ <div
31
+ className={cn(
32
+ "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-sm",
33
+ isUser
34
+ ? "bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400"
35
+ : "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400",
36
+ )}
37
+ >
38
+ {isUser ? "U" : "A"}
39
+ </div>
40
+
41
+ {/* Content */}
42
+ <div className={cn("flex-1 space-y-1", isUser ? "text-right" : "text-left")}>
43
+ {/* Edit composer (replaces content when editing) */}
44
+ {isEditing ? (
45
+ <ComposerPrimitive.EditRoot className="space-y-2">
46
+ {({ value, setValue, submit, cancel }) => (
47
+ <>
48
+ <textarea
49
+ value={value}
50
+ onChange={(e) => setValue(e.target.value)}
51
+ className="w-full resize-none rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-900"
52
+ rows={3}
53
+ />
54
+ <div className="flex gap-2">
55
+ <button
56
+ type="button"
57
+ onClick={submit}
58
+ className="rounded-lg bg-blue-600 px-3 py-1 text-xs font-medium text-white hover:bg-blue-700"
59
+ >
60
+ Save & Send
61
+ </button>
62
+ <button
63
+ type="button"
64
+ onClick={cancel}
65
+ className="rounded-lg border border-zinc-300 px-3 py-1 text-xs text-zinc-600 hover:bg-zinc-100 dark:border-zinc-600 dark:text-zinc-400 dark:hover:bg-zinc-800"
66
+ >
67
+ Cancel
68
+ </button>
69
+ </div>
70
+ </>
71
+ )}
72
+ </ComposerPrimitive.EditRoot>
73
+ ) : (
74
+ <MessagePrimitive.Content
75
+ components={{
76
+ Text: ({ text }) => <MarkdownContent text={text} />,
77
+ ToolInvocation: (props) => <ToolInvocation {...props} />,
78
+ }}
79
+ />
80
+ )}
81
+
82
+ {/* Action bar */}
83
+ {!isEditing && (
84
+ <>
85
+ {/* User messages: edit button */}
86
+ <MessagePrimitive.Role match="user">
87
+ <ActionBarPrimitive.Root className="flex gap-1 pt-1">
88
+ <ActionBarPrimitive.Edit className="rounded px-2 py-1 text-xs text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-zinc-300" />
89
+ </ActionBarPrimitive.Root>
90
+ </MessagePrimitive.Role>
91
+
92
+ {/* Assistant messages: copy + feedback */}
93
+ <MessagePrimitive.Role match="assistant">
94
+ <ActionBarPrimitive.Root className="flex gap-1 pt-1">
95
+ <ActionBarPrimitive.Copy className="rounded px-2 py-1 text-xs text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-zinc-300">
96
+ {(copied: boolean) => (copied ? "Copied!" : "Copy")}
97
+ </ActionBarPrimitive.Copy>
98
+ <ActionBarPrimitive.Feedback className="flex gap-1">
99
+ {({ selected, onPositive, onNegative }) => (
100
+ <>
101
+ <button
102
+ type="button"
103
+ onClick={onPositive}
104
+ className={cn(
105
+ "rounded px-1.5 py-1 text-xs transition-colors",
106
+ selected === "positive"
107
+ ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400"
108
+ : "text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-zinc-300",
109
+ )}
110
+ aria-label="Good response"
111
+ >
112
+ &#x1F44D;
113
+ </button>
114
+ <button
115
+ type="button"
116
+ onClick={onNegative}
117
+ className={cn(
118
+ "rounded px-1.5 py-1 text-xs transition-colors",
119
+ selected === "negative"
120
+ ? "bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400"
121
+ : "text-zinc-400 hover:bg-zinc-100 hover:text-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-zinc-300",
122
+ )}
123
+ aria-label="Bad response"
124
+ >
125
+ &#x1F44E;
126
+ </button>
127
+ </>
128
+ )}
129
+ </ActionBarPrimitive.Feedback>
130
+ </ActionBarPrimitive.Root>
131
+ </MessagePrimitive.Role>
132
+ </>
133
+ )}
134
+ </div>
135
+ </div>
136
+ );
137
+ }
138
+
139
+ export function Message({ message, index, className }: MessageProps) {
140
+ return (
141
+ <MessagePrimitive.Root message={message} index={index}>
142
+ <MessageInner className={className} />
143
+ </MessagePrimitive.Root>
144
+ );
145
+ }
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import { cn } from "../utils/cn";
3
+ import type { ChatStatus } from "ai";
4
+
5
+ export interface StatusIndicatorProps {
6
+ status: ChatStatus;
7
+ className?: string;
8
+ }
9
+
10
+ const statusConfig: Record<ChatStatus, { color: string; label: string }> = {
11
+ ready: { color: "bg-emerald-500", label: "Ready" },
12
+ submitted: { color: "bg-amber-500", label: "Sending..." },
13
+ streaming: { color: "bg-amber-500 animate-pulse", label: "Responding..." },
14
+ error: { color: "bg-red-500", label: "Error" },
15
+ };
16
+
17
+ export function StatusIndicator({ status, className }: StatusIndicatorProps) {
18
+ const config = statusConfig[status] ?? statusConfig.ready;
19
+
20
+ return (
21
+ <div className={cn("flex items-center gap-2 text-xs", className)} data-status={status}>
22
+ <div className={cn("h-2 w-2 rounded-full", config.color)} />
23
+ <span>{config.label}</span>
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+ import { cn } from "../utils/cn";
3
+ import { useKognitiveContext } from "../context/kognitive-context";
4
+
5
+ export interface SuggestionsProps {
6
+ className?: string;
7
+ }
8
+
9
+ export function Suggestions({ className }: SuggestionsProps) {
10
+ const { suggestions, send, isStreaming } = useKognitiveContext();
11
+ if (suggestions.length === 0 || isStreaming) return null;
12
+
13
+ return (
14
+ <div className={cn("flex flex-wrap gap-2 py-3", className)} data-suggestions>
15
+ {suggestions.map((suggestion, i) => (
16
+ <button
17
+ key={i}
18
+ type="button"
19
+ onClick={() => send(suggestion)}
20
+ className="rounded-full border border-zinc-200 bg-white px-3 py-1.5 text-xs text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-300 dark:hover:bg-zinc-800"
21
+ >
22
+ {suggestion}
23
+ </button>
24
+ ))}
25
+ </div>
26
+ );
27
+ }
@@ -0,0 +1,69 @@
1
+ import React from "react";
2
+ import { cn } from "../utils/cn";
3
+ import { ThreadListPrimitive } from "../primitives/thread-list";
4
+
5
+ export interface ThreadListProps {
6
+ className?: string;
7
+ }
8
+
9
+ const statusColors: Record<string, string> = {
10
+ running: "bg-amber-500",
11
+ completed: "bg-emerald-500",
12
+ interrupted: "bg-orange-500",
13
+ error: "bg-red-500",
14
+ };
15
+
16
+ export function ThreadList({ className }: ThreadListProps) {
17
+ return (
18
+ <ThreadListPrimitive.Root
19
+ className={cn(
20
+ "flex h-full flex-col border-r border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900",
21
+ className,
22
+ )}
23
+ >
24
+ {/* Header */}
25
+ <div className="flex items-center justify-between border-b border-zinc-200 px-4 py-3 dark:border-zinc-700">
26
+ <span className="text-sm font-medium">Conversations</span>
27
+ <ThreadListPrimitive.New className="rounded-lg bg-blue-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">
28
+ New
29
+ </ThreadListPrimitive.New>
30
+ </div>
31
+
32
+ {/* Thread items */}
33
+ <div className="flex-1 overflow-y-auto">
34
+ <ThreadListPrimitive.Items>
35
+ {(thread, isActive) => (
36
+ <ThreadListPrimitive.Item
37
+ key={thread.sessionId}
38
+ thread={thread}
39
+ isActive={isActive}
40
+ className={cn(
41
+ "flex w-full flex-col gap-1 border-b border-zinc-100 px-4 py-3 text-left text-sm transition-colors dark:border-zinc-800",
42
+ isActive
43
+ ? "bg-white dark:bg-zinc-800"
44
+ : "hover:bg-zinc-100 dark:hover:bg-zinc-800/50",
45
+ )}
46
+ >
47
+ <div className="flex items-center gap-2">
48
+ <div
49
+ className={cn(
50
+ "h-2 w-2 shrink-0 rounded-full",
51
+ statusColors[thread.status] ?? "bg-zinc-400",
52
+ )}
53
+ />
54
+ <span className="truncate font-medium">
55
+ {thread.title || thread.lastUserPreview || "New conversation"}
56
+ </span>
57
+ </div>
58
+ {thread.lastAssistantPreview && (
59
+ <p className="truncate text-xs text-zinc-500 dark:text-zinc-400">
60
+ {thread.lastAssistantPreview}
61
+ </p>
62
+ )}
63
+ </ThreadListPrimitive.Item>
64
+ )}
65
+ </ThreadListPrimitive.Items>
66
+ </div>
67
+ </ThreadListPrimitive.Root>
68
+ );
69
+ }