@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.
Files changed (80) hide show
  1. package/dist/cjs/api/client.js +46 -13
  2. package/dist/cjs/api/client.js.map +1 -1
  3. package/dist/cjs/components/AICommandBar/AICommandBar.js +314 -0
  4. package/dist/cjs/components/AICommandBar/AICommandBar.js.map +1 -0
  5. package/dist/cjs/components/AICommandBar/useAICommandBar.js +595 -0
  6. package/dist/cjs/components/AICommandBar/useAICommandBar.js.map +1 -0
  7. package/dist/cjs/components/ChatDrawer/ChatDrawer.js +200 -22
  8. package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
  9. package/dist/cjs/components/ChatDrawer/ChatInput.js +12 -12
  10. package/dist/cjs/components/ChatDrawer/ChatInput.js.map +1 -1
  11. package/dist/cjs/components/ChatDrawer/ChatMessages.js +137 -29
  12. package/dist/cjs/components/ChatDrawer/ChatMessages.js.map +1 -1
  13. package/dist/cjs/components/ChatDrawer/ConversationSelector.js +93 -0
  14. package/dist/cjs/components/ChatDrawer/ConversationSelector.js.map +1 -0
  15. package/dist/cjs/components/ChatDrawer/ErrorBoundary.js +25 -0
  16. package/dist/cjs/components/ChatDrawer/ErrorBoundary.js.map +1 -0
  17. package/dist/cjs/components/Feedback/FeedbackModal.js +87 -0
  18. package/dist/cjs/components/Feedback/FeedbackModal.js.map +1 -0
  19. package/dist/cjs/components/Feedback/MessageActions.js +74 -0
  20. package/dist/cjs/components/Feedback/MessageActions.js.map +1 -0
  21. package/dist/cjs/hooks/useDevicChat.js +54 -27
  22. package/dist/cjs/hooks/useDevicChat.js.map +1 -1
  23. package/dist/cjs/hooks/useModelInterface.js +6 -6
  24. package/dist/cjs/hooks/usePolling.js +64 -30
  25. package/dist/cjs/hooks/usePolling.js.map +1 -1
  26. package/dist/cjs/index.js +11 -0
  27. package/dist/cjs/index.js.map +1 -1
  28. package/dist/cjs/provider/DevicContext.js +4 -4
  29. package/dist/cjs/provider/DevicProvider.js +2 -2
  30. package/dist/cjs/styles.css +1 -1
  31. package/dist/esm/api/client.d.ts +19 -3
  32. package/dist/esm/api/client.js +46 -13
  33. package/dist/esm/api/client.js.map +1 -1
  34. package/dist/esm/api/types.d.ts +40 -0
  35. package/dist/esm/components/AICommandBar/AICommandBar.d.ts +22 -0
  36. package/dist/esm/components/AICommandBar/AICommandBar.js +312 -0
  37. package/dist/esm/components/AICommandBar/AICommandBar.js.map +1 -0
  38. package/dist/esm/components/AICommandBar/AICommandBar.types.d.ts +321 -0
  39. package/dist/esm/components/AICommandBar/index.d.ts +3 -0
  40. package/dist/esm/components/AICommandBar/useAICommandBar.d.ts +57 -0
  41. package/dist/esm/components/AICommandBar/useAICommandBar.js +592 -0
  42. package/dist/esm/components/AICommandBar/useAICommandBar.js.map +1 -0
  43. package/dist/esm/components/AutocompleteInput/AutocompleteInput.d.ts +4 -0
  44. package/dist/esm/components/AutocompleteInput/AutocompleteInput.types.d.ts +50 -0
  45. package/dist/esm/components/AutocompleteInput/index.d.ts +4 -0
  46. package/dist/esm/components/AutocompleteInput/useAutocomplete.d.ts +29 -0
  47. package/dist/esm/components/ChatDrawer/ChatDrawer.d.ts +4 -2
  48. package/dist/esm/components/ChatDrawer/ChatDrawer.js +191 -13
  49. package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
  50. package/dist/esm/components/ChatDrawer/ChatDrawer.types.d.ts +155 -5
  51. package/dist/esm/components/ChatDrawer/ChatInput.d.ts +2 -1
  52. package/dist/esm/components/ChatDrawer/ChatInput.js +2 -2
  53. package/dist/esm/components/ChatDrawer/ChatInput.js.map +1 -1
  54. package/dist/esm/components/ChatDrawer/ChatMessages.d.ts +2 -4
  55. package/dist/esm/components/ChatDrawer/ChatMessages.js +136 -28
  56. package/dist/esm/components/ChatDrawer/ChatMessages.js.map +1 -1
  57. package/dist/esm/components/ChatDrawer/ConversationSelector.d.ts +2 -0
  58. package/dist/esm/components/ChatDrawer/ConversationSelector.js +91 -0
  59. package/dist/esm/components/ChatDrawer/ConversationSelector.js.map +1 -0
  60. package/dist/esm/components/ChatDrawer/ErrorBoundary.d.ts +16 -0
  61. package/dist/esm/components/ChatDrawer/ErrorBoundary.js +23 -0
  62. package/dist/esm/components/ChatDrawer/ErrorBoundary.js.map +1 -0
  63. package/dist/esm/components/ChatDrawer/index.d.ts +2 -1
  64. package/dist/esm/components/Feedback/Feedback.types.d.ts +50 -0
  65. package/dist/esm/components/Feedback/FeedbackModal.d.ts +5 -0
  66. package/dist/esm/components/Feedback/FeedbackModal.js +85 -0
  67. package/dist/esm/components/Feedback/FeedbackModal.js.map +1 -0
  68. package/dist/esm/components/Feedback/MessageActions.d.ts +5 -0
  69. package/dist/esm/components/Feedback/MessageActions.js +72 -0
  70. package/dist/esm/components/Feedback/MessageActions.js.map +1 -0
  71. package/dist/esm/components/Feedback/index.d.ts +3 -0
  72. package/dist/esm/hooks/useDevicChat.js +37 -10
  73. package/dist/esm/hooks/useDevicChat.js.map +1 -1
  74. package/dist/esm/hooks/usePolling.js +46 -12
  75. package/dist/esm/hooks/usePolling.js.map +1 -1
  76. package/dist/esm/index.d.ts +7 -3
  77. package/dist/esm/index.js +5 -0
  78. package/dist/esm/index.js.map +1 -1
  79. package/dist/esm/styles.css +1 -1
  80. 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 react = require('react');
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] = 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);
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] = react.useState(false);
49
+ const [shouldPoll, setShouldPoll] = React.useState(false);
49
50
  // Refs for callbacks
