@meetsmore-oss/use-ai-client 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundled.js +148 -52
- package/dist/bundled.js.map +1 -1
- package/dist/index.d.ts +87 -8
- package/dist/index.js +147 -52
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -353,9 +353,8 @@ declare class UseAIClient {
|
|
|
353
353
|
private serverUrl;
|
|
354
354
|
private socket;
|
|
355
355
|
private eventHandlers;
|
|
356
|
-
private reconnectAttempts;
|
|
357
|
-
private maxReconnectAttempts;
|
|
358
356
|
private reconnectDelay;
|
|
357
|
+
private reconnectDelayMax;
|
|
359
358
|
private _threadId;
|
|
360
359
|
private _tools;
|
|
361
360
|
private _messages;
|
|
@@ -737,11 +736,22 @@ interface PersistedFileContent {
|
|
|
737
736
|
type: 'file';
|
|
738
737
|
file: PersistedFileMetadata;
|
|
739
738
|
}
|
|
739
|
+
/**
|
|
740
|
+
* Transformed file content part for persisted messages.
|
|
741
|
+
* Stores the text produced by a client-side FileTransformer (e.g. OCR result)
|
|
742
|
+
* so the full context survives a page reload / Socket.IO reconnect.
|
|
743
|
+
*/
|
|
744
|
+
interface PersistedTransformedFileContent {
|
|
745
|
+
type: 'transformed_file';
|
|
746
|
+
/** The transformed text representation (e.g. OCR'd markdown). */
|
|
747
|
+
text: string;
|
|
748
|
+
originalFile: PersistedFileMetadata;
|
|
749
|
+
}
|
|
740
750
|
/**
|
|
741
751
|
* Content part for persisted messages.
|
|
742
|
-
* Can be text or file
|
|
752
|
+
* Can be text, file metadata, or transformed file content.
|
|
743
753
|
*/
|
|
744
|
-
type PersistedContentPart = PersistedTextContent | PersistedFileContent;
|
|
754
|
+
type PersistedContentPart = PersistedTextContent | PersistedFileContent | PersistedTransformedFileContent;
|
|
745
755
|
/**
|
|
746
756
|
* Content that can be persisted.
|
|
747
757
|
* Simple string for text-only messages, or array for multimodal content.
|
|
@@ -1011,6 +1021,19 @@ interface UseToolSystemReturn {
|
|
|
1011
1021
|
*/
|
|
1012
1022
|
declare function useToolSystem({ clientRef, buildState, }: UseToolSystemOptions): UseToolSystemReturn;
|
|
1013
1023
|
|
|
1024
|
+
/**
|
|
1025
|
+
* How the chat textarea should treat the Enter key.
|
|
1026
|
+
*
|
|
1027
|
+
* - `'enter'` — Enter submits the message, Shift+Enter inserts a newline.
|
|
1028
|
+
* Typical desktop behavior.
|
|
1029
|
+
* - `'mod-enter'` — Enter inserts a newline. Cmd/Ctrl+Enter submits the message.
|
|
1030
|
+
* Recommended on mobile, where soft keyboards lack modifier
|
|
1031
|
+
* keys and the user is expected to tap the send button.
|
|
1032
|
+
* ("mod" follows the CodeMirror/ProseMirror convention of
|
|
1033
|
+
* meaning Cmd on macOS and Ctrl elsewhere.)
|
|
1034
|
+
*/
|
|
1035
|
+
type SubmitMode = 'enter' | 'mod-enter';
|
|
1036
|
+
|
|
1014
1037
|
/**
|
|
1015
1038
|
* Options for programmatically sending a message via sendMessage().
|
|
1016
1039
|
*/
|
|
@@ -1147,6 +1170,8 @@ declare const defaultStrings: {
|
|
|
1147
1170
|
API_OVERLOADED: string;
|
|
1148
1171
|
/** Error when rate limited */
|
|
1149
1172
|
RATE_LIMITED: string;
|
|
1173
|
+
/** Error when the connection was lost while a response was being generated */
|
|
1174
|
+
CONNECTION_LOST: string;
|
|
1150
1175
|
/** Error for unknown/unexpected errors */
|
|
1151
1176
|
UNKNOWN_ERROR: string;
|
|
1152
1177
|
};
|
|
@@ -1547,6 +1572,32 @@ interface UseAIProviderProps extends UseAIConfig {
|
|
|
1547
1572
|
* ```
|
|
1548
1573
|
*/
|
|
1549
1574
|
onOpenChange?: (isOpen: boolean) => void;
|
|
1575
|
+
/**
|
|
1576
|
+
* How the built-in chat panel should treat the Enter key.
|
|
1577
|
+
*
|
|
1578
|
+
* - `'enter'` (default): Enter submits, Shift+Enter inserts a newline.
|
|
1579
|
+
* Typical desktop behavior.
|
|
1580
|
+
* - `'mod-enter'`: Enter inserts a newline. Cmd/Ctrl+Enter submits.
|
|
1581
|
+
* Recommended for mobile/touch devices where soft keyboards lack modifier
|
|
1582
|
+
* keys and the user is expected to tap the send button.
|
|
1583
|
+
*
|
|
1584
|
+
* Can be overridden per-instance via the `submitMode` prop on `<UseAIChat>`.
|
|
1585
|
+
*
|
|
1586
|
+
* @default 'enter'
|
|
1587
|
+
*
|
|
1588
|
+
* @example
|
|
1589
|
+
* ```tsx
|
|
1590
|
+
* import { isMobileApp } from '@/lib/is-mobile';
|
|
1591
|
+
*
|
|
1592
|
+
* <UseAIProvider
|
|
1593
|
+
* serverUrl="wss://your-server.com"
|
|
1594
|
+
* submitMode={isMobileApp() ? 'mod-enter' : 'enter'}
|
|
1595
|
+
* >
|
|
1596
|
+
* <App />
|
|
1597
|
+
* </UseAIProvider>
|
|
1598
|
+
* ```
|
|
1599
|
+
*/
|
|
1600
|
+
submitMode?: SubmitMode;
|
|
1550
1601
|
}
|
|
1551
1602
|
/**
|
|
1552
1603
|
* Provider component that manages AI client connection and tool registration.
|
|
@@ -1576,7 +1627,7 @@ interface UseAIProviderProps extends UseAIConfig {
|
|
|
1576
1627
|
* }
|
|
1577
1628
|
* ```
|
|
1578
1629
|
*/
|
|
1579
|
-
declare function UseAIProvider({ serverUrl, children, systemPrompt, CustomButton, CustomChat, chatRepository, forwardedPropsProvider, fileUploadConfig: fileUploadConfigProp, commandRepository, renderChat, theme: customTheme, strings: customStrings, visibleAgentIds, onOpenChange, }: UseAIProviderProps): react_jsx_runtime.JSX.Element;
|
|
1630
|
+
declare function UseAIProvider({ serverUrl, children, systemPrompt, CustomButton, CustomChat, chatRepository, forwardedPropsProvider, fileUploadConfig: fileUploadConfigProp, commandRepository, renderChat, theme: customTheme, strings: customStrings, visibleAgentIds, onOpenChange, submitMode, }: UseAIProviderProps): react_jsx_runtime.JSX.Element;
|
|
1580
1631
|
/**
|
|
1581
1632
|
* Hook to access the UseAI context.
|
|
1582
1633
|
* When used outside a UseAIProvider, returns a no-op context and logs a warning.
|
|
@@ -1641,12 +1692,26 @@ interface UseAIChatPanelProps {
|
|
|
1641
1692
|
onApproveToolCall?: () => void;
|
|
1642
1693
|
/** Callback to reject all pending tool calls */
|
|
1643
1694
|
onRejectToolCall?: (reason?: string) => void;
|
|
1695
|
+
/**
|
|
1696
|
+
* How the textarea should treat the Enter key.
|
|
1697
|
+
*
|
|
1698
|
+
* - `'enter'` (default): Enter submits, Shift+Enter inserts a newline. Suitable
|
|
1699
|
+
* for desktop.
|
|
1700
|
+
* - `'mod-enter'`: Enter inserts a newline. Cmd/Ctrl+Enter submits. Recommended
|
|
1701
|
+
* for mobile, where soft keyboards lack modifier keys and the user is
|
|
1702
|
+
* expected to tap the send button.
|
|
1703
|
+
*
|
|
1704
|
+
* IME composition is always respected regardless of mode.
|
|
1705
|
+
*
|
|
1706
|
+
* @default 'enter'
|
|
1707
|
+
*/
|
|
1708
|
+
submitMode?: SubmitMode;
|
|
1644
1709
|
}
|
|
1645
1710
|
/**
|
|
1646
1711
|
* Chat panel content - fills its container.
|
|
1647
1712
|
* Use directly for embedded mode, or wrap with UseAIFloatingChatWrapper for floating mode.
|
|
1648
1713
|
*/
|
|
1649
|
-
declare function UseAIChatPanel({ onSendMessage, messages, loading, connected, streamingText, streamingReasoning, currentChatId, onNewChat, onLoadChat, onDeleteChat, onListChats, onGetChat, suggestions, availableAgents, defaultAgent, selectedAgent, onAgentChange, fileUploadConfig, fileProcessing, commands, onSaveCommand, onRenameCommand, onDeleteCommand, closeButton, executingTool, feedbackEnabled, onFeedback, pendingApprovals, onApproveToolCall, onRejectToolCall, }: UseAIChatPanelProps): react_jsx_runtime.JSX.Element;
|
|
1714
|
+
declare function UseAIChatPanel({ onSendMessage, messages, loading, connected, streamingText, streamingReasoning, currentChatId, onNewChat, onLoadChat, onDeleteChat, onListChats, onGetChat, suggestions, availableAgents, defaultAgent, selectedAgent, onAgentChange, fileUploadConfig, fileProcessing, commands, onSaveCommand, onRenameCommand, onDeleteCommand, closeButton, executingTool, feedbackEnabled, onFeedback, pendingApprovals, onApproveToolCall, onRejectToolCall, submitMode, }: UseAIChatPanelProps): react_jsx_runtime.JSX.Element;
|
|
1650
1715
|
|
|
1651
1716
|
/**
|
|
1652
1717
|
* Props for the floating chat wrapper.
|
|
@@ -1685,6 +1750,14 @@ interface UseAIChatProps {
|
|
|
1685
1750
|
* When false (default), renders inline filling its container.
|
|
1686
1751
|
*/
|
|
1687
1752
|
floating?: boolean;
|
|
1753
|
+
/**
|
|
1754
|
+
* How the chat textarea should treat the Enter key. Overrides the value
|
|
1755
|
+
* provided on `UseAIProvider` for this instance.
|
|
1756
|
+
*
|
|
1757
|
+
* - `'enter'` (default): Enter submits, Shift+Enter inserts a newline.
|
|
1758
|
+
* - `'mod-enter'`: Enter inserts a newline. Cmd/Ctrl+Enter submits.
|
|
1759
|
+
*/
|
|
1760
|
+
submitMode?: SubmitMode;
|
|
1688
1761
|
}
|
|
1689
1762
|
/**
|
|
1690
1763
|
* Standalone chat component that can be placed anywhere within UseAIProvider.
|
|
@@ -1711,7 +1784,7 @@ interface UseAIChatProps {
|
|
|
1711
1784
|
* </UseAIProvider>
|
|
1712
1785
|
* ```
|
|
1713
1786
|
*/
|
|
1714
|
-
declare function UseAIChat({ floating }: UseAIChatProps): react_jsx_runtime.JSX.Element;
|
|
1787
|
+
declare function UseAIChat({ floating, submitMode }: UseAIChatProps): react_jsx_runtime.JSX.Element;
|
|
1715
1788
|
|
|
1716
1789
|
/**
|
|
1717
1790
|
* LocalStorage-based implementation of ChatRepository.
|
|
@@ -2323,6 +2396,12 @@ interface UseServerEventsReturn {
|
|
|
2323
2396
|
* (currentToolCalls, currentMessageContent).
|
|
2324
2397
|
*/
|
|
2325
2398
|
handleServerEvent: (client: UseAIClient, event: AGUIEvent) => Promise<void>;
|
|
2399
|
+
/**
|
|
2400
|
+
* Handles a connection loss. When a disconnect occurs mid-run the server
|
|
2401
|
+
* session is destroyed and the run cannot resume, so we surface an error
|
|
2402
|
+
* message and clear in-flight UI state. No-op when no run is active.
|
|
2403
|
+
*/
|
|
2404
|
+
handleDisconnect: () => void;
|
|
2326
2405
|
}
|
|
2327
2406
|
/**
|
|
2328
2407
|
* Hook that owns all server event handling state and logic.
|
|
@@ -2458,4 +2537,4 @@ interface UseDropdownStateOptions {
|
|
|
2458
2537
|
*/
|
|
2459
2538
|
declare function useDropdownState(options?: UseDropdownStateOptions): UseDropdownStateReturn;
|
|
2460
2539
|
|
|
2461
|
-
export { type AgentContextValue, type Chat, type ChatContextValue, type ChatMetadata, type ChatPanelProps, type ChatRepository, CloseButton, type CommandContextValue, type CommandRepository, type CreateChatOptions, type CreateCommandOptions, DEFAULT_MAX_FILE_SIZE, type DefinedTool, type DropZoneProps, EmbedFileUploadBackend, type ExecutingToolDisplay, type FileAttachment, type FileProcessingState, type FileProcessingStatus, type FileTransformer, type FileTransformerContext, type FileTransformerMap, type FileUploadBackend, type FileUploadConfig, type FloatingButtonProps, type InlineSaveProps, type ListChatsOptions, type ListCommandsOptions, LocalStorageChatRepository, LocalStorageCommandRepository, type Message, type PendingToolApproval, type PersistedContentPart, type PersistedFileContent, type PersistedFileMetadata, type PersistedMessage, type PersistedMessageContent, type PersistedTextContent, type ProcessAttachmentsConfig, type PromptsContextValue, type RegisterToolsOptions, type SavedCommand, type SendMessageOptions, type ToolExecutionContext, type ToolOptions, type ToolRegistryContextValue, type ToolsDefinition, type TriggerWorkflowOptions, UseAIChat, UseAIChatPanel, type UseAIChatPanelProps, type UseAIChatPanelStrings, type UseAIChatPanelTheme, type UseAIChatProps, UseAIClient, type UseAIConfig, type UseAIContextValue, UseAIFloatingButton, UseAIFloatingChatWrapper, type UseAIOptions, UseAIProvider, type UseAIProviderProps, type UseAIResult, type UseAIStrings, type UseAITheme, type UseAIWorkflowResult, type UseAgentSelectionOptions, type UseAgentSelectionReturn, type UseChatManagementOptions, type UseChatManagementReturn, type UseCommandManagementOptions, type UseCommandManagementReturn, type UseDropdownStateOptions, type UseDropdownStateReturn, type UseFeedbackOptions, type UseFeedbackReturn, type UseFileUploadOptions, type UseFileUploadReturn, type UseMessageQueueOptions, type UseMessageQueueReturn, type UsePromptStateOptions, type UsePromptStateReturn, type UseServerEventsOptions, type UseServerEventsReturn, type UseSlashCommandsOptions, type UseSlashCommandsReturn, type UseToolSystemOptions, type UseToolSystemReturn, type WorkflowProgress, clearTransformationCache, convertToolsToDefinitions, defaultStrings, defaultTheme, defineTool, executeDefinedTool, findTransformerPattern, generateChatId, generateCommandId, generateMessageId, matchesMimeType, processAttachments, useAI, useAIContext, useAIWorkflow, useAgentSelection, useChatManagement, useCommandManagement, useDropdownState, useFeedback, useFileUpload, useMessageQueue, usePromptState, useServerEvents, useSlashCommands, useStableTools, useStrings, useTheme, useToolSystem, validateCommandName };
|
|
2540
|
+
export { type AgentContextValue, type Chat, type ChatContextValue, type ChatMetadata, type ChatPanelProps, type ChatRepository, CloseButton, type CommandContextValue, type CommandRepository, type CreateChatOptions, type CreateCommandOptions, DEFAULT_MAX_FILE_SIZE, type DefinedTool, type DropZoneProps, EmbedFileUploadBackend, type ExecutingToolDisplay, type FileAttachment, type FileProcessingState, type FileProcessingStatus, type FileTransformer, type FileTransformerContext, type FileTransformerMap, type FileUploadBackend, type FileUploadConfig, type FloatingButtonProps, type InlineSaveProps, type ListChatsOptions, type ListCommandsOptions, LocalStorageChatRepository, LocalStorageCommandRepository, type Message, type PendingToolApproval, type PersistedContentPart, type PersistedFileContent, type PersistedFileMetadata, type PersistedMessage, type PersistedMessageContent, type PersistedTextContent, type ProcessAttachmentsConfig, type PromptsContextValue, type RegisterToolsOptions, type SavedCommand, type SendMessageOptions, type SubmitMode, type ToolExecutionContext, type ToolOptions, type ToolRegistryContextValue, type ToolsDefinition, type TriggerWorkflowOptions, UseAIChat, UseAIChatPanel, type UseAIChatPanelProps, type UseAIChatPanelStrings, type UseAIChatPanelTheme, type UseAIChatProps, UseAIClient, type UseAIConfig, type UseAIContextValue, UseAIFloatingButton, UseAIFloatingChatWrapper, type UseAIOptions, UseAIProvider, type UseAIProviderProps, type UseAIResult, type UseAIStrings, type UseAITheme, type UseAIWorkflowResult, type UseAgentSelectionOptions, type UseAgentSelectionReturn, type UseChatManagementOptions, type UseChatManagementReturn, type UseCommandManagementOptions, type UseCommandManagementReturn, type UseDropdownStateOptions, type UseDropdownStateReturn, type UseFeedbackOptions, type UseFeedbackReturn, type UseFileUploadOptions, type UseFileUploadReturn, type UseMessageQueueOptions, type UseMessageQueueReturn, type UsePromptStateOptions, type UsePromptStateReturn, type UseServerEventsOptions, type UseServerEventsReturn, type UseSlashCommandsOptions, type UseSlashCommandsReturn, type UseToolSystemOptions, type UseToolSystemReturn, type WorkflowProgress, clearTransformationCache, convertToolsToDefinitions, defaultStrings, defaultTheme, defineTool, executeDefinedTool, findTransformerPattern, generateChatId, generateCommandId, generateMessageId, matchesMimeType, processAttachments, useAI, useAIContext, useAIWorkflow, useAgentSelection, useChatManagement, useCommandManagement, useDropdownState, useFeedback, useFileUpload, useMessageQueue, usePromptState, useServerEvents, useSlashCommands, useStableTools, useStrings, useTheme, useToolSystem, validateCommandName };
|
package/dist/index.js
CHANGED
|
@@ -94,6 +94,8 @@ var defaultStrings = {
|
|
|
94
94
|
API_OVERLOADED: "The AI service is currently experiencing high demand. Please try again in a moment.",
|
|
95
95
|
/** Error when rate limited */
|
|
96
96
|
RATE_LIMITED: "Too many requests. Please wait a moment before trying again.",
|
|
97
|
+
/** Error when the connection was lost while a response was being generated */
|
|
98
|
+
CONNECTION_LOST: "The connection was lost. Please send your message again.",
|
|
97
99
|
/** Error for unknown/unexpected errors */
|
|
98
100
|
UNKNOWN_ERROR: "An unexpected error occurred. Please try again."
|
|
99
101
|
},
|
|
@@ -280,7 +282,17 @@ function getTextFromContent(content) {
|
|
|
280
282
|
if (typeof content === "string") {
|
|
281
283
|
return content;
|
|
282
284
|
}
|
|
283
|
-
return content.
|
|
285
|
+
return content.flatMap((part) => {
|
|
286
|
+
if (part.type === "text") return [part.text];
|
|
287
|
+
if (part.type === "transformed_file") return [part.text];
|
|
288
|
+
return [];
|
|
289
|
+
}).join("\n");
|
|
290
|
+
}
|
|
291
|
+
function getDisplayTextFromContent(content) {
|
|
292
|
+
if (typeof content === "string") {
|
|
293
|
+
return content;
|
|
294
|
+
}
|
|
295
|
+
return content.flatMap((part) => part.type === "text" ? [part.text] : []).join("\n");
|
|
284
296
|
}
|
|
285
297
|
|
|
286
298
|
// src/utils/mergeAssistantMessages.ts
|
|
@@ -344,6 +356,17 @@ function mergeAssistantMessagesForDisplay(messages) {
|
|
|
344
356
|
return result;
|
|
345
357
|
}
|
|
346
358
|
|
|
359
|
+
// src/utils/keyboard.ts
|
|
360
|
+
function shouldSubmitOnEnter(e, mode) {
|
|
361
|
+
if (e.key !== "Enter" || e.nativeEvent.isComposing || e.keyCode === 229) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
if (mode === "enter") {
|
|
365
|
+
return !e.shiftKey;
|
|
366
|
+
}
|
|
367
|
+
return e.metaKey || e.ctrlKey;
|
|
368
|
+
}
|
|
369
|
+
|
|
347
370
|
// src/components/MarkdownContent.tsx
|
|
348
371
|
import ReactMarkdown from "react-markdown";
|
|
349
372
|
import remarkGfm from "remark-gfm";
|
|
@@ -2166,7 +2189,8 @@ function UseAIChatPanel({
|
|
|
2166
2189
|
onFeedback,
|
|
2167
2190
|
pendingApprovals = [],
|
|
2168
2191
|
onApproveToolCall,
|
|
2169
|
-
onRejectToolCall
|
|
2192
|
+
onRejectToolCall,
|
|
2193
|
+
submitMode = "enter"
|
|
2170
2194
|
}) {
|
|
2171
2195
|
const strings = useStrings();
|
|
2172
2196
|
const theme = useTheme();
|
|
@@ -2241,7 +2265,7 @@ function UseAIChatPanel({
|
|
|
2241
2265
|
if (slashCommands.handleKeyDown(e)) {
|
|
2242
2266
|
return;
|
|
2243
2267
|
}
|
|
2244
|
-
if (
|
|
2268
|
+
if (shouldSubmitOnEnter(e, submitMode)) {
|
|
2245
2269
|
e.preventDefault();
|
|
2246
2270
|
handleSend();
|
|
2247
2271
|
}
|
|
@@ -2339,7 +2363,7 @@ function UseAIChatPanel({
|
|
|
2339
2363
|
if (displayMessages.length > 0) {
|
|
2340
2364
|
const firstUserMsg = displayMessages.find((m) => m.role === "user");
|
|
2341
2365
|
if (firstUserMsg) {
|
|
2342
|
-
const textContent =
|
|
2366
|
+
const textContent = getDisplayTextFromContent(firstUserMsg.content);
|
|
2343
2367
|
const maxLength = 30;
|
|
2344
2368
|
return textContent.length > maxLength ? textContent.substring(0, maxLength) + "..." : textContent || strings.header.newChat;
|
|
2345
2369
|
}
|
|
@@ -2645,7 +2669,7 @@ function UseAIChatPanel({
|
|
|
2645
2669
|
{
|
|
2646
2670
|
style: {
|
|
2647
2671
|
display: "grid",
|
|
2648
|
-
gridTemplateColumns: "
|
|
2672
|
+
gridTemplateColumns: "1fr",
|
|
2649
2673
|
gap: "8px",
|
|
2650
2674
|
width: "100%",
|
|
2651
2675
|
maxWidth: "320px"
|
|
@@ -2721,7 +2745,7 @@ function UseAIChatPanel({
|
|
|
2721
2745
|
"data-testid": "save-command-button",
|
|
2722
2746
|
onClick: (e) => {
|
|
2723
2747
|
e.stopPropagation();
|
|
2724
|
-
const messageText =
|
|
2748
|
+
const messageText = getDisplayTextFromContent(message.content);
|
|
2725
2749
|
slashCommands.startSavingCommand(message.id, messageText);
|
|
2726
2750
|
},
|
|
2727
2751
|
title: "Save as slash command",
|
|
@@ -2791,13 +2815,17 @@ function UseAIChatPanel({
|
|
|
2791
2815
|
}
|
|
2792
2816
|
),
|
|
2793
2817
|
/* @__PURE__ */ jsx12(MarkdownContent, { content: getTextFromContent(message.content) })
|
|
2794
|
-
] }) :
|
|
2818
|
+
] }) : (
|
|
2819
|
+
// User/tool bubbles: display-only text so transformed_file
|
|
2820
|
+
// (e.g. OCR body) isn't dumped into the chat bubble.
|
|
2821
|
+
getDisplayTextFromContent(message.content)
|
|
2822
|
+
)
|
|
2795
2823
|
]
|
|
2796
2824
|
}
|
|
2797
2825
|
),
|
|
2798
2826
|
slashCommands.renderInlineSaveUI({
|
|
2799
2827
|
messageId: message.id,
|
|
2800
|
-
messageText:
|
|
2828
|
+
messageText: getDisplayTextFromContent(message.content)
|
|
2801
2829
|
})
|
|
2802
2830
|
]
|
|
2803
2831
|
}
|
|
@@ -3257,9 +3285,10 @@ function useChatUIContext() {
|
|
|
3257
3285
|
}
|
|
3258
3286
|
return context;
|
|
3259
3287
|
}
|
|
3260
|
-
function UseAIChat({ floating = false }) {
|
|
3288
|
+
function UseAIChat({ floating = false, submitMode }) {
|
|
3261
3289
|
const ctx = useChatUIContext();
|
|
3262
3290
|
const chatPanelProps = {
|
|
3291
|
+
submitMode: submitMode ?? ctx.submitMode,
|
|
3263
3292
|
onSendMessage: ctx.sendMessage,
|
|
3264
3293
|
messages: ctx.messages,
|
|
3265
3294
|
loading: ctx.loading,
|
|
@@ -3324,9 +3353,12 @@ var UseAIClient = class {
|
|
|
3324
3353
|
}
|
|
3325
3354
|
socket = null;
|
|
3326
3355
|
eventHandlers = /* @__PURE__ */ new Map();
|
|
3327
|
-
|
|
3328
|
-
|
|
3356
|
+
// Reconnect indefinitely so clients recover after extended outages (mobile
|
|
3357
|
+
// app backgrounded long enough for server pingTimeout, airplane mode, etc.).
|
|
3358
|
+
// Socket.IO applies exponential backoff capped at reconnectionDelayMax,
|
|
3359
|
+
// so steady-state retry frequency is ~one attempt per 10s.
|
|
3329
3360
|
reconnectDelay = 1e3;
|
|
3361
|
+
reconnectDelayMax = 1e4;
|
|
3330
3362
|
// Session state
|
|
3331
3363
|
_threadId = null;
|
|
3332
3364
|
_tools = [];
|
|
@@ -3364,14 +3396,14 @@ var UseAIClient = class {
|
|
|
3364
3396
|
this.socket = io(this.serverUrl, {
|
|
3365
3397
|
transports: ["polling", "websocket"],
|
|
3366
3398
|
reconnection: true,
|
|
3367
|
-
reconnectionAttempts:
|
|
3399
|
+
reconnectionAttempts: Infinity,
|
|
3368
3400
|
reconnectionDelay: this.reconnectDelay,
|
|
3401
|
+
reconnectionDelayMax: this.reconnectDelayMax,
|
|
3369
3402
|
withCredentials: true
|
|
3370
3403
|
});
|
|
3371
3404
|
this.socket.on("connect", () => {
|
|
3372
3405
|
console.log("[UseAI] Connected to server");
|
|
3373
3406
|
console.log("[UseAI] Transport:", this.socket?.io?.engine?.transport?.name);
|
|
3374
|
-
this.reconnectAttempts = 0;
|
|
3375
3407
|
const engine = this.socket?.io?.engine;
|
|
3376
3408
|
if (engine) {
|
|
3377
3409
|
engine.on("upgrade", (transport) => {
|
|
@@ -4176,35 +4208,89 @@ var LocalStorageChatRepository = class {
|
|
|
4176
4208
|
}
|
|
4177
4209
|
};
|
|
4178
4210
|
|
|
4211
|
+
// src/fileUpload/buildPersistedParts.ts
|
|
4212
|
+
function buildPersistedParts(message, attachments, fileContent) {
|
|
4213
|
+
const parts = [];
|
|
4214
|
+
if (message.trim()) {
|
|
4215
|
+
parts.push({ type: "text", text: message });
|
|
4216
|
+
}
|
|
4217
|
+
const transformedByKey = /* @__PURE__ */ new Map();
|
|
4218
|
+
for (const part of fileContent) {
|
|
4219
|
+
if (part.type === "transformed_file") {
|
|
4220
|
+
const key = `${part.originalFile.name}:${part.originalFile.size}:${part.originalFile.mimeType}`;
|
|
4221
|
+
const list = transformedByKey.get(key);
|
|
4222
|
+
if (list) {
|
|
4223
|
+
list.push(part);
|
|
4224
|
+
} else {
|
|
4225
|
+
transformedByKey.set(key, [part]);
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
for (const attachment of attachments) {
|
|
4230
|
+
const key = `${attachment.file.name}:${attachment.file.size}:${attachment.file.type}`;
|
|
4231
|
+
const transformed = transformedByKey.get(key)?.shift();
|
|
4232
|
+
if (transformed) {
|
|
4233
|
+
parts.push({
|
|
4234
|
+
type: "transformed_file",
|
|
4235
|
+
text: transformed.text,
|
|
4236
|
+
originalFile: transformed.originalFile
|
|
4237
|
+
});
|
|
4238
|
+
} else {
|
|
4239
|
+
parts.push({
|
|
4240
|
+
type: "file",
|
|
4241
|
+
file: {
|
|
4242
|
+
name: attachment.file.name,
|
|
4243
|
+
size: attachment.file.size,
|
|
4244
|
+
mimeType: attachment.file.type
|
|
4245
|
+
}
|
|
4246
|
+
});
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
return parts;
|
|
4250
|
+
}
|
|
4251
|
+
|
|
4179
4252
|
// src/hooks/useChatManagement.ts
|
|
4180
4253
|
import { useState as useState7, useCallback as useCallback5, useRef as useRef5, useEffect as useEffect5 } from "react";
|
|
4181
4254
|
|
|
4182
4255
|
// src/utils/messageConversion.ts
|
|
4183
4256
|
function transformMessagesToClientFormat(persistedMessages) {
|
|
4184
4257
|
return persistedMessages.map((msg) => {
|
|
4185
|
-
const textContent = getTextFromContent(msg.content);
|
|
4186
4258
|
switch (msg.role) {
|
|
4187
4259
|
case "tool":
|
|
4188
4260
|
return {
|
|
4189
4261
|
id: msg.id,
|
|
4190
4262
|
role: "tool",
|
|
4191
|
-
content:
|
|
4263
|
+
content: getTextFromContent(msg.content),
|
|
4192
4264
|
toolCallId: msg.toolCallId || ""
|
|
4193
4265
|
};
|
|
4194
4266
|
case "assistant":
|
|
4195
4267
|
return {
|
|
4196
4268
|
id: msg.id,
|
|
4197
4269
|
role: "assistant",
|
|
4198
|
-
content:
|
|
4270
|
+
content: getTextFromContent(msg.content),
|
|
4199
4271
|
...msg.toolCalls && msg.toolCalls.length > 0 ? { toolCalls: msg.toolCalls } : {},
|
|
4200
4272
|
...msg.reasoningParts && msg.reasoningParts.length > 0 ? { reasoningParts: msg.reasoningParts } : {}
|
|
4201
4273
|
};
|
|
4202
|
-
case "user":
|
|
4203
|
-
|
|
4204
|
-
id: msg.id,
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4274
|
+
case "user": {
|
|
4275
|
+
if (typeof msg.content === "string") {
|
|
4276
|
+
return { id: msg.id, role: "user", content: msg.content };
|
|
4277
|
+
}
|
|
4278
|
+
const parts = msg.content.flatMap((p) => {
|
|
4279
|
+
if (p.type === "text") {
|
|
4280
|
+
return [{ type: "text", text: p.text }];
|
|
4281
|
+
}
|
|
4282
|
+
if (p.type === "transformed_file") {
|
|
4283
|
+
return [{
|
|
4284
|
+
type: "text",
|
|
4285
|
+
text: `[Content of file "${p.originalFile.name}" (${p.originalFile.mimeType})]:
|
|
4286
|
+
|
|
4287
|
+
${p.text}`
|
|
4288
|
+
}];
|
|
4289
|
+
}
|
|
4290
|
+
return [];
|
|
4291
|
+
});
|
|
4292
|
+
return { id: msg.id, role: "user", content: parts };
|
|
4293
|
+
}
|
|
4208
4294
|
}
|
|
4209
4295
|
});
|
|
4210
4296
|
}
|
|
@@ -4377,7 +4463,7 @@ function useChatManagement({
|
|
|
4377
4463
|
};
|
|
4378
4464
|
chat.messages.push(newMessage);
|
|
4379
4465
|
if (!chat.title) {
|
|
4380
|
-
const text =
|
|
4466
|
+
const text = getDisplayTextFromContent(content);
|
|
4381
4467
|
if (text) {
|
|
4382
4468
|
chat.title = generateChatTitle(text);
|
|
4383
4469
|
}
|
|
@@ -4421,7 +4507,7 @@ function useChatManagement({
|
|
|
4421
4507
|
if (!chat.title) {
|
|
4422
4508
|
const firstUserMessage = chat.messages.find((msg) => msg.role === "user");
|
|
4423
4509
|
if (firstUserMessage) {
|
|
4424
|
-
const textContent =
|
|
4510
|
+
const textContent = getDisplayTextFromContent(firstUserMessage.content);
|
|
4425
4511
|
if (textContent) {
|
|
4426
4512
|
chat.title = generateChatTitle(textContent);
|
|
4427
4513
|
}
|
|
@@ -5143,6 +5229,8 @@ function useServerEvents({
|
|
|
5143
5229
|
const [streamingText, setStreamingText] = useState13("");
|
|
5144
5230
|
const [streamingReasoning, setStreamingReasoning] = useState13("");
|
|
5145
5231
|
const streamingChatIdRef = useRef10(null);
|
|
5232
|
+
const loadingRef = useRef10(loading);
|
|
5233
|
+
loadingRef.current = loading;
|
|
5146
5234
|
const messageCountAtRunStartRef = useRef10(0);
|
|
5147
5235
|
const hasTextFromPriorStepRef = useRef10(false);
|
|
5148
5236
|
const [executingToolRaw, setExecutingTool] = useState13(null);
|
|
@@ -5238,6 +5326,17 @@ function useServerEvents({
|
|
|
5238
5326
|
setLoading(false);
|
|
5239
5327
|
}
|
|
5240
5328
|
}, []);
|
|
5329
|
+
const handleDisconnect = useCallback11(() => {
|
|
5330
|
+
if (!loadingRef.current) return;
|
|
5331
|
+
const strs = stringsRef.current;
|
|
5332
|
+
const message = strs.errors[ErrorCode.CONNECTION_LOST] || strs.errors[ErrorCode.UNKNOWN_ERROR];
|
|
5333
|
+
saveAIResponseRef.current(message, "error");
|
|
5334
|
+
setStreamingText("");
|
|
5335
|
+
setStreamingReasoning("");
|
|
5336
|
+
streamingChatIdRef.current = null;
|
|
5337
|
+
setExecutingTool(null);
|
|
5338
|
+
setLoading(false);
|
|
5339
|
+
}, []);
|
|
5241
5340
|
const executingTool = executingToolRaw ? {
|
|
5242
5341
|
displayText: executingToolRaw.title ?? executingToolFallbackRef.current ?? strings.toolExecution.fallbackMessages[0]
|
|
5243
5342
|
} : null;
|
|
@@ -5249,7 +5348,8 @@ function useServerEvents({
|
|
|
5249
5348
|
executingTool,
|
|
5250
5349
|
streamingChatIdRef,
|
|
5251
5350
|
streamingReasoning,
|
|
5252
|
-
handleServerEvent
|
|
5351
|
+
handleServerEvent,
|
|
5352
|
+
handleDisconnect
|
|
5253
5353
|
};
|
|
5254
5354
|
}
|
|
5255
5355
|
|
|
@@ -5415,7 +5515,8 @@ function UseAIProvider({
|
|
|
5415
5515
|
theme: customTheme,
|
|
5416
5516
|
strings: customStrings,
|
|
5417
5517
|
visibleAgentIds,
|
|
5418
|
-
onOpenChange
|
|
5518
|
+
onOpenChange,
|
|
5519
|
+
submitMode = "enter"
|
|
5419
5520
|
}) {
|
|
5420
5521
|
const fileUploadConfig = fileUploadConfigProp === false ? void 0 : fileUploadConfigProp ?? DEFAULT_FILE_UPLOAD_CONFIG;
|
|
5421
5522
|
const theme = { ...defaultTheme, ...customTheme };
|
|
@@ -5474,12 +5575,17 @@ function UseAIProvider({
|
|
|
5474
5575
|
} = useCommandManagement({ repository: commandRepository });
|
|
5475
5576
|
const handleServerEventRef = useRef12(serverEvents.handleServerEvent);
|
|
5476
5577
|
handleServerEventRef.current = serverEvents.handleServerEvent;
|
|
5578
|
+
const handleDisconnectRef = useRef12(serverEvents.handleDisconnect);
|
|
5579
|
+
handleDisconnectRef.current = serverEvents.handleDisconnect;
|
|
5477
5580
|
useEffect11(() => {
|
|
5478
5581
|
console.log("[UseAIProvider] Initializing client with serverUrl:", serverUrl);
|
|
5479
5582
|
const client = new UseAIClient(serverUrl);
|
|
5480
5583
|
const unsubscribeConnection = client.onConnectionStateChange((isConnected) => {
|
|
5481
5584
|
console.log("[UseAIProvider] Connection state changed:", isConnected);
|
|
5482
5585
|
setConnected(isConnected);
|
|
5586
|
+
if (!isConnected) {
|
|
5587
|
+
handleDisconnectRef.current();
|
|
5588
|
+
}
|
|
5483
5589
|
});
|
|
5484
5590
|
console.log("[UseAIProvider] Connecting...");
|
|
5485
5591
|
client.connect();
|
|
@@ -5529,27 +5635,10 @@ function UseAIProvider({
|
|
|
5529
5635
|
let persistedContent = message;
|
|
5530
5636
|
let multimodalContent;
|
|
5531
5637
|
if (attachments && attachments.length > 0) {
|
|
5532
|
-
const persistedParts = [];
|
|
5533
|
-
if (message.trim()) {
|
|
5534
|
-
persistedParts.push({ type: "text", text: message });
|
|
5535
|
-
}
|
|
5536
|
-
for (const attachment of attachments) {
|
|
5537
|
-
persistedParts.push({
|
|
5538
|
-
type: "file",
|
|
5539
|
-
file: {
|
|
5540
|
-
name: attachment.file.name,
|
|
5541
|
-
size: attachment.file.size,
|
|
5542
|
-
mimeType: attachment.file.type
|
|
5543
|
-
}
|
|
5544
|
-
});
|
|
5545
|
-
}
|
|
5546
|
-
persistedContent = persistedParts;
|
|
5547
|
-
if (activeChatId) {
|
|
5548
|
-
await chatManagement.saveUserMessage(activeChatId, persistedContent);
|
|
5549
|
-
}
|
|
5550
5638
|
serverEvents.setLoading(true);
|
|
5639
|
+
let fileContent;
|
|
5551
5640
|
try {
|
|
5552
|
-
|
|
5641
|
+
fileContent = await processAttachments(attachments, {
|
|
5553
5642
|
getCurrentChat: chatManagement.getCurrentChat,
|
|
5554
5643
|
backend: fileUploadConfig?.backend,
|
|
5555
5644
|
transformers: fileUploadConfig?.transformers,
|
|
@@ -5557,17 +5646,21 @@ function UseAIProvider({
|
|
|
5557
5646
|
setFileProcessingState(state);
|
|
5558
5647
|
}
|
|
5559
5648
|
});
|
|
5560
|
-
multimodalContent = [];
|
|
5561
|
-
if (message.trim()) {
|
|
5562
|
-
multimodalContent.push({ type: "text", text: message });
|
|
5563
|
-
}
|
|
5564
|
-
multimodalContent.push(...fileContent);
|
|
5565
5649
|
} catch (error) {
|
|
5566
5650
|
serverEvents.setLoading(false);
|
|
5567
5651
|
throw error;
|
|
5568
5652
|
} finally {
|
|
5569
5653
|
setFileProcessingState(null);
|
|
5570
5654
|
}
|
|
5655
|
+
persistedContent = buildPersistedParts(message, attachments, fileContent);
|
|
5656
|
+
if (activeChatId) {
|
|
5657
|
+
await chatManagement.saveUserMessage(activeChatId, persistedContent);
|
|
5658
|
+
}
|
|
5659
|
+
multimodalContent = [];
|
|
5660
|
+
if (message.trim()) {
|
|
5661
|
+
multimodalContent.push({ type: "text", text: message });
|
|
5662
|
+
}
|
|
5663
|
+
multimodalContent.push(...fileContent);
|
|
5571
5664
|
} else {
|
|
5572
5665
|
if (activeChatId) {
|
|
5573
5666
|
await chatManagement.saveUserMessage(activeChatId, persistedContent);
|
|
@@ -5680,7 +5773,8 @@ function UseAIProvider({
|
|
|
5680
5773
|
feedback: {
|
|
5681
5774
|
enabled: feedback.enabled,
|
|
5682
5775
|
submit: feedback.submitFeedback
|
|
5683
|
-
}
|
|
5776
|
+
},
|
|
5777
|
+
submitMode
|
|
5684
5778
|
};
|
|
5685
5779
|
const isUIDisabled = CustomButton === null || CustomChat === null;
|
|
5686
5780
|
const ButtonComponent = isUIDisabled ? null : CustomButton || UseAIFloatingButton;
|
|
@@ -5713,7 +5807,8 @@ function UseAIProvider({
|
|
|
5713
5807
|
onFeedback: feedback.submitFeedback,
|
|
5714
5808
|
pendingApprovals: toolSystem.pendingApprovals,
|
|
5715
5809
|
onApproveToolCall: toolSystem.pendingApprovals.length > 0 ? toolSystem.approveAll : void 0,
|
|
5716
|
-
onRejectToolCall: toolSystem.pendingApprovals.length > 0 ? toolSystem.rejectAll : void 0
|
|
5810
|
+
onRejectToolCall: toolSystem.pendingApprovals.length > 0 ? toolSystem.rejectAll : void 0,
|
|
5811
|
+
submitMode
|
|
5717
5812
|
};
|
|
5718
5813
|
const renderDefaultChat = () => {
|
|
5719
5814
|
if (isUIDisabled) return null;
|