@devicai/ui 0.1.0 → 0.2.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 (52) hide show
  1. package/dist/cjs/api/client.js +19 -2
  2. package/dist/cjs/api/client.js.map +1 -1
  3. package/dist/cjs/components/ChatDrawer/ChatDrawer.js +132 -22
  4. package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
  5. package/dist/cjs/components/ChatDrawer/ChatInput.js +12 -12
  6. package/dist/cjs/components/ChatDrawer/ChatInput.js.map +1 -1
  7. package/dist/cjs/components/ChatDrawer/ChatMessages.js +134 -29
  8. package/dist/cjs/components/ChatDrawer/ChatMessages.js.map +1 -1
  9. package/dist/cjs/components/ChatDrawer/ConversationSelector.js +93 -0
  10. package/dist/cjs/components/ChatDrawer/ConversationSelector.js.map +1 -0
  11. package/dist/cjs/components/ChatDrawer/ErrorBoundary.js +25 -0
  12. package/dist/cjs/components/ChatDrawer/ErrorBoundary.js.map +1 -0
  13. package/dist/cjs/hooks/useDevicChat.js +54 -27
  14. package/dist/cjs/hooks/useDevicChat.js.map +1 -1
  15. package/dist/cjs/hooks/useModelInterface.js +6 -6
  16. package/dist/cjs/hooks/usePolling.js +64 -30
  17. package/dist/cjs/hooks/usePolling.js.map +1 -1
  18. package/dist/cjs/index.js +2 -0
  19. package/dist/cjs/index.js.map +1 -1
  20. package/dist/cjs/provider/DevicContext.js +4 -4
  21. package/dist/cjs/provider/DevicProvider.js +2 -2
  22. package/dist/cjs/styles.css +1 -1
  23. package/dist/esm/api/client.d.ts +10 -2
  24. package/dist/esm/api/client.js +19 -2
  25. package/dist/esm/api/client.js.map +1 -1
  26. package/dist/esm/api/types.d.ts +15 -0
  27. package/dist/esm/components/ChatDrawer/ChatDrawer.d.ts +1 -1
  28. package/dist/esm/components/ChatDrawer/ChatDrawer.js +123 -13
  29. package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
  30. package/dist/esm/components/ChatDrawer/ChatDrawer.types.d.ts +119 -5
  31. package/dist/esm/components/ChatDrawer/ChatInput.d.ts +2 -1
  32. package/dist/esm/components/ChatDrawer/ChatInput.js +2 -2
  33. package/dist/esm/components/ChatDrawer/ChatInput.js.map +1 -1
  34. package/dist/esm/components/ChatDrawer/ChatMessages.d.ts +1 -4
  35. package/dist/esm/components/ChatDrawer/ChatMessages.js +133 -28
  36. package/dist/esm/components/ChatDrawer/ChatMessages.js.map +1 -1
  37. package/dist/esm/components/ChatDrawer/ConversationSelector.d.ts +2 -0
  38. package/dist/esm/components/ChatDrawer/ConversationSelector.js +91 -0
  39. package/dist/esm/components/ChatDrawer/ConversationSelector.js.map +1 -0
  40. package/dist/esm/components/ChatDrawer/ErrorBoundary.d.ts +16 -0
  41. package/dist/esm/components/ChatDrawer/ErrorBoundary.js +23 -0
  42. package/dist/esm/components/ChatDrawer/ErrorBoundary.js.map +1 -0
  43. package/dist/esm/components/ChatDrawer/index.d.ts +2 -1
  44. package/dist/esm/hooks/useDevicChat.js +37 -10
  45. package/dist/esm/hooks/useDevicChat.js.map +1 -1
  46. package/dist/esm/hooks/usePolling.js +46 -12
  47. package/dist/esm/hooks/usePolling.js.map +1 -1
  48. package/dist/esm/index.d.ts +3 -3
  49. package/dist/esm/index.js +1 -0
  50. package/dist/esm/index.js.map +1 -1
  51. package/dist/esm/styles.css +1 -1
  52. package/package.json +10 -4
@@ -1,9 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
- var react = require('react');
5
- var ToolTimeline = require('./ToolTimeline.js');
4
+ var React = require('react');
5
+ var Markdown = require('markdown-to-jsx');
6
6
 
7
+ console.log('[devic-ui] ChatMessages: DEV-BUILD-005');
7
8
  /**
8
9
  * Format timestamp to readable time
9
10
  */
@@ -13,41 +14,145 @@ function formatTime(timestamp) {
13
14
  minute: '2-digit',
14
15
  });
15
16
  }
