@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 CHANGED
@@ -1371,6 +1371,8 @@ var defaultStrings = {
1371
1371
  API_OVERLOADED: "The AI service is currently experiencing high demand. Please try again in a moment.",
1372
1372
  /** Error when rate limited */
1373
1373
  RATE_LIMITED: "Too many requests. Please wait a moment before trying again.",
1374
+ /** Error when the connection was lost while a response was being generated */
1375
+ CONNECTION_LOST: "The connection was lost. Please send your message again.",
1374
1376
  /** Error for unknown/unexpected errors */
1375
1377
  UNKNOWN_ERROR: "An unexpected error occurred. Please try again."
1376
1378
  },
@@ -1557,7 +1559,17 @@ function getTextFromContent(content3) {
1557
1559
  if (typeof content3 === "string") {
1558
1560
  return content3;
1559
1561
  }
1560
- return content3.filter((part) => part.type === "text").map((part) => part.text).join("\n");
1562
+ return content3.flatMap((part) => {
1563
+ if (part.type === "text") return [part.text];
1564
+ if (part.type === "transformed_file") return [part.text];
1565
+ return [];
1566
+ }).join("\n");
1567
+ }
1568
+ function getDisplayTextFromContent(content3) {
1569
+ if (typeof content3 === "string") {
1570
+ return content3;
1571
+ }
1572
+ return content3.flatMap((part) => part.type === "text" ? [part.text] : []).join("\n");
1561
1573
  }
1562
1574
 
1563
1575
  // src/utils/mergeAssistantMessages.ts
@@ -1621,6 +1633,17 @@ function mergeAssistantMessagesForDisplay(messages) {
1621
1633
  return result;
1622
1634
  }
1623
1635
 
1636
+ // src/utils/keyboard.ts
1637
+ function shouldSubmitOnEnter(e, mode) {
1638
+ if (e.key !== "Enter" || e.nativeEvent.isComposing || e.keyCode === 229) {
1639
+ return false;
1640
+ }
1641
+ if (mode === "enter") {
1642
+ return !e.shiftKey;
1643
+ }
1644
+ return e.metaKey || e.ctrlKey;
1645
+ }
1646
+
1624
1647
  // ../../node_modules/.bun/react-markdown@8.0.7+32264e8fb3466d46/node_modules/react-markdown/lib/uri-transformer.js
1625
1648
  var protocols = ["http", "https", "mailto", "tel"];