50
- const onMessageReceivedRef = react.useRef(onMessageReceived);
51
- const onErrorRef = react.useRef(onError);
52
- const onChatCreatedRef = react.useRef(onChatCreated);
53
- react.useEffect(() => {
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 = react.useRef(null);
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
- react.useEffect(() => {
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
- return clientRef.current.getRealtimeHistory(assistantId, chatUid);
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
- setMessages(data.chatHistory);
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: async (data) => {
98
- setIsLoading(false);
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 === 'waiting_for_tool_response') {
106
- // Handle tool response
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 = react.useCallback(async (data) => {
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 = react.useCallback(async (message, sendOptions) => {
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 = react.useCallback(() => {
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 = react.useCallback(async (loadChatUid) => {
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 react = require('react');
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 = react.useMemo(() => {
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 = react.useMemo(() => {
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 = react.useCallback((toolName) => {
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 = react.useCallback(async (toolCalls) => {
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 = react.useCallback((messages) => {
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 react = require('react');
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] = 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 to avoid stale closures
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
- react.useEffect(() => {
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 = react.useCallback(() => {
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 = react.useCallback(async () => {
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
- if (stopStatuses.includes(result.status)) {
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
- }, [stopStatuses, clearPolling]);
64
- const start = react.useCallback(() => {
65
- if (!chatUid || intervalRef.current)
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, interval);
73
- }, [chatUid, interval, fetchData]);
74
- const stop = react.useCallback(() => {
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 = react.useCallback(async () => {
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
- react.useEffect(() => {
83
- if (enabled && chatUid && !isPolling) {
84
- start();
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
- return () => {
87
- clearPolling();
88
- };
89
- }, [enabled, chatUid, start, isPolling, clearPolling]);
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
- react.useEffect(() => {
125
+ React.useEffect(() => {
92
126
  isMountedRef.current = true;
93
127
  return () => {
94
128
  isMountedRef.current = false;