@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.
- package/.turbo/turbo-build.log +2 -0
- package/CHANGELOG.md +19 -0
- package/README.md +264 -0
- package/dist/__tests__/context-provider.test.d.ts +1 -0
- package/dist/__tests__/context-provider.test.js +38 -0
- package/dist/__tests__/event-emitter.test.d.ts +1 -0
- package/dist/__tests__/event-emitter.test.js +62 -0
- package/dist/__tests__/keyboard-shortcuts.test.d.ts +1 -0
- package/dist/__tests__/keyboard-shortcuts.test.js +36 -0
- package/dist/__tests__/kognitive-runtime.test.d.ts +1 -0
- package/dist/__tests__/kognitive-runtime.test.js +58 -0
- package/dist/__tests__/kognitive-transport.test.d.ts +1 -0
- package/dist/__tests__/kognitive-transport.test.js +96 -0
- package/dist/__tests__/make-tool-ui.test.d.ts +1 -0
- package/dist/__tests__/make-tool-ui.test.js +50 -0
- package/dist/__tests__/message-helpers.test.d.ts +1 -0
- package/dist/__tests__/message-helpers.test.js +80 -0
- package/dist/__tests__/thread-manager.test.d.ts +1 -0
- package/dist/__tests__/thread-manager.test.js +84 -0
- package/dist/__tests__/tool-ui-registry.test.d.ts +1 -0
- package/dist/__tests__/tool-ui-registry.test.js +68 -0
- package/dist/__tests__/toolkit.test.d.ts +1 -0
- package/dist/__tests__/toolkit.test.js +33 -0
- package/dist/components/attachment-preview.d.ts +8 -0
- package/dist/components/attachment-preview.js +10 -0
- package/dist/components/composer.d.ts +6 -0
- package/dist/components/composer.js +10 -0
- package/dist/components/error-banner.d.ts +6 -0
- package/dist/components/error-banner.js +8 -0
- package/dist/components/markdown-content.d.ts +13 -0
- package/dist/components/markdown-content.js +31 -0
- package/dist/components/message.d.ts +7 -0
- package/dist/components/message.js +28 -0
- package/dist/components/status-indicator.d.ts +6 -0
- package/dist/components/status-indicator.js +16 -0
- package/dist/components/suggestions.d.ts +4 -0
- package/dist/components/suggestions.js +12 -0
- package/dist/components/thread-list.d.ts +4 -0
- package/dist/components/thread-list.js +20 -0
- package/dist/components/thread.d.ts +15 -0
- package/dist/components/thread.js +19 -0
- package/dist/components/tool-approval.d.ts +7 -0
- package/dist/components/tool-approval.js +12 -0
- package/dist/components/tool-invocation.d.ts +5 -0
- package/dist/components/tool-invocation.js +33 -0
- package/dist/context/kognitive-context.d.ts +2 -0
- package/dist/context/kognitive-context.js +6 -0
- package/dist/context/types.d.ts +44 -0
- package/dist/context/types.js +2 -0
- package/dist/context/use-kognitive.d.ts +1 -0
- package/dist/context/use-kognitive.js +7 -0
- package/dist/hooks/use-auto-scroll.d.ts +2 -0
- package/dist/hooks/use-auto-scroll.js +18 -0
- package/dist/hooks/use-copy-to-clipboard.d.ts +4 -0
- package/dist/hooks/use-copy-to-clipboard.js +13 -0
- package/dist/hooks/use-keyboard-shortcuts.d.ts +8 -0
- package/dist/hooks/use-keyboard-shortcuts.js +24 -0
- package/dist/hooks/use-kognitive-chat.d.ts +38 -0
- package/dist/hooks/use-kognitive-chat.js +35 -0
- package/dist/hooks/use-kognitive-event.d.ts +12 -0
- package/dist/hooks/use-kognitive-event.js +47 -0
- package/dist/hooks/use-streaming-status.d.ts +2 -0
- package/dist/hooks/use-streaming-status.js +8 -0
- package/dist/hooks/use-thread-manager.d.ts +20 -0
- package/dist/hooks/use-thread-manager.js +83 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +92 -0
- package/dist/kognitive-ui.d.ts +48 -0
- package/dist/kognitive-ui.js +130 -0
- package/dist/primitives/action-bar/action-bar-copy.d.ts +6 -0
- package/dist/primitives/action-bar/action-bar-copy.js +16 -0
- package/dist/primitives/action-bar/action-bar-edit.d.ts +6 -0
- package/dist/primitives/action-bar/action-bar-edit.js +11 -0
- package/dist/primitives/action-bar/action-bar-feedback.d.ts +10 -0
- package/dist/primitives/action-bar/action-bar-feedback.js +30 -0
- package/dist/primitives/action-bar/action-bar-retry.d.ts +6 -0
- package/dist/primitives/action-bar/action-bar-retry.js +25 -0
- package/dist/primitives/action-bar/action-bar-root.d.ts +6 -0
- package/dist/primitives/action-bar/action-bar-root.js +7 -0
- package/dist/primitives/action-bar/action-bar-stop.d.ts +6 -0
- package/dist/primitives/action-bar/action-bar-stop.js +11 -0
- package/dist/primitives/action-bar/index.d.ts +14 -0
- package/dist/primitives/action-bar/index.js +17 -0
- package/dist/primitives/composer/composer-attachment-trigger.d.ts +7 -0
- package/dist/primitives/composer/composer-attachment-trigger.js +33 -0
- package/dist/primitives/composer/composer-attachments.d.ts +7 -0
- package/dist/primitives/composer/composer-attachments.js +16 -0
- package/dist/primitives/composer/composer-input.d.ts +6 -0
- package/dist/primitives/composer/composer-input.js +19 -0
- package/dist/primitives/composer/composer-root.d.ts +6 -0
- package/dist/primitives/composer/composer-root.js +91 -0
- package/dist/primitives/composer/composer-send.d.ts +6 -0
- package/dist/primitives/composer/composer-send.js +10 -0
- package/dist/primitives/composer/edit-composer-root.d.ts +11 -0
- package/dist/primitives/composer/edit-composer-root.js +24 -0
- package/dist/primitives/composer/index.d.ts +15 -0
- package/dist/primitives/composer/index.js +19 -0
- package/dist/primitives/composer/use-composer.d.ts +12 -0
- package/dist/primitives/composer/use-composer.js +6 -0
- package/dist/primitives/message/index.d.ts +9 -0
- package/dist/primitives/message/index.js +13 -0
- package/dist/primitives/message/message-content.d.ts +25 -0
- package/dist/primitives/message/message-content.js +70 -0
- package/dist/primitives/message/message-role.d.ts +6 -0
- package/dist/primitives/message/message-role.js +11 -0
- package/dist/primitives/message/message-root.d.ts +9 -0
- package/dist/primitives/message/message-root.js +38 -0
- package/dist/primitives/message/use-message.d.ts +10 -0
- package/dist/primitives/message/use-message.js +6 -0
- package/dist/primitives/thread/index.d.ts +17 -0
- package/dist/primitives/thread/index.js +21 -0
- package/dist/primitives/thread/thread-empty.d.ts +5 -0
- package/dist/primitives/thread/thread-empty.js +11 -0
- package/dist/primitives/thread/thread-error.d.ts +6 -0
- package/dist/primitives/thread/thread-error.js +11 -0
- package/dist/primitives/thread/thread-loading.d.ts +5 -0
- package/dist/primitives/thread/thread-loading.js +11 -0
- package/dist/primitives/thread/thread-messages.d.ts +6 -0
- package/dist/primitives/thread/thread-messages.js +9 -0
- package/dist/primitives/thread/thread-root.d.ts +6 -0
- package/dist/primitives/thread/thread-root.js +12 -0
- package/dist/primitives/thread/thread-scroll-to-bottom.d.ts +6 -0
- package/dist/primitives/thread/thread-scroll-to-bottom.js +14 -0
- package/dist/primitives/thread/thread-suggestions.d.ts +6 -0
- package/dist/primitives/thread/thread-suggestions.js +14 -0
- package/dist/primitives/thread/use-thread.d.ts +9 -0
- package/dist/primitives/thread/use-thread.js +6 -0
- package/dist/primitives/thread-list/index.d.ts +11 -0
- package/dist/primitives/thread-list/index.js +15 -0
- package/dist/primitives/thread-list/thread-list-item.d.ts +9 -0
- package/dist/primitives/thread-list/thread-list-item.js +13 -0
- package/dist/primitives/thread-list/thread-list-items.d.ts +6 -0
- package/dist/primitives/thread-list/thread-list-items.js +9 -0
- package/dist/primitives/thread-list/thread-list-new.d.ts +6 -0
- package/dist/primitives/thread-list/thread-list-new.js +13 -0
- package/dist/primitives/thread-list/thread-list-root.d.ts +6 -0
- package/dist/primitives/thread-list/thread-list-root.js +16 -0
- package/dist/primitives/thread-list/use-thread-list.d.ts +9 -0
- package/dist/primitives/thread-list/use-thread-list.js +6 -0
- package/dist/primitives/tool-ui/index.d.ts +7 -0
- package/dist/primitives/tool-ui/index.js +11 -0
- package/dist/primitives/tool-ui/tool-ui-fallback.d.ts +5 -0
- package/dist/primitives/tool-ui/tool-ui-fallback.js +20 -0
- package/dist/primitives/tool-ui/tool-ui-root.d.ts +5 -0
- package/dist/primitives/tool-ui/tool-ui-root.js +20 -0
- package/dist/primitives/tool-ui/use-tool-ui.d.ts +2 -0
- package/dist/primitives/tool-ui/use-tool-ui.js +8 -0
- package/dist/runtime/kognitive-runtime.d.ts +30 -0
- package/dist/runtime/kognitive-runtime.js +32 -0
- package/dist/runtime/kognitive-transport.d.ts +26 -0
- package/dist/runtime/kognitive-transport.js +42 -0
- package/dist/runtime/thread-manager.d.ts +17 -0
- package/dist/runtime/thread-manager.js +62 -0
- package/dist/runtime/types.d.ts +58 -0
- package/dist/runtime/types.js +2 -0
- package/dist/tool-ui/make-tool-ui.d.ts +59 -0
- package/dist/tool-ui/make-tool-ui.js +10 -0
- package/dist/tool-ui/registry.d.ts +9 -0
- package/dist/tool-ui/registry.js +26 -0
- package/dist/tool-ui/tool-ui-context.d.ts +2 -0
- package/dist/tool-ui/tool-ui-context.js +6 -0
- package/dist/tool-ui/toolkit.d.ts +31 -0
- package/dist/tool-ui/toolkit.js +33 -0
- package/dist/tool-ui/types.d.ts +19 -0
- package/dist/tool-ui/types.js +2 -0
- package/dist/utils/cn.d.ts +2 -0
- package/dist/utils/cn.js +8 -0
- package/dist/utils/create-context.d.ts +1 -0
- package/dist/utils/create-context.js +16 -0
- package/dist/utils/message-helpers.d.ts +6 -0
- package/dist/utils/message-helpers.js +46 -0
- package/package.json +56 -0
- package/src/__tests__/context-provider.test.ts +43 -0
- package/src/__tests__/event-emitter.test.ts +69 -0
- package/src/__tests__/keyboard-shortcuts.test.ts +55 -0
- package/src/__tests__/kognitive-runtime.test.ts +62 -0
- package/src/__tests__/kognitive-transport.test.ts +113 -0
- package/src/__tests__/make-tool-ui.test.ts +60 -0
- package/src/__tests__/message-helpers.test.ts +101 -0
- package/src/__tests__/thread-manager.test.ts +118 -0
- package/src/__tests__/tool-ui-registry.test.ts +80 -0
- package/src/__tests__/toolkit.test.ts +37 -0
- package/src/components/attachment-preview.tsx +46 -0
- package/src/components/composer.tsx +59 -0
- package/src/components/error-banner.tsx +33 -0
- package/src/components/markdown-content.tsx +64 -0
- package/src/components/message.tsx +145 -0
- package/src/components/status-indicator.tsx +26 -0
- package/src/components/suggestions.tsx +27 -0
- package/src/components/thread-list.tsx +69 -0
- package/src/components/thread.tsx +89 -0
- package/src/components/tool-approval.tsx +54 -0
- package/src/components/tool-invocation.tsx +94 -0
- package/src/context/kognitive-context.tsx +8 -0
- package/src/context/types.ts +43 -0
- package/src/context/use-kognitive.ts +5 -0
- package/src/hooks/use-auto-scroll.ts +19 -0
- package/src/hooks/use-copy-to-clipboard.ts +16 -0
- package/src/hooks/use-keyboard-shortcuts.ts +34 -0
- package/src/hooks/use-kognitive-chat.ts +73 -0
- package/src/hooks/use-kognitive-event.ts +56 -0
- package/src/hooks/use-streaming-status.ts +7 -0
- package/src/hooks/use-thread-manager.ts +114 -0
- package/src/index.ts +56 -0
- package/src/kognitive-ui.tsx +216 -0
- package/src/primitives/action-bar/action-bar-copy.tsx +30 -0
- package/src/primitives/action-bar/action-bar-edit.tsx +24 -0
- package/src/primitives/action-bar/action-bar-feedback.tsx +59 -0
- package/src/primitives/action-bar/action-bar-retry.tsx +38 -0
- package/src/primitives/action-bar/action-bar-root.tsx +14 -0
- package/src/primitives/action-bar/action-bar-stop.tsx +24 -0
- package/src/primitives/action-bar/index.ts +15 -0
- package/src/primitives/composer/composer-attachment-trigger.tsx +70 -0
- package/src/primitives/composer/composer-attachments.tsx +36 -0
- package/src/primitives/composer/composer-input.tsx +46 -0
- package/src/primitives/composer/composer-root.tsx +130 -0
- package/src/primitives/composer/composer-send.tsx +23 -0
- package/src/primitives/composer/edit-composer-root.tsx +52 -0
- package/src/primitives/composer/index.ts +17 -0
- package/src/primitives/composer/use-composer.ts +19 -0
- package/src/primitives/message/index.ts +11 -0
- package/src/primitives/message/message-content.tsx +117 -0
- package/src/primitives/message/message-role.tsx +13 -0
- package/src/primitives/message/message-root.tsx +64 -0
- package/src/primitives/message/use-message.ts +17 -0
- package/src/primitives/thread/index.ts +19 -0
- package/src/primitives/thread/thread-empty.tsx +12 -0
- package/src/primitives/thread/thread-error.tsx +18 -0
- package/src/primitives/thread/thread-loading.tsx +12 -0
- package/src/primitives/thread/thread-messages.tsx +12 -0
- package/src/primitives/thread/thread-root.tsx +28 -0
- package/src/primitives/thread/thread-scroll-to-bottom.tsx +26 -0
- package/src/primitives/thread/thread-suggestions.tsx +31 -0
- package/src/primitives/thread/use-thread.ts +16 -0
- package/src/primitives/thread-list/index.ts +13 -0
- package/src/primitives/thread-list/thread-list-item.tsx +37 -0
- package/src/primitives/thread-list/thread-list-items.tsx +19 -0
- package/src/primitives/thread-list/thread-list-new.tsx +26 -0
- package/src/primitives/thread-list/thread-list-root.tsx +29 -0
- package/src/primitives/thread-list/use-thread-list.ts +16 -0
- package/src/primitives/tool-ui/index.ts +9 -0
- package/src/primitives/tool-ui/tool-ui-fallback.tsx +63 -0
- package/src/primitives/tool-ui/tool-ui-root.tsx +26 -0
- package/src/primitives/tool-ui/use-tool-ui.ts +7 -0
- package/src/runtime/kognitive-runtime.ts +56 -0
- package/src/runtime/kognitive-transport.ts +56 -0
- package/src/runtime/thread-manager.ts +92 -0
- package/src/runtime/types.ts +63 -0
- package/src/tool-ui/make-tool-ui.ts +71 -0
- package/src/tool-ui/registry.ts +27 -0
- package/src/tool-ui/tool-ui-context.tsx +8 -0
- package/src/tool-ui/toolkit.ts +40 -0
- package/src/tool-ui/types.ts +29 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/create-context.ts +18 -0
- package/src/utils/message-helpers.ts +42 -0
- package/tsconfig.json +15 -0
- 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">⚠</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
|
+
👍
|
|
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
|
+
👎
|
|
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
|
+
}
|