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