@devicai/ui 0.1.0 → 0.3.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/dist/cjs/api/client.js +46 -13
- package/dist/cjs/api/client.js.map +1 -1
- package/dist/cjs/components/AICommandBar/AICommandBar.js +314 -0
- package/dist/cjs/components/AICommandBar/AICommandBar.js.map +1 -0
- package/dist/cjs/components/AICommandBar/useAICommandBar.js +595 -0
- package/dist/cjs/components/AICommandBar/useAICommandBar.js.map +1 -0
- package/dist/cjs/components/ChatDrawer/ChatDrawer.js +200 -22
- package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
- package/dist/cjs/components/ChatDrawer/ChatInput.js +12 -12
- package/dist/cjs/components/ChatDrawer/ChatInput.js.map +1 -1
- package/dist/cjs/components/ChatDrawer/ChatMessages.js +137 -29
- package/dist/cjs/components/ChatDrawer/ChatMessages.js.map +1 -1
- package/dist/cjs/components/ChatDrawer/ConversationSelector.js +93 -0
- package/dist/cjs/components/ChatDrawer/ConversationSelector.js.map +1 -0
- package/dist/cjs/components/ChatDrawer/ErrorBoundary.js +25 -0
- package/dist/cjs/components/ChatDrawer/ErrorBoundary.js.map +1 -0
- package/dist/cjs/components/Feedback/FeedbackModal.js +87 -0
- package/dist/cjs/components/Feedback/FeedbackModal.js.map +1 -0
- package/dist/cjs/components/Feedback/MessageActions.js +74 -0
- package/dist/cjs/components/Feedback/MessageActions.js.map +1 -0
- package/dist/cjs/hooks/useDevicChat.js +54 -27
- package/dist/cjs/hooks/useDevicChat.js.map +1 -1
- package/dist/cjs/hooks/useModelInterface.js +6 -6
- package/dist/cjs/hooks/usePolling.js +64 -30
- package/dist/cjs/hooks/usePolling.js.map +1 -1
- package/dist/cjs/index.js +11 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/provider/DevicContext.js +4 -4
- package/dist/cjs/provider/DevicProvider.js +2 -2
- package/dist/cjs/styles.css +1 -1
- package/dist/esm/api/client.d.ts +19 -3
- package/dist/esm/api/client.js +46 -13
- package/dist/esm/api/client.js.map +1 -1
- package/dist/esm/api/types.d.ts +40 -0
- package/dist/esm/components/AICommandBar/AICommandBar.d.ts +22 -0
- package/dist/esm/components/AICommandBar/AICommandBar.js +312 -0
- package/dist/esm/components/AICommandBar/AICommandBar.js.map +1 -0
- package/dist/esm/components/AICommandBar/AICommandBar.types.d.ts +321 -0
- package/dist/esm/components/AICommandBar/index.d.ts +3 -0
- package/dist/esm/components/AICommandBar/useAICommandBar.d.ts +57 -0
- package/dist/esm/components/AICommandBar/useAICommandBar.js +592 -0
- package/dist/esm/components/AICommandBar/useAICommandBar.js.map +1 -0
- package/dist/esm/components/AutocompleteInput/AutocompleteInput.d.ts +4 -0
- package/dist/esm/components/AutocompleteInput/AutocompleteInput.types.d.ts +50 -0
- package/dist/esm/components/AutocompleteInput/index.d.ts +4 -0
- package/dist/esm/components/AutocompleteInput/useAutocomplete.d.ts +29 -0
- package/dist/esm/components/ChatDrawer/ChatDrawer.d.ts +4 -2
- package/dist/esm/components/ChatDrawer/ChatDrawer.js +191 -13
- package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
- package/dist/esm/components/ChatDrawer/ChatDrawer.types.d.ts +155 -5
- package/dist/esm/components/ChatDrawer/ChatInput.d.ts +2 -1
- package/dist/esm/components/ChatDrawer/ChatInput.js +2 -2
- package/dist/esm/components/ChatDrawer/ChatInput.js.map +1 -1
- package/dist/esm/components/ChatDrawer/ChatMessages.d.ts +2 -4
- package/dist/esm/components/ChatDrawer/ChatMessages.js +136 -28
- package/dist/esm/components/ChatDrawer/ChatMessages.js.map +1 -1
- package/dist/esm/components/ChatDrawer/ConversationSelector.d.ts +2 -0
- package/dist/esm/components/ChatDrawer/ConversationSelector.js +91 -0
- package/dist/esm/components/ChatDrawer/ConversationSelector.js.map +1 -0
- package/dist/esm/components/ChatDrawer/ErrorBoundary.d.ts +16 -0
- package/dist/esm/components/ChatDrawer/ErrorBoundary.js +23 -0
- package/dist/esm/components/ChatDrawer/ErrorBoundary.js.map +1 -0
- package/dist/esm/components/ChatDrawer/index.d.ts +2 -1
- package/dist/esm/components/Feedback/Feedback.types.d.ts +50 -0
- package/dist/esm/components/Feedback/FeedbackModal.d.ts +5 -0
- package/dist/esm/components/Feedback/FeedbackModal.js +85 -0
- package/dist/esm/components/Feedback/FeedbackModal.js.map +1 -0
- package/dist/esm/components/Feedback/MessageActions.d.ts +5 -0
- package/dist/esm/components/Feedback/MessageActions.js +72 -0
- package/dist/esm/components/Feedback/MessageActions.js.map +1 -0
- package/dist/esm/components/Feedback/index.d.ts +3 -0
- package/dist/esm/hooks/useDevicChat.js +37 -10
- package/dist/esm/hooks/useDevicChat.js.map +1 -1
- package/dist/esm/hooks/usePolling.js +46 -12
- package/dist/esm/hooks/usePolling.js.map +1 -1
- package/dist/esm/index.d.ts +7 -3
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/styles.css +1 -1
- package/package.json +10 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FeedbackModal.js","sources":["../../../../../src/components/Feedback/FeedbackModal.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef, useMemo } from 'react';\nimport type { FeedbackModalProps } from './Feedback.types';\n\n/**\n * Modal for submitting feedback with optional comment\n */\nexport function FeedbackModal({\n isOpen,\n onClose,\n onSubmit,\n feedbackType,\n isSubmitting = false,\n theme,\n}: FeedbackModalProps): JSX.Element | null {\n const [comment, setComment] = useState('');\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const modalRef = useRef<HTMLDivElement>(null);\n\n // Build inline styles from theme\n const modalStyle = useMemo(() => {\n if (!theme) return undefined;\n return {\n '--devic-bg': theme.backgroundColor,\n '--devic-text': theme.textColor,\n '--devic-text-muted': theme.textMutedColor,\n '--devic-text-secondary': theme.textMutedColor,\n '--devic-bg-secondary': theme.secondaryBackgroundColor,\n '--devic-border': theme.borderColor,\n '--devic-primary': theme.primaryColor,\n '--devic-primary-hover': theme.primaryHoverColor,\n } as React.CSSProperties;\n }, [theme]);\n\n // Focus textarea when modal opens\n useEffect(() => {\n if (isOpen && textareaRef.current) {\n setTimeout(() => textareaRef.current?.focus(), 50);\n }\n }, [isOpen]);\n\n // Reset comment when modal closes\n useEffect(() => {\n if (!isOpen) {\n setComment('');\n }\n }, [isOpen]);\n\n // Handle click outside\n useEffect(() => {\n if (!isOpen) return;\n\n const handleClickOutside = (e: MouseEvent) => {\n if (modalRef.current && !modalRef.current.contains(e.target as Node)) {\n onClose();\n }\n };\n\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onClose();\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n document.addEventListener('keydown', handleEscape);\n return () => {\n document.removeEventListener('mousedown', handleClickOutside);\n document.removeEventListener('keydown', handleEscape);\n };\n }, [isOpen, onClose]);\n\n if (!isOpen) return null;\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n onSubmit(comment);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n onSubmit(comment);\n }\n };\n\n return (\n <div className=\"devic-feedback-overlay\">\n <div className=\"devic-feedback-modal\" ref={modalRef} style={modalStyle}>\n <div className=\"devic-feedback-modal-header\">\n <span className=\"devic-feedback-modal-icon\">\n {feedbackType === 'positive' ? <ThumbsUpIcon filled /> : <ThumbsDownIcon filled />}\n </span>\n <span className=\"devic-feedback-modal-title\">\n {feedbackType === 'positive' ? 'What did you like?' : 'What could be improved?'}\n </span>\n <button\n type=\"button\"\n className=\"devic-feedback-modal-close\"\n onClick={onClose}\n aria-label=\"Close\"\n >\n <CloseIcon />\n </button>\n </div>\n\n <form onSubmit={handleSubmit}>\n <textarea\n ref={textareaRef}\n className=\"devic-feedback-textarea\"\n placeholder=\"Add a comment (optional)...\"\n value={comment}\n onChange={(e) => setComment(e.target.value)}\n onKeyDown={handleKeyDown}\n rows={3}\n disabled={isSubmitting}\n />\n\n <div className=\"devic-feedback-modal-actions\">\n <button\n type=\"button\"\n className=\"devic-feedback-btn devic-feedback-btn--secondary\"\n onClick={onClose}\n disabled={isSubmitting}\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n className=\"devic-feedback-btn devic-feedback-btn--primary\"\n disabled={isSubmitting}\n >\n {isSubmitting ? 'Sending...' : 'Submit'}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n\n/* ── Icons ── */\n\nfunction ThumbsUpIcon({ filled = false }: { filled?: boolean }): JSX.Element {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill={filled ? 'currentColor' : 'none'}\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M7 10v12M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2h0a3.13 3.13 0 0 1 3 3.88Z\" />\n </svg>\n );\n}\n\nfunction ThumbsDownIcon({ filled = false }: { filled?: boolean }): JSX.Element {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill={filled ? 'currentColor' : 'none'}\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M17 14V2M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22h0a3.13 3.13 0 0 1-3-3.88Z\" />\n </svg>\n );\n}\n\nfunction CloseIcon(): JSX.Element {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n );\n}\n"],"names":["useState","useRef","useMemo","useEffect","_jsx","_jsxs"],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var FeedbackModal = require('./FeedbackModal.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Action buttons for a message (copy, thumbs up, thumbs down)
|
|
9
|
+
*/
|
|
10
|
+
function MessageActions({ messageId, messageContent, currentFeedback = 'none', onFeedback, onCopy, showCopy = true, showFeedback = true, disabled = false, theme, }) {
|
|
11
|
+
const [feedbackState, setFeedbackState] = React.useState(currentFeedback);
|
|
12
|
+
const [modalOpen, setModalOpen] = React.useState(false);
|
|
13
|
+
const [pendingFeedbackType, setPendingFeedbackType] = React.useState('positive');
|
|
14
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
15
|
+
const [copied, setCopied] = React.useState(false);
|
|
16
|
+
// Build inline styles from theme for the actions container
|
|
17
|
+
const containerStyle = React.useMemo(() => {
|
|
18
|
+
if (!theme)
|
|
19
|
+
return undefined;
|
|
20
|
+
return {
|
|
21
|
+
'--devic-bg-secondary': theme.secondaryBackgroundColor,
|
|
22
|
+
'--devic-text-secondary': theme.textMutedColor,
|
|
23
|
+
};
|
|
24
|
+
}, [theme]);
|
|
25
|
+
const handleCopy = React.useCallback(async () => {
|
|
26
|
+
if (!messageContent)
|
|
27
|
+
return;
|
|
28
|
+
try {
|
|
29
|
+
await navigator.clipboard.writeText(messageContent);
|
|
30
|
+
setCopied(true);
|
|
31
|
+
onCopy?.(messageContent);
|
|
32
|
+
setTimeout(() => setCopied(false), 2000);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
console.error('Failed to copy:', err);
|
|
36
|
+
}
|
|
37
|
+
}, [messageContent, onCopy]);
|
|
38
|
+
const handleFeedbackClick = React.useCallback((type) => {
|
|
39
|
+
setPendingFeedbackType(type);
|
|
40
|
+
setModalOpen(true);
|
|
41
|
+
}, [feedbackState]);
|
|
42
|
+
const handleModalSubmit = React.useCallback(async (comment) => {
|
|
43
|
+
setIsSubmitting(true);
|
|
44
|
+
try {
|
|
45
|
+
const isPositive = pendingFeedbackType === 'positive';
|
|
46
|
+
await onFeedback?.(messageId, isPositive, comment || undefined);
|
|
47
|
+
setFeedbackState(isPositive ? 'positive' : 'negative');
|
|
48
|
+
setModalOpen(false);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error('Failed to submit feedback:', err);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
setIsSubmitting(false);
|
|
55
|
+
}
|
|
56
|
+
}, [messageId, pendingFeedbackType, onFeedback]);
|
|
57
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "devic-message-actions", style: containerStyle, children: [showCopy && messageContent && (jsxRuntime.jsx("button", { type: "button", className: `devic-action-btn ${copied ? 'devic-action-btn--active' : ''}`, onClick: handleCopy, disabled: disabled, title: "Copy to clipboard", "aria-label": "Copy to clipboard", children: copied ? jsxRuntime.jsx(CheckIcon, {}) : jsxRuntime.jsx(CopyIcon, {}) })), showFeedback && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { type: "button", className: `devic-action-btn ${feedbackState === 'positive' ? 'devic-action-btn--active devic-action-btn--positive' : ''}`, onClick: () => handleFeedbackClick('positive'), disabled: disabled, title: "Good response", "aria-label": "Good response", children: jsxRuntime.jsx(ThumbsUpIcon, { filled: feedbackState === 'positive' }) }), jsxRuntime.jsx("button", { type: "button", className: `devic-action-btn ${feedbackState === 'negative' ? 'devic-action-btn--active devic-action-btn--negative' : ''}`, onClick: () => handleFeedbackClick('negative'), disabled: disabled, title: "Bad response", "aria-label": "Bad response", children: jsxRuntime.jsx(ThumbsDownIcon, { filled: feedbackState === 'negative' }) })] }))] }), jsxRuntime.jsx(FeedbackModal.FeedbackModal, { isOpen: modalOpen, onClose: () => setModalOpen(false), onSubmit: handleModalSubmit, feedbackType: pendingFeedbackType, isSubmitting: isSubmitting, theme: theme })] }));
|
|
58
|
+
}
|
|
59
|
+
/* ── Icons ── */
|
|
60
|
+
function CopyIcon() {
|
|
61
|
+
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("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), jsxRuntime.jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] }));
|
|
62
|
+
}
|
|
63
|
+
function CheckIcon() {
|
|
64
|
+
return (jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("polyline", { points: "20,6 9,17 4,12" }) }));
|
|
65
|
+
}
|
|
66
|
+
function ThumbsUpIcon({ filled = false }) {
|
|
67
|
+
return (jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: filled ? 'currentColor' : 'none', stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("path", { d: "M7 10v12M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2h0a3.13 3.13 0 0 1 3 3.88Z" }) }));
|
|
68
|
+
}
|
|
69
|
+
function ThumbsDownIcon({ filled = false }) {
|
|
70
|
+
return (jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: filled ? 'currentColor' : 'none', stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("path", { d: "M17 14V2M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22h0a3.13 3.13 0 0 1-3-3.88Z" }) }));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
exports.MessageActions = MessageActions;
|
|
74
|
+
//# sourceMappingURL=MessageActions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageActions.js","sources":["../../../../../src/components/Feedback/MessageActions.tsx"],"sourcesContent":["import React, { useState, useCallback, useMemo } from 'react';\nimport { FeedbackModal } from './FeedbackModal';\nimport type { MessageActionsProps, FeedbackState } from './Feedback.types';\n\n/**\n * Action buttons for a message (copy, thumbs up, thumbs down)\n */\nexport function MessageActions({\n messageId,\n messageContent,\n currentFeedback = 'none',\n onFeedback,\n onCopy,\n showCopy = true,\n showFeedback = true,\n disabled = false,\n theme,\n}: MessageActionsProps): JSX.Element {\n const [feedbackState, setFeedbackState] = useState<FeedbackState>(currentFeedback);\n const [modalOpen, setModalOpen] = useState(false);\n const [pendingFeedbackType, setPendingFeedbackType] = useState<'positive' | 'negative'>('positive');\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [copied, setCopied] = useState(false);\n\n // Build inline styles from theme for the actions container\n const containerStyle = useMemo(() => {\n if (!theme) return undefined;\n return {\n '--devic-bg-secondary': theme.secondaryBackgroundColor,\n '--devic-text-secondary': theme.textMutedColor,\n } as React.CSSProperties;\n }, [theme]);\n\n const handleCopy = useCallback(async () => {\n if (!messageContent) return;\n\n try {\n await navigator.clipboard.writeText(messageContent);\n setCopied(true);\n onCopy?.(messageContent);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.error('Failed to copy:', err);\n }\n }, [messageContent, onCopy]);\n\n const handleFeedbackClick = useCallback((type: 'positive' | 'negative') => {\n // If clicking the same feedback type that's already selected, toggle it off\n if ((type === 'positive' && feedbackState === 'positive') ||\n (type === 'negative' && feedbackState === 'negative')) {\n // Optionally could allow removal - for now we just open modal to change comment\n }\n\n setPendingFeedbackType(type);\n setModalOpen(true);\n }, [feedbackState]);\n\n const handleModalSubmit = useCallback(async (comment: string) => {\n setIsSubmitting(true);\n try {\n const isPositive = pendingFeedbackType === 'positive';\n await onFeedback?.(messageId, isPositive, comment || undefined);\n setFeedbackState(isPositive ? 'positive' : 'negative');\n setModalOpen(false);\n } catch (err) {\n console.error('Failed to submit feedback:', err);\n } finally {\n setIsSubmitting(false);\n }\n }, [messageId, pendingFeedbackType, onFeedback]);\n\n return (\n <>\n <div className=\"devic-message-actions\" style={containerStyle}>\n {showCopy && messageContent && (\n <button\n type=\"button\"\n className={`devic-action-btn ${copied ? 'devic-action-btn--active' : ''}`}\n onClick={handleCopy}\n disabled={disabled}\n title=\"Copy to clipboard\"\n aria-label=\"Copy to clipboard\"\n >\n {copied ? <CheckIcon /> : <CopyIcon />}\n </button>\n )}\n\n {showFeedback && (\n <>\n <button\n type=\"button\"\n className={`devic-action-btn ${feedbackState === 'positive' ? 'devic-action-btn--active devic-action-btn--positive' : ''}`}\n onClick={() => handleFeedbackClick('positive')}\n disabled={disabled}\n title=\"Good response\"\n aria-label=\"Good response\"\n >\n <ThumbsUpIcon filled={feedbackState === 'positive'} />\n </button>\n\n <button\n type=\"button\"\n className={`devic-action-btn ${feedbackState === 'negative' ? 'devic-action-btn--active devic-action-btn--negative' : ''}`}\n onClick={() => handleFeedbackClick('negative')}\n disabled={disabled}\n title=\"Bad response\"\n aria-label=\"Bad response\"\n >\n <ThumbsDownIcon filled={feedbackState === 'negative'} />\n </button>\n </>\n )}\n </div>\n\n <FeedbackModal\n isOpen={modalOpen}\n onClose={() => setModalOpen(false)}\n onSubmit={handleModalSubmit}\n feedbackType={pendingFeedbackType}\n isSubmitting={isSubmitting}\n theme={theme}\n />\n </>\n );\n}\n\n/* ── Icons ── */\n\nfunction CopyIcon(): 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 <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\" />\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\" />\n </svg>\n );\n}\n\nfunction CheckIcon(): 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.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"20,6 9,17 4,12\" />\n </svg>\n );\n}\n\nfunction ThumbsUpIcon({ filled = false }: { filled?: boolean }): JSX.Element {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill={filled ? 'currentColor' : 'none'}\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M7 10v12M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2h0a3.13 3.13 0 0 1 3 3.88Z\" />\n </svg>\n );\n}\n\nfunction ThumbsDownIcon({ filled = false }: { filled?: boolean }): JSX.Element {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill={filled ? 'currentColor' : 'none'}\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M17 14V2M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22h0a3.13 3.13 0 0 1-3-3.88Z\" />\n </svg>\n );\n}\n"],"names":["useState","useMemo","useCallback","_jsxs","_jsx","_Fragment","FeedbackModal"],"mappings":"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"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React = require('react');
|
|
4
4
|
require('react/jsx-runtime');
|
|
5
5
|
var DevicContext = require('../provider/DevicContext.js');
|
|
6
6
|
var client = require('../api/client.js');
|
|
7
7
|
var usePolling = require('./usePolling.js');
|
|
8
8
|
var useModelInterface = require('./useModelInterface.js');
|
|
9
9
|
|
|
10
|
+
console.log('[devic-ui] Version: DEV-BUILD-001 (2026-01-26 18:20)');
|
|
10
11
|
/**
|
|
11
12
|
* Main hook for managing chat with a Devic assistant
|
|
12
13
|
*
|
|
@@ -39,29 +40,29 @@ function useDevicChat(options) {
|
|
|
39
40
|
const resolvedTenantId = tenantId || context?.tenantId;
|
|
40
41
|
const resolvedTenantMetadata = { ...context?.tenantMetadata, ...tenantMetadata };
|
|
41
42
|
// State
|
|
42
|
-
const [messages, setMessages] =
|
|
43
|
-
const [chatUid, setChatUid] =
|
|
44
|
-
const [isLoading, setIsLoading] =
|
|
45
|
-
const [status, setStatus] =
|
|
46
|
-
const [error, setError] =
|
|
43
|
+
const [messages, setMessages] = React.useState([]);
|
|
44
|
+
const [chatUid, setChatUid] = React.useState(initialChatUid || null);
|
|
45
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
46
|
+
const [status, setStatus] = React.useState('idle');
|
|
47
|
+
const [error, setError] = React.useState(null);
|
|
47
48
|
// Polling state
|
|
48
|
-
const [shouldPoll, setShouldPoll] =
|
|
49
|
+
const [shouldPoll, setShouldPoll] = React.useState(false);
|
|
49
50
|
// Refs for callbacks
|
|
50
|
-
const onMessageReceivedRef =
|
|
51
|
-
const onErrorRef =
|
|
52
|
-
const onChatCreatedRef =
|
|
53
|
-
|
|
51
|
+
const onMessageReceivedRef = React.useRef(onMessageReceived);
|
|
52
|
+
const onErrorRef = React.useRef(onError);
|
|
53
|
+
const onChatCreatedRef = React.useRef(onChatCreated);
|
|
54
|
+
React.useEffect(() => {
|
|
54
55
|
onMessageReceivedRef.current = onMessageReceived;
|
|
55
56
|
onErrorRef.current = onError;
|
|
56
57
|
onChatCreatedRef.current = onChatCreated;
|
|
57
58
|
});
|
|
58
59
|
// Create API client
|
|
59
|
-
const clientRef =
|
|
60
|
+
const clientRef = React.useRef(null);
|
|
60
61
|
if (!clientRef.current && apiKey) {
|
|
61
62
|
clientRef.current = new client.DevicApiClient({ apiKey, baseUrl });
|
|
62
63
|
}
|
|
63
64
|
// Update client config if it changes
|
|
64
|
-
|
|
65
|
+
React.useEffect(() => {
|
|
65
66
|
if (clientRef.current && apiKey) {
|
|
66
67
|
clientRef.current.setConfig({ apiKey, baseUrl });
|
|
67
68
|
}
|
|
@@ -71,18 +72,38 @@ function useDevicChat(options) {
|
|
|
71
72
|
tools: modelInterfaceTools,
|
|
72
73
|
onToolExecute: onToolCall,
|
|
73
74
|
});
|
|
74
|
-
// Polling hook
|
|
75
|
+
// Polling hook - uses callbacks for side effects, return value not needed
|
|
76
|
+
console.log('[useDevicChat] Render - shouldPoll:', shouldPoll, 'chatUid:', chatUid);
|
|
75
77
|
usePolling.usePolling(shouldPoll ? chatUid : null, async () => {
|
|
78
|
+
console.log('[useDevicChat] fetchFn called, chatUid:', chatUid);
|
|
76
79
|
if (!clientRef.current || !chatUid) {
|
|
77
80
|
throw new Error('Cannot poll without client or chatUid');
|
|
78
81
|
}
|
|
79
|
-
|
|
82
|
+
const result = await clientRef.current.getRealtimeHistory(assistantId, chatUid);
|
|
83
|
+
console.log('[useDevicChat] getRealtimeHistory result:', result);
|
|
84
|
+
return result;
|
|
80
85
|
}, {
|
|
81
86
|
interval: pollingInterval,
|
|
82
87
|
enabled: shouldPoll,
|
|
83
88
|
stopStatuses: ['completed', 'error', 'waiting_for_tool_response'],
|
|
84
89
|
onUpdate: async (data) => {
|
|
85
|
-
|
|
90
|
+
console.log('[useDevicChat] onUpdate called, status:', data.status);
|
|
91
|
+
// Merge realtime data with optimistic messages
|
|
92
|
+
setMessages((prev) => {
|
|
93
|
+
const realtimeUIDs = new Set(data.chatHistory.map((m) => m.uid));
|
|
94
|
+
const realtimeUserMessages = new Set(data.chatHistory
|
|
95
|
+
.filter((m) => m.role === 'user')
|
|
96
|
+
.map((m) => m.content?.message));
|
|
97
|
+
// Keep optimistic messages not yet in realtime data
|
|
98
|
+
const optimistic = prev.filter((m) => {
|
|
99
|
+
if (realtimeUIDs.has(m.uid))
|
|
100
|
+
return false;
|
|
101
|
+
if (m.role === 'user' && realtimeUserMessages.has(m.content?.message))
|
|
102
|
+
return false;
|
|
103
|
+
return true;
|
|
104
|
+
});
|
|
105
|
+
return [...data.chatHistory, ...optimistic];
|
|
106
|
+
});
|
|
86
107
|
setStatus(data.status);
|
|
87
108
|
// Notify about new messages
|
|
88
109
|
const lastMessage = data.chatHistory[data.chatHistory.length - 1];
|
|
@@ -94,20 +115,22 @@ function useDevicChat(options) {
|
|
|
94
115
|
await handlePendingToolCalls(data);
|
|
95
116
|
}
|
|
96
117
|
},
|
|
97
|
-
onStop:
|
|
98
|
-
|
|
118
|
+
onStop: (data) => {
|
|
119
|
+
console.log('[useDevicChat] onStop called, status:', data?.status);
|
|
99
120
|
setShouldPoll(false);
|
|
100
121
|
if (data?.status === 'error') {
|
|
122
|
+
setIsLoading(false);
|
|
101
123
|
const err = new Error('Chat processing failed');
|
|
102
124
|
setError(err);
|
|
103
125
|
onErrorRef.current?.(err);
|
|
104
126
|
}
|
|
105
|
-
else if (data?.status === '
|
|
106
|
-
|
|
107
|
-
await handlePendingToolCalls(data);
|
|
127
|
+
else if (data?.status === 'completed') {
|
|
128
|
+
setIsLoading(false);
|
|
108
129
|
}
|
|
130
|
+
// Note: waiting_for_tool_response is handled in onUpdate to avoid double execution
|
|
109
131
|
},
|
|
110
132
|
onError: (err) => {
|
|
133
|
+
console.error('[useDevicChat] onError called:', err);
|
|
111
134
|
setError(err);
|
|
112
135
|
setIsLoading(false);
|
|
113
136
|
setShouldPoll(false);
|
|
@@ -115,7 +138,7 @@ function useDevicChat(options) {
|
|
|
115
138
|
},
|
|
116
139
|
});
|
|
117
140
|
// Handle pending tool calls from model interface
|
|
118
|
-
const handlePendingToolCalls =
|
|
141
|
+
const handlePendingToolCalls = React.useCallback(async (data) => {
|
|
119
142
|
if (!clientRef.current || !chatUid)
|
|
120
143
|
return;
|
|
121
144
|
// Get pending tool calls
|
|
@@ -140,7 +163,7 @@ function useDevicChat(options) {
|
|
|
140
163
|
}
|
|
141
164
|
}, [chatUid, assistantId, handleToolCalls, extractPendingToolCalls]);
|
|
142
165
|
// Send a message
|
|
143
|
-
const sendMessage =
|
|
166
|
+
const sendMessage = React.useCallback(async (message, sendOptions) => {
|
|
144
167
|
if (!clientRef.current) {
|
|
145
168
|
const err = new Error('API client not configured. Please provide an API key.');
|
|
146
169
|
setError(err);
|
|
@@ -182,13 +205,17 @@ function useDevicChat(options) {
|
|
|
182
205
|
...(toolSchemas.length > 0 && { tools: toolSchemas }),
|
|
183
206
|
};
|
|
184
207
|
// Send message in async mode
|
|
208
|
+
console.log('[useDevicChat] Sending message async...');
|
|
185
209
|
const response = await clientRef.current.sendMessageAsync(assistantId, dto);
|
|
210
|
+
console.log('[useDevicChat] sendMessageAsync response:', response);
|
|
186
211
|
// Update chat UID if this is a new chat
|
|
187
212
|
if (response.chatUid && response.chatUid !== chatUid) {
|
|
213
|
+
console.log('[useDevicChat] Setting chatUid:', response.chatUid);
|
|
188
214
|
setChatUid(response.chatUid);
|
|
189
215
|
onChatCreatedRef.current?.(response.chatUid);
|
|
190
216
|
}
|
|
191
217
|
// Start polling for results
|
|
218
|
+
console.log('[useDevicChat] Setting shouldPoll to true');
|
|
192
219
|
setShouldPoll(true);
|
|
193
220
|
}
|
|
194
221
|
catch (err) {
|
|
@@ -210,7 +237,7 @@ function useDevicChat(options) {
|
|
|
210
237
|
onMessageSent,
|
|
211
238
|
]);
|
|
212
239
|
// Clear chat
|
|
213
|
-
const clearChat =
|
|
240
|
+
const clearChat = React.useCallback(() => {
|
|
214
241
|
setMessages([]);
|
|
215
242
|
setChatUid(null);
|
|
216
243
|
setStatus('idle');
|
|
@@ -218,7 +245,7 @@ function useDevicChat(options) {
|
|
|
218
245
|
setShouldPoll(false);
|
|
219
246
|
}, []);
|
|
220
247
|
// Load existing chat
|
|
221
|
-
const loadChat =
|
|
248
|
+
const loadChat = React.useCallback(async (loadChatUid) => {
|
|
222
249
|
if (!clientRef.current) {
|
|
223
250
|
const err = new Error('API client not configured');
|
|
224
251
|
setError(err);
|
|
@@ -228,7 +255,7 @@ function useDevicChat(options) {
|
|
|
228
255
|
setIsLoading(true);
|
|
229
256
|
setError(null);
|
|
230
257
|
try {
|
|
231
|
-
const history = await clientRef.current.getChatHistory(assistantId, loadChatUid);
|
|
258
|
+
const history = await clientRef.current.getChatHistory(assistantId, loadChatUid, { tenantId: resolvedTenantId });
|
|
232
259
|
setMessages(history.chatContent);
|
|
233
260
|
setChatUid(loadChatUid);
|
|
234
261
|
setStatus('completed');
|
|
@@ -241,7 +268,7 @@ function useDevicChat(options) {
|
|
|
241
268
|
finally {
|
|
242
269
|
setIsLoading(false);
|
|
243
270
|
}
|
|
244
|
-
}, [assistantId]);
|
|
271
|
+
}, [assistantId, resolvedTenantId]);
|
|
245
272
|
return {
|
|
246
273
|
messages,
|
|
247
274
|
chatUid,
|
|
@@ -1 +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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"}
|
|
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';\n\nconsole.log('[devic-ui] Version: DEV-BUILD-001 (2026-01-26 18:20)');\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 - uses callbacks for side effects, return value not needed\n console.log('[useDevicChat] Render - shouldPoll:', shouldPoll, 'chatUid:', chatUid);\n usePolling(\n shouldPoll ? chatUid : null,\n async () => {\n console.log('[useDevicChat] fetchFn called, chatUid:', chatUid);\n if (!clientRef.current || !chatUid) {\n throw new Error('Cannot poll without client or chatUid');\n }\n const result = await clientRef.current.getRealtimeHistory(assistantId, chatUid);\n console.log('[useDevicChat] getRealtimeHistory result:', result);\n return result;\n },\n {\n interval: pollingInterval,\n enabled: shouldPoll,\n stopStatuses: ['completed', 'error', 'waiting_for_tool_response'],\n onUpdate: async (data: RealtimeChatHistory) => {\n console.log('[useDevicChat] onUpdate called, status:', data.status);\n\n // Merge realtime data with optimistic messages\n setMessages((prev) => {\n const realtimeUIDs = new Set(data.chatHistory.map((m) => m.uid));\n const realtimeUserMessages = new Set(\n data.chatHistory\n .filter((m) => m.role === 'user')\n .map((m) => m.content?.message)\n );\n\n // Keep optimistic messages not yet in realtime data\n const optimistic = prev.filter((m) => {\n if (realtimeUIDs.has(m.uid)) return false;\n if (m.role === 'user' && realtimeUserMessages.has(m.content?.message)) return false;\n return true;\n });\n\n return [...data.chatHistory, ...optimistic];\n });\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: (data) => {\n console.log('[useDevicChat] onStop called, status:', data?.status);\n setShouldPoll(false);\n\n if (data?.status === 'error') {\n setIsLoading(false);\n const err = new Error('Chat processing failed');\n setError(err);\n onErrorRef.current?.(err);\n } else if (data?.status === 'completed') {\n setIsLoading(false);\n }\n // Note: waiting_for_tool_response is handled in onUpdate to avoid double execution\n },\n onError: (err) => {\n console.error('[useDevicChat] onError called:', 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 console.log('[useDevicChat] Sending message async...');\n const response = await clientRef.current.sendMessageAsync(assistantId, dto);\n console.log('[useDevicChat] sendMessageAsync response:', response);\n\n // Update chat UID if this is a new chat\n if (response.chatUid && response.chatUid !== chatUid) {\n console.log('[useDevicChat] Setting chatUid:', response.chatUid);\n setChatUid(response.chatUid);\n onChatCreatedRef.current?.(response.chatUid);\n }\n\n // Start polling for results\n console.log('[useDevicChat] Setting shouldPoll to true');\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 { tenantId: resolvedTenantId }\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, resolvedTenantId]\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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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React = require('react');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Hook for implementing the Model Interface Protocol
|
|
@@ -35,19 +35,19 @@ var react = require('react');
|
|
|
35
35
|
function useModelInterface(options) {
|
|
36
36
|
const { tools, onToolExecute, onToolComplete, onToolError } = options;
|
|
37
37
|
// Extract tool schemas for API
|
|
38
|
-
const toolSchemas =
|
|
38
|
+
const toolSchemas = React.useMemo(() => {
|
|
39
39
|
return tools.map((tool) => tool.schema);
|
|
40
40
|
}, [tools]);
|
|
41
41
|
// Map of tool name to tool definition
|
|
42
|
-
const toolMap =
|
|
42
|
+
const toolMap = React.useMemo(() => {
|
|
43
43
|
return new Map(tools.map((tool) => [tool.toolName, tool]));
|
|
44
44
|
}, [tools]);
|
|
45
45
|
// Check if a tool is a client-side tool
|
|
46
|
-
const isClientTool =
|
|
46
|
+
const isClientTool = React.useCallback((toolName) => {
|
|
47
47
|
return toolMap.has(toolName);
|
|
48
48
|
}, [toolMap]);
|
|
49
49
|
// Handle tool calls and execute client-side tools
|
|
50
|
-
const handleToolCalls =
|
|
50
|
+
const handleToolCalls = React.useCallback(async (toolCalls) => {
|
|
51
51
|
const responses = [];
|
|
52
52
|
for (const toolCall of toolCalls) {
|
|
53
53
|
const toolName = toolCall.function.name;
|
|
@@ -93,7 +93,7 @@ function useModelInterface(options) {
|
|
|
93
93
|
return responses;
|
|
94
94
|
}, [toolMap, onToolExecute, onToolComplete, onToolError]);
|
|
95
95
|
// Extract pending tool calls from messages that need client handling
|
|
96
|
-
const extractPendingToolCalls =
|
|
96
|
+
const extractPendingToolCalls = React.useCallback((messages) => {
|
|
97
97
|
const pendingCalls = [];
|
|
98
98
|
// Look at the last assistant message
|
|
99
99
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React = require('react');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Hook for polling real-time chat history
|
|
@@ -11,46 +11,59 @@ var react = require('react');
|
|
|
11
11
|
*/
|
|
12
12
|
function usePolling(chatUid, fetchFn, options = {}) {
|
|
13
13
|
const { interval = 1000, enabled = true, stopStatuses = ['completed', 'error'], onStop, onUpdate, onError, } = options;
|
|
14
|
-
const [data, setData] =
|
|
15
|
-
const [isPolling, setIsPolling] =
|
|
16
|
-
const [error, setError] =
|
|
17
|
-
const intervalRef =
|
|
18
|
-
const isMountedRef =
|
|
19
|
-
// Refs for callbacks to avoid stale closures
|
|
20
|
-
const onStopRef =
|
|
21
|
-
const onUpdateRef =
|
|
22
|
-
const onErrorRef =
|
|
23
|
-
const fetchFnRef =
|
|
24
|
-
|
|
14
|
+
const [data, setData] = React.useState(null);
|
|
15
|
+
const [isPolling, setIsPolling] = React.useState(false);
|
|
16
|
+
const [error, setError] = React.useState(null);
|
|
17
|
+
const intervalRef = React.useRef(null);
|
|
18
|
+
const isMountedRef = React.useRef(true);
|
|
19
|
+
// Refs for callbacks and options to avoid stale closures and unnecessary re-renders
|
|
20
|
+
const onStopRef = React.useRef(onStop);
|
|
21
|
+
const onUpdateRef = React.useRef(onUpdate);
|
|
22
|
+
const onErrorRef = React.useRef(onError);
|
|
23
|
+
const fetchFnRef = React.useRef(fetchFn);
|
|
24
|
+
const stopStatusesRef = React.useRef(stopStatuses);
|
|
25
|
+
const intervalValueRef = React.useRef(interval);
|
|
26
|
+
const isPollingRef = React.useRef(false);
|
|
27
|
+
React.useEffect(() => {
|
|
25
28
|
onStopRef.current = onStop;
|
|
26
29
|
onUpdateRef.current = onUpdate;
|
|
27
30
|
onErrorRef.current = onError;
|
|
28
31
|
fetchFnRef.current = fetchFn;
|
|
32
|
+
stopStatusesRef.current = stopStatuses;
|
|
33
|
+
intervalValueRef.current = interval;
|
|
29
34
|
});
|
|
30
|
-
const clearPolling =
|
|
35
|
+
const clearPolling = React.useCallback(() => {
|
|
31
36
|
if (intervalRef.current) {
|
|
32
37
|
clearInterval(intervalRef.current);
|
|
33
38
|
intervalRef.current = null;
|
|
34
39
|
}
|
|
40
|
+
isPollingRef.current = false;
|
|
35
41
|
}, []);
|
|
36
|
-
const fetchData =
|
|
42
|
+
const fetchData = React.useCallback(async () => {
|
|
43
|
+
console.log('[usePolling] fetchData called, isMounted:', isMountedRef.current);
|
|
37
44
|
if (!isMountedRef.current)
|
|
38
45
|
return;
|
|
39
46
|
try {
|
|
47
|
+
console.log('[usePolling] Fetching...');
|
|
40
48
|
const result = await fetchFnRef.current();
|
|
49
|
+
console.log('[usePolling] Fetch result:', { status: result.status, messageCount: result.chatHistory?.length });
|
|
41
50
|
if (!isMountedRef.current)
|
|
42
51
|
return;
|
|
43
52
|
setData(result);
|
|
44
53
|
setError(null);
|
|
45
54
|
onUpdateRef.current?.(result);
|
|
46
55
|
// Check if we should stop polling
|
|
47
|
-
|
|
56
|
+
const shouldStop = stopStatusesRef.current.includes(result.status);
|
|
57
|
+
console.log('[usePolling] Should stop?', shouldStop, 'stopStatuses:', stopStatusesRef.current, 'current status:', result.status);
|
|
58
|
+
if (shouldStop) {
|
|
59
|
+
console.log('[usePolling] Stopping polling due to status:', result.status);
|
|
48
60
|
clearPolling();
|
|
49
61
|
setIsPolling(false);
|
|
50
62
|
onStopRef.current?.(result);
|
|
51
63
|
}
|
|
52
64
|
}
|
|
53
65
|
catch (err) {
|
|
66
|
+
console.error('[usePolling] Fetch error:', err);
|
|
54
67
|
if (!isMountedRef.current)
|
|
55
68
|
return;
|
|
56
69
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -60,35 +73,56 @@ function usePolling(chatUid, fetchFn, options = {}) {
|
|
|
60
73
|
clearPolling();
|
|
61
74
|
setIsPolling(false);
|
|
62
75
|
}
|
|
63
|
-
}, [
|
|
64
|
-
const start =
|
|
65
|
-
if (
|
|
76
|
+
}, [clearPolling]);
|
|
77
|
+
const start = React.useCallback(() => {
|
|
78
|
+
if (intervalRef.current)
|
|
66
79
|
return;
|
|
80
|
+
isPollingRef.current = true;
|
|
67
81
|
setIsPolling(true);
|
|
68
82
|
setError(null);
|
|
69
83
|
// Immediate first fetch
|
|
70
84
|
fetchData();
|
|
71
85
|
// Set up interval
|
|
72
|
-
intervalRef.current = setInterval(fetchData,
|
|
73
|
-
}, [
|
|
74
|
-
const stop =
|
|
86
|
+
intervalRef.current = setInterval(fetchData, intervalValueRef.current);
|
|
87
|
+
}, [fetchData]);
|
|
88
|
+
const stop = React.useCallback(() => {
|
|
75
89
|
clearPolling();
|
|
76
90
|
setIsPolling(false);
|
|
77
91
|
}, [clearPolling]);
|
|
78
|
-
const refetch =
|
|
92
|
+
const refetch = React.useCallback(async () => {
|
|
79
93
|
await fetchData();
|
|
80
94
|
}, [fetchData]);
|
|
81
95
|
// Auto-start polling when enabled and chatUid is set
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
console.log('[usePolling] Auto-start effect triggered:', { enabled, chatUid, isPollingRef: isPollingRef.current, intervalRef: !!intervalRef.current });
|
|
98
|
+
if (!enabled || !chatUid) {
|
|
99
|
+
console.log('[usePolling] Not enabled or no chatUid, stopping if active');
|
|
100
|
+
// Stop polling if disabled or no chatUid
|
|
101
|
+
if (isPollingRef.current) {
|
|
102
|
+
clearPolling();
|
|
103
|
+
setIsPolling(false);
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
85
106
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
// Start polling if not already polling
|
|
108
|
+
if (!isPollingRef.current) {
|
|
109
|
+
console.log('[usePolling] Starting polling, interval:', intervalValueRef.current);
|
|
110
|
+
isPollingRef.current = true;
|
|
111
|
+
setIsPolling(true);
|
|
112
|
+
setError(null);
|
|
113
|
+
// Immediate first fetch
|
|
114
|
+
fetchData();
|
|
115
|
+
// Set up interval
|
|
116
|
+
intervalRef.current = setInterval(fetchData, intervalValueRef.current);
|
|
117
|
+
console.log('[usePolling] Interval set:', intervalRef.current);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log('[usePolling] Already polling, skipping start');
|
|
121
|
+
}
|
|
122
|
+
// Only cleanup on unmount, not on every dependency change
|
|
123
|
+
}, [enabled, chatUid, fetchData, clearPolling]);
|
|
90
124
|
// Cleanup on unmount
|
|
91
|
-
|
|
125
|
+
React.useEffect(() => {
|
|
92
126
|
isMountedRef.current = true;
|
|
93
127
|
return () => {
|
|
94
128
|
isMountedRef.current = false;
|