@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
package/src/index.ts ADDED
@@ -0,0 +1,56 @@
1
+ // ── Root Provider ──
2
+ export { KognitiveUI, type KognitiveUIProps } from "./kognitive-ui";
3
+
4
+ // ── Tool UI System ──
5
+ export { makeToolUI, type MakeToolUIOptions, type MakeToolUIByNameOptions } from "./tool-ui/make-tool-ui";
6
+ export { toolkit, type ToolkitEntry } from "./tool-ui/toolkit";
7
+ export { ToolUIRegistry } from "./tool-ui/registry";
8
+ export type { ToolUIRegistration, ToolUIRenderProps, ToolUIComponent } from "./tool-ui/types";
9
+
10
+ // ── Primitives ──
11
+ export { ThreadPrimitive } from "./primitives/thread";
12
+ export { MessagePrimitive } from "./primitives/message";
13
+ export { ComposerPrimitive } from "./primitives/composer";
14
+ export { ActionBarPrimitive } from "./primitives/action-bar";
15
+ export { ToolUIPrimitive } from "./primitives/tool-ui";
16
+ export { ThreadListPrimitive } from "./primitives/thread-list";
17
+
18
+ // ── Composed Components ──
19
+ export { Thread } from "./components/thread";
20
+ export { Message } from "./components/message";
21
+ export { Composer } from "./components/composer";
22
+ export { ThreadList } from "./components/thread-list";
23
+ export { ToolInvocation } from "./components/tool-invocation";
24
+ export { ToolApproval } from "./components/tool-approval";
25
+ export { MarkdownContent } from "./components/markdown-content";
26
+ export { AttachmentPreview } from "./components/attachment-preview";
27
+ export { StatusIndicator } from "./components/status-indicator";
28
+ export { ErrorBanner } from "./components/error-banner";
29
+ export { Suggestions } from "./components/suggestions";
30
+
31
+ // ── Hooks ──
32
+ export { useKognitiveChat, type UseKognitiveChatConfig, type UseKognitiveChatReturn } from "./hooks/use-kognitive-chat";
33
+ export { useKognitive } from "./context/use-kognitive";
34
+ export { useThreadManager, type UseThreadManagerConfig, type UseThreadManagerReturn } from "./hooks/use-thread-manager";
35
+ export { useAutoScroll } from "./hooks/use-auto-scroll";
36
+ export { useCopyToClipboard } from "./hooks/use-copy-to-clipboard";
37
+ export { useStreamingStatus } from "./hooks/use-streaming-status";
38
+ export { useKeyboardShortcuts, type KeyboardShortcut } from "./hooks/use-keyboard-shortcuts";
39
+ export { useKognitiveEvent, KognitiveEventEmitter, type KognitiveEventType, type KognitiveEventHandler } from "./hooks/use-kognitive-event";
40
+
41
+ // ── Primitive Hooks ──
42
+ export { useThread } from "./primitives/thread";
43
+ export { useMessage } from "./primitives/message";
44
+ export { useComposer } from "./primitives/composer";
45
+ export { useThreadList } from "./primitives/thread-list";
46
+
47
+ // ── Runtime (advanced) ──
48
+ export { createKognitiveTransport, type KognitiveTransportConfig } from "./runtime/kognitive-transport";
49
+ export { KognitiveRuntime, type KognitiveRuntimeConfig } from "./runtime/kognitive-runtime";
50
+ export { ThreadManager, type ThreadManagerConfig } from "./runtime/thread-manager";
51
+ export type { ThreadSummary, ThreadDetail, ThreadEvent, ThreadTrace, ThreadRun } from "./runtime/types";
52
+ export type { ToolInvocationState } from "./tool-ui/types";
53
+
54
+ // ── Utilities ──
55
+ export { cn } from "./utils/cn";
56
+ export { getMessageText, getMessagePreview, getToolInvocations, hasToolInvocations, extractTextContent } from "./utils/message-helpers";
@@ -0,0 +1,216 @@
1
+ import React, { useMemo, useState, useEffect, useCallback, useRef } from "react";
2
+ import { useChat } from "@ai-sdk/react";
3
+ import type { UIMessage, FileUIPart, ChatStatus } from "ai";
4
+ import { createKognitiveTransport } from "./runtime/kognitive-transport";
5
+ import { ThreadManager } from "./runtime/thread-manager";
6
+ import { ToolUIRegistry } from "./tool-ui/registry";
7
+ import type { ToolUIRegistration } from "./tool-ui/types";
8
+ import { KognitiveContextProvider } from "./context/kognitive-context";
9
+ import { ToolUIRegistryProvider } from "./tool-ui/tool-ui-context";
10
+ import type { ThreadSummary } from "./runtime/types";
11
+ import { Thread } from "./components/thread";
12
+ import { ThreadList } from "./components/thread-list";
13
+ import { Composer } from "./components/composer";
14
+
15
+ export interface KognitiveUIProps {
16
+ /**
17
+ * Explicit API endpoint URL (e.g., "/api/chat").
18
+ * When set, takes priority over baseUrl + agentName for URL construction.
19
+ * agentName is sent in the request body instead.
20
+ */
21
+ api?: string;
22
+ /** Agent name to connect to */
23
+ agentName: string;
24
+ /** Base URL of the Kognitive runtime (or proxy). Used with agentName to build stream URL. */
25
+ baseUrl?: string;
26
+ /** Auth headers */
27
+ headers?: Record<string, string>;
28
+ /** User identity */
29
+ resourceId?: { userId?: string; sessionId?: string; organizationId?: string };
30
+ /** Session/thread ID (controlled mode — disables thread management) */
31
+ sessionId?: string;
32
+ /** Enable thread management */
33
+ threads?: boolean;
34
+ /** Thread API base URL. Defaults to {baseUrl}/api/agents/{agentName}/threads */
35
+ threadApiBase?: string;
36
+ /** Tool UI registrations from makeToolUI() */
37
+ toolUIs?: ToolUIRegistration[];
38
+ /** Tool UI registrations from toolkit() */
39
+ toolkit?: ToolUIRegistration[];
40
+ /** Extra body fields merged into every request */
41
+ body?: Record<string, unknown>;
42
+ /** Callback when user rates an assistant message (thumbs up/down) */
43
+ onFeedback?: (messageId: string, type: "positive" | "negative") => void;
44
+ /** Callback when user approves or rejects a tool call */
45
+ onToolApproval?: (toolCallId: string, approved: boolean) => void;
46
+ /** Suggested follow-up prompts shown after assistant response */
47
+ suggestions?: string[];
48
+ /** Children */
49
+ children: React.ReactNode;
50
+ }
51
+
52
+ function KognitiveUIInner(props: KognitiveUIProps) {
53
+ const {
54
+ api,
55
+ agentName,
56
+ baseUrl,
57
+ headers,
58
+ resourceId,
59
+ sessionId: controlledSessionId,
60
+ threads: threadsEnabled,
61
+ threadApiBase,
62
+ toolUIs,
63
+ toolkit: toolkitRegistrations,
64
+ body: extraBody,
65
+ onFeedback,
66
+ onToolApproval,
67
+ suggestions = [],
68
+ children,
69
+ } = props;
70
+
71
+ // ── Tool UI Registry ──
72
+ const toolUIRegistry = useMemo(() => {
73
+ const registry = new ToolUIRegistry();
74
+ if (toolUIs) registry.registerAll(toolUIs);
75
+ if (toolkitRegistrations) registry.registerAll(toolkitRegistrations);
76
+ return registry;
77
+ }, [toolUIs, toolkitRegistrations]);
78
+
79
+ // ── Thread Management ──
80
+ const [threadsList, setThreadsList] = useState<ThreadSummary[]>([]);
81
+ const [activeSessionId, setActiveSessionId] = useState<string | null>(
82
+ controlledSessionId ?? null,
83
+ );
84
+
85
+ const threadManager = useMemo(() => {
86
+ if (!threadsEnabled || !baseUrl) return null;
87
+ const base =
88
+ threadApiBase ??
89
+ `${baseUrl.replace(/\/$/, "")}/api/agents/${encodeURIComponent(agentName)}/threads`;
90
+ return new ThreadManager({ baseUrl: base, headers });
91
+ }, [threadsEnabled, threadApiBase, baseUrl, agentName, headers]);
92
+
93
+ const refreshThreads = useCallback(
94
+ async (preferredSessionId?: string) => {
95
+ if (!threadManager) return;
96
+ const threads = await threadManager.list();
97
+ setThreadsList(threads);
98
+ if (preferredSessionId) {
99
+ setActiveSessionId(preferredSessionId);
100
+ } else if (!activeSessionId && threads.length > 0) {
101
+ setActiveSessionId(threads[0].sessionId);
102
+ } else if (threads.length === 0) {
103
+ const thread = await threadManager.create();
104
+ setThreadsList([thread]);
105
+ setActiveSessionId(thread.sessionId);
106
+ }
107
+ },
108
+ [threadManager, activeSessionId],
109
+ );
110
+
111
+ const createThread = useCallback(async () => {
112
+ if (!threadManager) return;
113
+ const thread = await threadManager.create();
114
+ await refreshThreads(thread.sessionId);
115
+ }, [threadManager, refreshThreads]);
116
+
117
+ useEffect(() => {
118
+ if (threadManager) {
119
+ void refreshThreads();
120
+ }
121
+ // eslint-disable-next-line react-hooks/exhaustive-deps
122
+ }, [threadManager]);
123
+
124
+ useEffect(() => {
125
+ if (controlledSessionId !== undefined) {
126
+ setActiveSessionId(controlledSessionId);
127
+ }
128
+ }, [controlledSessionId]);
129
+
130
+ // ── Transport + Chat ──
131
+ const transport = useMemo(
132
+ () =>
133
+ createKognitiveTransport({
134
+ api,
135
+ baseUrl,
136
+ agentName,
137
+ sessionId: activeSessionId ?? undefined,
138
+ resourceId,
139
+ headers,
140
+ body: extraBody,
141
+ }),
142
+ [api, baseUrl, agentName, activeSessionId, resourceId, headers, extraBody],
143
+ );
144
+
145
+ const { messages, sendMessage, status, stop, setMessages, error } = useChat({
146
+ transport,
147
+ });
148
+
149
+ useEffect(() => {
150
+ setMessages([]);
151
+ }, [activeSessionId, setMessages]);
152
+
153
+ // ── Send helper ──
154
+ const send = useCallback(
155
+ (text: string, options?: { files?: FileUIPart[] }) => {
156
+ const parts: UIMessage["parts"] = [{ type: "text" as const, text }];
157
+ if (options?.files) {
158
+ parts.push(...options.files);
159
+ }
160
+ sendMessage({ role: "user", parts });
161
+ },
162
+ [sendMessage],
163
+ );
164
+
165
+ // Refresh threads after a stream completes
166
+ const prevStatusRef = useRef<ChatStatus>(status);
167
+ useEffect(() => {
168
+ if (
169
+ prevStatusRef.current === "streaming" &&
170
+ status === "ready" &&
171
+ threadManager
172
+ ) {
173
+ void refreshThreads(activeSessionId ?? undefined);
174
+ }
175
+ prevStatusRef.current = status;
176
+ }, [status, threadManager, refreshThreads, activeSessionId]);
177
+
178
+ const isStreaming = status === "streaming";
179
+
180
+ return (
181
+ <ToolUIRegistryProvider value={toolUIRegistry}>
182
+ <KognitiveContextProvider
183
+ value={{
184
+ messages,
185
+ send,
186
+ status,
187
+ isStreaming,
188
+ stop,
189
+ setMessages,
190
+ threadManager,
191
+ toolUIRegistry,
192
+ activeSessionId,
193
+ setActiveSessionId,
194
+ threads: threadsList,
195
+ refreshThreads: () => refreshThreads(),
196
+ createThread,
197
+ agentName,
198
+ error,
199
+ onFeedback,
200
+ onToolApproval,
201
+ suggestions,
202
+ }}
203
+ >
204
+ {children}
205
+ </KognitiveContextProvider>
206
+ </ToolUIRegistryProvider>
207
+ );
208
+ }
209
+
210
+ export function KognitiveUI(props: KognitiveUIProps) {
211
+ return <KognitiveUIInner {...props} />;
212
+ }
213
+
214
+ KognitiveUI.Thread = Thread;
215
+ KognitiveUI.ThreadList = ThreadList;
216
+ KognitiveUI.Composer = Composer;
@@ -0,0 +1,30 @@
1
+ import React, { useCallback, type ReactNode } from "react";
2
+ import { useMessage } from "../message/use-message";
3
+ import { useCopyToClipboard } from "../../hooks/use-copy-to-clipboard";
4
+ import { getMessageText } from "../../utils/message-helpers";
5
+
6
+ export interface ActionBarCopyProps {
7
+ className?: string;
8
+ children?: ReactNode | ((copied: boolean) => ReactNode);
9
+ }
10
+
11
+ export function ActionBarCopy({ className, children }: ActionBarCopyProps) {
12
+ const { message } = useMessage();
13
+ const { copied, copy } = useCopyToClipboard();
14
+
15
+ const handleCopy = useCallback(() => {
16
+ void copy(getMessageText(message));
17
+ }, [copy, message]);
18
+
19
+ return (
20
+ <button
21
+ type="button"
22
+ className={className}
23
+ onClick={handleCopy}
24
+ aria-label={copied ? "Copied" : "Copy message"}
25
+ data-copied={copied || undefined}
26
+ >
27
+ {typeof children === "function" ? children(copied) : children ?? (copied ? "Copied" : "Copy")}
28
+ </button>
29
+ );
30
+ }
@@ -0,0 +1,24 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { useMessage } from "../message/use-message";
3
+
4
+ export interface ActionBarEditProps {
5
+ className?: string;
6
+ children?: ReactNode;
7
+ }
8
+
9
+ export function ActionBarEdit({ className, children }: ActionBarEditProps) {
10
+ const { message, startEdit, isEditing } = useMessage();
11
+
12
+ if (message.role !== "user" || isEditing) return null;
13
+
14
+ return (
15
+ <button
16
+ type="button"
17
+ className={className}
18
+ onClick={startEdit}
19
+ aria-label="Edit message"
20
+ >
21
+ {children ?? "Edit"}
22
+ </button>
23
+ );
24
+ }
@@ -0,0 +1,59 @@
1
+ import React, { useState, useCallback, type ReactNode } from "react";
2
+ import { useMessage } from "../message/use-message";
3
+ import { useKognitiveContext } from "../../context/kognitive-context";
4
+
5
+ export interface ActionBarFeedbackProps {
6
+ className?: string;
7
+ children?: (props: {
8
+ selected: "positive" | "negative" | null;
9
+ onPositive: () => void;
10
+ onNegative: () => void;
11
+ }) => ReactNode;
12
+ }
13
+
14
+ export function ActionBarFeedback({ className, children }: ActionBarFeedbackProps) {
15
+ const { message } = useMessage();
16
+ const { onFeedback } = useKognitiveContext();
17
+ const [selected, setSelected] = useState<"positive" | "negative" | null>(null);
18
+
19
+ const handlePositive = useCallback(() => {
20
+ const next = selected === "positive" ? null : "positive" as const;
21
+ setSelected(next);
22
+ if (next && onFeedback) onFeedback(message.id, "positive");
23
+ }, [selected, onFeedback, message.id]);
24
+
25
+ const handleNegative = useCallback(() => {
26
+ const next = selected === "negative" ? null : "negative" as const;
27
+ setSelected(next);
28
+ if (next && onFeedback) onFeedback(message.id, "negative");
29
+ }, [selected, onFeedback, message.id]);
30
+
31
+ if (!onFeedback) return null;
32
+
33
+ if (children) {
34
+ return <>{children({ selected, onPositive: handlePositive, onNegative: handleNegative })}</>;
35
+ }
36
+
37
+ return (
38
+ <div className={className} role="group" aria-label="Message feedback">
39
+ <button
40
+ type="button"
41
+ onClick={handlePositive}
42
+ aria-label="Good response"
43
+ aria-pressed={selected === "positive"}
44
+ data-selected={selected === "positive" || undefined}
45
+ >
46
+ {selected === "positive" ? "\u{1F44D}" : "\u{1F44D}\u{200D}"}
47
+ </button>
48
+ <button
49
+ type="button"
50
+ onClick={handleNegative}
51
+ aria-label="Bad response"
52
+ aria-pressed={selected === "negative"}
53
+ data-selected={selected === "negative" || undefined}
54
+ >
55
+ {selected === "negative" ? "\u{1F44E}" : "\u{1F44E}\u{200D}"}
56
+ </button>
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,38 @@
1
+ import React, { useCallback, type ReactNode } from "react";
2
+ import { useKognitiveContext } from "../../context/kognitive-context";
3
+
4
+ export interface ActionBarRetryProps {
5
+ className?: string;
6
+ children?: ReactNode;
7
+ }
8
+
9
+ export function ActionBarRetry({ className, children }: ActionBarRetryProps) {
10
+ const { messages, send } = useKognitiveContext();
11
+
12
+ const handleRetry = useCallback(() => {
13
+ // Find the last user message and re-send it
14
+ for (let i = messages.length - 1; i >= 0; i--) {
15
+ if (messages[i].role === "user") {
16
+ const text = messages[i].parts
17
+ .filter((p): p is Extract<typeof p, { type: "text" }> => p.type === "text")
18
+ .map((p) => p.text)
19
+ .join("\n");
20
+ if (text) {
21
+ send(text);
22
+ return;
23
+ }
24
+ }
25
+ }
26
+ }, [messages, send]);
27
+
28
+ return (
29
+ <button
30
+ type="button"
31
+ className={className}
32
+ onClick={handleRetry}
33
+ aria-label="Retry"
34
+ >
35
+ {children ?? "Retry"}
36
+ </button>
37
+ );
38
+ }
@@ -0,0 +1,14 @@
1
+ import React, { type ReactNode } from "react";
2
+
3
+ export interface ActionBarRootProps {
4
+ className?: string;
5
+ children: ReactNode;
6
+ }
7
+
8
+ export function ActionBarRoot({ className, children }: ActionBarRootProps) {
9
+ return (
10
+ <div className={className} role="toolbar" aria-label="Message actions">
11
+ {children}
12
+ </div>
13
+ );
14
+ }
@@ -0,0 +1,24 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { useKognitiveContext } from "../../context/kognitive-context";
3
+
4
+ export interface ActionBarStopProps {
5
+ className?: string;
6
+ children?: ReactNode;
7
+ }
8
+
9
+ export function ActionBarStop({ className, children }: ActionBarStopProps) {
10
+ const { stop, isStreaming } = useKognitiveContext();
11
+
12
+ if (!isStreaming) return null;
13
+
14
+ return (
15
+ <button
16
+ type="button"
17
+ className={className}
18
+ onClick={stop}
19
+ aria-label="Stop"
20
+ >
21
+ {children ?? "Stop"}
22
+ </button>
23
+ );
24
+ }
@@ -0,0 +1,15 @@
1
+ import { ActionBarRoot } from "./action-bar-root";
2
+ import { ActionBarCopy } from "./action-bar-copy";
3
+ import { ActionBarRetry } from "./action-bar-retry";
4
+ import { ActionBarStop } from "./action-bar-stop";
5
+ import { ActionBarFeedback } from "./action-bar-feedback";
6
+ import { ActionBarEdit } from "./action-bar-edit";
7
+
8
+ export const ActionBarPrimitive = {
9
+ Root: ActionBarRoot,
10
+ Copy: ActionBarCopy,
11
+ Retry: ActionBarRetry,
12
+ Stop: ActionBarStop,
13
+ Feedback: ActionBarFeedback,
14
+ Edit: ActionBarEdit,
15
+ };
@@ -0,0 +1,70 @@
1
+ import React, { useRef, useCallback, type ReactNode, type ChangeEvent } from "react";
2
+ import { useComposer } from "./use-composer";
3
+
4
+ export interface ComposerAttachmentTriggerProps {
5
+ accept?: string;
6
+ className?: string;
7
+ children?: ReactNode;
8
+ }
9
+
10
+ export function ComposerAttachmentTrigger({
11
+ accept = "image/*",
12
+ className,
13
+ children,
14
+ }: ComposerAttachmentTriggerProps) {
15
+ const { addFile } = useComposer();
16
+ const inputRef = useRef<HTMLInputElement>(null);
17
+
18
+ const handleClick = useCallback(() => {
19
+ inputRef.current?.click();
20
+ }, []);
21
+
22
+ const handleChange = useCallback(
23
+ async (e: ChangeEvent<HTMLInputElement>) => {
24
+ const fileList = e.target.files;
25
+ if (!fileList) return;
26
+
27
+ for (const file of Array.from(fileList)) {
28
+ const buffer = await file.arrayBuffer();
29
+ const base64 = btoa(
30
+ new Uint8Array(buffer).reduce(
31
+ (data, byte) => data + String.fromCharCode(byte),
32
+ "",
33
+ ),
34
+ );
35
+ addFile({
36
+ type: "file",
37
+ mediaType: file.type,
38
+ url: `data:${file.type};base64,${base64}`,
39
+ filename: file.name,
40
+ });
41
+ }
42
+
43
+ // Reset input so the same file can be selected again
44
+ if (inputRef.current) inputRef.current.value = "";
45
+ },
46
+ [addFile],
47
+ );
48
+
49
+ return (
50
+ <>
51
+ <input
52
+ ref={inputRef}
53
+ type="file"
54
+ accept={accept}
55
+ multiple
56
+ onChange={handleChange}
57
+ style={{ display: "none" }}
58
+ aria-hidden
59
+ />
60
+ <button
61
+ type="button"
62
+ className={className}
63
+ onClick={handleClick}
64
+ aria-label="Attach file"
65
+ >
66
+ {children ?? "+"}
67
+ </button>
68
+ </>
69
+ );
70
+ }
@@ -0,0 +1,36 @@
1
+ import React, { type ReactNode } from "react";
2
+ import type { FileUIPart } from "ai";
3
+ import { useComposer } from "./use-composer";
4
+
5
+ export interface ComposerAttachmentsProps {
6
+ className?: string;
7
+ children?: (file: FileUIPart, index: number, remove: () => void) => ReactNode;
8
+ }
9
+
10
+ export function ComposerAttachments({ className, children }: ComposerAttachmentsProps) {
11
+ const { files, removeFile } = useComposer();
12
+
13
+ if (files.length === 0) return null;
14
+
15
+ return (
16
+ <div className={className} data-attachment-count={files.length}>
17
+ {files.map((file, index) => {
18
+ if (children) {
19
+ return children(file, index, () => removeFile(index));
20
+ }
21
+ return (
22
+ <div key={index} data-attachment>
23
+ <span>{file.mediaType}</span>
24
+ <button
25
+ type="button"
26
+ onClick={() => removeFile(index)}
27
+ aria-label="Remove attachment"
28
+ >
29
+ x
30
+ </button>
31
+ </div>
32
+ );
33
+ })}
34
+ </div>
35
+ );
36
+ }
@@ -0,0 +1,46 @@
1
+ import React, { useCallback, type KeyboardEvent, type ChangeEvent } from "react";
2
+ import { useComposer } from "./use-composer";
3
+
4
+ export interface ComposerInputProps {
5
+ placeholder?: string;
6
+ className?: string;
7
+ rows?: number;
8
+ }
9
+
10
+ export function ComposerInput({
11
+ placeholder = "Send a message...",
12
+ className,
13
+ rows = 1,
14
+ }: ComposerInputProps) {
15
+ const { input, setInput, submit, isDisabled } = useComposer();
16
+
17
+ const handleKeyDown = useCallback(
18
+ (e: KeyboardEvent<HTMLTextAreaElement>) => {
19
+ if (e.key === "Enter" && !e.shiftKey) {
20
+ e.preventDefault();
21
+ submit();
22
+ }
23
+ },
24
+ [submit],
25
+ );
26
+
27
+ const handleChange = useCallback(
28
+ (e: ChangeEvent<HTMLTextAreaElement>) => {
29
+ setInput(e.target.value);
30
+ },
31
+ [setInput],
32
+ );
33
+
34
+ return (
35
+ <textarea
36
+ className={className}
37
+ value={input}
38
+ onChange={handleChange}
39
+ onKeyDown={handleKeyDown}
40
+ placeholder={placeholder}
41
+ disabled={isDisabled}
42
+ rows={rows}
43
+ aria-label="Message input"
44
+ />
45
+ );
46
+ }