@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,91 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useState, useRef, useEffect, useCallback } from 'react';
3
+ import { useOptionalDevicContext } from '../../provider/DevicContext.js';
4
+ import { DevicApiClient } from '../../api/client.js';
5
+
6
+ function ConversationSelector({ assistantId, currentChatUid, onSelect, onNewChat, apiKey: propsApiKey, baseUrl: propsBaseUrl, tenantId: propsTenantId, }) {
7
+ const context = useOptionalDevicContext();
8
+ const apiKey = propsApiKey || context?.apiKey;
9
+ const baseUrl = propsBaseUrl || context?.baseUrl || 'https://api.devic.ai';
10
+ const tenantId = propsTenantId || context?.tenantId;
11
+ const [isOpen, setIsOpen] = useState(false);
12
+ const [conversations, setConversations] = useState([]);
13
+ const [search, setSearch] = useState('');
14
+ const [loading, setLoading] = useState(false);
15
+ const dropdownRef = useRef(null);
16
+ const clientRef = useRef(null);
17
+ if (!clientRef.current && apiKey) {
18
+ clientRef.current = new DevicApiClient({ apiKey, baseUrl });
19
+ }
20
+ // Update client config when props/context change
21
+ useEffect(() => {
22
+ if (clientRef.current && apiKey) {
23
+ clientRef.current.setConfig({ apiKey, baseUrl });
24
+ }
25
+ else if (!clientRef.current && apiKey) {
26
+ clientRef.current = new DevicApiClient({ apiKey, baseUrl });
27
+ }
28
+ }, [apiKey, baseUrl]);
29
+ const fetchConversations = useCallback(async () => {
30
+ if (!clientRef.current)
31
+ return;
32
+ setLoading(true);
33
+ try {
34
+ const list = await clientRef.current.listConversations(assistantId, { tenantId });
35
+ setConversations(list);
36
+ }
37
+ catch {
38
+ // silently fail
39
+ }
40
+ finally {
41
+ setLoading(false);
42
+ }
43
+ }, [assistantId, tenantId]);
44
+ useEffect(() => {
45
+ if (isOpen) {
46
+ fetchConversations();
47
+ }
48
+ }, [isOpen, fetchConversations]);
49
+ // Close on outside click
50
+ useEffect(() => {
51
+ if (!isOpen)
52
+ return;
53
+ const handler = (e) => {
54
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
55
+ setIsOpen(false);
56
+ }
57
+ };
58
+ document.addEventListener('mousedown', handler);
59
+ return () => document.removeEventListener('mousedown', handler);
60
+ }, [isOpen]);
61
+ const currentConv = conversations.find((c) => c.chatUID === currentChatUid);
62
+ const currentName = currentConv
63
+ ? currentConv.name || formatDate(currentConv.creationTimestampMs)
64
+ : 'New chat';
65
+ const filtered = conversations.filter((c) => !search || (c.name || '').toLowerCase().includes(search.toLowerCase()));
66
+ return (jsxs("div", { className: "devic-conversation-selector", ref: dropdownRef, children: [jsxs("button", { className: "devic-conversation-selector-trigger", onClick: () => setIsOpen(!isOpen), type: "button", children: [jsx("span", { className: "devic-conversation-selector-label", children: currentName }), jsx(ChevronIcon, { open: isOpen })] }), isOpen && (jsxs("div", { className: "devic-conversation-dropdown", children: [jsx("div", { className: "devic-conversation-search-wrapper", children: jsx("input", { className: "devic-conversation-search", type: "text", placeholder: "Search conversations...", value: search, onChange: (e) => setSearch(e.target.value), autoFocus: true }) }), jsxs("div", { className: "devic-conversation-list", children: [loading && (jsx("div", { className: "devic-conversation-loading", children: "Loading..." })), !loading && filtered.length === 0 && (jsx("div", { className: "devic-conversation-empty", children: "No conversations" })), !loading &&
67
+ filtered.map((conv) => (jsxs("button", { className: "devic-conversation-item", "data-active": conv.chatUID === currentChatUid, type: "button", onClick: () => {
68
+ onSelect(conv.chatUID);
69
+ setIsOpen(false);
70
+ }, children: [conv.chatUID === currentChatUid && (jsx("span", { className: "devic-conversation-item-check", children: jsx(CheckIcon, {}) })), jsx("span", { className: "devic-conversation-item-name", children: conv.name || formatDate(conv.creationTimestampMs) }), jsx("span", { className: "devic-conversation-item-date", children: formatDate(conv.lastEditTimestampMs || conv.creationTimestampMs) })] }, conv.chatUID)))] }), jsx("button", { className: "devic-conversation-new", type: "button", onClick: () => {
71
+ onNewChat();
72
+ setIsOpen(false);
73
+ }, children: "+ Start a new chat" })] }))] }));
74
+ }
75
+ function ChevronIcon({ open }) {
76
+ return (jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { transform: open ? 'rotate(180deg)' : undefined, transition: '0.2s' }, children: jsx("polyline", { points: "6 9 12 15 18 9" }) }));
77
+ }
78
+ function CheckIcon() {
79
+ return (jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "20,6 9,17 4,12" }) }));
80
+ }
81
+ function formatDate(ms) {
82
+ const d = new Date(ms);
83
+ const now = new Date();
84
+ if (d.toDateString() === now.toDateString()) {
85
+ return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
86
+ }
87
+ return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
88
+ }
89
+
90
+ export { ConversationSelector };
91
+ //# sourceMappingURL=ConversationSelector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationSelector.js","sources":["../../../../src/components/ChatDrawer/ConversationSelector.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef, useCallback } from 'react';\nimport { useOptionalDevicContext } from '../../provider';\nimport { DevicApiClient } from '../../api/client';\nimport type { ConversationSummary } from '../../api/types';\nimport type { ConversationSelectorProps } from './ChatDrawer.types';\n\nexport function ConversationSelector({\n assistantId,\n currentChatUid,\n onSelect,\n onNewChat,\n apiKey: propsApiKey,\n baseUrl: propsBaseUrl,\n tenantId: propsTenantId,\n}: ConversationSelectorProps): JSX.Element {\n const context = useOptionalDevicContext();\n const apiKey = propsApiKey || context?.apiKey;\n const baseUrl = propsBaseUrl || context?.baseUrl || 'https://api.devic.ai';\n const tenantId = propsTenantId || context?.tenantId;\n\n const [isOpen, setIsOpen] = useState(false);\n const [conversations, setConversations] = useState<ConversationSummary[]>([]);\n const [search, setSearch] = useState('');\n const [loading, setLoading] = useState(false);\n const dropdownRef = useRef<HTMLDivElement>(null);\n const clientRef = useRef<DevicApiClient | null>(null);\n\n if (!clientRef.current && apiKey) {\n clientRef.current = new DevicApiClient({ apiKey, baseUrl });\n }\n\n // Update client config when props/context change\n useEffect(() => {\n if (clientRef.current && apiKey) {\n clientRef.current.setConfig({ apiKey, baseUrl });\n } else if (!clientRef.current && apiKey) {\n clientRef.current = new DevicApiClient({ apiKey, baseUrl });\n }\n }, [apiKey, baseUrl]);\n\n const fetchConversations = useCallback(async () => {\n if (!clientRef.current) return;\n setLoading(true);\n try {\n const list = await clientRef.current.listConversations(assistantId, { tenantId });\n setConversations(list);\n } catch {\n // silently fail\n } finally {\n setLoading(false);\n }\n }, [assistantId, tenantId]);\n\n useEffect(() => {\n if (isOpen) {\n fetchConversations();\n }\n }, [isOpen, fetchConversations]);\n\n // Close on outside click\n useEffect(() => {\n if (!isOpen) return;\n const handler = (e: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {\n setIsOpen(false);\n }\n };\n document.addEventListener('mousedown', handler);\n return () => document.removeEventListener('mousedown', handler);\n }, [isOpen]);\n\n const currentConv = conversations.find((c) => c.chatUID === currentChatUid);\n const currentName = currentConv\n ? currentConv.name || formatDate(currentConv.creationTimestampMs)\n : 'New chat';\n\n const filtered = conversations.filter((c) =>\n !search || (c.name || '').toLowerCase().includes(search.toLowerCase())\n );\n\n return (\n <div className=\"devic-conversation-selector\" ref={dropdownRef}>\n <button\n className=\"devic-conversation-selector-trigger\"\n onClick={() => setIsOpen(!isOpen)}\n type=\"button\"\n >\n <span className=\"devic-conversation-selector-label\">{currentName}</span>\n <ChevronIcon open={isOpen} />\n </button>\n\n {isOpen && (\n <div className=\"devic-conversation-dropdown\">\n <div className=\"devic-conversation-search-wrapper\">\n <input\n className=\"devic-conversation-search\"\n type=\"text\"\n placeholder=\"Search conversations...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n autoFocus\n />\n </div>\n\n <div className=\"devic-conversation-list\">\n {loading && (\n <div className=\"devic-conversation-loading\">Loading...</div>\n )}\n {!loading && filtered.length === 0 && (\n <div className=\"devic-conversation-empty\">No conversations</div>\n )}\n {!loading &&\n filtered.map((conv) => (\n <button\n key={conv.chatUID}\n className=\"devic-conversation-item\"\n data-active={conv.chatUID === currentChatUid}\n type=\"button\"\n onClick={() => {\n onSelect(conv.chatUID);\n setIsOpen(false);\n }}\n >\n {conv.chatUID === currentChatUid && (\n <span className=\"devic-conversation-item-check\">\n <CheckIcon />\n </span>\n )}\n <span className=\"devic-conversation-item-name\">\n {conv.name || formatDate(conv.creationTimestampMs)}\n </span>\n <span className=\"devic-conversation-item-date\">\n {formatDate(conv.lastEditTimestampMs || conv.creationTimestampMs)}\n </span>\n </button>\n ))}\n </div>\n\n <button\n className=\"devic-conversation-new\"\n type=\"button\"\n onClick={() => {\n onNewChat();\n setIsOpen(false);\n }}\n >\n + Start a new chat\n </button>\n </div>\n )}\n </div>\n );\n}\n\nfunction ChevronIcon({ open }: { open: boolean }): JSX.Element {\n return (\n <svg\n width=\"12\"\n height=\"12\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={{ transform: open ? 'rotate(180deg)' : undefined, transition: '0.2s' }}\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n );\n}\n\nfunction CheckIcon(): JSX.Element {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <polyline points=\"20,6 9,17 4,12\" />\n </svg>\n );\n}\n\nfunction formatDate(ms: number): string {\n const d = new Date(ms);\n const now = new Date();\n if (d.toDateString() === now.toDateString()) {\n return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });\n }\n return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });\n}\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Component } from 'react';
2
+ import type { ReactNode, ErrorInfo } from 'react';
3
+ interface Props {
4
+ children: ReactNode;
5
+ fallback?: ReactNode;
6
+ }
7
+ interface State {
8
+ hasError: boolean;
9
+ }
10
+ export declare class ChatDrawerErrorBoundary extends Component<Props, State> {
11
+ state: State;
12
+ static getDerivedStateFromError(): State;
13
+ componentDidCatch(error: Error, info: ErrorInfo): void;
14
+ render(): ReactNode;
15
+ }
16
+ export {};
@@ -0,0 +1,23 @@
1
+ import { Component } from 'react';
2
+
3
+ class ChatDrawerErrorBoundary extends Component {
4
+ constructor() {
5
+ super(...arguments);
6
+ this.state = { hasError: false };
7
+ }
8
+ static getDerivedStateFromError() {
9
+ return { hasError: true };
10
+ }
11
+ componentDidCatch(error, info) {
12
+ console.error('[devic-ui] ChatDrawer error:', error, info.componentStack);
13
+ }
14
+ render() {
15
+ if (this.state.hasError) {
16
+ return this.props.fallback ?? null;
17
+ }
18
+ return this.props.children;
19
+ }
20
+ }
21
+
22
+ export { ChatDrawerErrorBoundary };
23
+ //# sourceMappingURL=ErrorBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBoundary.js","sources":["../../../../src/components/ChatDrawer/ErrorBoundary.tsx"],"sourcesContent":["import React, { Component } from 'react';\nimport type { ReactNode, ErrorInfo } from 'react';\n\ninterface Props {\n children: ReactNode;\n fallback?: ReactNode;\n}\n\ninterface State {\n hasError: boolean;\n}\n\nexport class ChatDrawerErrorBoundary extends Component<Props, State> {\n state: State = { hasError: false };\n\n static getDerivedStateFromError(): State {\n return { hasError: true };\n }\n\n componentDidCatch(error: Error, info: ErrorInfo): void {\n console.error('[devic-ui] ChatDrawer error:', error, info.componentStack);\n }\n\n render(): ReactNode {\n if (this.state.hasError) {\n return this.props.fallback ?? null;\n }\n return this.props.children;\n }\n}\n"],"names":[],"mappings":";;AAYM,MAAO,uBAAwB,SAAQ,SAAuB,CAAA;AAApE,IAAA,WAAA,GAAA;;AACE,QAAA,IAAA,CAAA,KAAK,GAAU,EAAE,QAAQ,EAAE,KAAK,EAAE;IAgBpC;AAdE,IAAA,OAAO,wBAAwB,GAAA;AAC7B,QAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE;IAC3B;IAEA,iBAAiB,CAAC,KAAY,EAAE,IAAe,EAAA;QAC7C,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC;IAC3E;IAEA,MAAM,GAAA;AACJ,QAAA,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE;AACvB,YAAA,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI;QACpC;AACA,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ;IAC5B;AACD;;;;"}
@@ -2,4 +2,5 @@ export { ChatDrawer } from './ChatDrawer';
2
2
  export { ChatMessages } from './ChatMessages';