17
+ /**
18
+ * Groups consecutive tool-call assistant messages (no text content)
19
+ * into { toolMessages, isActive } groups, interleaved with regular messages.
20
+ */
21
+ function groupMessages(messages, isLoading) {
22
+ const result = [];
23
+ let currentToolGroup = [];
24
+ const flushToolGroup = (isActive) => {
25
+ if (currentToolGroup.length > 0) {
26
+ result.push({ type: 'toolGroup', toolMessages: [...currentToolGroup], isActive });
27
+ currentToolGroup = [];
28
+ }
29
+ };
30
+ for (let i = 0; i < messages.length; i++) {
31
+ const msg = messages[i];
32
+ // Skip developer and tool response messages
33
+ if (msg.role === 'developer' || msg.role === 'tool') {
34
+ continue;
35
+ }
36
+ const hasToolCalls = msg.tool_calls && msg.tool_calls.length > 0;
37
+ const hasText = !!msg.content?.message;
38
+ const hasFiles = msg.content?.files && msg.content.files.length > 0;
39
+ if (hasToolCalls) {
40
+ // If message has both text and tool_calls, show text first
41
+ if (hasText || hasFiles) {
42
+ // Flush any prior tool group before inserting the text message
43
+ const remainingMeaningful = messages.slice(i + 1).some((m) => (m.role === 'assistant' && m.content?.message) || m.role === 'user');
44
+ flushToolGroup(isLoading && !remainingMeaningful);
45
+ result.push({ type: 'message', message: msg });
46
+ }
47
+ // Always accumulate the tool call
48
+ currentToolGroup.push(msg);
49
+ }
50
+ else {
51
+ // Regular message → flush any accumulated tool group first
52
+ const remainingMeaningful = messages.slice(i).some((m) => (m.role === 'assistant' && m.content?.message) || m.role === 'user');
53
+ flushToolGroup(isLoading && !remainingMeaningful);
54
+ if (hasText || hasFiles) {
55
+ result.push({ type: 'message', message: msg });
56
+ }
57
+ }
58
+ }
59
+ // Flush remaining tool group (is active if still loading)
60
+ flushToolGroup(isLoading);
61
+ return result;
62
+ }
63
+ /**
64
+ * Collapsible tool actions group
65
+ */
66
+ function ToolGroup({ toolMessages, isActive, allMessages, toolRenderers, toolIcons, }) {
67
+ const [isCollapsed, setIsCollapsed] = React.useState(false);
68
+ const shouldCollapse = toolMessages.length > 3 && !isActive;
69
+ // Auto-collapse when transitioning from active to completed
70
+ const wasActiveRef = React.useRef(isActive);
71
+ React.useEffect(() => {
72
+ if (wasActiveRef.current && !isActive && toolMessages.length > 3) {
73
+ setIsCollapsed(true);
74
+ }
75
+ wasActiveRef.current = isActive;
76
+ }, [isActive, toolMessages.length]);
77
+ const lastIndex = toolMessages.length - 1;
78
+ const renderToolItem = (msg, opts) => {
79
+ const toolCall = msg.tool_calls?.[0];
80
+ const toolName = toolCall?.function?.name;
81
+ const summaryText = msg.summary || toolName || (opts.active ? 'Processing...' : 'Completed');
82
+ // Custom renderer for completed tools
83
+ if (!opts.active && toolName && toolRenderers?.[toolName] && allMessages) {
84
+ const toolResponse = allMessages.find((m) => m.role === 'tool' && m.tool_call_id === toolCall.id);
85
+ let input = {};
86
+ try {
87
+ input = JSON.parse(toolCall.function.arguments);
88
+ }
89
+ catch { }
90
+ const output = toolResponse?.content?.data ?? toolResponse?.content?.message;
91
+ return toolRenderers[toolName](input, output);
92
+ }
93
+ const icon = opts.showSpinner
94
+ ? jsxRuntime.jsx(SpinnerIcon, {})
95
+ : (toolName && toolIcons?.[toolName]) || jsxRuntime.jsx(ToolDoneIcon, {});
96
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "devic-tool-activity-icon", children: icon }), jsxRuntime.jsx("span", { className: `devic-tool-activity-text ${opts.active ? 'devic-glow-text' : ''}`, children: summaryText })] }));
97
+ };
98
+ // If active, show all items; last one gets the glow treatment
99
+ if (isActive) {
100
+ return (jsxRuntime.jsx("div", { className: "devic-tool-group", children: toolMessages.map((msg, idx) => {
101
+ const isLast = idx === lastIndex;
102
+ return (jsxRuntime.jsx("div", { className: `devic-tool-activity ${isLast ? 'devic-tool-activity--active' : ''}`, children: renderToolItem(msg, { active: isLast, showSpinner: isLast }) }, msg.uid));
103
+ }) }));
104
+ }
105
+ // Completed: collapse if > 3 actions
106
+ if (shouldCollapse && isCollapsed) {
107
+ return (jsxRuntime.jsx("div", { className: "devic-tool-group", children: jsxRuntime.jsxs("button", { className: "devic-tool-collapse-btn", onClick: () => setIsCollapsed(false), type: "button", children: [jsxRuntime.jsx(ToolDoneIcon, {}), jsxRuntime.jsxs("span", { children: [toolMessages.length, " actions"] }), jsxRuntime.jsx(ChevronDownIcon, {})] }) }));
108
+ }
109
+ return (jsxRuntime.jsxs("div", { className: "devic-tool-group", children: [shouldCollapse && (jsxRuntime.jsxs("button", { className: "devic-tool-collapse-btn", onClick: () => setIsCollapsed(true), type: "button", children: [jsxRuntime.jsxs("span", { children: [toolMessages.length, " actions"] }), jsxRuntime.jsx(ChevronUpIcon, {})] })), jsxRuntime.jsx("div", { className: "devic-tool-group-items", "data-expanded": "true", children: toolMessages.map((msg) => (jsxRuntime.jsx("div", { className: "devic-tool-activity", children: renderToolItem(msg, {}) }, msg.uid))) })] }));
110
+ }
16
111
  /**
17
112
  * Messages list component
18
113
  */
