@devicai/ui 0.1.0
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/README.md +363 -0
- package/dist/cjs/api/client.js +118 -0
- package/dist/cjs/api/client.js.map +1 -0
- package/dist/cjs/components/ChatDrawer/ChatDrawer.js +122 -0
- package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -0
- package/dist/cjs/components/ChatDrawer/ChatInput.js +128 -0
- package/dist/cjs/components/ChatDrawer/ChatInput.js.map +1 -0
- package/dist/cjs/components/ChatDrawer/ChatMessages.js +56 -0
- package/dist/cjs/components/ChatDrawer/ChatMessages.js.map +1 -0
- package/dist/cjs/components/ChatDrawer/ToolTimeline.js +26 -0
- package/dist/cjs/components/ChatDrawer/ToolTimeline.js.map +1 -0
- package/dist/cjs/hooks/useDevicChat.js +258 -0
- package/dist/cjs/hooks/useDevicChat.js.map +1 -0
- package/dist/cjs/hooks/useModelInterface.js +130 -0
- package/dist/cjs/hooks/useModelInterface.js.map +1 -0
- package/dist/cjs/hooks/usePolling.js +109 -0
- package/dist/cjs/hooks/usePolling.js.map +1 -0
- package/dist/cjs/index.js +36 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/provider/DevicContext.js +32 -0
- package/dist/cjs/provider/DevicContext.js.map +1 -0
- package/dist/cjs/provider/DevicProvider.js +43 -0
- package/dist/cjs/provider/DevicProvider.js.map +1 -0
- package/dist/cjs/styles.css +1 -0
- package/dist/cjs/utils/index.js +117 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/esm/api/client.d.ts +56 -0
- package/dist/esm/api/client.js +115 -0
- package/dist/esm/api/client.js.map +1 -0
- package/dist/esm/api/types.d.ts +184 -0
- package/dist/esm/components/ChatDrawer/ChatDrawer.d.ts +27 -0
- package/dist/esm/components/ChatDrawer/ChatDrawer.js +120 -0
- package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -0
- package/dist/esm/components/ChatDrawer/ChatDrawer.types.d.ts +197 -0
- package/dist/esm/components/ChatDrawer/ChatInput.d.ts +5 -0
- package/dist/esm/components/ChatDrawer/ChatInput.js +126 -0
- package/dist/esm/components/ChatDrawer/ChatInput.js.map +1 -0
- package/dist/esm/components/ChatDrawer/ChatMessages.d.ts +5 -0
- package/dist/esm/components/ChatDrawer/ChatMessages.js +54 -0
- package/dist/esm/components/ChatDrawer/ChatMessages.js.map +1 -0
- package/dist/esm/components/ChatDrawer/ToolTimeline.d.ts +5 -0
- package/dist/esm/components/ChatDrawer/ToolTimeline.js +24 -0
- package/dist/esm/components/ChatDrawer/ToolTimeline.js.map +1 -0
- package/dist/esm/components/ChatDrawer/index.d.ts +5 -0
- package/dist/esm/hooks/index.d.ts +6 -0
- package/dist/esm/hooks/useDevicChat.d.ts +120 -0
- package/dist/esm/hooks/useDevicChat.js +256 -0
- package/dist/esm/hooks/useDevicChat.js.map +1 -0
- package/dist/esm/hooks/useModelInterface.d.ts +68 -0
- package/dist/esm/hooks/useModelInterface.js +128 -0
- package/dist/esm/hooks/useModelInterface.js.map +1 -0
- package/dist/esm/hooks/usePolling.d.ts +64 -0
- package/dist/esm/hooks/usePolling.js +107 -0
- package/dist/esm/hooks/usePolling.js.map +1 -0
- package/dist/esm/index.d.ts +10 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/provider/DevicContext.d.ts +15 -0
- package/dist/esm/provider/DevicContext.js +28 -0
- package/dist/esm/provider/DevicContext.js.map +1 -0
- package/dist/esm/provider/DevicProvider.d.ts +17 -0
- package/dist/esm/provider/DevicProvider.js +41 -0
- package/dist/esm/provider/DevicProvider.js.map +1 -0
- package/dist/esm/provider/index.d.ts +3 -0
- package/dist/esm/provider/types.d.ts +58 -0
- package/dist/esm/styles.css +1 -0
- package/dist/esm/utils/index.d.ts +32 -0
- package/dist/esm/utils/index.js +109 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
|
|
6
|
+
const FILE_TYPE_ACCEPT = {
|
|
7
|
+
images: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
|
8
|
+
documents: [
|
|
9
|
+
'application/pdf',
|
|
10
|
+
'application/msword',
|
|
11
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
12
|
+
'text/plain',
|
|
13
|
+
'text/csv',
|
|
14
|
+
],
|
|
15
|
+
audio: ['audio/mpeg', 'audio/wav', 'audio/ogg'],
|
|
16
|
+
video: ['video/mp4', 'video/webm', 'video/ogg'],
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Chat input component with file upload support
|
|
20
|
+
*/
|
|
21
|
+
function ChatInput({ onSend, disabled = false, placeholder = 'Type a message...', enableFileUploads = false, allowedFileTypes = { images: true, documents: true }, maxFileSize = 10 * 1024 * 1024, // 10MB
|
|
22
|
+
}) {
|
|
23
|
+
const [message, setMessage] = react.useState('');
|
|
24
|
+
const [files, setFiles] = react.useState([]);
|
|
25
|
+
const textareaRef = react.useRef(null);
|
|
26
|
+
const fileInputRef = react.useRef(null);
|
|
27
|
+
// Calculate accepted file types
|
|
28
|
+
const acceptedTypes = Object.entries(allowedFileTypes)
|
|
29
|
+
.filter(([, enabled]) => enabled)
|
|
30
|
+
.flatMap(([type]) => FILE_TYPE_ACCEPT[type] || [])
|
|
31
|
+
.join(',');
|
|
32
|
+
// Auto-resize textarea
|
|
33
|
+
const handleInput = react.useCallback(() => {
|
|
34
|
+
const textarea = textareaRef.current;
|
|
35
|
+
if (textarea) {
|
|
36
|
+
textarea.style.height = 'auto';
|
|
37
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
// Handle send
|
|
41
|
+
const handleSend = react.useCallback(() => {
|
|
42
|
+
const trimmedMessage = message.trim();
|
|
43
|
+
if (!trimmedMessage && files.length === 0)
|
|
44
|
+
return;
|
|
45
|
+
// Convert File objects to ChatFile format
|
|
46
|
+
const chatFiles = files.map((file) => ({
|
|
47
|
+
name: file.name,
|
|
48
|
+
fileType: getFileType(file.type),
|
|
49
|
+
}));
|
|
50
|
+
onSend(trimmedMessage, chatFiles.length > 0 ? chatFiles : undefined);
|
|
51
|
+
setMessage('');
|
|
52
|
+
setFiles([]);
|
|
53
|
+
// Reset textarea height
|
|
54
|
+
if (textareaRef.current) {
|
|
55
|
+
textareaRef.current.style.height = 'auto';
|
|
56
|
+
}
|
|
57
|
+
}, [message, files, onSend]);
|
|
58
|
+
// Handle key press
|
|
59
|
+
const handleKeyDown = react.useCallback((e) => {
|
|
60
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
handleSend();
|
|
63
|
+
}
|
|
64
|
+
}, [handleSend]);
|
|
65
|
+
// Handle file selection
|
|
66
|
+
const handleFileSelect = react.useCallback((e) => {
|
|
67
|
+
const selectedFiles = Array.from(e.target.files || []);
|
|
68
|
+
// Filter valid files
|
|
69
|
+
const validFiles = selectedFiles.filter((file) => {
|
|
70
|
+
if (file.size > maxFileSize) {
|
|
71
|
+
console.warn(`File ${file.name} exceeds maximum size`);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
});
|
|
76
|
+
setFiles((prev) => [...prev, ...validFiles]);
|
|
77
|
+
// Reset input
|
|
78
|
+
if (fileInputRef.current) {
|
|
79
|
+
fileInputRef.current.value = '';
|
|
80
|
+
}
|
|
81
|
+
}, [maxFileSize]);
|
|
82
|
+
// Remove file
|
|
83
|
+
const removeFile = react.useCallback((index) => {
|
|
84
|
+
setFiles((prev) => prev.filter((_, i) => i !== index));
|
|
85
|
+
}, []);
|
|
86
|
+
return (jsxRuntime.jsxs("div", { className: "devic-input-area", children: [files.length > 0 && (jsxRuntime.jsx("div", { className: "devic-file-preview", children: files.map((file, idx) => (jsxRuntime.jsxs("div", { className: "devic-file-preview-item", children: [jsxRuntime.jsx(FileIcon, {}), jsxRuntime.jsx("span", { children: file.name }), jsxRuntime.jsx("button", { className: "devic-file-remove", onClick: () => removeFile(idx), type: "button", children: "\u00D7" })] }, idx))) })), jsxRuntime.jsxs("div", { className: "devic-input-wrapper", children: [enableFileUploads && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", accept: acceptedTypes, multiple: true, onChange: handleFileSelect, style: { display: 'none' } }), jsxRuntime.jsx("button", { className: "devic-input-btn", onClick: () => fileInputRef.current?.click(), disabled: disabled, type: "button", title: "Attach file", children: jsxRuntime.jsx(AttachIcon, {}) })] })), jsxRuntime.jsx("textarea", { ref: textareaRef, className: "devic-input", value: message, onChange: (e) => {
|
|
87
|
+
setMessage(e.target.value);
|
|
88
|
+
handleInput();
|
|
89
|
+
}, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: disabled, rows: 1 }), jsxRuntime.jsx("button", { className: "devic-input-btn devic-send-btn", onClick: handleSend, disabled: disabled || (!message.trim() && files.length === 0), type: "button", title: "Send message", children: jsxRuntime.jsx(SendIcon, {}) })] })] }));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get file type category from MIME type
|
|
93
|
+
*/
|
|
94
|
+
function getFileType(mimeType) {
|
|
95
|
+
if (mimeType.startsWith('image/'))
|
|
96
|
+
return 'image';
|
|
97
|
+
if (mimeType.startsWith('audio/'))
|
|
98
|
+
return 'audio';
|
|
99
|
+
if (mimeType.startsWith('video/'))
|
|
100
|
+
return 'video';
|
|
101
|
+
if (mimeType.startsWith('application/pdf') ||
|
|
102
|
+
mimeType.startsWith('application/msword') ||
|
|
103
|
+
mimeType.startsWith('text/')) {
|
|
104
|
+
return 'document';
|
|
105
|
+
}
|
|
106
|
+
return 'other';
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Attach icon
|
|
110
|
+
*/
|
|
111
|
+
function AttachIcon() {
|
|
112
|
+
return (jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) }));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Send icon
|
|
116
|
+
*/
|
|
117
|
+
function SendIcon() {
|
|
118
|
+
return (jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", children: jsxRuntime.jsx("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) }));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* File icon
|
|
122
|
+
*/
|
|
123
|
+
function FileIcon() {
|
|
124
|
+
return (jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), jsxRuntime.jsx("polyline", { points: "14,2 14,8 20,8" })] }));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
exports.ChatInput = ChatInput;
|
|
128
|
+
//# sourceMappingURL=ChatInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatInput.js","sources":["../../../../../src/components/ChatDrawer/ChatInput.tsx"],"sourcesContent":["import React, { useState, useRef, useCallback } from 'react';\nimport type { ChatInputProps } from './ChatDrawer.types';\nimport type { ChatFile } from '../../api/types';\n\nconst FILE_TYPE_ACCEPT: Record<string, string[]> = {\n images: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],\n documents: [\n 'application/pdf',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'text/plain',\n 'text/csv',\n ],\n audio: ['audio/mpeg', 'audio/wav', 'audio/ogg'],\n video: ['video/mp4', 'video/webm', 'video/ogg'],\n};\n\n/**\n * Chat input component with file upload support\n */\nexport function ChatInput({\n onSend,\n disabled = false,\n placeholder = 'Type a message...',\n enableFileUploads = false,\n allowedFileTypes = { images: true, documents: true },\n maxFileSize = 10 * 1024 * 1024, // 10MB\n}: ChatInputProps): JSX.Element {\n const [message, setMessage] = useState('');\n const [files, setFiles] = useState<File[]>([]);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n // Calculate accepted file types\n const acceptedTypes = Object.entries(allowedFileTypes)\n .filter(([, enabled]) => enabled)\n .flatMap(([type]) => FILE_TYPE_ACCEPT[type] || [])\n .join(',');\n\n // Auto-resize textarea\n const handleInput = useCallback(() => {\n const textarea = textareaRef.current;\n if (textarea) {\n textarea.style.height = 'auto';\n textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;\n }\n }, []);\n\n // Handle send\n const handleSend = useCallback(() => {\n const trimmedMessage = message.trim();\n if (!trimmedMessage && files.length === 0) return;\n\n // Convert File objects to ChatFile format\n const chatFiles: ChatFile[] = files.map((file) => ({\n name: file.name,\n fileType: getFileType(file.type),\n }));\n\n onSend(trimmedMessage, chatFiles.length > 0 ? chatFiles : undefined);\n setMessage('');\n setFiles([]);\n\n // Reset textarea height\n if (textareaRef.current) {\n textareaRef.current.style.height = 'auto';\n }\n }, [message, files, onSend]);\n\n // Handle key press\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n },\n [handleSend]\n );\n\n // Handle file selection\n const handleFileSelect = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const selectedFiles = Array.from(e.target.files || []);\n\n // Filter valid files\n const validFiles = selectedFiles.filter((file) => {\n if (file.size > maxFileSize) {\n console.warn(`File ${file.name} exceeds maximum size`);\n return false;\n }\n return true;\n });\n\n setFiles((prev) => [...prev, ...validFiles]);\n\n // Reset input\n if (fileInputRef.current) {\n fileInputRef.current.value = '';\n }\n },\n [maxFileSize]\n );\n\n // Remove file\n const removeFile = useCallback((index: number) => {\n setFiles((prev) => prev.filter((_, i) => i !== index));\n }, []);\n\n return (\n <div className=\"devic-input-area\">\n {files.length > 0 && (\n <div className=\"devic-file-preview\">\n {files.map((file, idx) => (\n <div key={idx} className=\"devic-file-preview-item\">\n <FileIcon />\n <span>{file.name}</span>\n <button\n className=\"devic-file-remove\"\n onClick={() => removeFile(idx)}\n type=\"button\"\n >\n ×\n </button>\n </div>\n ))}\n </div>\n )}\n\n <div className=\"devic-input-wrapper\">\n {enableFileUploads && (\n <>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={acceptedTypes}\n multiple\n onChange={handleFileSelect}\n style={{ display: 'none' }}\n />\n <button\n className=\"devic-input-btn\"\n onClick={() => fileInputRef.current?.click()}\n disabled={disabled}\n type=\"button\"\n title=\"Attach file\"\n >\n <AttachIcon />\n </button>\n </>\n )}\n\n <textarea\n ref={textareaRef}\n className=\"devic-input\"\n value={message}\n onChange={(e) => {\n setMessage(e.target.value);\n handleInput();\n }}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled}\n rows={1}\n />\n\n <button\n className=\"devic-input-btn devic-send-btn\"\n onClick={handleSend}\n disabled={disabled || (!message.trim() && files.length === 0)}\n type=\"button\"\n title=\"Send message\"\n >\n <SendIcon />\n </button>\n </div>\n </div>\n );\n}\n\n/**\n * Get file type category from MIME type\n */\nfunction getFileType(\n mimeType: string\n): 'image' | 'document' | 'audio' | 'video' | 'other' {\n if (mimeType.startsWith('image/')) return 'image';\n if (mimeType.startsWith('audio/')) return 'audio';\n if (mimeType.startsWith('video/')) return 'video';\n if (\n mimeType.startsWith('application/pdf') ||\n mimeType.startsWith('application/msword') ||\n mimeType.startsWith('text/')\n ) {\n return 'document';\n }\n return 'other';\n}\n\n/**\n * Attach icon\n */\nfunction AttachIcon(): JSX.Element {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48\" />\n </svg>\n );\n}\n\n/**\n * Send icon\n */\nfunction SendIcon(): JSX.Element {\n return (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <path d=\"M2.01 21L23 12 2.01 3 2 10l15 2-15 2z\" />\n </svg>\n );\n}\n\n/**\n * File icon\n */\nfunction FileIcon(): JSX.Element {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n <polyline points=\"14,2 14,8 20,8\" />\n </svg>\n );\n}\n"],"names":["useState","useRef","useCallback","_jsxs","_jsx","_Fragment"],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var ToolTimeline = require('./ToolTimeline.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Format timestamp to readable time
|
|
9
|
+
*/
|
|
10
|
+
function formatTime(timestamp) {
|
|
11
|
+
return new Date(timestamp).toLocaleTimeString([], {
|
|
12
|
+
hour: '2-digit',
|
|
13
|
+
minute: '2-digit',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Messages list component
|
|
18
|
+
*/
|
|
19
|
+
function ChatMessages({ messages, isLoading, welcomeMessage, suggestedMessages, onSuggestedClick, showToolTimeline = true, }) {
|
|
20
|
+
const containerRef = react.useRef(null);
|
|
21
|
+
const lastMessageCountRef = react.useRef(messages.length);
|
|
22
|
+
// Auto-scroll to bottom when new messages arrive
|
|
23
|
+
react.useEffect(() => {
|
|
24
|
+
if (messages.length > lastMessageCountRef.current && containerRef.current) {
|
|
25
|
+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
26
|
+
}
|
|
27
|
+
lastMessageCountRef.current = messages.length;
|
|
28
|
+
}, [messages.length]);
|
|
29
|
+
// Extract tool calls for timeline
|
|
30
|
+
const toolCalls = showToolTimeline
|
|
31
|
+
? messages
|
|
32
|
+
.filter((m) => m.tool_calls?.length)
|
|
33
|
+
.flatMap((m) => m.tool_calls.map((tc) => ({
|
|
34
|
+
id: tc.id,
|
|
35
|
+
name: tc.function.name,
|
|
36
|
+
status: 'completed',
|
|
37
|
+
timestamp: m.timestamp,
|
|
38
|
+
})))
|
|
39
|
+
: [];
|
|
40
|
+
return (jsxRuntime.jsxs("div", { className: "devic-messages-container", ref: containerRef, children: [messages.length === 0 && (welcomeMessage || suggestedMessages?.length) && (jsxRuntime.jsxs("div", { className: "devic-welcome", children: [welcomeMessage && (jsxRuntime.jsx("p", { className: "devic-welcome-text", children: welcomeMessage })), suggestedMessages && suggestedMessages.length > 0 && (jsxRuntime.jsx("div", { className: "devic-suggested-messages", children: suggestedMessages.map((msg, idx) => (jsxRuntime.jsx("button", { className: "devic-suggested-btn", onClick: () => onSuggestedClick?.(msg), children: msg }, idx))) }))] })), messages.map((message) => {
|
|
41
|
+
// Skip tool messages in main display (shown in timeline)
|
|
42
|
+
if (message.role === 'tool' && showToolTimeline) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return (jsxRuntime.jsxs("div", { className: "devic-message", "data-role": message.role, children: [jsxRuntime.jsxs("div", { className: "devic-message-bubble", children: [message.content.message, message.content.files && message.content.files.length > 0 && (jsxRuntime.jsx("div", { className: "devic-message-files", children: message.content.files.map((file, idx) => (jsxRuntime.jsxs("div", { className: "devic-message-file", children: [jsxRuntime.jsx(FileIcon, {}), jsxRuntime.jsx("span", { children: file.name })] }, idx))) }))] }), jsxRuntime.jsx("span", { className: "devic-message-time", children: formatTime(message.timestamp) })] }, message.uid));
|
|
46
|
+
}), showToolTimeline && toolCalls.length > 0 && (jsxRuntime.jsx(ToolTimeline.ToolTimeline, { toolCalls: toolCalls })), isLoading && (jsxRuntime.jsxs("div", { className: "devic-loading", children: [jsxRuntime.jsx("span", { className: "devic-loading-dot" }), jsxRuntime.jsx("span", { className: "devic-loading-dot" }), jsxRuntime.jsx("span", { className: "devic-loading-dot" })] }))] }));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Simple file icon
|
|
50
|
+
*/
|
|
51
|
+
function FileIcon() {
|
|
52
|
+
return (jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), jsxRuntime.jsx("polyline", { points: "14,2 14,8 20,8" })] }));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
exports.ChatMessages = ChatMessages;
|
|
56
|
+
//# sourceMappingURL=ChatMessages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatMessages.js","sources":["../../../../../src/components/ChatDrawer/ChatMessages.tsx"],"sourcesContent":["import React, { useEffect, useRef } from 'react';\nimport type { ChatMessagesProps } from './ChatDrawer.types';\nimport { ToolTimeline } from './ToolTimeline';\n\n/**\n * Format timestamp to readable time\n */\nfunction formatTime(timestamp: number): string {\n return new Date(timestamp).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\n/**\n * Messages list component\n */\nexport function ChatMessages({\n messages,\n isLoading,\n welcomeMessage,\n suggestedMessages,\n onSuggestedClick,\n showToolTimeline = true,\n}: ChatMessagesProps): JSX.Element {\n const containerRef = useRef<HTMLDivElement>(null);\n const lastMessageCountRef = useRef(messages.length);\n\n // Auto-scroll to bottom when new messages arrive\n useEffect(() => {\n if (messages.length > lastMessageCountRef.current && containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n lastMessageCountRef.current = messages.length;\n }, [messages.length]);\n\n // Extract tool calls for timeline\n const toolCalls = showToolTimeline\n ? messages\n .filter((m) => m.tool_calls?.length)\n .flatMap((m) =>\n m.tool_calls!.map((tc) => ({\n id: tc.id,\n name: tc.function.name,\n status: 'completed' as const,\n timestamp: m.timestamp,\n }))\n )\n : [];\n\n return (\n <div className=\"devic-messages-container\" ref={containerRef}>\n {messages.length === 0 && (welcomeMessage || suggestedMessages?.length) && (\n <div className=\"devic-welcome\">\n {welcomeMessage && (\n <p className=\"devic-welcome-text\">{welcomeMessage}</p>\n )}\n {suggestedMessages && suggestedMessages.length > 0 && (\n <div className=\"devic-suggested-messages\">\n {suggestedMessages.map((msg, idx) => (\n <button\n key={idx}\n className=\"devic-suggested-btn\"\n onClick={() => onSuggestedClick?.(msg)}\n >\n {msg}\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n\n {messages.map((message) => {\n // Skip tool messages in main display (shown in timeline)\n if (message.role === 'tool' && showToolTimeline) {\n return null;\n }\n\n return (\n <div\n key={message.uid}\n className=\"devic-message\"\n data-role={message.role}\n >\n <div className=\"devic-message-bubble\">\n {message.content.message}\n {message.content.files && message.content.files.length > 0 && (\n <div className=\"devic-message-files\">\n {message.content.files.map((file, idx) => (\n <div key={idx} className=\"devic-message-file\">\n <FileIcon />\n <span>{file.name}</span>\n </div>\n ))}\n </div>\n )}\n </div>\n <span className=\"devic-message-time\">\n {formatTime(message.timestamp)}\n </span>\n </div>\n );\n })}\n\n {showToolTimeline && toolCalls.length > 0 && (\n <ToolTimeline toolCalls={toolCalls} />\n )}\n\n {isLoading && (\n <div className=\"devic-loading\">\n <span className=\"devic-loading-dot\"></span>\n <span className=\"devic-loading-dot\"></span>\n <span className=\"devic-loading-dot\"></span>\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Simple file icon\n */\nfunction FileIcon(): JSX.Element {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n <polyline points=\"14,2 14,8 20,8\" />\n </svg>\n );\n}\n"],"names":["useRef","useEffect","_jsxs","_jsx","ToolTimeline"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tool execution timeline component
|
|
7
|
+
*/
|
|
8
|
+
function ToolTimeline({ toolCalls }) {
|
|
9
|
+
if (toolCalls.length === 0) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return (jsxRuntime.jsx("div", { className: "devic-tool-timeline", children: toolCalls.map((tool) => (jsxRuntime.jsxs("div", { className: "devic-tool-item", children: [jsxRuntime.jsx("span", { className: "devic-tool-status", "data-status": tool.status }), jsxRuntime.jsx("span", { className: "devic-tool-name", children: formatToolName(tool.name) }), tool.status === 'error' && tool.error && (jsxRuntime.jsx("span", { className: "devic-tool-error", children: tool.error }))] }, tool.id))) }));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Format tool name for display
|
|
16
|
+
*/
|
|
17
|
+
function formatToolName(name) {
|
|
18
|
+
// Convert snake_case or camelCase to Title Case with spaces
|
|
19
|
+
return name
|
|
20
|
+
.replace(/_/g, ' ')
|
|
21
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
22
|
+
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
exports.ToolTimeline = ToolTimeline;
|
|
26
|
+
//# sourceMappingURL=ToolTimeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToolTimeline.js","sources":["../../../../../src/components/ChatDrawer/ToolTimeline.tsx"],"sourcesContent":["import React from 'react';\nimport type { ToolTimelineProps } from './ChatDrawer.types';\n\n/**\n * Tool execution timeline component\n */\nexport function ToolTimeline({ toolCalls }: ToolTimelineProps): JSX.Element | null {\n if (toolCalls.length === 0) {\n return null;\n }\n\n return (\n <div className=\"devic-tool-timeline\">\n {toolCalls.map((tool) => (\n <div key={tool.id} className=\"devic-tool-item\">\n <span className=\"devic-tool-status\" data-status={tool.status} />\n <span className=\"devic-tool-name\">{formatToolName(tool.name)}</span>\n {tool.status === 'error' && tool.error && (\n <span className=\"devic-tool-error\">{tool.error}</span>\n )}\n </div>\n ))}\n </div>\n );\n}\n\n/**\n * Format tool name for display\n */\nfunction formatToolName(name: string): string {\n // Convert snake_case or camelCase to Title Case with spaces\n return name\n .replace(/_/g, ' ')\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n"],"names":["_jsx","_jsxs"],"mappings":";;;;AAGA;;AAEG;AACG,SAAU,YAAY,CAAC,EAAE,SAAS,EAAqB,EAAA;AAC3D,IAAA,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE;AAC1B,QAAA,OAAO,IAAI;IACb;AAEA,IAAA,QACEA,cAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,qBAAqB,EAAA,QAAA,EACjC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,MAClBC,eAAA,CAAA,KAAA,EAAA,EAAmB,SAAS,EAAC,iBAAiB,EAAA,QAAA,EAAA,CAC5CD,cAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,mBAAmB,EAAA,aAAA,EAAc,IAAI,CAAC,MAAM,GAAI,EAChEA,cAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,iBAAiB,EAAA,QAAA,EAAE,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAA,CAAQ,EACnE,IAAI,CAAC,MAAM,KAAK,OAAO,IAAI,IAAI,CAAC,KAAK,KACpCA,cAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,kBAAkB,EAAA,QAAA,EAAE,IAAI,CAAC,KAAK,GAAQ,CACvD,CAAA,EAAA,EALO,IAAI,CAAC,EAAE,CAMX,CACP,CAAC,EAAA,CACE;AAEV;AAEA;;AAEG;AACH,SAAS,cAAc,CAAC,IAAY,EAAA;;AAElC,IAAA,OAAO;AACJ,SAAA,OAAO,CAAC,IAAI,EAAE,GAAG;AACjB,SAAA,OAAO,CAAC,iBAAiB,EAAE,OAAO;AAClC,SAAA,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;AACnD;;;;"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
require('react/jsx-runtime');
|
|
5
|
+
var DevicContext = require('../provider/DevicContext.js');
|
|
6
|
+
var client = require('../api/client.js');
|
|
7
|
+
var usePolling = require('./usePolling.js');
|
|
8
|
+
var useModelInterface = require('./useModelInterface.js');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Main hook for managing chat with a Devic assistant
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const {
|
|
16
|
+
* messages,
|
|
17
|
+
* isLoading,
|
|
18
|
+
* sendMessage,
|
|
19
|
+
* } = useDevicChat({
|
|
20
|
+
* assistantId: 'my-assistant',
|
|
21
|
+
* modelInterfaceTools: [
|
|
22
|
+
* {
|
|
23
|
+
* toolName: 'get_user_location',
|
|
24
|
+
* schema: { ... },
|
|
25
|
+
* callback: async () => ({ lat: 40.7, lng: -74.0 })
|
|
26
|
+
* }
|
|
27
|
+
* ],
|
|
28
|
+
* onMessageReceived: (msg) => console.log('Received:', msg),
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function useDevicChat(options) {
|
|
33
|
+
const { assistantId, chatUid: initialChatUid, apiKey: propsApiKey, baseUrl: propsBaseUrl, tenantId, tenantMetadata, enabledTools, modelInterfaceTools = [], pollingInterval = 1000, onMessageSent, onMessageReceived, onToolCall, onError, onChatCreated, } = options;
|
|
34
|
+
// Get context (may be null if not wrapped in provider)
|
|
35
|
+
const context = DevicContext.useOptionalDevicContext();
|
|
36
|
+
// Resolve configuration
|
|
37
|
+
const apiKey = propsApiKey || context?.apiKey;
|
|
38
|
+
const baseUrl = propsBaseUrl || context?.baseUrl || 'https://api.devic.ai';
|
|
39
|
+
const resolvedTenantId = tenantId || context?.tenantId;
|
|
40
|
+
const resolvedTenantMetadata = { ...context?.tenantMetadata, ...tenantMetadata };
|
|
41
|
+
// State
|
|
42
|
+
const [messages, setMessages] = react.useState([]);
|
|
43
|
+
const [chatUid, setChatUid] = react.useState(initialChatUid || null);
|
|
44
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
45
|
+
const [status, setStatus] = react.useState('idle');
|
|
46
|
+
const [error, setError] = react.useState(null);
|
|
47
|
+
// Polling state
|
|
48
|
+
const [shouldPoll, setShouldPoll] = react.useState(false);
|
|
49
|
+
// Refs for callbacks
|
|
50
|
+
const onMessageReceivedRef = react.useRef(onMessageReceived);
|
|
51
|
+
const onErrorRef = react.useRef(onError);
|
|
52
|
+
const onChatCreatedRef = react.useRef(onChatCreated);
|
|
53
|
+
react.useEffect(() => {
|
|
54
|
+
onMessageReceivedRef.current = onMessageReceived;
|
|
55
|
+
onErrorRef.current = onError;
|
|
56
|
+
onChatCreatedRef.current = onChatCreated;
|
|
57
|
+
});
|
|
58
|
+
// Create API client
|
|
59
|
+
const clientRef = react.useRef(null);
|
|
60
|
+
if (!clientRef.current && apiKey) {
|
|
61
|
+
clientRef.current = new client.DevicApiClient({ apiKey, baseUrl });
|
|
62
|
+
}
|
|
63
|
+
// Update client config if it changes
|
|
64
|
+
react.useEffect(() => {
|
|
65
|
+
if (clientRef.current && apiKey) {
|
|
66
|
+
clientRef.current.setConfig({ apiKey, baseUrl });
|
|
67
|
+
}
|
|
68
|
+
}, [apiKey, baseUrl]);
|
|
69
|
+
// Model interface hook
|
|
70
|
+
const { toolSchemas, handleToolCalls, extractPendingToolCalls, } = useModelInterface.useModelInterface({
|
|
71
|
+
tools: modelInterfaceTools,
|
|
72
|
+
onToolExecute: onToolCall,
|
|
73
|
+
});
|
|
74
|
+
// Polling hook
|
|
75
|
+
usePolling.usePolling(shouldPoll ? chatUid : null, async () => {
|
|
76
|
+
if (!clientRef.current || !chatUid) {
|
|
77
|
+
throw new Error('Cannot poll without client or chatUid');
|
|
78
|
+
}
|
|
79
|
+
return clientRef.current.getRealtimeHistory(assistantId, chatUid);
|
|
80
|
+
}, {
|
|
81
|
+
interval: pollingInterval,
|
|
82
|
+
enabled: shouldPoll,
|
|
83
|
+
stopStatuses: ['completed', 'error', 'waiting_for_tool_response'],
|
|
84
|
+
onUpdate: async (data) => {
|
|
85
|
+
setMessages(data.chatHistory);
|
|
86
|
+
setStatus(data.status);
|
|
87
|
+
// Notify about new messages
|
|
88
|
+
const lastMessage = data.chatHistory[data.chatHistory.length - 1];
|
|
89
|
+
if (lastMessage && lastMessage.role === 'assistant') {
|
|
90
|
+
onMessageReceivedRef.current?.(lastMessage);
|
|
91
|
+
}
|
|
92
|
+
// Handle model interface - check for pending tool calls
|
|
93
|
+
if (data.status === 'waiting_for_tool_response' || data.pendingToolCalls?.length) {
|
|
94
|
+
await handlePendingToolCalls(data);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
onStop: async (data) => {
|
|
98
|
+
setIsLoading(false);
|
|
99
|
+
setShouldPoll(false);
|
|
100
|
+
if (data?.status === 'error') {
|
|
101
|
+
const err = new Error('Chat processing failed');
|
|
102
|
+
setError(err);
|
|
103
|
+
onErrorRef.current?.(err);
|
|
104
|
+
}
|
|
105
|
+
else if (data?.status === 'waiting_for_tool_response') {
|
|
106
|
+
// Handle tool response
|
|
107
|
+
await handlePendingToolCalls(data);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
onError: (err) => {
|
|
111
|
+
setError(err);
|
|
112
|
+
setIsLoading(false);
|
|
113
|
+
setShouldPoll(false);
|
|
114
|
+
onErrorRef.current?.(err);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
// Handle pending tool calls from model interface
|
|
118
|
+
const handlePendingToolCalls = react.useCallback(async (data) => {
|
|
119
|
+
if (!clientRef.current || !chatUid)
|
|
120
|
+
return;
|
|
121
|
+
// Get pending tool calls
|
|
122
|
+
const pendingCalls = data.pendingToolCalls || extractPendingToolCalls(data.chatHistory);
|
|
123
|
+
if (pendingCalls.length === 0)
|
|
124
|
+
return;
|
|
125
|
+
try {
|
|
126
|
+
// Execute client-side tools
|
|
127
|
+
const responses = await handleToolCalls(pendingCalls);
|
|
128
|
+
if (responses.length > 0) {
|
|
129
|
+
// Send tool responses back to the API
|
|
130
|
+
await clientRef.current.sendToolResponses(assistantId, chatUid, responses);
|
|
131
|
+
// Resume polling
|
|
132
|
+
setShouldPoll(true);
|
|
133
|
+
setIsLoading(true);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
138
|
+
setError(error);
|
|
139
|
+
onErrorRef.current?.(error);
|
|
140
|
+
}
|
|
141
|
+
}, [chatUid, assistantId, handleToolCalls, extractPendingToolCalls]);
|
|
142
|
+
// Send a message
|
|
143
|
+
const sendMessage = react.useCallback(async (message, sendOptions) => {
|
|
144
|
+
if (!clientRef.current) {
|
|
145
|
+
const err = new Error('API client not configured. Please provide an API key.');
|
|
146
|
+
setError(err);
|
|
147
|
+
onErrorRef.current?.(err);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
setIsLoading(true);
|
|
151
|
+
setError(null);
|
|
152
|
+
setStatus('processing');
|
|
153
|
+
// Add user message optimistically
|
|
154
|
+
const userMessage = {
|
|
155
|
+
uid: `temp-${Date.now()}`,
|
|
156
|
+
role: 'user',
|
|
157
|
+
content: {
|
|
158
|
+
message,
|
|
159
|
+
files: sendOptions?.files?.map((f) => ({
|
|
160
|
+
name: f.name,
|
|
161
|
+
url: f.downloadUrl || '',
|
|
162
|
+
type: f.fileType || 'other',
|
|
163
|
+
})),
|
|
164
|
+
},
|
|
165
|
+
timestamp: Date.now(),
|
|
166
|
+
};
|
|
167
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
168
|
+
onMessageSent?.(userMessage);
|
|
169
|
+
try {
|
|
170
|
+
// Build request DTO
|
|
171
|
+
const dto = {
|
|
172
|
+
message,
|
|
173
|
+
chatUid: chatUid || undefined,
|
|
174
|
+
files: sendOptions?.files,
|
|
175
|
+
metadata: {
|
|
176
|
+
...resolvedTenantMetadata,
|
|
177
|
+
...sendOptions?.metadata,
|
|
178
|
+
},
|
|
179
|
+
tenantId: resolvedTenantId,
|
|
180
|
+
enabledTools,
|
|
181
|
+
// Include model interface tools if any
|
|
182
|
+
...(toolSchemas.length > 0 && { tools: toolSchemas }),
|
|
183
|
+
};
|
|
184
|
+
// Send message in async mode
|
|
185
|
+
const response = await clientRef.current.sendMessageAsync(assistantId, dto);
|
|
186
|
+
// Update chat UID if this is a new chat
|
|
187
|
+
if (response.chatUid && response.chatUid !== chatUid) {
|
|
188
|
+
setChatUid(response.chatUid);
|
|
189
|
+
onChatCreatedRef.current?.(response.chatUid);
|
|
190
|
+
}
|
|
191
|
+
// Start polling for results
|
|
192
|
+
setShouldPoll(true);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
196
|
+
setError(error);
|
|
197
|
+
setIsLoading(false);
|
|
198
|
+
setStatus('error');
|
|
199
|
+
onErrorRef.current?.(error);
|
|
200
|
+
// Remove optimistic user message on error
|
|
201
|
+
setMessages((prev) => prev.filter((m) => m.uid !== userMessage.uid));
|
|
202
|
+
}
|
|
203
|
+
}, [
|
|
204
|
+
chatUid,
|
|
205
|
+
assistantId,
|
|
206
|
+
enabledTools,
|
|
207
|
+
resolvedTenantId,
|
|
208
|
+
resolvedTenantMetadata,
|
|
209
|
+
toolSchemas,
|
|
210
|
+
onMessageSent,
|
|
211
|
+
]);
|
|
212
|
+
// Clear chat
|
|
213
|
+
const clearChat = react.useCallback(() => {
|
|
214
|
+
setMessages([]);
|
|
215
|
+
setChatUid(null);
|
|
216
|
+
setStatus('idle');
|
|
217
|
+
setError(null);
|
|
218
|
+
setShouldPoll(false);
|
|
219
|
+
}, []);
|
|
220
|
+
// Load existing chat
|
|
221
|
+
const loadChat = react.useCallback(async (loadChatUid) => {
|
|
222
|
+
if (!clientRef.current) {
|
|
223
|
+
const err = new Error('API client not configured');
|
|
224
|
+
setError(err);
|
|
225
|
+
onErrorRef.current?.(err);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
setIsLoading(true);
|
|
229
|
+
setError(null);
|
|
230
|
+
try {
|
|
231
|
+
const history = await clientRef.current.getChatHistory(assistantId, loadChatUid);
|
|
232
|
+
setMessages(history.chatContent);
|
|
233
|
+
setChatUid(loadChatUid);
|
|
234
|
+
setStatus('completed');
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
238
|
+
setError(error);
|
|
239
|
+
onErrorRef.current?.(error);
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
setIsLoading(false);
|
|
243
|
+
}
|
|
244
|
+
}, [assistantId]);
|
|
245
|
+
return {
|
|
246
|
+
messages,
|
|
247
|
+
chatUid,
|
|
248
|
+
isLoading,
|
|
249
|
+
status,
|
|
250
|
+
error,
|
|
251
|
+
sendMessage,
|
|
252
|
+
clearChat,
|
|
253
|
+
loadChat,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
exports.useDevicChat = useDevicChat;
|
|
258
|
+
//# sourceMappingURL=useDevicChat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDevicChat.js","sources":["../../../../src/hooks/useDevicChat.ts"],"sourcesContent":["import { useState, useCallback, useEffect, useRef } from 'react';\nimport { useOptionalDevicContext } from '../provider';\nimport { DevicApiClient } from '../api/client';\nimport { usePolling } from './usePolling';\nimport { useModelInterface } from './useModelInterface';\nimport type {\n ChatMessage,\n ChatFile,\n ModelInterfaceTool,\n RealtimeChatHistory,\n RealtimeStatus,\n} from '../api/types';\n\nexport interface UseDevicChatOptions {\n /**\n * Assistant identifier\n */\n assistantId: string;\n\n /**\n * Existing chat UID to continue a conversation\n */\n chatUid?: string;\n\n /**\n * API key (overrides provider context)\n */\n apiKey?: string;\n\n /**\n * Base URL (overrides provider context)\n */\n baseUrl?: string;\n\n /**\n * Tenant ID for multi-tenant environments\n */\n tenantId?: string;\n\n /**\n * Tenant metadata\n */\n tenantMetadata?: Record<string, any>;\n\n /**\n * Tools enabled from the assistant's configured tool groups\n */\n enabledTools?: string[];\n\n /**\n * Client-side tools for model interface protocol\n */\n modelInterfaceTools?: ModelInterfaceTool[];\n\n /**\n * Polling interval for async mode (ms)\n * @default 1000\n */\n pollingInterval?: number;\n\n /**\n * Callback when a message is sent\n */\n onMessageSent?: (message: ChatMessage) => void;\n\n /**\n * Callback when a message is received\n */\n onMessageReceived?: (message: ChatMessage) => void;\n\n /**\n * Callback when a tool is called\n */\n onToolCall?: (toolName: string, params: any) => void;\n\n /**\n * Callback when an error occurs\n */\n onError?: (error: Error) => void;\n\n /**\n * Callback when a new chat is created\n */\n onChatCreated?: (chatUid: string) => void;\n}\n\nexport interface UseDevicChatResult {\n /**\n * Current chat messages\n */\n messages: ChatMessage[];\n\n /**\n * Current chat UID\n */\n chatUid: string | null;\n\n /**\n * Whether a message is being processed\n */\n isLoading: boolean;\n\n /**\n * Current status\n */\n status: RealtimeStatus | 'idle';\n\n /**\n * Last error\n */\n error: Error | null;\n\n /**\n * Send a message\n */\n sendMessage: (\n message: string,\n options?: {\n files?: ChatFile[];\n metadata?: Record<string, any>;\n }\n ) => Promise<void>;\n\n /**\n * Clear the chat and start a new conversation\n */\n clearChat: () => void;\n\n /**\n * Load an existing chat\n */\n loadChat: (chatUid: string) => Promise<void>;\n}\n\n/**\n * Main hook for managing chat with a Devic assistant\n *\n * @example\n * ```tsx\n * const {\n * messages,\n * isLoading,\n * sendMessage,\n * } = useDevicChat({\n * assistantId: 'my-assistant',\n * modelInterfaceTools: [\n * {\n * toolName: 'get_user_location',\n * schema: { ... },\n * callback: async () => ({ lat: 40.7, lng: -74.0 })\n * }\n * ],\n * onMessageReceived: (msg) => console.log('Received:', msg),\n * });\n * ```\n */\nexport function useDevicChat(options: UseDevicChatOptions): UseDevicChatResult {\n const {\n assistantId,\n chatUid: initialChatUid,\n apiKey: propsApiKey,\n baseUrl: propsBaseUrl,\n tenantId,\n tenantMetadata,\n enabledTools,\n modelInterfaceTools = [],\n pollingInterval = 1000,\n onMessageSent,\n onMessageReceived,\n onToolCall,\n onError,\n onChatCreated,\n } = options;\n\n // Get context (may be null if not wrapped in provider)\n const context = useOptionalDevicContext();\n\n // Resolve configuration\n const apiKey = propsApiKey || context?.apiKey;\n const baseUrl = propsBaseUrl || context?.baseUrl || 'https://api.devic.ai';\n const resolvedTenantId = tenantId || context?.tenantId;\n const resolvedTenantMetadata = { ...context?.tenantMetadata, ...tenantMetadata };\n\n // State\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [chatUid, setChatUid] = useState<string | null>(initialChatUid || null);\n const [isLoading, setIsLoading] = useState(false);\n const [status, setStatus] = useState<RealtimeStatus | 'idle'>('idle');\n const [error, setError] = useState<Error | null>(null);\n\n // Polling state\n const [shouldPoll, setShouldPoll] = useState(false);\n\n // Refs for callbacks\n const onMessageReceivedRef = useRef(onMessageReceived);\n const onErrorRef = useRef(onError);\n const onChatCreatedRef = useRef(onChatCreated);\n\n useEffect(() => {\n onMessageReceivedRef.current = onMessageReceived;\n onErrorRef.current = onError;\n onChatCreatedRef.current = onChatCreated;\n });\n\n // Create API client\n const clientRef = useRef<DevicApiClient | null>(null);\n if (!clientRef.current && apiKey) {\n clientRef.current = new DevicApiClient({ apiKey, baseUrl });\n }\n\n // Update client config if it changes\n useEffect(() => {\n if (clientRef.current && apiKey) {\n clientRef.current.setConfig({ apiKey, baseUrl });\n }\n }, [apiKey, baseUrl]);\n\n // Model interface hook\n const {\n toolSchemas,\n handleToolCalls,\n extractPendingToolCalls,\n } = useModelInterface({\n tools: modelInterfaceTools,\n onToolExecute: onToolCall,\n });\n\n // Polling hook\n const polling = usePolling(\n shouldPoll ? chatUid : null,\n async () => {\n if (!clientRef.current || !chatUid) {\n throw new Error('Cannot poll without client or chatUid');\n }\n return clientRef.current.getRealtimeHistory(assistantId, chatUid);\n },\n {\n interval: pollingInterval,\n enabled: shouldPoll,\n stopStatuses: ['completed', 'error', 'waiting_for_tool_response'],\n onUpdate: async (data: RealtimeChatHistory) => {\n setMessages(data.chatHistory);\n setStatus(data.status);\n\n // Notify about new messages\n const lastMessage = data.chatHistory[data.chatHistory.length - 1];\n if (lastMessage && lastMessage.role === 'assistant') {\n onMessageReceivedRef.current?.(lastMessage);\n }\n\n // Handle model interface - check for pending tool calls\n if (data.status === 'waiting_for_tool_response' || data.pendingToolCalls?.length) {\n await handlePendingToolCalls(data);\n }\n },\n onStop: async (data) => {\n setIsLoading(false);\n setShouldPoll(false);\n\n if (data?.status === 'error') {\n const err = new Error('Chat processing failed');\n setError(err);\n onErrorRef.current?.(err);\n } else if (data?.status === 'waiting_for_tool_response') {\n // Handle tool response\n await handlePendingToolCalls(data);\n }\n },\n onError: (err) => {\n setError(err);\n setIsLoading(false);\n setShouldPoll(false);\n onErrorRef.current?.(err);\n },\n }\n );\n\n // Handle pending tool calls from model interface\n const handlePendingToolCalls = useCallback(\n async (data: RealtimeChatHistory) => {\n if (!clientRef.current || !chatUid) return;\n\n // Get pending tool calls\n const pendingCalls = data.pendingToolCalls || extractPendingToolCalls(data.chatHistory);\n\n if (pendingCalls.length === 0) return;\n\n try {\n // Execute client-side tools\n const responses = await handleToolCalls(pendingCalls);\n\n if (responses.length > 0) {\n // Send tool responses back to the API\n await clientRef.current.sendToolResponses(assistantId, chatUid, responses);\n\n // Resume polling\n setShouldPoll(true);\n setIsLoading(true);\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n onErrorRef.current?.(error);\n }\n },\n [chatUid, assistantId, handleToolCalls, extractPendingToolCalls]\n );\n\n // Send a message\n const sendMessage = useCallback(\n async (\n message: string,\n sendOptions?: {\n files?: ChatFile[];\n metadata?: Record<string, any>;\n }\n ) => {\n if (!clientRef.current) {\n const err = new Error(\n 'API client not configured. Please provide an API key.'\n );\n setError(err);\n onErrorRef.current?.(err);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n setStatus('processing');\n\n // Add user message optimistically\n const userMessage: ChatMessage = {\n uid: `temp-${Date.now()}`,\n role: 'user',\n content: {\n message,\n files: sendOptions?.files?.map((f) => ({\n name: f.name,\n url: f.downloadUrl || '',\n type: f.fileType || 'other',\n })),\n },\n timestamp: Date.now(),\n };\n\n setMessages((prev) => [...prev, userMessage]);\n onMessageSent?.(userMessage);\n\n try {\n // Build request DTO\n const dto = {\n message,\n chatUid: chatUid || undefined,\n files: sendOptions?.files,\n metadata: {\n ...resolvedTenantMetadata,\n ...sendOptions?.metadata,\n },\n tenantId: resolvedTenantId,\n enabledTools,\n // Include model interface tools if any\n ...(toolSchemas.length > 0 && { tools: toolSchemas }),\n };\n\n // Send message in async mode\n const response = await clientRef.current.sendMessageAsync(assistantId, dto);\n\n // Update chat UID if this is a new chat\n if (response.chatUid && response.chatUid !== chatUid) {\n setChatUid(response.chatUid);\n onChatCreatedRef.current?.(response.chatUid);\n }\n\n // Start polling for results\n setShouldPoll(true);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n setIsLoading(false);\n setStatus('error');\n onErrorRef.current?.(error);\n\n // Remove optimistic user message on error\n setMessages((prev) => prev.filter((m) => m.uid !== userMessage.uid));\n }\n },\n [\n chatUid,\n assistantId,\n enabledTools,\n resolvedTenantId,\n resolvedTenantMetadata,\n toolSchemas,\n onMessageSent,\n ]\n );\n\n // Clear chat\n const clearChat = useCallback(() => {\n setMessages([]);\n setChatUid(null);\n setStatus('idle');\n setError(null);\n setShouldPoll(false);\n }, []);\n\n // Load existing chat\n const loadChat = useCallback(\n async (loadChatUid: string) => {\n if (!clientRef.current) {\n const err = new Error('API client not configured');\n setError(err);\n onErrorRef.current?.(err);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const history = await clientRef.current.getChatHistory(\n assistantId,\n loadChatUid\n );\n\n setMessages(history.chatContent);\n setChatUid(loadChatUid);\n setStatus('completed');\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n onErrorRef.current?.(error);\n } finally {\n setIsLoading(false);\n }\n },\n [assistantId]\n );\n\n return {\n messages,\n chatUid,\n isLoading,\n status,\n error,\n sendMessage,\n clearChat,\n loadChat,\n };\n}\n"],"names":["useOptionalDevicContext","useState","useRef","useEffect","DevicApiClient","useModelInterface","usePolling","useCallback"],"mappings":"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"}
|