3
3
  export { ChatInput } from './ChatInput';
4
4
  export { ToolTimeline } from './ToolTimeline';
5
- export type { ChatDrawerProps, ChatDrawerOptions, ChatMessagesProps, ChatInputProps, ToolTimelineProps, AllowedFileTypes, } from './ChatDrawer.types';
5
+ export { ConversationSelector } from './ConversationSelector';
6
+ export type { ChatDrawerProps, ChatDrawerOptions, ChatDrawerHandle, ChatMessagesProps, ChatInputProps, ToolTimelineProps, AllowedFileTypes, ConversationSelectorProps, } from './ChatDrawer.types';
@@ -0,0 +1,50 @@
1
+ import type { FeedbackEntry } from '../../api/types';
2
+ /**
3
+ * Feedback state for a message
4
+ */
5
+ export type FeedbackState = 'none' | 'positive' | 'negative';
6
+ /**
7
+ * Theme configuration for feedback components
8
+ */
9
+ export interface FeedbackTheme {
10
+ backgroundColor?: string;
11
+ textColor?: string;
12
+ textMutedColor?: string;
13
+ secondaryBackgroundColor?: string;
14
+ borderColor?: string;
15
+ primaryColor?: string;
16
+ primaryHoverColor?: string;
17
+ }
18
+ /**
19
+ * Props for FeedbackModal component
20
+ */
21
+ export interface FeedbackModalProps {
22
+ isOpen: boolean;
23
+ onClose: () => void;
24
+ onSubmit: (comment: string) => void;
25
+ feedbackType: 'positive' | 'negative';
26
+ isSubmitting?: boolean;
27
+ theme?: FeedbackTheme;
28
+ }
29
+ /**
30
+ * Props for MessageActions component
31
+ */
32
+ export interface MessageActionsProps {
33
+ messageId: string;
34
+ messageContent?: string;
35
+ currentFeedback?: FeedbackState;
36
+ onFeedback?: (messageId: string, positive: boolean, comment?: string) => void;
37
+ onCopy?: (content: string) => void;
38
+ showCopy?: boolean;
39
+ showFeedback?: boolean;
40
+ disabled?: boolean;
41
+ theme?: FeedbackTheme;
42
+ }
43
+ /**
44
+ * Context value for feedback
45
+ */
46
+ export interface FeedbackContextValue {
47
+ feedbackMap: Map<string, FeedbackEntry>;
48
+ submitFeedback: (messageId: string, positive: boolean, comment?: string) => Promise<void>;
49
+ isSubmitting: boolean;
50
+ }
@@ -0,0 +1,5 @@
1
+ import type { FeedbackModalProps } from './Feedback.types';
2
+ /**
3
+ * Modal for submitting feedback with optional comment
4
+ */
5
+ export declare function FeedbackModal({ isOpen, onClose, onSubmit, feedbackType, isSubmitting, theme, }: FeedbackModalProps): JSX.Element | null;
@@ -0,0 +1,85 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { useState, useRef, useMemo, useEffect } from 'react';
3
+
4
+ /**
5
+ * Modal for submitting feedback with optional comment
6
+ */
7
+ function FeedbackModal({ isOpen, onClose, onSubmit, feedbackType, isSubmitting = false, theme, }) {
8
+ const [comment, setComment] = useState('');
9
+ const textareaRef = useRef(null);
10
+ const modalRef = useRef(null);
11
+ // Build inline styles from theme
12
+ const modalStyle = useMemo(() => {
13
+ if (!theme)
14
+ return undefined;
15
+ return {
16
+ '--devic-bg': theme.backgroundColor,
17
+ '--devic-text': theme.textColor,
18
+ '--devic-text-muted': theme.textMutedColor,
19
+ '--devic-text-secondary': theme.textMutedColor,
20
+ '--devic-bg-secondary': theme.secondaryBackgroundColor,
21
+ '--devic-border': theme.borderColor,
22
+ '--devic-primary': theme.primaryColor,
23
+ '--devic-primary-hover': theme.primaryHoverColor,
24
+ };
25
+ }, [theme]);
26
+ // Focus textarea when modal opens
27
+ useEffect(() => {
28
+ if (isOpen && textareaRef.current) {
29
+ setTimeout(() => textareaRef.current?.focus(), 50);
30
+ }
31
+ }, [isOpen]);
32
+ // Reset comment when modal closes
33
+ useEffect(() => {
34
+ if (!isOpen) {
35
+ setComment('');
36
+ }
37
+ }, [isOpen]);
38
+ // Handle click outside
39
+ useEffect(() => {
40
+ if (!isOpen)
41
+ return;
42
+ const handleClickOutside = (e) => {
43
+ if (modalRef.current && !modalRef.current.contains(e.target)) {
44
+ onClose();
45
+ }
46
+ };
47
+ const handleEscape = (e) => {
48
+ if (e.key === 'Escape') {
49
+ onClose();
50
+ }
51
+ };
52
+ document.addEventListener('mousedown', handleClickOutside);
53
+ document.addEventListener('keydown', handleEscape);
54
+ return () => {
55
+ document.removeEventListener('mousedown', handleClickOutside);
56
+ document.removeEventListener('keydown', handleEscape);
57
+ };
58
+ }, [isOpen, onClose]);
59
+ if (!isOpen)
60
+ return null;
61
+ const handleSubmit = (e) => {
62
+ e.preventDefault();
63
+ onSubmit(comment);
64
+ };
65
+ const handleKeyDown = (e) => {
66
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
67
+ e.preventDefault();
68
+ onSubmit(comment);
69
+ }
70
+ };
71
+ return (jsx("div", { className: "devic-feedback-overlay", children: jsxs("div", { className: "devic-feedback-modal", ref: modalRef, style: modalStyle, children: [jsxs("div", { className: "devic-feedback-modal-header", children: [jsx("span", { className: "devic-feedback-modal-icon", children: feedbackType === 'positive' ? jsx(ThumbsUpIcon, { filled: true }) : jsx(ThumbsDownIcon, { filled: true }) }), jsx("span", { className: "devic-feedback-modal-title", children: feedbackType === 'positive' ? 'What did you like?' : 'What could be improved?' }), jsx("button", { type: "button", className: "devic-feedback-modal-close", onClick: onClose, "aria-label": "Close", children: jsx(CloseIcon, {}) })] }), jsxs("form", { onSubmit: handleSubmit, children: [jsx("textarea", { ref: textareaRef, className: "devic-feedback-textarea", placeholder: "Add a comment (optional)...", value: comment, onChange: (e) => setComment(e.target.value), onKeyDown: handleKeyDown, rows: 3, disabled: isSubmitting }), jsxs("div", { className: "devic-feedback-modal-actions", children: [jsx("button", { type: "button", className: "devic-feedback-btn devic-feedback-btn--secondary", onClick: onClose, disabled: isSubmitting, children: "Cancel" }), jsx("button", { type: "submit", className: "devic-feedback-btn devic-feedback-btn--primary", disabled: isSubmitting, children: isSubmitting ? 'Sending...' : 'Submit' })] })] })] }) }));
72
+ }
73
+ /* ── Icons ── */
74
+ function ThumbsUpIcon({ filled = false }) {
75
+ return (jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: filled ? 'currentColor' : 'none', stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: 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" }) }));
76
+ }
77
+ function ThumbsDownIcon({ filled = false }) {
78
+ return (jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: filled ? 'currentColor' : 'none', stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: 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" }) }));
79
+ }
80
+ function CloseIcon() {
81
+ return (jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }));
82
+ }
83
+
84
+ export { FeedbackModal };
85
+ //# sourceMappingURL=FeedbackModal.js.map
@@ -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":["_jsx","_jsxs"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { MessageActionsProps } from './Feedback.types';
2
+ /**
3
+ * Action buttons for a message (copy, thumbs up, thumbs down)
4
+ */
5
+ export declare function MessageActions({ messageId, messageContent, currentFeedback, onFeedback, onCopy, showCopy, showFeedback, disabled, theme, }: MessageActionsProps): JSX.Element;
@@ -0,0 +1,72 @@
1
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
+ import { useState, useMemo, useCallback } from 'react';
3
+ import { FeedbackModal } from './FeedbackModal.js';
4
+
5
+ /**
6
+ * Action buttons for a message (copy, thumbs up, thumbs down)
7
+ */
8
+ function MessageActions({ messageId, messageContent, currentFeedback = 'none', onFeedback, onCopy, showCopy = true, showFeedback = true, disabled = false, theme, }) {
9
+ const [feedbackState, setFeedbackState] = useState(currentFeedback);
10
+ const [modalOpen, setModalOpen] = useState(false);
11
+ const [pendingFeedbackType, setPendingFeedbackType] = useState('positive');
12
+ const [isSubmitting, setIsSubmitting] = useState(false);
13
+ const [copied, setCopied] = useState(false);
14
+ // Build inline styles from theme for the actions container
15
+ const containerStyle = useMemo(() => {
16
+ if (!theme)
17
+ return undefined;
18
+ return {
19
+ '--devic-bg-secondary': theme.secondaryBackgroundColor,
20
+ '--devic-text-secondary': theme.textMutedColor,
21
+ };
22
+ }, [theme]);
23
+ const handleCopy = useCallback(async () => {
24
+ if (!messageContent)
25
+ return;
26
+ try {
27
+ await navigator.clipboard.writeText(messageContent);
28
+ setCopied(true);
29
+ onCopy?.(messageContent);
30
+ setTimeout(() => setCopied(false), 2000);
31
+ }
32
+ catch (err) {
33
+ console.error('Failed to copy:', err);
34
+ }
35
+ }, [messageContent, onCopy]);
36
+ const handleFeedbackClick = useCallback((type) => {
37
+ setPendingFeedbackType(type);
38
+ setModalOpen(true);
39
+ }, [feedbackState]);
40
+ const handleModalSubmit = useCallback(async (comment) => {
41
+ setIsSubmitting(true);
42
+ try {
43
+ const isPositive = pendingFeedbackType === 'positive';
44
+ await onFeedback?.(messageId, isPositive, comment || undefined);
45
+ setFeedbackState(isPositive ? 'positive' : 'negative');
46
+ setModalOpen(false);
47
+ }
48
+ catch (err) {
49
+ console.error('Failed to submit feedback:', err);
50
+ }
51
+ finally {
52
+ setIsSubmitting(false);
53
+ }
54
+ }, [messageId, pendingFeedbackType, onFeedback]);
55
+ return (jsxs(Fragment, { children: [jsxs("div", { className: "devic-message-actions", style: containerStyle, children: [showCopy && messageContent && (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 ? jsx(CheckIcon, {}) : jsx(CopyIcon, {}) })), showFeedback && (jsxs(Fragment, { children: [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: jsx(ThumbsUpIcon, { filled: feedbackState === 'positive' }) }), 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: jsx(ThumbsDownIcon, { filled: feedbackState === 'negative' }) })] }))] }), jsx(FeedbackModal, { isOpen: modalOpen, onClose: () => setModalOpen(false), onSubmit: handleModalSubmit, feedbackType: pendingFeedbackType, isSubmitting: isSubmitting, theme: theme })] }));
56
+ }
57
+ /* ── Icons ── */
58
+ function CopyIcon() {
59
+ return (jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] }));
60
+ }
61
+ function CheckIcon() {
62
+ return (jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "20,6 9,17 4,12" }) }));
63
+ }
64
+ function ThumbsUpIcon({ filled = false }) {
65
+ return (jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: filled ? 'currentColor' : 'none', stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: 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" }) }));
66
+ }
67
+ function ThumbsDownIcon({ filled = false }) {
68
+ return (jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: filled ? 'currentColor' : 'none', stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: 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" }) }));
69
+ }
70
+
71
+ export { MessageActions };
72
+ //# 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":["_jsxs","_jsx","_Fragment"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { FeedbackModal } from './FeedbackModal';
2
+ export { MessageActions } from './MessageActions';
3
+ export type { FeedbackModalProps, MessageActionsProps, FeedbackState, FeedbackContextValue, FeedbackTheme, } from './Feedback.types';
@@ -5,6 +5,7 @@ import { DevicApiClient } from '../api/client.js';
5
5
  import { usePolling } from './usePolling.js';
6
6
  import { useModelInterface } from './useModelInterface.js';
7
7
 
8
+ console.log('[devic-ui] Version: DEV-BUILD-001 (2026-01-26 18:20)');
8
9
  /**
9
10
  * Main hook for managing chat with a Devic assistant
10
11
  *
@@ -69,18 +70,38 @@ function useDevicChat(options) {
69
70
  tools: modelInterfaceTools,
70
71
  onToolExecute: onToolCall,
71
72
  });
72
- // Polling hook
73
+ // Polling hook - uses callbacks for side effects, return value not needed
74
+ console.log('[useDevicChat] Render - shouldPoll:', shouldPoll, 'chatUid:', chatUid);
73
75
  usePolling(shouldPoll ? chatUid : null, async () => {
76
+ console.log('[useDevicChat] fetchFn called, chatUid:', chatUid);
74
77
  if (!clientRef.current || !chatUid) {
75
78
  throw new Error('Cannot poll without client or chatUid');
76
79
  }
77
- return clientRef.current.getRealtimeHistory(assistantId, chatUid);
80
+ const result = await clientRef.current.getRealtimeHistory(assistantId, chatUid);
81
+ console.log('[useDevicChat] getRealtimeHistory result:', result);
82
+ return result;
78
83
  }, {
79
84
  interval: pollingInterval,
80
85
  enabled: shouldPoll,
81
86
  stopStatuses: ['completed', 'error', 'waiting_for_tool_response'],
82
87
  onUpdate: async (data) => {
83
- setMessages(data.chatHistory);
88
+ console.log('[useDevicChat] onUpdate called, status:', data.status);
89
+ // Merge realtime data with optimistic messages
90
+ setMessages((prev) => {
91
+ const realtimeUIDs = new Set(data.chatHistory.map((m) => m.uid));
92
+ const realtimeUserMessages = new Set(data.chatHistory
93
+ .filter((m) => m.role === 'user')
94
+ .map((m) => m.content?.message));
95
+ // Keep optimistic messages not yet in realtime data
96
+ const optimistic = prev.filter((m) => {
97
+ if (realtimeUIDs.has(m.uid))
98
+ return false;
99
+ if (m.role === 'user' && realtimeUserMessages.has(m.content?.message))
100
+ return false;
101
+ return true;
102
+ });
103
+ return [...data.chatHistory, ...optimistic];
104
+ });
84
105
  setStatus(data.status);
85
106
  // Notify about new messages
86
107
  const lastMessage = data.chatHistory[data.chatHistory.length - 1];
@@ -92,20 +113,22 @@ function useDevicChat(options) {
92
113
  await handlePendingToolCalls(data);
93
114
  }
94
115
  },
95
- onStop: async (data) => {
96
- setIsLoading(false);
116
+ onStop: (data) => {
117
+ console.log('[useDevicChat] onStop called, status:', data?.status);
97
118
  setShouldPoll(false);
98
119
  if (data?.status === 'error') {
120
+ setIsLoading(false);
99
121
  const err = new Error('Chat processing failed');
100
122
  setError(err);
101
123
  onErrorRef.current?.(err);
102
124
  }
103
- else if (data?.status === 'waiting_for_tool_response') {
104
- // Handle tool response
105
- await handlePendingToolCalls(data);
125
+ else if (data?.status === 'completed') {
126
+ setIsLoading(false);
106
127
  }
128
+ // Note: waiting_for_tool_response is handled in onUpdate to avoid double execution
107
129
  },
108
130
  onError: (err) => {
131
+ console.error('[useDevicChat] onError called:', err);
109
132
  setError(err);
110
133
  setIsLoading(false);
111
134
  setShouldPoll(false);
@@ -180,13 +203,17 @@ function useDevicChat(options) {
180
203
  ...(toolSchemas.length > 0 && { tools: toolSchemas }),
181
204
  };
182
205
  // Send message in async mode
206
+ console.log('[useDevicChat] Sending message async...');
183
207
  const response = await clientRef.current.sendMessageAsync(assistantId, dto);
208
+ console.log('[useDevicChat] sendMessageAsync response:', response);
184
209
  // Update chat UID if this is a new chat
185
210
  if (response.chatUid && response.chatUid !== chatUid) {
211
+ console.log('[useDevicChat] Setting chatUid:', response.chatUid);
186
212
  setChatUid(response.chatUid);
187
213
  onChatCreatedRef.current?.(response.chatUid);
188
214
  }
189
215
  // Start polling for results
216
+ console.log('[useDevicChat] Setting shouldPoll to true');
190
217
  setShouldPoll(true);
191
218
  }
192
219
  catch (err) {
@@ -226,7 +253,7 @@ function useDevicChat(options) {
226
253
  setIsLoading(true);
227
254
  setError(null);
228
255
  try {
229
- const history = await clientRef.current.getChatHistory(assistantId, loadChatUid);
256
+ const history = await clientRef.current.getChatHistory(assistantId, loadChatUid, { tenantId: resolvedTenantId });
230
257
  setMessages(history.chatContent);
231
258
  setChatUid(loadChatUid);
232
259
  setStatus('completed');
@@ -239,7 +266,7 @@ function useDevicChat(options) {
239
266
  finally {
240
267
  setIsLoading(false);
241
268
  }
242
- }, [assistantId]);
269
+ }, [assistantId, resolvedTenantId]);
243
270
  return {
244
271
  messages,
245
272
  chatUid,