19
- function ChatMessages({ messages, isLoading, welcomeMessage, suggestedMessages, onSuggestedClick, showToolTimeline = true, }) {
20
- const containerRef = react.useRef(null);
21
- const lastMessageCountRef = react.useRef(messages.length);
114
+ const markdownOverrides = {
115
+ table: {
116
+ component: ({ children, ...props }) => React.createElement('div', { className: 'markdown-table' }, React.createElement('table', props, children)),
117
+ },
118
+ };
119
+ function ChatMessages({ messages, allMessages, isLoading, welcomeMessage, suggestedMessages, onSuggestedClick, toolRenderers, toolIcons, loadingIndicator, }) {
120
+ const containerRef = React.useRef(null);
121
+ const prevLengthRef = React.useRef(messages.length);
22
122
  // Auto-scroll to bottom when new messages arrive
23
- react.useEffect(() => {
24
- if (messages.length > lastMessageCountRef.current && containerRef.current) {
123
+ React.useEffect(() => {
124
+ if (containerRef.current) {
25
125
  containerRef.current.scrollTop = containerRef.current.scrollHeight;
26
126
  }
27
- lastMessageCountRef.current = messages.length;
28
- }, [messages.length]);
29
- // Extract tool calls for timeline
30
- const toolCalls = showToolTimeline
31
- ? messages
32
- .filter((m) => m.tool_calls?.length)
33
- .flatMap((m) => m.tool_calls.map((tc) => ({
34
- id: tc.id,
35
- name: tc.function.name,
36
- status: 'completed',
37
- timestamp: m.timestamp,
38
- })))
39
- : [];
40
- return (jsxRuntime.jsxs("div", { className: "devic-messages-container", ref: containerRef, children: [messages.length === 0 && (welcomeMessage || suggestedMessages?.length) && (jsxRuntime.jsxs("div", { className: "devic-welcome", children: [welcomeMessage && (jsxRuntime.jsx("p", { className: "devic-welcome-text", children: welcomeMessage })), suggestedMessages && suggestedMessages.length > 0 && (jsxRuntime.jsx("div", { className: "devic-suggested-messages", children: suggestedMessages.map((msg, idx) => (jsxRuntime.jsx("button", { className: "devic-suggested-btn", onClick: () => onSuggestedClick?.(msg), children: msg }, idx))) }))] })), messages.map((message) => {
41
- // Skip tool messages in main display (shown in timeline)
42
- if (message.role === 'tool' && showToolTimeline) {
43
- return null;
127
+ prevLengthRef.current = messages.length;
128
+ }, [messages.length, isLoading]);
129
+ const grouped = groupMessages(messages, isLoading);
130
+ // Show loading dots only if there's no active tool group at the end
131
+ const lastGroup = grouped[grouped.length - 1];
132
+ const showLoadingDots = isLoading && !(lastGroup?.type === 'toolGroup' && lastGroup.isActive);
133
+ return (jsxRuntime.jsxs("div", { className: "devic-messages-container", ref: containerRef, children: [messages.length === 0 && !isLoading && (welcomeMessage || suggestedMessages?.length) && (jsxRuntime.jsxs("div", { className: "devic-welcome", children: [welcomeMessage && (jsxRuntime.jsx("p", { className: "devic-welcome-text", children: welcomeMessage })), suggestedMessages && suggestedMessages.length > 0 && (jsxRuntime.jsx("div", { className: "devic-suggested-messages", children: suggestedMessages.map((msg, idx) => (jsxRuntime.jsx("button", { className: "devic-suggested-btn", onClick: () => onSuggestedClick?.(msg), children: msg }, idx))) }))] })), grouped.map((item) => {
134
+ if (item.type === 'toolGroup') {
135
+ return (jsxRuntime.jsx(ToolGroup, { toolMessages: item.toolMessages, isActive: item.isActive, allMessages: allMessages, toolRenderers: toolRenderers, toolIcons: toolIcons }, `tg-${item.toolMessages[0].uid}`));
44
136
  }
45
- return (jsxRuntime.jsxs("div", { className: "devic-message", "data-role": message.role, children: [jsxRuntime.jsxs("div", { className: "devic-message-bubble", children: [message.content.message, message.content.files && message.content.files.length > 0 && (jsxRuntime.jsx("div", { className: "devic-message-files", children: message.content.files.map((file, idx) => (jsxRuntime.jsxs("div", { className: "devic-message-file", children: [jsxRuntime.jsx(FileIcon, {}), jsxRuntime.jsx("span", { children: file.name })] }, idx))) }))] }), jsxRuntime.jsx("span", { className: "devic-message-time", children: formatTime(message.timestamp) })] }, message.uid));
46
- }), showToolTimeline && toolCalls.length > 0 && (jsxRuntime.jsx(ToolTimeline.ToolTimeline, { toolCalls: toolCalls })), isLoading && (jsxRuntime.jsxs("div", { className: "devic-loading", children: [jsxRuntime.jsx("span", { className: "devic-loading-dot" }), jsxRuntime.jsx("span", { className: "devic-loading-dot" }), jsxRuntime.jsx("span", { className: "devic-loading-dot" })] }))] }));
137
+ const message = item.message;
138
+ const messageText = message.content?.message;
139
+ const hasFiles = message.content?.files && message.content.files.length > 0;
140
+ return (jsxRuntime.jsxs("div", { className: "devic-message", "data-role": message.role, children: [jsxRuntime.jsxs("div", { className: "devic-message-bubble", children: [messageText && message.role === 'assistant' ? (jsxRuntime.jsx(Markdown, { options: { overrides: markdownOverrides }, children: messageText })) : (messageText), hasFiles && (jsxRuntime.jsx("div", { className: "devic-message-files", children: message.content.files.map((file, fileIdx) => (jsxRuntime.jsxs("div", { className: "devic-message-file", children: [jsxRuntime.jsx(FileIcon, {}), jsxRuntime.jsx("span", { children: file.name })] }, fileIdx))) }))] }), jsxRuntime.jsx("span", { className: "devic-message-time", children: formatTime(message.timestamp) })] }, message.uid));
141
+ }), showLoadingDots && (loadingIndicator ? (jsxRuntime.jsx("div", { className: "devic-loading", children: loadingIndicator })) : (jsxRuntime.jsxs("div", { className: "devic-loading", children: [jsxRuntime.jsx("span", { className: "devic-loading-dot" }), jsxRuntime.jsx("span", { className: "devic-loading-dot" }), jsxRuntime.jsx("span", { className: "devic-loading-dot" })] })))] }));
142
+ }
143
+ /* ── Icons ── */
144
+ function SpinnerIcon() {
145
+ return (jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", className: "devic-spinner", children: jsxRuntime.jsx("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }));
146
+ }
147
+ function ToolDoneIcon() {
148
+ 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" }) }));
149
+ }
150
+ function ChevronDownIcon() {
151
+ return (jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("polyline", { points: "6,9 12,15 18,9" }) }));
152
+ }
153
+ function ChevronUpIcon() {
154
+ return (jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("polyline", { points: "6,15 12,9 18,15" }) }));
47
155
  }
