@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,130 @@
|
|
|
1
|
+
import React, { useState, useCallback, type ReactNode, type FormEvent, type DragEvent, type ClipboardEvent } from "react";
|
|
2
|
+
import type { FileUIPart } from "ai";
|
|
3
|
+
import { useKognitiveContext } from "../../context/kognitive-context";
|
|
4
|
+
import { ComposerContextProvider } from "./use-composer";
|
|
5
|
+
|
|
6
|
+
async function fileToFileUIPart(file: File): Promise<FileUIPart> {
|
|
7
|
+
const buffer = await file.arrayBuffer();
|
|
8
|
+
const base64 = btoa(
|
|
9
|
+
new Uint8Array(buffer).reduce(
|
|
10
|
+
(data, byte) => data + String.fromCharCode(byte),
|
|
11
|
+
"",
|
|
12
|
+
),
|
|
13
|
+
);
|
|
14
|
+
return {
|
|
15
|
+
type: "file",
|
|
16
|
+
mediaType: file.type || "application/octet-stream",
|
|
17
|
+
url: `data:${file.type || "application/octet-stream"};base64,${base64}`,
|
|
18
|
+
filename: file.name,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ComposerRootProps {
|
|
23
|
+
className?: string;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function ComposerRoot({ className, children }: ComposerRootProps) {
|
|
28
|
+
const { send, isStreaming } = useKognitiveContext();
|
|
29
|
+
const [input, setInput] = useState("");
|
|
30
|
+
const [files, setFiles] = useState<FileUIPart[]>([]);
|
|
31
|
+
const [isDragOver, setIsDragOver] = useState(false);
|
|
32
|
+
|
|
33
|
+
const addFile = useCallback((file: FileUIPart) => {
|
|
34
|
+
setFiles((prev) => [...prev, file]);
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const removeFile = useCallback((index: number) => {
|
|
38
|
+
setFiles((prev) => prev.filter((_, i) => i !== index));
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
const clearFiles = useCallback(() => {
|
|
42
|
+
setFiles([]);
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
const submit = useCallback(() => {
|
|
46
|
+
const text = input.trim();
|
|
47
|
+
if (!text && files.length === 0) return;
|
|
48
|
+
send(text, files.length > 0 ? { files } : undefined);
|
|
49
|
+
setInput("");
|
|
50
|
+
setFiles([]);
|
|
51
|
+
}, [input, files, send]);
|
|
52
|
+
|
|
53
|
+
const handleSubmit = useCallback(
|
|
54
|
+
(e: FormEvent) => {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
submit();
|
|
57
|
+
},
|
|
58
|
+
[submit],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// ── Drag-and-drop ──
|
|
62
|
+
const handleDragOver = useCallback((e: DragEvent) => {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
setIsDragOver(true);
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
const handleDragLeave = useCallback((e: DragEvent) => {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
setIsDragOver(false);
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
const handleDrop = useCallback(
|
|
73
|
+
async (e: DragEvent) => {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
setIsDragOver(false);
|
|
76
|
+
const droppedFiles = e.dataTransfer?.files;
|
|
77
|
+
if (!droppedFiles) return;
|
|
78
|
+
for (const file of Array.from(droppedFiles)) {
|
|
79
|
+
const part = await fileToFileUIPart(file);
|
|
80
|
+
addFile(part);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
[addFile],
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// ── Paste upload ──
|
|
87
|
+
const handlePaste = useCallback(
|
|
88
|
+
async (e: ClipboardEvent) => {
|
|
89
|
+
const items = e.clipboardData?.items;
|
|
90
|
+
if (!items) return;
|
|
91
|
+
for (const item of Array.from(items)) {
|
|
92
|
+
if (item.kind === "file") {
|
|
93
|
+
const file = item.getAsFile();
|
|
94
|
+
if (file) {
|
|
95
|
+
const part = await fileToFileUIPart(file);
|
|
96
|
+
addFile(part);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
[addFile],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<ComposerContextProvider
|
|
106
|
+
value={{
|
|
107
|
+
input,
|
|
108
|
+
setInput,
|
|
109
|
+
files,
|
|
110
|
+
addFile,
|
|
111
|
+
removeFile,
|
|
112
|
+
clearFiles,
|
|
113
|
+
submit,
|
|
114
|
+
isDisabled: isStreaming,
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
<form
|
|
118
|
+
className={className}
|
|
119
|
+
onSubmit={handleSubmit}
|
|
120
|
+
onDragOver={handleDragOver}
|
|
121
|
+
onDragLeave={handleDragLeave}
|
|
122
|
+
onDrop={handleDrop}
|
|
123
|
+
onPaste={handlePaste}
|
|
124
|
+
data-drag-over={isDragOver || undefined}
|
|
125
|
+
>
|
|
126
|
+
{children}
|
|
127
|
+
</form>
|
|
128
|
+
</ComposerContextProvider>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { useComposer } from "./use-composer";
|
|
3
|
+
|
|
4
|
+
export interface ComposerSendProps {
|
|
5
|
+
className?: string;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ComposerSend({ className, children }: ComposerSendProps) {
|
|
10
|
+
const { input, files, isDisabled } = useComposer();
|
|
11
|
+
const isEmpty = !input.trim() && files.length === 0;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<button
|
|
15
|
+
type="submit"
|
|
16
|
+
className={className}
|
|
17
|
+
disabled={isEmpty || isDisabled}
|
|
18
|
+
aria-label="Send message"
|
|
19
|
+
>
|
|
20
|
+
{children ?? "Send"}
|
|
21
|
+
</button>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { useState, useCallback, type ReactNode, type FormEvent } from "react";
|
|
2
|
+
import { useMessage } from "../message/use-message";
|
|
3
|
+
import { getMessageText } from "../../utils/message-helpers";
|
|
4
|
+
|
|
5
|
+
export interface EditComposerRootProps {
|
|
6
|
+
className?: string;
|
|
7
|
+
children?: ReactNode | ((props: {
|
|
8
|
+
value: string;
|
|
9
|
+
setValue: (v: string) => void;
|
|
10
|
+
submit: () => void;
|
|
11
|
+
cancel: () => void;
|
|
12
|
+
}) => ReactNode);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function EditComposerRoot({ className, children }: EditComposerRootProps) {
|
|
16
|
+
const { message, isEditing, submitEdit, cancelEdit } = useMessage();
|
|
17
|
+
const [value, setValue] = useState(() => getMessageText(message));
|
|
18
|
+
|
|
19
|
+
const handleSubmit = useCallback(
|
|
20
|
+
(e: FormEvent) => {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
const text = value.trim();
|
|
23
|
+
if (text) submitEdit(text);
|
|
24
|
+
},
|
|
25
|
+
[value, submitEdit],
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
if (!isEditing) return null;
|
|
29
|
+
|
|
30
|
+
if (typeof children === "function") {
|
|
31
|
+
return (
|
|
32
|
+
<form className={className} onSubmit={handleSubmit}>
|
|
33
|
+
{children({ value, setValue, submit: () => { const t = value.trim(); if (t) submitEdit(t); }, cancel: cancelEdit })}
|
|
34
|
+
</form>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<form className={className} onSubmit={handleSubmit}>
|
|
40
|
+
<textarea
|
|
41
|
+
value={value}
|
|
42
|
+
onChange={(e) => setValue(e.target.value)}
|
|
43
|
+
rows={3}
|
|
44
|
+
aria-label="Edit message"
|
|
45
|
+
/>
|
|
46
|
+
<div>
|
|
47
|
+
<button type="submit">Save & Send</button>
|
|
48
|
+
<button type="button" onClick={cancelEdit}>Cancel</button>
|
|
49
|
+
</div>
|
|
50
|
+
</form>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ComposerRoot } from "./composer-root";
|
|
2
|
+
import { ComposerInput } from "./composer-input";
|
|
3
|
+
import { ComposerSend } from "./composer-send";
|
|
4
|
+
import { ComposerAttachments } from "./composer-attachments";
|
|
5
|
+
import { ComposerAttachmentTrigger } from "./composer-attachment-trigger";
|
|
6
|
+
import { EditComposerRoot } from "./edit-composer-root";
|
|
7
|
+
|
|
8
|
+
export const ComposerPrimitive = {
|
|
9
|
+
Root: ComposerRoot,
|
|
10
|
+
Input: ComposerInput,
|
|
11
|
+
Send: ComposerSend,
|
|
12
|
+
Attachments: ComposerAttachments,
|
|
13
|
+
AttachmentTrigger: ComposerAttachmentTrigger,
|
|
14
|
+
EditRoot: EditComposerRoot,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export { useComposer } from "./use-composer";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createContext } from "../../utils/create-context";
|
|
2
|
+
import type { FileUIPart } from "ai";
|
|
3
|
+
|
|
4
|
+
export interface ComposerContextValue {
|
|
5
|
+
input: string;
|
|
6
|
+
setInput: (value: string) => void;
|
|
7
|
+
files: FileUIPart[];
|
|
8
|
+
addFile: (file: FileUIPart) => void;
|
|
9
|
+
removeFile: (index: number) => void;
|
|
10
|
+
clearFiles: () => void;
|
|
11
|
+
submit: () => void;
|
|
12
|
+
isDisabled: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const [
|
|
16
|
+
ComposerContextProvider,
|
|
17
|
+
useComposer,
|
|
18
|
+
ComposerContext,
|
|
19
|
+
] = createContext<ComposerContextValue>("Composer");
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MessageRoot } from "./message-root";
|
|
2
|
+
import { MessageContent } from "./message-content";
|
|
3
|
+
import { MessageRole } from "./message-role";
|
|
4
|
+
|
|
5
|
+
export const MessagePrimitive = {
|
|
6
|
+
Root: MessageRoot,
|
|
7
|
+
Content: MessageContent,
|
|
8
|
+
Role: MessageRole,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { useMessage } from "./use-message";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React, { type ComponentType } from "react";
|
|
2
|
+
import type { FileUIPart } from "ai";
|
|
3
|
+
import { useMessage } from "./use-message";
|
|
4
|
+
import { useToolUIRegistry } from "../../tool-ui/tool-ui-context";
|
|
5
|
+
import type { ToolUIRenderProps, ToolInvocationState } from "../../tool-ui/types";
|
|
6
|
+
|
|
7
|
+
export interface TextComponentProps {
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ToolInvocationComponentProps {
|
|
12
|
+
toolCallId: string;
|
|
13
|
+
toolName: string;
|
|
14
|
+
input: any;
|
|
15
|
+
output: any;
|
|
16
|
+
state: ToolInvocationState;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FileComponentProps {
|
|
20
|
+
file: FileUIPart;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface MessageContentComponents {
|
|
24
|
+
Text?: ComponentType<TextComponentProps>;
|
|
25
|
+
ToolInvocation?: ComponentType<ToolInvocationComponentProps>;
|
|
26
|
+
File?: ComponentType<FileComponentProps>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface MessageContentProps {
|
|
30
|
+
components?: MessageContentComponents;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function DefaultText({ text }: TextComponentProps) {
|
|
34
|
+
return <p>{text}</p>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function DefaultFile({ file }: FileComponentProps) {
|
|
38
|
+
if (file.mediaType.startsWith("image/")) {
|
|
39
|
+
return <img src={file.url} alt={file.filename ?? "attachment"} />;
|
|
40
|
+
}
|
|
41
|
+
return <div data-file-type={file.mediaType}>File: {file.filename ?? file.mediaType}</div>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isToolPart(part: { type: string }): boolean {
|
|
45
|
+
return part.type.startsWith("tool-") || part.type === "dynamic-tool";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getToolName(part: any): string {
|
|
49
|
+
if (part.type === "dynamic-tool") return part.toolName;
|
|
50
|
+
// For typed tool parts, type is "tool-{name}"
|
|
51
|
+
return part.type.replace(/^tool-/, "");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function MessageContent({ components }: MessageContentProps) {
|
|
55
|
+
const { message } = useMessage();
|
|
56
|
+
let registry: ReturnType<typeof useToolUIRegistry> | undefined;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
registry = useToolUIRegistry();
|
|
60
|
+
} catch {
|
|
61
|
+
// ToolUIRegistry is optional
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const TextComponent = components?.Text ?? DefaultText;
|
|
65
|
+
const ToolInvocationComponent = components?.ToolInvocation;
|
|
66
|
+
const FileComponent = components?.File ?? DefaultFile;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<>
|
|
70
|
+
{message.parts.map((part, i) => {
|
|
71
|
+
if (part.type === "text") {
|
|
72
|
+
return <TextComponent key={i} text={part.text} />;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isToolPart(part)) {
|
|
76
|
+
const toolPart = part as any;
|
|
77
|
+
const toolName = getToolName(toolPart);
|
|
78
|
+
const renderProps: ToolUIRenderProps = {
|
|
79
|
+
toolCallId: toolPart.toolCallId,
|
|
80
|
+
toolName,
|
|
81
|
+
input: toolPart.input,
|
|
82
|
+
output: toolPart.output,
|
|
83
|
+
state: toolPart.state,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Check registry first
|
|
87
|
+
const RegisteredUI = registry?.get(toolName);
|
|
88
|
+
if (RegisteredUI) {
|
|
89
|
+
return <RegisteredUI key={i} {...renderProps} />;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Then check component override
|
|
93
|
+
if (ToolInvocationComponent) {
|
|
94
|
+
return <ToolInvocationComponent key={i} {...renderProps} />;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Default: simple JSON display
|
|
98
|
+
return (
|
|
99
|
+
<div key={i} data-tool={toolName} data-tool-state={toolPart.state}>
|
|
100
|
+
<strong>{toolName}</strong>
|
|
101
|
+
<pre>{JSON.stringify(toolPart.input, null, 2)}</pre>
|
|
102
|
+
{toolPart.output !== undefined && (
|
|
103
|
+
<pre>{JSON.stringify(toolPart.output, null, 2)}</pre>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (part.type === "file") {
|
|
110
|
+
return <FileComponent key={i} file={part} />;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
})}
|
|
115
|
+
</>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { useMessage } from "./use-message";
|
|
3
|
+
|
|
4
|
+
export interface MessageRoleProps {
|
|
5
|
+
match: "user" | "assistant" | "system";
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function MessageRole({ match, children }: MessageRoleProps) {
|
|
10
|
+
const { message } = useMessage();
|
|
11
|
+
if (message.role !== match) return null;
|
|
12
|
+
return <>{children}</>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React, { useState, useCallback, type ReactNode } from "react";
|
|
2
|
+
import type { UIMessage } from "ai";
|
|
3
|
+
import { MessageContextProvider } from "./use-message";
|
|
4
|
+
import { useKognitiveContext } from "../../context/kognitive-context";
|
|
5
|
+
import { getMessageText } from "../../utils/message-helpers";
|
|
6
|
+
|
|
7
|
+
export interface MessageRootProps {
|
|
8
|
+
message?: UIMessage;
|
|
9
|
+
index?: number;
|
|
10
|
+
className?: string;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function MessageRoot({ message, index, className, children }: MessageRootProps) {
|
|
15
|
+
if (!message) {
|
|
16
|
+
throw new Error("MessagePrimitive.Root requires a `message` prop or must be used inside ThreadPrimitive.Messages");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { messages, setMessages, send } = useKognitiveContext();
|
|
20
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
21
|
+
|
|
22
|
+
const startEdit = useCallback(() => {
|
|
23
|
+
if (message.role === "user") setIsEditing(true);
|
|
24
|
+
}, [message.role]);
|
|
25
|
+
|
|
26
|
+
const cancelEdit = useCallback(() => {
|
|
27
|
+
setIsEditing(false);
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
const submitEdit = useCallback(
|
|
31
|
+
(newText: string) => {
|
|
32
|
+
setIsEditing(false);
|
|
33
|
+
// Trim conversation to before this message and re-send
|
|
34
|
+
const msgIndex = messages.findIndex((m) => m.id === message.id);
|
|
35
|
+
if (msgIndex >= 0) {
|
|
36
|
+
setMessages(messages.slice(0, msgIndex));
|
|
37
|
+
send(newText);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
[messages, message.id, setMessages, send],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<MessageContextProvider
|
|
45
|
+
value={{
|
|
46
|
+
message,
|
|
47
|
+
index: index ?? 0,
|
|
48
|
+
isEditing,
|
|
49
|
+
startEdit,
|
|
50
|
+
cancelEdit,
|
|
51
|
+
submitEdit,
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<div
|
|
55
|
+
className={className}
|
|
56
|
+
data-role={message.role}
|
|
57
|
+
data-message-id={message.id}
|
|
58
|
+
data-editing={isEditing || undefined}
|
|
59
|
+
>
|
|
60
|
+
{children}
|
|
61
|
+
</div>
|
|
62
|
+
</MessageContextProvider>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createContext } from "../../utils/create-context";
|
|
2
|
+
import type { UIMessage } from "ai";
|
|
3
|
+
|
|
4
|
+
export interface MessageContextValue {
|
|
5
|
+
message: UIMessage;
|
|
6
|
+
index: number;
|
|
7
|
+
isEditing: boolean;
|
|
8
|
+
startEdit: () => void;
|
|
9
|
+
cancelEdit: () => void;
|
|
10
|
+
submitEdit: (newText: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const [
|
|
14
|
+
MessageContextProvider,
|
|
15
|
+
useMessage,
|
|
16
|
+
MessageContext,
|
|
17
|
+
] = createContext<MessageContextValue>("Message");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ThreadRoot } from "./thread-root";
|
|
2
|
+
import { ThreadMessages } from "./thread-messages";
|
|
3
|
+
import { ThreadEmpty } from "./thread-empty";
|
|
4
|
+
import { ThreadLoading } from "./thread-loading";
|
|
5
|
+
import { ThreadScrollToBottom } from "./thread-scroll-to-bottom";
|
|
6
|
+
import { ThreadError } from "./thread-error";
|
|
7
|
+
import { ThreadSuggestions } from "./thread-suggestions";
|
|
8
|
+
|
|
9
|
+
export const ThreadPrimitive = {
|
|
10
|
+
Root: ThreadRoot,
|
|
11
|
+
Messages: ThreadMessages,
|
|
12
|
+
Empty: ThreadEmpty,
|
|
13
|
+
Loading: ThreadLoading,
|
|
14
|
+
ScrollToBottom: ThreadScrollToBottom,
|
|
15
|
+
Error: ThreadError,
|
|
16
|
+
Suggestions: ThreadSuggestions,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export { useThread } from "./use-thread";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { useThread } from "./use-thread";
|
|
3
|
+
|
|
4
|
+
export interface ThreadEmptyProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ThreadEmpty({ children }: ThreadEmptyProps) {
|
|
9
|
+
const { messages } = useThread();
|
|
10
|
+
if (messages.length > 0) return null;
|
|
11
|
+
return <>{children}</>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { useKognitiveContext } from "../../context/kognitive-context";
|
|
3
|
+
|
|
4
|
+
export interface ThreadErrorProps {
|
|
5
|
+
children?: ReactNode | ((error: Error) => ReactNode);
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ThreadError({ children, className }: ThreadErrorProps) {
|
|
10
|
+
const { error } = useKognitiveContext();
|
|
11
|
+
if (!error) return null;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className={className} role="alert" data-error>
|
|
15
|
+
{typeof children === "function" ? children(error) : children ?? error.message}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { useThread } from "./use-thread";
|
|
3
|
+
|
|
4
|
+
export interface ThreadLoadingProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ThreadLoading({ children }: ThreadLoadingProps) {
|
|
9
|
+
const { isStreaming } = useThread();
|
|
10
|
+
if (!isStreaming) return null;
|
|
11
|
+
return <>{children}</>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import type { UIMessage } from "ai";
|
|
3
|
+
import { useThread } from "./use-thread";
|
|
4
|
+
|
|
5
|
+
export interface ThreadMessagesProps {
|
|
6
|
+
children: (message: UIMessage, index: number) => ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ThreadMessages({ children }: ThreadMessagesProps) {
|
|
10
|
+
const { messages } = useThread();
|
|
11
|
+
return <>{messages.map((message, index) => children(message, index))}</>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { useRef, type ReactNode } from "react";
|
|
2
|
+
import { useKognitiveContext } from "../../context/kognitive-context";
|
|
3
|
+
import { ThreadContextProvider } from "./use-thread";
|
|
4
|
+
|
|
5
|
+
export interface ThreadRootProps {
|
|
6
|
+
className?: string;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ThreadRoot({ className, children }: ThreadRootProps) {
|
|
11
|
+
const { messages, status, isStreaming, send, stop } = useKognitiveContext();
|
|
12
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<ThreadContextProvider
|
|
16
|
+
value={{ messages, status, isStreaming, send, stop }}
|
|
17
|
+
>
|
|
18
|
+
<div
|
|
19
|
+
ref={containerRef}
|
|
20
|
+
className={className}
|
|
21
|
+
data-status={status}
|
|
22
|
+
data-streaming={isStreaming || undefined}
|
|
23
|
+
>
|
|
24
|
+
{children}
|
|
25
|
+
</div>
|
|
26
|
+
</ThreadContextProvider>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, { useCallback, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export interface ThreadScrollToBottomProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ThreadScrollToBottom({ className, children }: ThreadScrollToBottomProps) {
|
|
9
|
+
const scrollToBottom = useCallback(() => {
|
|
10
|
+
const container = document.querySelector("[data-kognitive-thread-root]");
|
|
11
|
+
if (container) {
|
|
12
|
+
container.scrollTop = container.scrollHeight;
|
|
13
|
+
}
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
className={className}
|
|
20
|
+
onClick={scrollToBottom}
|
|
21
|
+
aria-label="Scroll to bottom"
|
|
22
|
+
>
|
|
23
|
+
{children ?? "↓"}
|
|
24
|
+
</button>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { useKognitiveContext } from "../../context/kognitive-context";
|
|
3
|
+
|
|
4
|
+
export interface ThreadSuggestionsProps {
|
|
5
|
+
className?: string;
|
|
6
|
+
children?: (suggestions: string[], send: (text: string) => void) => ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ThreadSuggestions({ className, children }: ThreadSuggestionsProps) {
|
|
10
|
+
const { suggestions, send, isStreaming } = useKognitiveContext();
|
|
11
|
+
if (suggestions.length === 0 || isStreaming) return null;
|
|
12
|
+
|
|
13
|
+
if (children) {
|
|
14
|
+
return <>{children(suggestions, send)}</>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className={className} data-suggestions>
|
|
19
|
+
{suggestions.map((suggestion, i) => (
|
|
20
|
+
<button
|
|
21
|
+
key={i}
|
|
22
|
+
type="button"
|
|
23
|
+
onClick={() => send(suggestion)}
|
|
24
|
+
data-suggestion
|
|
25
|
+
>
|
|
26
|
+
{suggestion}
|
|
27
|
+
</button>
|
|
28
|
+
))}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createContext } from "../../utils/create-context";
|
|
2
|
+
import type { UIMessage, ChatStatus } from "ai";
|
|
3
|
+
|
|
4
|
+
export interface ThreadContextValue {
|
|
5
|
+
messages: UIMessage[];
|
|
6
|
+
status: ChatStatus;
|
|
7
|
+
isStreaming: boolean;
|
|
8
|
+
send: (text: string) => void;
|
|
9
|
+
stop: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const [
|
|
13
|
+
ThreadContextProvider,
|
|
14
|
+
useThread,
|
|
15
|
+
ThreadContext,
|
|
16
|
+
] = createContext<ThreadContextValue>("Thread");
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ThreadListRoot } from "./thread-list-root";
|
|
2
|
+
import { ThreadListItems } from "./thread-list-items";
|
|
3
|
+
import { ThreadListItem } from "./thread-list-item";
|
|
4
|
+
import { ThreadListNew } from "./thread-list-new";
|
|
5
|
+
|
|
6
|
+
export const ThreadListPrimitive = {
|
|
7
|
+
Root: ThreadListRoot,
|
|
8
|
+
Items: ThreadListItems,
|
|
9
|
+
Item: ThreadListItem,
|
|
10
|
+
New: ThreadListNew,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { useThreadList } from "./use-thread-list";
|