@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,89 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cn } from "../utils/cn";
|
|
3
|
+
import { ThreadPrimitive } from "../primitives/thread";
|
|
4
|
+
import { Message } from "./message";
|
|
5
|
+
import { Composer } from "./composer";
|
|
6
|
+
import { StatusIndicator } from "./status-indicator";
|
|
7
|
+
import { ErrorBanner } from "./error-banner";
|
|
8
|
+
import { Suggestions } from "./suggestions";
|
|
9
|
+
import { useKognitiveContext } from "../context/kognitive-context";
|
|
10
|
+
import type { ComponentType } from "react";
|
|
11
|
+
|
|
12
|
+
export interface ThreadProps {
|
|
13
|
+
className?: string;
|
|
14
|
+
emptyState?: React.ReactNode;
|
|
15
|
+
allowAttachments?: boolean;
|
|
16
|
+
components?: {
|
|
17
|
+
Message?: ComponentType<{ message: any; index: number }>;
|
|
18
|
+
Composer?: ComponentType;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function Thread({
|
|
23
|
+
className,
|
|
24
|
+
emptyState,
|
|
25
|
+
allowAttachments = false,
|
|
26
|
+
components,
|
|
27
|
+
}: ThreadProps) {
|
|
28
|
+
const { status } = useKognitiveContext();
|
|
29
|
+
const MessageComponent = components?.Message ?? Message;
|
|
30
|
+
const ComposerComponent = components?.Composer ?? Composer;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<ThreadPrimitive.Root
|
|
34
|
+
className={cn("flex h-full flex-col", className)}
|
|
35
|
+
>
|
|
36
|
+
{/* Status indicator */}
|
|
37
|
+
<div className="flex items-center justify-between border-b border-zinc-200 px-4 py-2 dark:border-zinc-700">
|
|
38
|
+
<StatusIndicator status={status} />
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{/* Error banner */}
|
|
42
|
+
<ThreadPrimitive.Error className="px-4 pt-3">
|
|
43
|
+
{(error: Error) => <ErrorBanner message={error.message} />}
|
|
44
|
+
</ThreadPrimitive.Error>
|
|
45
|
+
|
|
46
|
+
{/* Messages area */}
|
|
47
|
+
<div className="flex-1 overflow-y-auto px-4" data-kognitive-thread-root>
|
|
48
|
+
<ThreadPrimitive.Empty>
|
|
49
|
+
{emptyState ?? (
|
|
50
|
+
<div className="flex h-full items-center justify-center text-zinc-400">
|
|
51
|
+
<p>Start a conversation</p>
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
</ThreadPrimitive.Empty>
|
|
55
|
+
|
|
56
|
+
<ThreadPrimitive.Messages>
|
|
57
|
+
{(message, index) => (
|
|
58
|
+
<MessageComponent key={message.id} message={message} index={index} />
|
|
59
|
+
)}
|
|
60
|
+
</ThreadPrimitive.Messages>
|
|
61
|
+
|
|
62
|
+
<ThreadPrimitive.Loading>
|
|
63
|
+
<div className="flex gap-3 py-4">
|
|
64
|
+
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-sm text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400">
|
|
65
|
+
A
|
|
66
|
+
</div>
|
|
67
|
+
<div className="flex items-center">
|
|
68
|
+
<div className="flex gap-1">
|
|
69
|
+
<div className="h-2 w-2 animate-bounce rounded-full bg-zinc-400" style={{ animationDelay: "0ms" }} />
|
|
70
|
+
<div className="h-2 w-2 animate-bounce rounded-full bg-zinc-400" style={{ animationDelay: "150ms" }} />
|
|
71
|
+
<div className="h-2 w-2 animate-bounce rounded-full bg-zinc-400" style={{ animationDelay: "300ms" }} />
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</ThreadPrimitive.Loading>
|
|
76
|
+
|
|
77
|
+
{/* Suggested follow-ups */}
|
|
78
|
+
<Suggestions />
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* Composer */}
|
|
82
|
+
{components?.Composer ? (
|
|
83
|
+
<ComposerComponent />
|
|
84
|
+
) : (
|
|
85
|
+
<Composer allowAttachments={allowAttachments} />
|
|
86
|
+
)}
|
|
87
|
+
</ThreadPrimitive.Root>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cn } from "../utils/cn";
|
|
3
|
+
import { useKognitiveContext } from "../context/kognitive-context";
|
|
4
|
+
|
|
5
|
+
export interface ToolApprovalProps {
|
|
6
|
+
toolCallId: string;
|
|
7
|
+
toolName: string;
|
|
8
|
+
input: unknown;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ToolApproval({ toolCallId, toolName, input, className }: ToolApprovalProps) {
|
|
13
|
+
const { onToolApproval } = useKognitiveContext();
|
|
14
|
+
|
|
15
|
+
if (!onToolApproval) return null;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
className={cn(
|
|
20
|
+
"my-2 rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-950/30",
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
data-tool-approval={toolCallId}
|
|
24
|
+
>
|
|
25
|
+
<div className="mb-2 flex items-center gap-2">
|
|
26
|
+
<span className="text-amber-600">⚠</span>
|
|
27
|
+
<span className="text-sm font-medium">
|
|
28
|
+
<strong>{toolName}</strong> requires approval
|
|
29
|
+
</span>
|
|
30
|
+
</div>
|
|
31
|
+
{input !== undefined && input !== null && (
|
|
32
|
+
<pre className="mb-3 overflow-auto rounded bg-amber-100/50 p-2 text-xs dark:bg-amber-900/20">
|
|
33
|
+
{JSON.stringify(input, null, 2)}
|
|
34
|
+
</pre>
|
|
35
|
+
)}
|
|
36
|
+
<div className="flex gap-2">
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={() => onToolApproval(toolCallId, true)}
|
|
40
|
+
className="rounded-lg bg-emerald-600 px-4 py-1.5 text-sm font-medium text-white hover:bg-emerald-700"
|
|
41
|
+
>
|
|
42
|
+
Approve
|
|
43
|
+
</button>
|
|
44
|
+
<button
|
|
45
|
+
type="button"
|
|
46
|
+
onClick={() => onToolApproval(toolCallId, false)}
|
|
47
|
+
className="rounded-lg border border-zinc-300 px-4 py-1.5 text-sm font-medium text-zinc-700 hover:bg-zinc-100 dark:border-zinc-600 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
|
48
|
+
>
|
|
49
|
+
Reject
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { cn } from "../utils/cn";
|
|
3
|
+
import type { ToolUIRenderProps } from "../tool-ui/types";
|
|
4
|
+
import { ToolApproval } from "./tool-approval";
|
|
5
|
+
|
|
6
|
+
export interface ToolInvocationProps extends ToolUIRenderProps {
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isComplete(state: string): boolean {
|
|
11
|
+
return state === "output-available";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function stateLabel(state: string): string {
|
|
15
|
+
switch (state) {
|
|
16
|
+
case "input-streaming": return "streaming...";
|
|
17
|
+
case "input-available": return "calling...";
|
|
18
|
+
case "approval-requested": return "awaiting approval";
|
|
19
|
+
case "approval-responded": return "approved";
|
|
20
|
+
case "output-available": return "completed";
|
|
21
|
+
case "error": return "error";
|
|
22
|
+
default: return state;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ToolInvocation({
|
|
27
|
+
toolName,
|
|
28
|
+
toolCallId,
|
|
29
|
+
input,
|
|
30
|
+
output,
|
|
31
|
+
state,
|
|
32
|
+
className,
|
|
33
|
+
}: ToolInvocationProps) {
|
|
34
|
+
const [expanded, setExpanded] = useState(false);
|
|
35
|
+
const done = isComplete(state);
|
|
36
|
+
|
|
37
|
+
if (state === "approval-requested") {
|
|
38
|
+
return <ToolApproval toolCallId={toolCallId} toolName={toolName} input={input} className={className} />;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
className={cn(
|
|
44
|
+
"my-2 rounded-lg border border-zinc-200 dark:border-zinc-700",
|
|
45
|
+
className,
|
|
46
|
+
)}
|
|
47
|
+
data-tool={toolName}
|
|
48
|
+
data-tool-state={state}
|
|
49
|
+
>
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
onClick={() => setExpanded(!expanded)}
|
|
53
|
+
className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800/50"
|
|
54
|
+
aria-expanded={expanded}
|
|
55
|
+
>
|
|
56
|
+
<span className="text-amber-500">🔧</span>
|
|
57
|
+
<span className="font-medium">{toolName}</span>
|
|
58
|
+
<span
|
|
59
|
+
className={cn(
|
|
60
|
+
"ml-auto rounded-full px-2 py-0.5 text-xs",
|
|
61
|
+
done
|
|
62
|
+
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
|
|
63
|
+
: state === "error"
|
|
64
|
+
? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
|
|
65
|
+
: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400",
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
{stateLabel(state)}
|
|
69
|
+
</span>
|
|
70
|
+
<span className="text-zinc-400">{expanded ? "\u25BC" : "\u25B6"}</span>
|
|
71
|
+
</button>
|
|
72
|
+
{expanded && (
|
|
73
|
+
<div className="border-t border-zinc-200 px-3 py-2 dark:border-zinc-700">
|
|
74
|
+
{input !== undefined && (
|
|
75
|
+
<div className="mb-2">
|
|
76
|
+
<div className="mb-1 text-xs font-medium text-zinc-500">Arguments</div>
|
|
77
|
+
<pre className="overflow-auto rounded bg-zinc-50 p-2 text-xs dark:bg-zinc-800/50">
|
|
78
|
+
{JSON.stringify(input, null, 2)}
|
|
79
|
+
</pre>
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
{output !== undefined && (
|
|
83
|
+
<div>
|
|
84
|
+
<div className="mb-1 text-xs font-medium text-zinc-500">Result</div>
|
|
85
|
+
<pre className="overflow-auto rounded bg-zinc-50 p-2 text-xs dark:bg-zinc-800/50">
|
|
86
|
+
{JSON.stringify(output, null, 2)}
|
|
87
|
+
</pre>
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { UIMessage, ChatStatus, FileUIPart } from "ai";
|
|
2
|
+
import type { ThreadManager } from "../runtime/thread-manager";
|
|
3
|
+
import type { ToolUIRegistry } from "../tool-ui/registry";
|
|
4
|
+
import type { ThreadSummary } from "../runtime/types";
|
|
5
|
+
|
|
6
|
+
export interface KognitiveContextValue {
|
|
7
|
+
/** Current messages in the thread */
|
|
8
|
+
messages: UIMessage[];
|
|
9
|
+
/** Send a message to the agent */
|
|
10
|
+
send: (text: string, options?: { files?: FileUIPart[] }) => void;
|
|
11
|
+
/** Current runtime status */
|
|
12
|
+
status: ChatStatus;
|
|
13
|
+
/** Whether the agent is currently streaming */
|
|
14
|
+
isStreaming: boolean;
|
|
15
|
+
/** Stop the current stream */
|
|
16
|
+
stop: () => void;
|
|
17
|
+
/** Replace the message list */
|
|
18
|
+
setMessages: (messages: UIMessage[]) => void;
|
|
19
|
+
/** Thread manager (null if threads not enabled) */
|
|
20
|
+
threadManager: ThreadManager | null;
|
|
21
|
+
/** Tool UI registry */
|
|
22
|
+
toolUIRegistry: ToolUIRegistry;
|
|
23
|
+
/** Active session ID */
|
|
24
|
+
activeSessionId: string | null;
|
|
25
|
+
/** Switch to a different thread */
|
|
26
|
+
setActiveSessionId: (sessionId: string | null) => void;
|
|
27
|
+
/** List of threads (empty if threads not enabled) */
|
|
28
|
+
threads: ThreadSummary[];
|
|
29
|
+
/** Reload threads list */
|
|
30
|
+
refreshThreads: () => Promise<void>;
|
|
31
|
+
/** Create a new thread and switch to it */
|
|
32
|
+
createThread: () => Promise<void>;
|
|
33
|
+
/** Agent name */
|
|
34
|
+
agentName: string;
|
|
35
|
+
/** Current error (from stream or network failure) */
|
|
36
|
+
error: Error | undefined;
|
|
37
|
+
/** Feedback callback — called when user rates a message */
|
|
38
|
+
onFeedback?: (messageId: string, type: "positive" | "negative") => void;
|
|
39
|
+
/** Tool approval callback — called when user approves/rejects a tool call */
|
|
40
|
+
onToolApproval?: (toolCallId: string, approved: boolean) => void;
|
|
41
|
+
/** Suggested follow-up prompts shown after assistant responds */
|
|
42
|
+
suggestions: string[];
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useRef, type RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
export function useAutoScroll(
|
|
4
|
+
deps: unknown[],
|
|
5
|
+
containerRef?: RefObject<HTMLElement | null>,
|
|
6
|
+
): RefObject<HTMLDivElement | null> {
|
|
7
|
+
const endRef = useRef<HTMLDivElement>(null);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (containerRef?.current) {
|
|
11
|
+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
12
|
+
} else {
|
|
13
|
+
endRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
14
|
+
}
|
|
15
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
16
|
+
}, deps);
|
|
17
|
+
|
|
18
|
+
return endRef;
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
export function useCopyToClipboard(resetDelay = 2000) {
|
|
4
|
+
const [copied, setCopied] = useState(false);
|
|
5
|
+
|
|
6
|
+
const copy = useCallback(
|
|
7
|
+
async (text: string) => {
|
|
8
|
+
await navigator.clipboard.writeText(text);
|
|
9
|
+
setCopied(true);
|
|
10
|
+
setTimeout(() => setCopied(false), resetDelay);
|
|
11
|
+
},
|
|
12
|
+
[resetDelay],
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
return { copied, copy };
|
|
16
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
export interface KeyboardShortcut {
|
|
4
|
+
key: string;
|
|
5
|
+
ctrl?: boolean;
|
|
6
|
+
meta?: boolean;
|
|
7
|
+
shift?: boolean;
|
|
8
|
+
handler: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[]) {
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
14
|
+
for (const shortcut of shortcuts) {
|
|
15
|
+
const ctrlOrMeta = shortcut.ctrl || shortcut.meta;
|
|
16
|
+
const matchesMod = ctrlOrMeta ? (e.ctrlKey || e.metaKey) : true;
|
|
17
|
+
const matchesShift = shortcut.shift ? e.shiftKey : !e.shiftKey;
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
e.key.toLowerCase() === shortcut.key.toLowerCase() &&
|
|
21
|
+
matchesMod &&
|
|
22
|
+
matchesShift
|
|
23
|
+
) {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
shortcut.handler();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
32
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
33
|
+
}, [shortcuts]);
|
|
34
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useMemo } 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
|
+
|
|
6
|
+
export interface UseKognitiveChatConfig {
|
|
7
|
+
/** Explicit API endpoint URL (e.g., "/api/chat"). Takes priority over baseUrl+agentName. */
|
|
8
|
+
api?: string;
|
|
9
|
+
/** Agent name to connect to */
|
|
10
|
+
agentName?: string;
|
|
11
|
+
/** Base URL of the Kognitive runtime */
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
/** Current session/thread ID */
|
|
14
|
+
sessionId?: string;
|
|
15
|
+
/** User identity */
|
|
16
|
+
resourceId?: { userId?: string; sessionId?: string; organizationId?: string };
|
|
17
|
+
/** Additional headers (e.g., auth) */
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
/** Extra body fields merged into every request */
|
|
20
|
+
body?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UseKognitiveChatReturn {
|
|
24
|
+
/** Current messages */
|
|
25
|
+
messages: UIMessage[];
|
|
26
|
+
/** Send a text message, optionally with file attachments */
|
|
27
|
+
send: (text: string, options?: { files?: FileUIPart[] }) => void;
|
|
28
|
+
/** Current status */
|
|
29
|
+
status: ChatStatus;
|
|
30
|
+
/** Whether the agent is streaming */
|
|
31
|
+
isStreaming: boolean;
|
|
32
|
+
/** Stop the current stream */
|
|
33
|
+
stop: () => void;
|
|
34
|
+
/** Replace the message list */
|
|
35
|
+
setMessages: (messages: UIMessage[]) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useKognitiveChat(config: UseKognitiveChatConfig): UseKognitiveChatReturn {
|
|
39
|
+
const transport = useMemo(
|
|
40
|
+
() =>
|
|
41
|
+
createKognitiveTransport({
|
|
42
|
+
api: config.api,
|
|
43
|
+
baseUrl: config.baseUrl,
|
|
44
|
+
agentName: config.agentName,
|
|
45
|
+
sessionId: config.sessionId,
|
|
46
|
+
resourceId: config.resourceId,
|
|
47
|
+
headers: config.headers,
|
|
48
|
+
body: config.body,
|
|
49
|
+
}),
|
|
50
|
+
[config.api, config.baseUrl, config.agentName, config.sessionId, config.resourceId, config.headers, config.body],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const { messages, sendMessage, status, stop, setMessages } = useChat({
|
|
54
|
+
transport,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const send = (text: string, options?: { files?: FileUIPart[] }) => {
|
|
58
|
+
const parts: UIMessage["parts"] = [{ type: "text" as const, text }];
|
|
59
|
+
if (options?.files) {
|
|
60
|
+
parts.push(...options.files);
|
|
61
|
+
}
|
|
62
|
+
sendMessage({ role: "user", parts });
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
messages,
|
|
67
|
+
send,
|
|
68
|
+
status,
|
|
69
|
+
isStreaming: status === "streaming",
|
|
70
|
+
stop,
|
|
71
|
+
setMessages,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
export type KognitiveEventType =
|
|
4
|
+
| "message:send"
|
|
5
|
+
| "message:received"
|
|
6
|
+
| "status:change"
|
|
7
|
+
| "tool:invoked"
|
|
8
|
+
| "tool:completed"
|
|
9
|
+
| "error";
|
|
10
|
+
|
|
11
|
+
export type KognitiveEventHandler = (data: unknown) => void;
|
|
12
|
+
|
|
13
|
+
export class KognitiveEventEmitter {
|
|
14
|
+
private listeners = new Map<KognitiveEventType, Set<KognitiveEventHandler>>();
|
|
15
|
+
|
|
16
|
+
on(event: KognitiveEventType, handler: KognitiveEventHandler): () => void {
|
|
17
|
+
if (!this.listeners.has(event)) {
|
|
18
|
+
this.listeners.set(event, new Set());
|
|
19
|
+
}
|
|
20
|
+
this.listeners.get(event)!.add(handler);
|
|
21
|
+
return () => {
|
|
22
|
+
this.listeners.get(event)?.delete(handler);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
emit(event: KognitiveEventType, data?: unknown): void {
|
|
27
|
+
const handlers = this.listeners.get(event);
|
|
28
|
+
if (handlers) {
|
|
29
|
+
for (const handler of handlers) {
|
|
30
|
+
try {
|
|
31
|
+
handler(data);
|
|
32
|
+
} catch {
|
|
33
|
+
// Swallow listener errors
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Subscribe to Kognitive lifecycle events.
|
|
42
|
+
* The handler is automatically cleaned up on unmount.
|
|
43
|
+
*/
|
|
44
|
+
export function useKognitiveEvent(
|
|
45
|
+
emitter: KognitiveEventEmitter | undefined,
|
|
46
|
+
event: KognitiveEventType,
|
|
47
|
+
handler: KognitiveEventHandler,
|
|
48
|
+
): void {
|
|
49
|
+
const handlerRef = useRef(handler);
|
|
50
|
+
handlerRef.current = handler;
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!emitter) return;
|
|
54
|
+
return emitter.on(event, (data) => handlerRef.current(data));
|
|
55
|
+
}, [emitter, event]);
|
|
56
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { ThreadManager, type ThreadManagerConfig } from "../runtime/thread-manager";
|
|
3
|
+
import type { ThreadSummary, ThreadDetail } from "../runtime/types";
|
|
4
|
+
|
|
5
|
+
export interface UseThreadManagerConfig extends ThreadManagerConfig {
|
|
6
|
+
/** Auto-create a thread if none exist */
|
|
7
|
+
autoCreate?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface UseThreadManagerReturn {
|
|
11
|
+
threads: ThreadSummary[];
|
|
12
|
+
activeSessionId: string | null;
|
|
13
|
+
threadDetail: ThreadDetail | null;
|
|
14
|
+
loadingThreads: boolean;
|
|
15
|
+
loadingThread: boolean;
|
|
16
|
+
setActiveSessionId: (sessionId: string | null) => void;
|
|
17
|
+
createThread: () => Promise<ThreadSummary>;
|
|
18
|
+
forkThread: (sessionId: string) => Promise<ThreadSummary>;
|
|
19
|
+
interruptThread: (sessionId: string) => Promise<void>;
|
|
20
|
+
refreshThreads: () => Promise<void>;
|
|
21
|
+
refreshThreadDetail: () => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useThreadManager(config: UseThreadManagerConfig): UseThreadManagerReturn {
|
|
25
|
+
const [manager] = useState(() => new ThreadManager(config));
|
|
26
|
+
const [threads, setThreads] = useState<ThreadSummary[]>([]);
|
|
27
|
+
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
|
28
|
+
const [threadDetail, setThreadDetail] = useState<ThreadDetail | null>(null);
|
|
29
|
+
const [loadingThreads, setLoadingThreads] = useState(true);
|
|
30
|
+
const [loadingThread, setLoadingThread] = useState(false);
|
|
31
|
+
|
|
32
|
+
const refreshThreads = useCallback(
|
|
33
|
+
async (preferredSessionId?: string) => {
|
|
34
|
+
setLoadingThreads(true);
|
|
35
|
+
try {
|
|
36
|
+
const nextThreads = await manager.list();
|
|
37
|
+
setThreads(nextThreads);
|
|
38
|
+
|
|
39
|
+
if (preferredSessionId) {
|
|
40
|
+
setActiveSessionId(preferredSessionId);
|
|
41
|
+
} else if (!activeSessionId && nextThreads.length > 0) {
|
|
42
|
+
setActiveSessionId(nextThreads[0].sessionId);
|
|
43
|
+
} else if (nextThreads.length === 0 && config.autoCreate) {
|
|
44
|
+
const thread = await manager.create();
|
|
45
|
+
setThreads([thread]);
|
|
46
|
+
setActiveSessionId(thread.sessionId);
|
|
47
|
+
}
|
|
48
|
+
} finally {
|
|
49
|
+
setLoadingThreads(false);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
[manager, activeSessionId, config.autoCreate],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const refreshThreadDetail = useCallback(async () => {
|
|
56
|
+
if (!activeSessionId) return;
|
|
57
|
+
setLoadingThread(true);
|
|
58
|
+
try {
|
|
59
|
+
const detail = await manager.get(activeSessionId);
|
|
60
|
+
setThreadDetail(detail);
|
|
61
|
+
} finally {
|
|
62
|
+
setLoadingThread(false);
|
|
63
|
+
}
|
|
64
|
+
}, [manager, activeSessionId]);
|
|
65
|
+
|
|
66
|
+
const createThread = useCallback(async () => {
|
|
67
|
+
const thread = await manager.create();
|
|
68
|
+
await refreshThreads(thread.sessionId);
|
|
69
|
+
return thread;
|
|
70
|
+
}, [manager, refreshThreads]);
|
|
71
|
+
|
|
72
|
+
const forkThread = useCallback(
|
|
73
|
+
async (sessionId: string) => {
|
|
74
|
+
const thread = await manager.fork(sessionId);
|
|
75
|
+
await refreshThreads(thread.sessionId);
|
|
76
|
+
return thread;
|
|
77
|
+
},
|
|
78
|
+
[manager, refreshThreads],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const interruptThread = useCallback(
|
|
82
|
+
async (sessionId: string) => {
|
|
83
|
+
await manager.interrupt(sessionId);
|
|
84
|
+
await refreshThreads(sessionId);
|
|
85
|
+
await refreshThreadDetail();
|
|
86
|
+
},
|
|
87
|
+
[manager, refreshThreads, refreshThreadDetail],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
void refreshThreads();
|
|
92
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (activeSessionId) {
|
|
97
|
+
void refreshThreadDetail();
|
|
98
|
+
}
|
|
99
|
+
}, [activeSessionId, refreshThreadDetail]);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
threads,
|
|
103
|
+
activeSessionId,
|
|
104
|
+
threadDetail,
|
|
105
|
+
loadingThreads,
|
|
106
|
+
loadingThread,
|
|
107
|
+
setActiveSessionId,
|
|
108
|
+
createThread,
|
|
109
|
+
forkThread,
|
|
110
|
+
interruptThread,
|
|
111
|
+
refreshThreads: () => refreshThreads(),
|
|
112
|
+
refreshThreadDetail,
|
|
113
|
+
};
|
|
114
|
+
}
|