48
- /**
49
- * Simple file icon
50
- */
51
156
  function FileIcon() {
52
157
  return (jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), jsxRuntime.jsx("polyline", { points: "14,2 14,8 20,8" })] }));
53
158
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ChatMessages.js","sources":["../../../../../src/components/ChatDrawer/ChatMessages.tsx"],"sourcesContent":["import React, { useEffect, useRef } from 'react';\nimport type { ChatMessagesProps } from './ChatDrawer.types';\nimport { ToolTimeline } from './ToolTimeline';\n\n/**\n * Format timestamp to readable time\n */\nfunction formatTime(timestamp: number): string {\n return new Date(timestamp).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\n/**\n * Messages list component\n */\nexport function ChatMessages({\n messages,\n isLoading,\n welcomeMessage,\n suggestedMessages,\n onSuggestedClick,\n showToolTimeline = true,\n}: ChatMessagesProps): JSX.Element {\n const containerRef = useRef<HTMLDivElement>(null);\n const lastMessageCountRef = useRef(messages.length);\n\n // Auto-scroll to bottom when new messages arrive\n useEffect(() => {\n if (messages.length > lastMessageCountRef.current && containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n lastMessageCountRef.current = messages.length;\n }, [messages.length]);\n\n // Extract tool calls for timeline\n const toolCalls = showToolTimeline\n ? messages\n .filter((m) => m.tool_calls?.length)\n .flatMap((m) =>\n m.tool_calls!.map((tc) => ({\n id: tc.id,\n name: tc.function.name,\n status: 'completed' as const,\n timestamp: m.timestamp,\n }))\n )\n : [];\n\n return (\n <div className=\"devic-messages-container\" ref={containerRef}>\n {messages.length === 0 && (welcomeMessage || suggestedMessages?.length) && (\n <div className=\"devic-welcome\">\n {welcomeMessage && (\n <p className=\"devic-welcome-text\">{welcomeMessage}</p>\n )}\n {suggestedMessages && suggestedMessages.length > 0 && (\n <div className=\"devic-suggested-messages\">\n {suggestedMessages.map((msg, idx) => (\n <button\n key={idx}\n className=\"devic-suggested-btn\"\n onClick={() => onSuggestedClick?.(msg)}\n >\n {msg}\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n\n {messages.map((message) => {\n // Skip tool messages in main display (shown in timeline)\n if (message.role === 'tool' && showToolTimeline) {\n return null;\n }\n\n return (\n <div\n key={message.uid}\n className=\"devic-message\"\n data-role={message.role}\n >\n <div className=\"devic-message-bubble\">\n {message.content.message}\n {message.content.files && message.content.files.length > 0 && (\n <div className=\"devic-message-files\">\n {message.content.files.map((file, idx) => (\n <div key={idx} className=\"devic-message-file\">\n <FileIcon />\n <span>{file.name}</span>\n </div>\n ))}\n </div>\n )}\n </div>\n <span className=\"devic-message-time\">\n {formatTime(message.timestamp)}\n </span>\n </div>\n );\n })}\n\n {showToolTimeline && toolCalls.length > 0 && (\n <ToolTimeline toolCalls={toolCalls} />\n )}\n\n {isLoading && (\n <div className=\"devic-loading\">\n <span className=\"devic-loading-dot\"></span>\n <span className=\"devic-loading-dot\"></span>\n <span className=\"devic-loading-dot\"></span>\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Simple file icon\n */\nfunction FileIcon(): JSX.Element {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n <polyline points=\"14,2 14,8 20,8\" />\n </svg>\n );\n}\n"],"names":["useRef","useEffect","_jsxs","_jsx","ToolTimeline"],"mappings":"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"}
1
+ {"version":3,"file":"ChatMessages.js","sources":["../../../../../src/components/ChatDrawer/ChatMessages.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef } from 'react';\nimport Markdown from 'markdown-to-jsx';\nimport type { ChatMessagesProps } from './ChatDrawer.types';\nimport type { ChatMessage } from '../../api/types';\n\nconsole.log('[devic-ui] ChatMessages: DEV-BUILD-005');\n\n/**\n * Format timestamp to readable time\n */\nfunction formatTime(timestamp: number): string {\n return new Date(timestamp).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\n/**\n * Groups consecutive tool-call assistant messages (no text content)\n * into { toolMessages, isActive } groups, interleaved with regular messages.\n */\nfunction groupMessages(\n messages: ChatMessage[],\n isLoading: boolean\n): Array<\n | { type: 'message'; message: ChatMessage }\n | { type: 'toolGroup'; toolMessages: ChatMessage[]; isActive: boolean }\n> {\n const result: Array<\n | { type: 'message'; message: ChatMessage }\n | { type: 'toolGroup'; toolMessages: ChatMessage[]; isActive: boolean }\n > = [];\n\n let currentToolGroup: ChatMessage[] = [];\n\n const flushToolGroup = (isActive: boolean) => {\n if (currentToolGroup.length > 0) {\n result.push({ type: 'toolGroup', toolMessages: [...currentToolGroup], isActive });\n currentToolGroup = [];\n }\n };\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n\n // Skip developer and tool response messages\n if (msg.role === 'developer' || msg.role === 'tool') {\n continue;\n }\n\n const hasToolCalls = msg.tool_calls && msg.tool_calls.length > 0;\n const hasText = !!msg.content?.message;\n const hasFiles = msg.content?.files && msg.content.files.length > 0;\n\n if (hasToolCalls) {\n // If message has both text and tool_calls, show text first\n if (hasText || hasFiles) {\n // Flush any prior tool group before inserting the text message\n const remainingMeaningful = messages.slice(i + 1).some(\n (m) => (m.role === 'assistant' && m.content?.message) || m.role === 'user'\n );\n flushToolGroup(isLoading && !remainingMeaningful);\n result.push({ type: 'message', message: msg });\n }\n // Always accumulate the tool call\n currentToolGroup.push(msg);\n } else {\n // Regular message → flush any accumulated tool group first\n const remainingMeaningful = messages.slice(i).some(\n (m) => (m.role === 'assistant' && m.content?.message) || m.role === 'user'\n );\n flushToolGroup(isLoading && !remainingMeaningful);\n\n if (hasText || hasFiles) {\n result.push({ type: 'message', message: msg });\n }\n }\n }\n\n // Flush remaining tool group (is active if still loading)\n flushToolGroup(isLoading);\n\n return result;\n}\n\n/**\n * Collapsible tool actions group\n */\nfunction ToolGroup({\n toolMessages,\n isActive,\n allMessages,\n toolRenderers,\n toolIcons,\n}: {\n toolMessages: ChatMessage[];\n isActive: boolean;\n allMessages?: ChatMessage[];\n toolRenderers?: Record<string, (input: any, output: any) => React.ReactNode>;\n toolIcons?: Record<string, React.ReactNode>;\n}): JSX.Element {\n const [isCollapsed, setIsCollapsed] = useState(false);\n const shouldCollapse = toolMessages.length > 3 && !isActive;\n\n // Auto-collapse when transitioning from active to completed\n const wasActiveRef = useRef(isActive);\n useEffect(() => {\n if (wasActiveRef.current && !isActive && toolMessages.length > 3) {\n setIsCollapsed(true);\n }\n wasActiveRef.current = isActive;\n }, [isActive, toolMessages.length]);\n\n const lastIndex = toolMessages.length - 1;\n\n const renderToolItem = (msg: ChatMessage, opts: { active?: boolean; showSpinner?: boolean }) => {\n const toolCall = msg.tool_calls?.[0];\n const toolName = toolCall?.function?.name;\n const summaryText = msg.summary || toolName || (opts.active ? 'Processing...' : 'Completed');\n\n // Custom renderer for completed tools\n if (!opts.active && toolName && toolRenderers?.[toolName] && allMessages) {\n const toolResponse = allMessages.find(\n (m) => m.role === 'tool' && m.tool_call_id === toolCall!.id\n );\n let input: any = {};\n try { input = JSON.parse(toolCall!.function.arguments); } catch {}\n const output = toolResponse?.content?.data ?? toolResponse?.content?.message;\n return toolRenderers[toolName](input, output);\n }\n\n const icon = opts.showSpinner\n ? <SpinnerIcon />\n : (toolName && toolIcons?.[toolName]) || <ToolDoneIcon />;\n\n return (\n <>\n <span className=\"devic-tool-activity-icon\">{icon}</span>\n <span className={`devic-tool-activity-text ${opts.active ? 'devic-glow-text' : ''}`}>\n {summaryText}\n </span>\n </>\n );\n };\n\n // If active, show all items; last one gets the glow treatment\n if (isActive) {\n return (\n <div className=\"devic-tool-group\">\n {toolMessages.map((msg, idx) => {\n const isLast = idx === lastIndex;\n return (\n <div\n key={msg.uid}\n className={`devic-tool-activity ${isLast ? 'devic-tool-activity--active' : ''}`}\n >\n {renderToolItem(msg, { active: isLast, showSpinner: isLast })}\n </div>\n );\n })}\n </div>\n );\n }\n\n // Completed: collapse if > 3 actions\n if (shouldCollapse && isCollapsed) {\n return (\n <div className=\"devic-tool-group\">\n <button\n className=\"devic-tool-collapse-btn\"\n onClick={() => setIsCollapsed(false)}\n type=\"button\"\n >\n <ToolDoneIcon />\n <span>{toolMessages.length} actions</span>\n <ChevronDownIcon />\n </button>\n </div>\n );\n }\n\n return (\n <div className=\"devic-tool-group\">\n {shouldCollapse && (\n <button\n className=\"devic-tool-collapse-btn\"\n onClick={() => setIsCollapsed(true)}\n type=\"button\"\n >\n <span>{toolMessages.length} actions</span>\n <ChevronUpIcon />\n </button>\n )}\n <div className=\"devic-tool-group-items\" data-expanded=\"true\">\n {toolMessages.map((msg) => (\n <div key={msg.uid} className=\"devic-tool-activity\">\n {renderToolItem(msg, {})}\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n/**\n * Messages list component\n */\nconst markdownOverrides = {\n table: {\n component: ({ children, ...props }: any) =>\n React.createElement('div', { className: 'markdown-table' },\n React.createElement('table', props, children)\n ),\n },\n};\n\nexport function ChatMessages({\n messages,\n allMessages,\n isLoading,\n welcomeMessage,\n suggestedMessages,\n onSuggestedClick,\n toolRenderers,\n toolIcons,\n loadingIndicator,\n}: ChatMessagesProps): JSX.Element {\n const containerRef = useRef<HTMLDivElement>(null);\n const prevLengthRef = useRef(messages.length);\n\n // Auto-scroll to bottom when new messages arrive\n useEffect(() => {\n if (containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n prevLengthRef.current = messages.length;\n }, [messages.length, isLoading]);\n\n const grouped = groupMessages(messages, isLoading);\n\n // Show loading dots only if there's no active tool group at the end\n const lastGroup = grouped[grouped.length - 1];\n const showLoadingDots = isLoading && !(lastGroup?.type === 'toolGroup' && lastGroup.isActive);\n\n return (\n <div className=\"devic-messages-container\" ref={containerRef}>\n {messages.length === 0 && !isLoading && (welcomeMessage || suggestedMessages?.length) && (\n <div className=\"devic-welcome\">\n {welcomeMessage && (\n <p className=\"devic-welcome-text\">{welcomeMessage}</p>\n )}\n {suggestedMessages && suggestedMessages.length > 0 && (\n <div className=\"devic-suggested-messages\">\n {suggestedMessages.map((msg, idx) => (\n <button\n key={idx}\n className=\"devic-suggested-btn\"\n onClick={() => onSuggestedClick?.(msg)}\n >\n {msg}\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n\n {grouped.map((item) => {\n if (item.type === 'toolGroup') {\n return (\n <ToolGroup\n key={`tg-${item.toolMessages[0].uid}`}\n toolMessages={item.toolMessages}\n isActive={item.isActive}\n allMessages={allMessages}\n toolRenderers={toolRenderers}\n toolIcons={toolIcons}\n />\n );\n }\n\n const message = item.message;\n const messageText = message.content?.message;\n const hasFiles = message.content?.files && message.content.files.length > 0;\n\n return (\n <div\n key={message.uid}\n className=\"devic-message\"\n data-role={message.role}\n >\n <div className=\"devic-message-bubble\">\n {messageText && message.role === 'assistant' ? (\n <Markdown options={{ overrides: markdownOverrides }}>{messageText}</Markdown>\n ) : (\n messageText\n )}\n {hasFiles && (\n <div className=\"devic-message-files\">\n {message.content!.files!.map((file, fileIdx) => (\n <div key={fileIdx} className=\"devic-message-file\">\n <FileIcon />\n <span>{file.name}</span>\n </div>\n ))}\n </div>\n )}\n </div>\n <span className=\"devic-message-time\">\n {formatTime(message.timestamp)}\n </span>\n </div>\n );\n })}\n\n {showLoadingDots && (\n loadingIndicator ? (\n <div className=\"devic-loading\">{loadingIndicator}</div>\n ) : (\n <div className=\"devic-loading\">\n <span className=\"devic-loading-dot\"></span>\n <span className=\"devic-loading-dot\"></span>\n <span className=\"devic-loading-dot\"></span>\n </div>\n )\n )}\n </div>\n );\n}\n\n/* ── Icons ── */\n\nfunction SpinnerIcon(): 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.5\"\n strokeLinecap=\"round\"\n className=\"devic-spinner\"\n >\n <path d=\"M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83\" />\n </svg>\n );\n}\n\nfunction ToolDoneIcon(): 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 ChevronDownIcon(): JSX.Element {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n );\n}\n\nfunction ChevronUpIcon(): JSX.Element {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <polyline points=\"6,15 12,9 18,15\" />\n </svg>\n );\n}\n\nfunction FileIcon(): JSX.Element {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n <polyline points=\"14,2 14,8 20,8\" />\n </svg>\n );\n}\n"],"names":["useState","useRef","useEffect","_jsx","_jsxs","_Fragment"],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var React = require('react');
5
+ var DevicContext = require('../../provider/DevicContext.js');
6
+ var client = require('../../api/client.js');
7
+
8
+ function ConversationSelector({ assistantId, currentChatUid, onSelect, onNewChat, apiKey: propsApiKey, baseUrl: propsBaseUrl, tenantId: propsTenantId, }) {
9
+ const context = DevicContext.useOptionalDevicContext();
10
+ const apiKey = propsApiKey || context?.apiKey;
11
+ const baseUrl = propsBaseUrl || context?.baseUrl || 'https://api.devic.ai';
12
+ const tenantId = propsTenantId || context?.tenantId;
13
+ const [isOpen, setIsOpen] = React.useState(false);
14
+ const [conversations, setConversations] = React.useState([]);
15
+ const [search, setSearch] = React.useState('');
16
+ const [loading, setLoading] = React.useState(false);
17
+ const dropdownRef = React.useRef(null);
18
+ const clientRef = React.useRef(null);
19
+ if (!clientRef.current && apiKey) {
20
+ clientRef.current = new client.DevicApiClient({ apiKey, baseUrl });
21
+ }
22
+ // Update client config when props/context change
23
+ React.useEffect(() => {
24
+ if (clientRef.current && apiKey) {
25
+ clientRef.current.setConfig({ apiKey, baseUrl });
26
+ }
27
+ else if (!clientRef.current && apiKey) {
28
+ clientRef.current = new client.DevicApiClient({ apiKey, baseUrl });
29
+ }
30
+ }, [apiKey, baseUrl]);
31
+ const fetchConversations = React.useCallback(async () => {
32
+ if (!clientRef.current)
33
+ return;
34
+ setLoading(true);
35
+ try {
36
+ const list = await clientRef.current.listConversations(assistantId, { tenantId });
37
+ setConversations(list);
38
+ }
39
+ catch {
40
+ // silently fail
41
+ }
42
+ finally {
43
+ setLoading(false);
44
+ }
45
+ }, [assistantId, tenantId]);
46
+ React.useEffect(() => {
47
+ if (isOpen) {
48
+ fetchConversations();
49
+ }
50
+ }, [isOpen, fetchConversations]);
51
+ // Close on outside click
52
+ React.useEffect(() => {
53
+ if (!isOpen)
54
+ return;
55
+ const handler = (e) => {
56
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
57
+ setIsOpen(false);
58
+ }
59
+ };
60
+ document.addEventListener('mousedown', handler);
61
+ return () => document.removeEventListener('mousedown', handler);
62
+ }, [isOpen]);
63
+ const currentConv = conversations.find((c) => c.chatUID === currentChatUid);
64
+ const currentName = currentConv
65
+ ? currentConv.name || formatDate(currentConv.creationTimestampMs)
66
+ : 'New chat';
67
+ const filtered = conversations.filter((c) => !search || (c.name || '').toLowerCase().includes(search.toLowerCase()));
68
+ return (jsxRuntime.jsxs("div", { className: "devic-conversation-selector", ref: dropdownRef, children: [jsxRuntime.jsxs("button", { className: "devic-conversation-selector-trigger", onClick: () => setIsOpen(!isOpen), type: "button", children: [jsxRuntime.jsx("span", { className: "devic-conversation-selector-label", children: currentName }), jsxRuntime.jsx(ChevronIcon, { open: isOpen })] }), isOpen && (jsxRuntime.jsxs("div", { className: "devic-conversation-dropdown", children: [jsxRuntime.jsx("div", { className: "devic-conversation-search-wrapper", children: jsxRuntime.jsx("input", { className: "devic-conversation-search", type: "text", placeholder: "Search conversations...", value: search, onChange: (e) => setSearch(e.target.value), autoFocus: true }) }), jsxRuntime.jsxs("div", { className: "devic-conversation-list", children: [loading && (jsxRuntime.jsx("div", { className: "devic-conversation-loading", children: "Loading..." })), !loading && filtered.length === 0 && (jsxRuntime.jsx("div", { className: "devic-conversation-empty", children: "No conversations" })), !loading &&
69
+ filtered.map((conv) => (jsxRuntime.jsxs("button", { className: "devic-conversation-item", "data-active": conv.chatUID === currentChatUid, type: "button", onClick: () => {
70
+ onSelect(conv.chatUID);
71
+ setIsOpen(false);
72
+ }, children: [conv.chatUID === currentChatUid && (jsxRuntime.jsx("span", { className: "devic-conversation-item-check", children: jsxRuntime.jsx(CheckIcon, {}) })), jsxRuntime.jsx("span", { className: "devic-conversation-item-name", children: conv.name || formatDate(conv.creationTimestampMs) }), jsxRuntime.jsx("span", { className: "devic-conversation-item-date", children: formatDate(conv.lastEditTimestampMs || conv.creationTimestampMs) })] }, conv.chatUID)))] }), jsxRuntime.jsx("button", { className: "devic-conversation-new", type: "button", onClick: () => {
73
+ onNewChat();
74
+ setIsOpen(false);
75
+ }, children: "+ Start a new chat" })] }))] }));
76
+ }
77
+ function ChevronIcon({ open }) {
78
+ return (jsxRuntime.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: jsxRuntime.jsx("polyline", { points: "6 9 12 15 18 9" }) }));
79
+ }
80
+ function CheckIcon() {
81
+ 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" }) }));
82
+ }
83
+ function formatDate(ms) {
84
+ const d = new Date(ms);
85
+ const now = new Date();
86
+ if (d.toDateString() === now.toDateString()) {
87
+ return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
88
+ }
89
+ return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
90
+ }
91
+
92
+ exports.ConversationSelector = ConversationSelector;
93
+ //# 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":["useOptionalDevicContext","useState","useRef","DevicApiClient","useEffect","useCallback","_jsxs","_jsx"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+
5
+ class ChatDrawerErrorBoundary extends React.Component {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.state = { hasError: false };
9
+ }
10
+ static getDerivedStateFromError() {
11
+ return { hasError: true };
12
+ }
13
+ componentDidCatch(error, info) {
14
+ console.error('[devic-ui] ChatDrawer error:', error, info.componentStack);
15
+ }
16
+ render() {
17
+ if (this.state.hasError) {
18
+ return this.props.fallback ?? null;
19
+ }
20
+ return this.props.children;
21
+ }
22
+ }
23
+
24
+ exports.ChatDrawerErrorBoundary = ChatDrawerErrorBoundary;
25
+ //# 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":["Component"],"mappings":";;;;AAYM,MAAO,uBAAwB,SAAQA,eAAuB,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;;;;"}
@@ -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,