1626
1649
  function uriTransformer(uri) {
@@ -14668,7 +14691,8 @@ function UseAIChatPanel({
14668
14691
  onFeedback,
14669
14692
  pendingApprovals = [],
14670
14693
  onApproveToolCall,
14671
- onRejectToolCall
14694
+ onRejectToolCall,
14695
+ submitMode = "enter"
14672
14696
  }) {
14673
14697
  const strings = useStrings();
14674
14698
  const theme = useTheme();
@@ -14743,7 +14767,7 @@ function UseAIChatPanel({
14743
14767
  if (slashCommands.handleKeyDown(e)) {
14744
14768
  return;
14745
14769
  }
14746
- if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing && !(e.keyCode === 229)) {
14770
+ if (shouldSubmitOnEnter(e, submitMode)) {
14747
14771
  e.preventDefault();
14748
14772
  handleSend();
14749
14773
  }
@@ -14841,7 +14865,7 @@ function UseAIChatPanel({
14841
14865
  if (displayMessages.length > 0) {
14842
14866
  const firstUserMsg = displayMessages.find((m) => m.role === "user");
14843
14867
  if (firstUserMsg) {
14844
- const textContent = getTextFromContent(firstUserMsg.content);
14868
+ const textContent = getDisplayTextFromContent(firstUserMsg.content);
14845
14869
  const maxLength = 30;
14846
14870
  return textContent.length > maxLength ? textContent.substring(0, maxLength) + "..." : textContent || strings.header.newChat;
14847
14871
  }
@@ -15147,7 +15171,7 @@ function UseAIChatPanel({
15147
15171
  {
15148
15172
  style: {
15149
15173
  display: "grid",
15150
- gridTemplateColumns: "repeat(2, 1fr)",
15174
+ gridTemplateColumns: "1fr",
15151
15175
  gap: "8px",
15152
15176
  width: "100%",
15153
15177
  maxWidth: "320px"
@@ -15223,7 +15247,7 @@ function UseAIChatPanel({
15223
15247
  "data-testid": "save-command-button",
15224
15248
  onClick: (e) => {
15225
15249
  e.stopPropagation();
15226
- const messageText = getTextFromContent(message.content);
15250
+ const messageText = getDisplayTextFromContent(message.content);
15227
15251
  slashCommands.startSavingCommand(message.id, messageText);
15228
15252
  },
15229
15253
  title: "Save as slash command",
@@ -15293,13 +15317,17 @@ function UseAIChatPanel({
15293
15317
  }
15294
15318
  ),
15295
15319
  /* @__PURE__ */ jsx12(MarkdownContent, { content: getTextFromContent(message.content) })
15296
- ] }) : getTextFromContent(message.content)
15320
+ ] }) : (
15321
+ // User/tool bubbles: display-only text so transformed_file
15322
+ // (e.g. OCR body) isn't dumped into the chat bubble.
15323
+ getDisplayTextFromContent(message.content)
15324
+ )
15297
15325
  ]
15298
15326
  }
15299
15327
  ),
15300
15328
  slashCommands.renderInlineSaveUI({
15301
15329
  messageId: message.id,
15302
- messageText: getTextFromContent(message.content)
15330
+ messageText: getDisplayTextFromContent(message.content)
15303
15331
  })
15304
15332
  ]
15305
15333
  }
@@ -15759,9 +15787,10 @@ function useChatUIContext() {
15759
15787
  }
15760
15788
  return context;
15761
15789
  }
15762
- function UseAIChat({ floating = false }) {
15790
+ function UseAIChat({ floating = false, submitMode }) {
15763
15791
  const ctx = useChatUIContext();
15764
15792
  const chatPanelProps = {
15793
+ submitMode: submitMode ?? ctx.submitMode,
15765
15794
  onSendMessage: ctx.sendMessage,
15766
15795
  messages: ctx.messages,
15767
15796
  loading: ctx.loading,
@@ -23633,6 +23662,7 @@ var ErrorCode;
23633
23662
  ((ErrorCode2) => {
23634
23663
  ErrorCode2["API_OVERLOADED"] = "API_OVERLOADED";
23635
23664
  ErrorCode2["RATE_LIMITED"] = "RATE_LIMITED";
23665
+ ErrorCode2["CONNECTION_LOST"] = "CONNECTION_LOST";
23636
23666
  ErrorCode2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
23637
23667
  })(ErrorCode ||= {});
23638
23668
  var TOOL_APPROVAL_REQUEST = "TOOL_APPROVAL_REQUEST";
@@ -23649,9 +23679,12 @@ var UseAIClient = class {
23649
23679
  }
23650
23680
  socket = null;
23651
23681
  eventHandlers = /* @__PURE__ */ new Map();
23652
- reconnectAttempts = 0;
23653
- maxReconnectAttempts = 5;
23682
+ // Reconnect indefinitely so clients recover after extended outages (mobile
23683
+ // app backgrounded long enough for server pingTimeout, airplane mode, etc.).
23684
+ // Socket.IO applies exponential backoff capped at reconnectionDelayMax,
23685
+ // so steady-state retry frequency is ~one attempt per 10s.
23654
23686
  reconnectDelay = 1e3;
23687
+ reconnectDelayMax = 1e4;
23655
23688
  // Session state
23656
23689
  _threadId = null;
23657
23690
  _tools = [];
@@ -23689,14 +23722,14 @@ var UseAIClient = class {
23689
23722
  this.socket = lookup2(this.serverUrl, {
23690
23723
  transports: ["polling", "websocket"],
23691
23724
  reconnection: true,
23692
- reconnectionAttempts: this.maxReconnectAttempts,
23725
+ reconnectionAttempts: Infinity,
23693
23726
  reconnectionDelay: this.reconnectDelay,
23727
+ reconnectionDelayMax: this.reconnectDelayMax,
23694
23728
  withCredentials: true
23695
23729
  });
23696
23730
  this.socket.on("connect", () => {
23697
23731
  console.log("[UseAI] Connected to server");
23698
23732
  console.log("[UseAI] Transport:", this.socket?.io?.engine?.transport?.name);
23699
- this.reconnectAttempts = 0;
23700
23733
  const engine = this.socket?.io?.engine;
23701
23734
  if (engine) {
23702
23735
  engine.on("upgrade", (transport) => {
@@ -38268,35 +38301,89 @@ var LocalStorageChatRepository = class {
38268
38301
  }
38269
38302
  };
38270
38303
 
38304
+ // src/fileUpload/buildPersistedParts.ts
38305
+ function buildPersistedParts(message, attachments, fileContent) {
38306
+ const parts2 = [];
38307
+ if (message.trim()) {
38308
+ parts2.push({ type: "text", text: message });
38309
+ }
38310
+ const transformedByKey = /* @__PURE__ */ new Map();
38311
+ for (const part of fileContent) {
38312
+ if (part.type === "transformed_file") {
38313
+ const key = `${part.originalFile.name}:${part.originalFile.size}:${part.originalFile.mimeType}`;
38314
+ const list3 = transformedByKey.get(key);
38315
+ if (list3) {
38316
+ list3.push(part);
38317
+ } else {
38318
+ transformedByKey.set(key, [part]);
38319
+ }
38320
+ }
38321
+ }
38322
+ for (const attachment of attachments) {
38323
+ const key = `${attachment.file.name}:${attachment.file.size}:${attachment.file.type}`;
38324
+ const transformed = transformedByKey.get(key)?.shift();
38325
+ if (transformed) {
38326
+ parts2.push({
38327
+ type: "transformed_file",
38328
+ text: transformed.text,
38329
+ originalFile: transformed.originalFile
38330
+ });
38331
+ } else {
38332
+ parts2.push({
38333
+ type: "file",
38334
+ file: {
38335
+ name: attachment.file.name,
38336
+ size: attachment.file.size,
38337
+ mimeType: attachment.file.type
38338
+ }
38339
+ });
38340
+ }
38341
+ }
38342
+ return parts2;
38343
+ }
38344
+
38271
38345
  // src/hooks/useChatManagement.ts
38272
38346
  import { useState as useState7, useCallback as useCallback5, useRef as useRef5, useEffect as useEffect5 } from "react";
38273
38347
 
38274
38348
  // src/utils/messageConversion.ts
38275
38349
  function transformMessagesToClientFormat(persistedMessages) {
38276
38350
  return persistedMessages.map((msg) => {
38277
- const textContent = getTextFromContent(msg.content);
38278
38351
  switch (msg.role) {
38279
38352
  case "tool":
38280
38353
  return {
38281
38354
  id: msg.id,
38282
38355
  role: "tool",
38283
- content: textContent,
38356
+ content: getTextFromContent(msg.content),
38284
38357
  toolCallId: msg.toolCallId || ""
38285
38358
  };
38286
38359
  case "assistant":
38287
38360
  return {
38288
38361
  id: msg.id,
38289
38362
  role: "assistant",
38290
- content: textContent,
38363
+ content: getTextFromContent(msg.content),
38291
38364
  ...msg.toolCalls && msg.toolCalls.length > 0 ? { toolCalls: msg.toolCalls } : {},
38292
38365
  ...msg.reasoningParts && msg.reasoningParts.length > 0 ? { reasoningParts: msg.reasoningParts } : {}
38293
38366
  };
38294
- case "user":
38295
- return {
38296
- id: msg.id,
38297
- role: "user",
38298
- content: textContent
38299
- };
38367
+ case "user": {
38368
+ if (typeof msg.content === "string") {
38369
+ return { id: msg.id, role: "user", content: msg.content };
38370
+ }
38371
+ const parts2 = msg.content.flatMap((p) => {
38372
+ if (p.type === "text") {
38373
+ return [{ type: "text", text: p.text }];
38374
+ }
38375
+ if (p.type === "transformed_file") {
38376
+ return [{
38377
+ type: "text",
38378
+ text: `[Content of file "${p.originalFile.name}" (${p.originalFile.mimeType})]:
38379
+
38380
+ ${p.text}`
38381
+ }];
38382
+ }
38383
+ return [];
38384
+ });
38385
+ return { id: msg.id, role: "user", content: parts2 };
38386
+ }
38300
38387
  }
38301
38388
  });
38302
38389
  }
@@ -38469,7 +38556,7 @@ function useChatManagement({
38469
38556
  };
38470
38557
  chat.messages.push(newMessage);
38471
38558
  if (!chat.title) {
38472
- const text5 = getTextFromContent(content3);
38559
+ const text5 = getDisplayTextFromContent(content3);
38473
38560
  if (text5) {
38474
38561
  chat.title = generateChatTitle(text5);
38475
38562
  }
@@ -38513,7 +38600,7 @@ function useChatManagement({
38513
38600
  if (!chat.title) {
38514
38601
  const firstUserMessage = chat.messages.find((msg) => msg.role === "user");
38515
38602
  if (firstUserMessage) {
38516
- const textContent = getTextFromContent(firstUserMessage.content);
38603
+ const textContent = getDisplayTextFromContent(firstUserMessage.content);
38517
38604
  if (textContent) {
38518
38605
  chat.title = generateChatTitle(textContent);
38519
38606
  }
@@ -39230,6 +39317,8 @@ function useServerEvents({
39230
39317
  const [streamingText, setStreamingText] = useState13("");
39231
39318
  const [streamingReasoning, setStreamingReasoning] = useState13("");
39232
39319
  const streamingChatIdRef = useRef10(null);
39320
+ const loadingRef = useRef10(loading);
39321
+ loadingRef.current = loading;
39233
39322
  const messageCountAtRunStartRef = useRef10(0);
39234
39323
  const hasTextFromPriorStepRef = useRef10(false);
39235
39324
  const [executingToolRaw, setExecutingTool] = useState13(null);
@@ -39325,6 +39414,17 @@ function useServerEvents({
39325
39414
  setLoading(false);
39326
39415
  }
39327
39416
  }, []);
39417
+ const handleDisconnect = useCallback11(() => {
39418
+ if (!loadingRef.current) return;
39419
+ const strs = stringsRef.current;
39420
+ const message = strs.errors[ErrorCode.CONNECTION_LOST] || strs.errors[ErrorCode.UNKNOWN_ERROR];
39421
+ saveAIResponseRef.current(message, "error");
39422
+ setStreamingText("");
39423
+ setStreamingReasoning("");
39424
+ streamingChatIdRef.current = null;
39425
+ setExecutingTool(null);
39426
+ setLoading(false);
39427
+ }, []);
39328
39428
  const executingTool = executingToolRaw ? {
39329
39429
  displayText: executingToolRaw.title ?? executingToolFallbackRef.current ?? strings.toolExecution.fallbackMessages[0]
39330
39430
  } : null;
@@ -39336,7 +39436,8 @@ function useServerEvents({
39336
39436
  executingTool,
39337
39437
  streamingChatIdRef,
39338
39438
  streamingReasoning,
39339
- handleServerEvent
39439
+ handleServerEvent,
39440
+ handleDisconnect
39340
39441
  };
39341
39442
  }
39342
39443
 
@@ -39502,7 +39603,8 @@ function UseAIProvider({
39502
39603
  theme: customTheme,
39503
39604
  strings: customStrings,
39504
39605
  visibleAgentIds,
39505
- onOpenChange
39606
+ onOpenChange,
39607
+ submitMode = "enter"
39506
39608
  }) {
39507
39609
  const fileUploadConfig = fileUploadConfigProp === false ? void 0 : fileUploadConfigProp ?? DEFAULT_FILE_UPLOAD_CONFIG;
39508
39610
  const theme = { ...defaultTheme, ...customTheme };
@@ -39561,12 +39663,17 @@ function UseAIProvider({
39561
39663
  } = useCommandManagement({ repository: commandRepository });
39562
39664
  const handleServerEventRef = useRef12(serverEvents.handleServerEvent);
39563
39665
  handleServerEventRef.current = serverEvents.handleServerEvent;
39666
+ const handleDisconnectRef = useRef12(serverEvents.handleDisconnect);
39667
+ handleDisconnectRef.current = serverEvents.handleDisconnect;
39564
39668
  useEffect11(() => {
39565
39669
  console.log("[UseAIProvider] Initializing client with serverUrl:", serverUrl);
39566
39670
  const client = new UseAIClient(serverUrl);
39567
39671
  const unsubscribeConnection = client.onConnectionStateChange((isConnected) => {
39568
39672
  console.log("[UseAIProvider] Connection state changed:", isConnected);
39569
39673
  setConnected(isConnected);
39674
+ if (!isConnected) {
39675
+ handleDisconnectRef.current();
39676
+ }
39570
39677
  });
39571
39678
  console.log("[UseAIProvider] Connecting...");
39572
39679
  client.connect();
@@ -39616,27 +39723,10 @@ function UseAIProvider({
39616
39723
  let persistedContent = message;
39617
39724
  let multimodalContent;
39618
39725
  if (attachments && attachments.length > 0) {
39619
- const persistedParts = [];
39620
- if (message.trim()) {
39621
- persistedParts.push({ type: "text", text: message });
39622
- }
39623
- for (const attachment of attachments) {
39624
- persistedParts.push({
39625
- type: "file",
39626
- file: {
39627
- name: attachment.file.name,
39628
- size: attachment.file.size,
39629
- mimeType: attachment.file.type
39630
- }
39631
- });
39632
- }
39633
- persistedContent = persistedParts;
39634
- if (activeChatId) {
39635
- await chatManagement.saveUserMessage(activeChatId, persistedContent);
39636
- }
39637
39726
  serverEvents.setLoading(true);
39727
+ let fileContent;
39638
39728
  try {
39639
- const fileContent = await processAttachments(attachments, {
39729
+ fileContent = await processAttachments(attachments, {
39640
39730
  getCurrentChat: chatManagement.getCurrentChat,
39641
39731
  backend: fileUploadConfig?.backend,
39642
39732
  transformers: fileUploadConfig?.transformers,
@@ -39644,17 +39734,21 @@ function UseAIProvider({
39644
39734
  setFileProcessingState(state);
39645
39735
  }
39646
39736
  });
39647
- multimodalContent = [];
39648
- if (message.trim()) {
39649
- multimodalContent.push({ type: "text", text: message });
39650
- }
39651
- multimodalContent.push(...fileContent);
39652
39737
  } catch (error48) {
39653
39738
  serverEvents.setLoading(false);
39654
39739
  throw error48;
39655
39740
  } finally {
39656
39741
  setFileProcessingState(null);
39657
39742
  }
39743
+ persistedContent = buildPersistedParts(message, attachments, fileContent);
39744
+ if (activeChatId) {
39745
+ await chatManagement.saveUserMessage(activeChatId, persistedContent);
39746
+ }
39747
+ multimodalContent = [];
39748
+ if (message.trim()) {
39749
+ multimodalContent.push({ type: "text", text: message });
39750
+ }
39751
+ multimodalContent.push(...fileContent);
39658
39752
  } else {
39659
39753
  if (activeChatId) {
39660
39754
  await chatManagement.saveUserMessage(activeChatId, persistedContent);
@@ -39767,7 +39861,8 @@ function UseAIProvider({
39767
39861
  feedback: {
39768
39862
  enabled: feedback.enabled,
39769
39863
  submit: feedback.submitFeedback
39770
- }
39864
+ },
39865
+ submitMode
39771
39866
  };
39772
39867
  const isUIDisabled = CustomButton === null || CustomChat === null;
39773
39868
  const ButtonComponent = isUIDisabled ? null : CustomButton || UseAIFloatingButton;
@@ -39800,7 +39895,8 @@ function UseAIProvider({
39800
39895
  onFeedback: feedback.submitFeedback,
39801
39896
  pendingApprovals: toolSystem.pendingApprovals,
39802
39897
  onApproveToolCall: toolSystem.pendingApprovals.length > 0 ? toolSystem.approveAll : void 0,
39803
- onRejectToolCall: toolSystem.pendingApprovals.length > 0 ? toolSystem.rejectAll : void 0
39898
+ onRejectToolCall: toolSystem.pendingApprovals.length > 0 ? toolSystem.rejectAll : void 0,
39899
+ submitMode
39804
39900
  };
39805
39901
  const renderDefaultChat = () => {
39806
39902
  if (isUIDisabled) return null;