@meetsmore-oss/use-ai-client 1.13.1 → 1.14.1

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
@@ -1376,6 +1376,11 @@ var defaultStrings = {
1376
1376
  /** Error for unknown/unexpected errors */
1377
1377
  UNKNOWN_ERROR: "An unexpected error occurred. Please try again."
1378
1378
  },
1379
+ // Non-error notices shown as system-style bubbles
1380
+ notices: {
1381
+ /** Shown as a separate bubble after the user aborts generation */
1382
+ aborted: "Generation stopped. You can continue the conversation."
1383
+ },
1379
1384
  // Thinking/reasoning display
1380
1385
  thinking: {
1381
1386
  /** Label shown while thinking is in progress */
@@ -1434,6 +1439,8 @@ var defaultTheme = {
1434
1439
  activeBackground: "#f0f0ff",
1435
1440
  /** Disabled button background */
1436
1441
  buttonDisabledBackground: "#e5e7eb",
1442
+ /** Stop (abort) button background — neutral so it doesn't read as a primary action */
1443
+ stopButtonBackground: "#e5e7eb",
1437
1444
  // Text colors
1438
1445
  /** Primary text color */
1439
1446
  textColor: "#1f2937",
@@ -1578,10 +1585,29 @@ function mergeAssistantMessagesForDisplay(messages) {
1578
1585
  let pendingTexts = [];
1579
1586
  let pendingIds = [];
1580
1587
  let pendingReasoningParts = [];
1588
+ const flushPending = () => {
1589
+ if (pendingTexts.length > 0 || pendingReasoningParts.length > 0) {
1590
+ result.push({
1591
+ id: `merged-${pendingIds.join("-")}`,
1592
+ role: "assistant",
1593
+ content: pendingTexts.join("\n\n"),
1594
+ createdAt: /* @__PURE__ */ new Date(),
1595
+ ...pendingReasoningParts.length > 0 ? { reasoningParts: pendingReasoningParts } : {}
1596
+ });
1597
+ pendingTexts = [];
1598
+ pendingIds = [];
1599
+ pendingReasoningParts = [];
1600
+ }
1601
+ };
1581
1602
  for (const msg of messages) {
1582
1603
  if (msg.role === "tool") {
1583
1604
  continue;
1584
1605
  }
1606
+ if (msg.displayMode === "info") {
1607
+ flushPending();
1608
+ result.push(msg);
1609
+ continue;
1610
+ }
1585
1611
  if (msg.role === "assistant") {
1586
1612
  const text5 = getTextFromContent(msg.content);
1587
1613
  if (msg.toolCalls && msg.toolCalls.length > 0) {
@@ -1606,30 +1632,11 @@ function mergeAssistantMessagesForDisplay(messages) {
1606
1632
  pendingReasoningParts = [];
1607
1633
  }
1608
1634
  } else {
1609
- if (pendingTexts.length > 0 || pendingReasoningParts.length > 0) {
1610
- result.push({
1611
- id: `merged-${pendingIds.join("-")}`,
1612
- role: "assistant",
1613
- content: pendingTexts.join("\n\n"),
1614
- createdAt: /* @__PURE__ */ new Date(),
1615
- ...pendingReasoningParts.length > 0 ? { reasoningParts: pendingReasoningParts } : {}
1616
- });
1617
- pendingTexts = [];
1618
- pendingIds = [];
1619
- pendingReasoningParts = [];
1620
- }
1635
+ flushPending();
1621
1636
  result.push(msg);
1622
1637
  }
1623
1638
  }
1624
- if (pendingTexts.length > 0 || pendingReasoningParts.length > 0) {
1625
- result.push({
1626
- id: `merged-${pendingIds.join("-")}`,
1627
- role: "assistant",
1628
- content: pendingTexts.join("\n\n"),
1629
- createdAt: /* @__PURE__ */ new Date(),
1630
- ...pendingReasoningParts.length > 0 ? { reasoningParts: pendingReasoningParts } : {}
1631
- });
1632
- }
1639
+ flushPending();
1633
1640
  return result;
1634
1641
  }
1635
1642
 
@@ -14663,6 +14670,7 @@ function hasFileContent(content3) {
14663
14670
  }
14664
14671
  function UseAIChatPanel({
14665
14672
  onSendMessage,
14673
+ onAbort,
14666
14674
  messages,
14667
14675
  loading,
14668
14676
  connected,
@@ -15220,177 +15228,207 @@ function UseAIChatPanel({
15220
15228
  ]
15221
15229
  }
15222
15230
  ),
15223
- displayMessages.map((message) => /* @__PURE__ */ jsxs9(
15224
- "div",
15225
- {
15226
- "data-testid": `chat-message-${message.role}`,
15227
- className: `chat-message chat-message-${message.role}`,
15228
- style: {
15229
- display: "flex",
15230
- flexDirection: "column",
15231
- alignItems: message.role === "user" ? "flex-end" : "flex-start"
15232
- },
15233
- onMouseEnter: () => message.role === "user" && setHoveredMessageId(message.id),
15234
- onMouseLeave: () => setHoveredMessageId(null),
15235
- children: [
15236
- /* @__PURE__ */ jsxs9(
15237
- "div",
15238
- {
15239
- style: {
15240
- position: "relative",
15241
- maxWidth: "80%"
15242
- },
15243
- children: [
15244
- message.role === "user" && hoveredMessageId === message.id && onSaveCommand && !slashCommands.isSavingCommand(message.id) && /* @__PURE__ */ jsx12(
15245
- "button",
15246
- {
15247
- "data-testid": "save-command-button",
15248
- onClick: (e) => {
15249
- e.stopPropagation();
15250
- const messageText = getDisplayTextFromContent(message.content);
15251
- slashCommands.startSavingCommand(message.id, messageText);
15252
- },
15253
- title: "Save as slash command",
15254
- style: {
15255
- position: "absolute",
15256
- top: "-8px",
15257
- right: "-8px",
15258
- width: "24px",
15259
- height: "24px",
15260
- borderRadius: "50%",
15261
- border: "none",
15262
- background: theme.backgroundColor,
15263
- boxShadow: "0 2px 6px rgba(0, 0, 0, 0.15)",
15264
- cursor: "pointer",
15265
- display: "flex",
15266
- alignItems: "center",
15267
- justifyContent: "center",
15268
- color: theme.primaryColor,
15269
- transition: "all 0.15s",
15270
- zIndex: 10
15271
- },
15272
- onMouseEnter: (e) => {
15273
- e.currentTarget.style.transform = "scale(1.1)";
15274
- e.currentTarget.style.boxShadow = "0 3px 8px rgba(0, 0, 0, 0.2)";
15275
- },
15276
- onMouseLeave: (e) => {
15277
- e.currentTarget.style.transform = "scale(1)";
15278
- e.currentTarget.style.boxShadow = "0 2px 6px rgba(0, 0, 0, 0.15)";
15279
- },
15280
- children: /* @__PURE__ */ jsxs9("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
15281
- /* @__PURE__ */ jsx12("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }),
15282
- /* @__PURE__ */ jsx12("polyline", { points: "17 21 17 13 7 13 7 21" }),
15283
- /* @__PURE__ */ jsx12("polyline", { points: "7 3 7 8 15 8" })
15284
- ] })
15285
- }
15286
- ),
15287
- /* @__PURE__ */ jsxs9(
15288
- "div",
15289
- {
15290
- "data-testid": "chat-message-content",
15291
- className: `chat-message-content${message.role === "assistant" ? " markdown-content" : ""}`,
15292
- style: {
15293
- padding: "10px 14px",
15294
- borderRadius: slashCommands.isSavingCommand(message.id) ? "12px 12px 0 0" : "12px",
15295
- background: message.displayMode === "error" ? theme.errorBackground : message.role === "user" ? theme.primaryGradient : theme.assistantMessageBackground,
15296
- color: message.displayMode === "error" ? theme.errorTextColor : message.role === "user" ? "white" : theme.textColor,
15297
- fontSize: "14px",
15298
- lineHeight: "1.5",
15299
- wordWrap: "break-word"
15300
- },
15301
- children: [
15302
- message.role === "user" && hasFileContent(message.content) && /* @__PURE__ */ jsx12("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px", marginBottom: "8px" }, children: message.content.filter((part) => part.type === "file").map((part, idx) => /* @__PURE__ */ jsx12(
15303
- FilePlaceholder,
15304
- {
15305
- name: part.file.name,
15306
- size: part.file.size
15307
- },
15308
- idx
15309
- )) }),
15310
- message.role === "assistant" ? /* @__PURE__ */ jsxs9(Fragment, { children: [
15311
- message.reasoningParts && message.reasoningParts.length > 0 && /* @__PURE__ */ jsx12(
15312
- Reasoning,
15231
+ displayMessages.map((message) => {
15232
+ if (message.displayMode === "info") {
15233
+ return /* @__PURE__ */ jsx12(
15234
+ "div",
15235
+ {
15236
+ "data-testid": "chat-message-info",
15237
+ className: "chat-message chat-message-info",
15238
+ style: { display: "flex", justifyContent: "center", padding: "2px 0" },
15239
+ children: /* @__PURE__ */ jsx12(
15240
+ "div",
15241
+ {
15242
+ style: {
15243
+ maxWidth: "80%",
15244
+ padding: "6px 12px",
15245
+ borderRadius: "12px",
15246
+ background: theme.assistantMessageBackground,
15247
+ color: theme.secondaryTextColor,
15248
+ fontSize: "12px",
15249
+ lineHeight: "1.4",
15250
+ textAlign: "center",
15251
+ wordWrap: "break-word"
15252
+ },
15253
+ children: getDisplayTextFromContent(message.content)
15254
+ }
15255
+ )
15256
+ },
15257
+ message.id
15258
+ );
15259
+ }
15260
+ return /* @__PURE__ */ jsxs9(
15261
+ "div",
15262
+ {
15263
+ "data-testid": `chat-message-${message.role}`,
15264
+ className: `chat-message chat-message-${message.role}`,
15265
+ style: {
15266
+ display: "flex",
15267
+ flexDirection: "column",
15268
+ alignItems: message.role === "user" ? "flex-end" : "flex-start"
15269
+ },
15270
+ onMouseEnter: () => message.role === "user" && setHoveredMessageId(message.id),
15271
+ onMouseLeave: () => setHoveredMessageId(null),
15272
+ children: [
15273
+ /* @__PURE__ */ jsxs9(
15274
+ "div",
15275
+ {
15276
+ style: {
15277
+ position: "relative",
15278
+ maxWidth: "80%"
15279
+ },
15280
+ children: [
15281
+ message.role === "user" && hoveredMessageId === message.id && onSaveCommand && !slashCommands.isSavingCommand(message.id) && /* @__PURE__ */ jsx12(
15282
+ "button",
15283
+ {
15284
+ "data-testid": "save-command-button",
15285
+ onClick: (e) => {
15286
+ e.stopPropagation();
15287
+ const messageText = getDisplayTextFromContent(message.content);
15288
+ slashCommands.startSavingCommand(message.id, messageText);
15289
+ },
15290
+ title: "Save as slash command",
15291
+ style: {
15292
+ position: "absolute",
15293
+ top: "-8px",
15294
+ right: "-8px",
15295
+ width: "24px",
15296
+ height: "24px",
15297
+ borderRadius: "50%",
15298
+ border: "none",
15299
+ background: theme.backgroundColor,
15300
+ boxShadow: "0 2px 6px rgba(0, 0, 0, 0.15)",
15301
+ cursor: "pointer",
15302
+ display: "flex",
15303
+ alignItems: "center",
15304
+ justifyContent: "center",
15305
+ color: theme.primaryColor,
15306
+ transition: "all 0.15s",
15307
+ zIndex: 10
15308
+ },
15309
+ onMouseEnter: (e) => {
15310
+ e.currentTarget.style.transform = "scale(1.1)";
15311
+ e.currentTarget.style.boxShadow = "0 3px 8px rgba(0, 0, 0, 0.2)";
15312
+ },
15313
+ onMouseLeave: (e) => {
15314
+ e.currentTarget.style.transform = "scale(1)";
15315
+ e.currentTarget.style.boxShadow = "0 2px 6px rgba(0, 0, 0, 0.15)";
15316
+ },
15317
+ children: /* @__PURE__ */ jsxs9("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
15318
+ /* @__PURE__ */ jsx12("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }),
15319
+ /* @__PURE__ */ jsx12("polyline", { points: "17 21 17 13 7 13 7 21" }),
15320
+ /* @__PURE__ */ jsx12("polyline", { points: "7 3 7 8 15 8" })
15321
+ ] })
15322
+ }
15323
+ ),
15324
+ /* @__PURE__ */ jsxs9(
15325
+ "div",
15326
+ {
15327
+ "data-testid": "chat-message-content",
15328
+ className: `chat-message-content${message.role === "assistant" ? " markdown-content" : ""}`,
15329
+ style: {
15330
+ padding: "10px 14px",
15331
+ borderRadius: slashCommands.isSavingCommand(message.id) ? "12px 12px 0 0" : "12px",
15332
+ background: message.displayMode === "error" ? theme.errorBackground : message.role === "user" ? theme.primaryGradient : theme.assistantMessageBackground,
15333
+ color: message.displayMode === "error" ? theme.errorTextColor : message.role === "user" ? "white" : theme.textColor,
15334
+ fontSize: "14px",
15335
+ lineHeight: "1.5",
15336
+ wordWrap: "break-word"
15337
+ },
15338
+ children: [
15339
+ message.role === "user" && hasFileContent(message.content) && /* @__PURE__ */ jsx12("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px", marginBottom: "8px" }, children: message.content.filter((part) => part.type === "file").map((part, idx) => /* @__PURE__ */ jsx12(
15340
+ FilePlaceholder,
15313
15341
  {
15314
- reasoningParts: message.reasoningParts,
15315
- theme,
15316
- strings
15317
- }
15318
- ),
15319
- /* @__PURE__ */ jsx12(MarkdownContent, { content: 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
- )
15325
- ]
15326
- }
15327
- ),
15328
- slashCommands.renderInlineSaveUI({
15329
- messageId: message.id,
15330
- messageText: getDisplayTextFromContent(message.content)
15342
+ name: part.file.name,
15343
+ size: part.file.size
15344
+ },
15345
+ idx
15346
+ )) }),
15347
+ message.role === "assistant" ? /* @__PURE__ */ jsxs9(Fragment, { children: [
15348
+ message.reasoningParts && message.reasoningParts.length > 0 && /* @__PURE__ */ jsx12(
15349
+ Reasoning,
15350
+ {
15351
+ reasoningParts: message.reasoningParts,
15352
+ theme,
15353
+ strings
15354
+ }
15355
+ ),
15356
+ /* @__PURE__ */ jsx12(MarkdownContent, { content: getTextFromContent(message.content) })
15357
+ ] }) : (
15358
+ // User/tool bubbles: display-only text so transformed_file
15359
+ // (e.g. OCR body) isn't dumped into the chat bubble.
15360
+ getDisplayTextFromContent(message.content)
15361
+ )
15362
+ ]
15363
+ }
15364
+ ),
15365
+ slashCommands.renderInlineSaveUI({
15366
+ messageId: message.id,
15367
+ messageText: getDisplayTextFromContent(message.content)
15368
+ })
15369
+ ]
15370
+ }
15371
+ ),
15372
+ message.role === "assistant" && message.traceId && feedbackEnabled && onFeedback && /* @__PURE__ */ jsxs9(
15373
+ "div",
15374
+ {
15375
+ "data-testid": "feedback-buttons",
15376
+ style: {
15377
+ display: "flex",
15378
+ gap: "4px",
15379
+ marginTop: "4px",
15380
+ padding: "0 4px"
15381
+ },
15382
+ children: [
15383
+ /* @__PURE__ */ jsx12(
15384
+ FeedbackButton,
15385
+ {
15386
+ type: "upvote",
15387
+ isSelected: message.feedback === "upvote",
15388
+ onClick: () => {
15389
+ const newFeedback = message.feedback === "upvote" ? null : "upvote";
15390
+ onFeedback(message.id, message.traceId, newFeedback);
15391
+ },
15392
+ selectedColor: theme.primaryColor,
15393
+ unselectedColor: theme.secondaryTextColor
15394
+ }
15395
+ ),
15396
+ /* @__PURE__ */ jsx12(
15397
+ FeedbackButton,
15398
+ {
15399
+ type: "downvote",
15400
+ isSelected: message.feedback === "downvote",
15401
+ onClick: () => {
15402
+ const newFeedback = message.feedback === "downvote" ? null : "downvote";
15403
+ onFeedback(message.id, message.traceId, newFeedback);
15404
+ },
15405
+ selectedColor: theme.errorTextColor,
15406
+ unselectedColor: theme.secondaryTextColor
15407
+ }
15408
+ )
15409
+ ]
15410
+ }
15411
+ ),
15412
+ /* @__PURE__ */ jsx12(
15413
+ "div",
15414
+ {
15415
+ style: {
15416
+ fontSize: "11px",
15417
+ color: theme.secondaryTextColor,
15418
+ marginTop: "4px",
15419
+ padding: "0 4px"
15420
+ },
15421
+ children: message.createdAt.toLocaleTimeString([], {
15422
+ hour: "2-digit",
15423
+ minute: "2-digit"
15331
15424
  })
15332
- ]
15333
- }
15334
- ),
15335
- message.role === "assistant" && message.traceId && feedbackEnabled && onFeedback && /* @__PURE__ */ jsxs9(
15336
- "div",
15337
- {
15338
- "data-testid": "feedback-buttons",
15339
- style: {
15340
- display: "flex",
15341
- gap: "4px",
15342
- marginTop: "4px",
15343
- padding: "0 4px"
15344
- },
15345
- children: [
15346
- /* @__PURE__ */ jsx12(
15347
- FeedbackButton,
15348
- {
15349
- type: "upvote",
15350
- isSelected: message.feedback === "upvote",
15351
- onClick: () => {
15352
- const newFeedback = message.feedback === "upvote" ? null : "upvote";
15353
- onFeedback(message.id, message.traceId, newFeedback);
15354
- },
15355
- selectedColor: theme.primaryColor,
15356
- unselectedColor: theme.secondaryTextColor
15357
- }
15358
- ),
15359
- /* @__PURE__ */ jsx12(
15360
- FeedbackButton,
15361
- {
15362
- type: "downvote",
15363
- isSelected: message.feedback === "downvote",
15364
- onClick: () => {
15365
- const newFeedback = message.feedback === "downvote" ? null : "downvote";
15366
- onFeedback(message.id, message.traceId, newFeedback);
15367
- },
15368
- selectedColor: theme.errorTextColor,
15369
- unselectedColor: theme.secondaryTextColor
15370
- }
15371
- )
15372
- ]
15373
- }
15374
- ),
15375
- /* @__PURE__ */ jsx12(
15376
- "div",
15377
- {
15378
- style: {
15379
- fontSize: "11px",
15380
- color: theme.secondaryTextColor,
15381
- marginTop: "4px",
15382
- padding: "0 4px"
15383
- },
15384
- children: message.createdAt.toLocaleTimeString([], {
15385
- hour: "2-digit",
15386
- minute: "2-digit"
15387
- })
15388
- }
15389
- )
15390
- ]
15391
- },
15392
- message.id
15393
- )),
15425
+ }
15426
+ )
15427
+ ]
15428
+ },
15429
+ message.id
15430
+ );
15431
+ }),
15394
15432
  loading && /* @__PURE__ */ jsx12(
15395
15433
  "div",
15396
15434
  {
@@ -15615,33 +15653,65 @@ function UseAIChatPanel({
15615
15653
  ] })
15616
15654
  }
15617
15655
  ) }),
15618
- /* @__PURE__ */ jsx12(
15619
- "button",
15620
- {
15621
- "data-testid": "chat-send-button",
15622
- className: "chat-send-button",
15623
- onClick: handleSend,
15624
- disabled: !connected || loading || pendingApprovals.length > 0 || !input.trim() && attachments.length === 0,
15625
- style: {
15626
- padding: "6px",
15627
- background: connected && !loading && pendingApprovals.length === 0 && (input.trim() || attachments.length > 0) ? theme.primaryGradient : theme.buttonDisabledBackground,
15628
- color: connected && !loading && pendingApprovals.length === 0 && (input.trim() || attachments.length > 0) ? "white" : theme.secondaryTextColor,
15629
- border: "none",
15630
- borderRadius: "50%",
15631
- cursor: connected && !loading && pendingApprovals.length === 0 && (input.trim() || attachments.length > 0) ? "pointer" : "not-allowed",
15632
- display: "flex",
15633
- alignItems: "center",
15634
- justifyContent: "center",
15635
- width: "32px",
15636
- height: "32px",
15637
- transition: "all 0.2s"
15638
- },
15639
- children: /* @__PURE__ */ jsxs9("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
15640
- /* @__PURE__ */ jsx12("line", { x1: "12", y1: "19", x2: "12", y2: "5" }),
15641
- /* @__PURE__ */ jsx12("polyline", { points: "5 12 12 5 19 12" })
15642
- ] })
15656
+ (() => {
15657
+ const canAbort = loading && !!onAbort;
15658
+ const canSend = connected && !loading && pendingApprovals.length === 0 && (input.trim() || attachments.length > 0);
15659
+ if (loading && onAbort) {
15660
+ return /* @__PURE__ */ jsx12(
15661
+ "button",
15662
+ {
15663
+ "data-testid": "chat-stop-button",
15664
+ className: "chat-stop-button",
15665
+ onClick: onAbort,
15666
+ disabled: !canAbort,
15667
+ title: canAbort ? "Stop generating" : "Cannot stop while a tool is running",
15668
+ "aria-label": "Stop generating",
15669
+ style: {
15670
+ padding: "6px",
15671
+ background: canAbort ? theme.stopButtonBackground : theme.buttonDisabledBackground,
15672
+ color: theme.secondaryTextColor,
15673
+ border: "none",
15674
+ borderRadius: "50%",
15675
+ cursor: canAbort ? "pointer" : "not-allowed",
15676
+ display: "flex",
15677
+ alignItems: "center",
15678
+ justifyContent: "center",
15679
+ width: "32px",
15680
+ height: "32px",
15681
+ transition: "all 0.2s"
15682
+ },
15683
+ children: /* @__PURE__ */ jsx12("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", stroke: "none", children: /* @__PURE__ */ jsx12("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) })
15684
+ }
15685
+ );
15643
15686
  }
15644
- )
15687
+ return /* @__PURE__ */ jsx12(
15688
+ "button",
15689
+ {
15690
+ "data-testid": "chat-send-button",
15691
+ className: "chat-send-button",
15692
+ onClick: handleSend,
15693
+ disabled: !canSend,
15694
+ style: {
15695
+ padding: "6px",
15696
+ background: canSend ? theme.primaryGradient : theme.buttonDisabledBackground,
15697
+ color: canSend ? "white" : theme.secondaryTextColor,
15698
+ border: "none",
15699
+ borderRadius: "50%",
15700
+ cursor: canSend ? "pointer" : "not-allowed",
15701
+ display: "flex",
15702
+ alignItems: "center",
15703
+ justifyContent: "center",
15704
+ width: "32px",
15705
+ height: "32px",
15706
+ transition: "all 0.2s"
15707
+ },
15708
+ children: /* @__PURE__ */ jsxs9("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
15709
+ /* @__PURE__ */ jsx12("line", { x1: "12", y1: "19", x2: "12", y2: "5" }),
15710
+ /* @__PURE__ */ jsx12("polyline", { points: "5 12 12 5 19 12" })
15711
+ ] })
15712
+ }
15713
+ );
15714
+ })()
15645
15715
  ]
15646
15716
  }
15647
15717
  )
@@ -15819,7 +15889,8 @@ function UseAIChat({ floating = false, submitMode }) {
15819
15889
  onFeedback: ctx.feedback?.submit,
15820
15890
  pendingApprovals: ctx.tools.pending.tools,
15821
15891
  onApproveToolCall: ctx.tools.pending.tools.length > 0 ? ctx.tools.pending.approveAll : void 0,
15822
- onRejectToolCall: ctx.tools.pending.tools.length > 0 ? ctx.tools.pending.rejectAll : void 0
15892
+ onRejectToolCall: ctx.tools.pending.tools.length > 0 ? ctx.tools.pending.rejectAll : void 0,
15893
+ onAbort: ctx.abortRun
15823
15894
  };
15824
15895
  if (floating) {
15825
15896
  return /* @__PURE__ */ jsx14(
@@ -23663,6 +23734,7 @@ var ErrorCode;
23663
23734
  ErrorCode2["API_OVERLOADED"] = "API_OVERLOADED";
23664
23735
  ErrorCode2["RATE_LIMITED"] = "RATE_LIMITED";
23665
23736
  ErrorCode2["CONNECTION_LOST"] = "CONNECTION_LOST";
23737
+ ErrorCode2["ABORTED"] = "ABORTED";
23666
23738
  ErrorCode2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
23667
23739
  })(ErrorCode ||= {});
23668
23740
  var TOOL_APPROVAL_REQUEST = "TOOL_APPROVAL_REQUEST";
@@ -23690,6 +23762,9 @@ var UseAIClient = class {
23690
23762
  _tools = [];
23691
23763
  _messages = [];
23692
23764
  _state = null;
23765
+ // Tracks the in-flight run so abortRun() can target it. Set by sendPrompt
23766
+ // and cleared at RUN_FINISHED / RUN_ERROR.
23767
+ _currentRunId = null;
23693
23768
  // Agent selection
23694
23769
  _availableAgents = [];
23695
23770
  _defaultAgent = null;
@@ -23779,6 +23854,7 @@ var UseAIClient = class {
23779
23854
  this._pendingToolResults = [];
23780
23855
  this._currentReasoningBlocks = [];
23781
23856
  this._currentReasoningBlockText = "";
23857
+ this._currentMessageContent = "";
23782
23858
  }
23783
23859
  if (event.type === EventType.TEXT_MESSAGE_START) {
23784
23860
  const e = event;
@@ -23867,40 +23943,13 @@ var UseAIClient = class {
23867
23943
  this._currentAssistantToolCalls = [];
23868
23944
  this._pendingToolResults = [];
23869
23945
  this._currentReasoningBlocks = [];
23946
+ this._currentMessageContent = "";
23870
23947
  }
23871
23948
  } else if (event.type === EventType.RUN_FINISHED) {
23872
- if (this._currentAssistantMessage) {
23873
- const reasoningParts = this._currentReasoningBlocks.length > 0 ? [...this._currentReasoningBlocks] : void 0;
23874
- if (this._currentAssistantToolCalls.length > 0) {
23875
- const toolCallMessage = {
23876
- id: v4_default(),
23877
- role: "assistant",
23878
- content: "",
23879
- toolCalls: this._currentAssistantToolCalls,
23880
- ...reasoningParts ? { reasoningParts } : {}
23881
- };
23882
- this._messages.push(toolCallMessage);
23883
- this._messages.push(...this._pendingToolResults);
23884
- const textMessage = {
23885
- id: this._currentAssistantMessage.id,
23886
- role: "assistant",
23887
- content: this._currentAssistantMessage.content || ""
23888
- };
23889
- this._messages.push(textMessage);
23890
- } else {
23891
- const assistantMessage = {
23892
- id: this._currentAssistantMessage.id,
23893
- role: "assistant",
23894
- content: this._currentAssistantMessage.content || "",
23895
- ...reasoningParts ? { reasoningParts } : {}
23896
- };
23897
- this._messages.push(assistantMessage);
23898
- }
23899
- this._currentAssistantMessage = null;
23900
- this._currentAssistantToolCalls = [];
23901
- this._pendingToolResults = [];
23902
- this._currentReasoningBlockText = "";
23903
- }
23949
+ this.finalizeRun({ aborted: false });
23950
+ }
23951
+ if (event.type === EventType.RUN_FINISHED || event.type === EventType.RUN_ERROR) {
23952
+ this._currentRunId = null;
23904
23953
  }
23905
23954
  this.eventHandlers.forEach((handler) => handler(event));
23906
23955
  }
@@ -23964,10 +24013,12 @@ var UseAIClient = class {
23964
24013
  // Type cast needed for Message type compatibility
23965
24014
  };
23966
24015
  this._messages.push(userMessage);
24016
+ const runId = v4_default();
24017
+ this._currentRunId = runId;
23967
24018
  const runInput = {
23968
24019
  threadId: this.threadId,
23969
24020
  // Use getter to ensure non-null
23970
- runId: v4_default(),
24021
+ runId,
23971
24022
  messages: this._messages,
23972
24023
  tools: this._tools.map((t) => ({
23973
24024
  name: t.name,
@@ -24022,6 +24073,115 @@ var UseAIClient = class {
24022
24073
  this._pendingToolResults.push(toolResultMsg);
24023
24074
  this.send(toolResultMessage);
24024
24075
  }
24076
+ /**
24077
+ * Aborts the in-flight run, if any.
24078
+ * Sends an `abort_run` message to the server which cancels the AI stream
24079
+ * and rejects any pending tool/approval waits. The server then emits
24080
+ * `RUN_ERROR` with `ErrorCode.ABORTED`, which the client handles by
24081
+ * persisting the partial response.
24082
+ *
24083
+ * No-op when no run is in flight.
24084
+ */
24085
+ abortRun() {
24086
+ const runId = this._currentRunId;
24087
+ if (!runId) return;
24088
+ this.send({
24089
+ type: "abort_run",
24090
+ data: { runId }
24091
+ });
24092
+ }
24093
+ /**
24094
+ * Flushes the final in-progress step into `_messages` when a run terminates.
24095
+ *
24096
+ * Only two terminations reach here. Truncation (maxOutputTokens / finish
24097
+ * reason 'length') and tool-execution errors never do — the server absorbs
24098
+ * them: truncation continues via a fallback step and ends as a normal
24099
+ * RUN_FINISHED, and tool errors come back as tool_results that keep the run
24100
+ * going. So the dispatch is binary:
24101
+ * - `aborted: false` (RUN_FINISHED): every tool-call step was already flushed
24102
+ * at STEP_FINISHED, so the in-progress step is always text-only.
24103
+ * - `aborted: true` (RUN_ERROR / ABORTED): the run may have been cut
24104
+ * mid-tool-call or mid-reasoning, so extra repair is needed.
24105
+ *
24106
+ * After this returns, `currentMessageContent` and `currentReasoningBlocks`
24107
+ * remain readable for the persistence helper. The next RUN_STARTED clears them.
24108
+ */
24109
+ finalizeRun(opts) {
24110
+ if (opts.aborted) {
24111
+ this.finalizeAbortedRun();
24112
+ } else {
24113
+ this.finalizeCompletedRun();
24114
+ }
24115
+ }
24116
+ /**
24117
+ * Normal completion (RUN_FINISHED). The in-progress step is text-only —
24118
+ * tool-call steps were already flushed at STEP_FINISHED — so just push the
24119
+ * trailing assistant text with its reasoning.
24120
+ */
24121
+ finalizeCompletedRun() {
24122
+ this._currentReasoningBlockText = "";
24123
+ if (!this._currentAssistantMessage) return;
24124
+ if (this._currentMessageContent) {
24125
+ const stepReasoning = this._currentReasoningBlocks.length > 0 ? [...this._currentReasoningBlocks] : void 0;
24126
+ this._messages.push({
24127
+ id: this._currentAssistantMessage.id || v4_default(),
24128
+ role: "assistant",
24129
+ content: this._currentMessageContent,
24130
+ ...stepReasoning ? { reasoningParts: stepReasoning } : {}
24131
+ });
24132
+ }
24133
+ this._currentAssistantMessage = null;
24134
+ this._currentAssistantToolCalls = [];
24135
+ this._pendingToolResults = [];
24136
+ }
24137
+ /**
24138
+ * User-initiated abort (RUN_ERROR / ABORTED).
24139
+ *
24140
+ * Drops the in-progress step's reasoning blocks. A block gets its encrypted
24141
+ * signature on REASONING_ENCRYPTED_VALUE, which arrives after
24142
+ * REASONING_MESSAGE_END — so a mid-stream abort can leave signature-less
24143
+ * blocks. Persisting those would corrupt the next turn. Reasoning from
24144
+ * already-completed prior steps lives on STEP_FINISHED-flushed assistant
24145
+ * messages and is untouched. Aborted-step messages therefore never carry
24146
+ * reasoningParts.
24147
+ */
24148
+ finalizeAbortedRun() {
24149
+ this._currentReasoningBlocks = [];
24150
+ this._currentReasoningBlockText = "";
24151
+ if (!this._currentAssistantMessage) return;
24152
+ if (this._currentAssistantToolCalls.length > 0) {
24153
+ this._messages.push({
24154
+ id: this._currentAssistantMessage.id || v4_default(),
24155
+ role: "assistant",
24156
+ content: this._currentMessageContent || "",
24157
+ toolCalls: [...this._currentAssistantToolCalls]
24158
+ });
24159
+ this._messages.push(...this._pendingToolResults);
24160
+ const respondedIds = new Set(
24161
+ this._pendingToolResults.map((m) => "toolCallId" in m ? m.toolCallId : void 0).filter((id) => typeof id === "string")
24162
+ );
24163
+ for (const tc of this._currentAssistantToolCalls) {
24164
+ if (!respondedIds.has(tc.id)) {
24165
+ this._messages.push({
24166
+ id: v4_default(),
24167
+ role: "tool",
24168
+ content: JSON.stringify({ aborted: true, reason: "Cancelled by user before tool finished" }),
24169
+ toolCallId: tc.id
24170
+ });
24171
+ }
24172
+ }
24173
+ this._currentMessageContent = "";
24174
+ } else if (this._currentMessageContent) {
24175
+ this._messages.push({
24176
+ id: this._currentAssistantMessage.id || v4_default(),
24177
+ role: "assistant",
24178
+ content: this._currentMessageContent
24179
+ });
24180
+ }
24181
+ this._currentAssistantMessage = null;
24182
+ this._currentAssistantToolCalls = [];
24183
+ this._pendingToolResults = [];
24184
+ }
24025
24185
  /**
24026
24186
  * Sends a tool approval response back to the server.
24027
24187
  *
@@ -24116,6 +24276,12 @@ var UseAIClient = class {
24116
24276
  get currentMessageContent() {
24117
24277
  return this._currentMessageContent;
24118
24278
  }
24279
+ /**
24280
+ * Gets the runId of the in-flight run, or null when no run is active.
24281
+ */
24282
+ get currentRunId() {
24283
+ return this._currentRunId;
24284
+ }
24119
24285
  /**
24120
24286
  * Gets the current reasoning blocks collected during the current run.
24121
24287
  */
@@ -38348,7 +38514,7 @@ import { useState as useState7, useCallback as useCallback5, useRef as useRef5,
38348
38514
 
38349
38515
  // src/utils/messageConversion.ts
38350
38516
  function transformMessagesToClientFormat(persistedMessages) {
38351
- return persistedMessages.map((msg) => {
38517
+ return persistedMessages.filter((msg) => msg.displayMode !== "info").map((msg) => {
38352
38518
  switch (msg.role) {
38353
38519
  case "tool":
38354
38520
  return {
@@ -39321,6 +39487,7 @@ function useServerEvents({
39321
39487
  const loadingRef = useRef10(loading);
39322
39488
  loadingRef.current = loading;
39323
39489
  const messageCountAtRunStartRef = useRef10(0);
39490
+ const runIdAtRunStartRef = useRef10(void 0);
39324
39491
  const hasTextFromPriorStepRef = useRef10(false);
39325
39492
  const [executingToolRaw, setExecutingTool] = useState13(null);
39326
39493
  const executingToolFallbackRef = useRef10(null);
@@ -39333,11 +39500,37 @@ function useServerEvents({
39333
39500
  saveAIResponseRef.current = saveAIResponse;
39334
39501
  const stringsRef = useRef10(strings);
39335
39502
  stringsRef.current = strings;
39503
+ const persistFinalResponse = useCallback11(async (client, opts) => {
39504
+ const content3 = client.currentMessageContent;
39505
+ const reasoningParts = client.currentReasoningBlocks.length > 0 ? [...client.currentReasoningBlocks] : void 0;
39506
+ const turnMessages = extractTurnMessages(client.messages, messageCountAtRunStartRef.current);
39507
+ if (opts.aborted) {
39508
+ const notice = stringsRef.current.notices.aborted;
39509
+ if (content3) {
39510
+ await saveAIResponseRef.current(content3, void 0, opts.traceId, turnMessages, reasoningParts);
39511
+ await saveAIResponseRef.current(notice, "info");
39512
+ } else {
39513
+ await saveAIResponseRef.current(notice, "info", opts.traceId, turnMessages);
39514
+ }
39515
+ return;
39516
+ }
39517
+ if (content3) {
39518
+ await saveAIResponseRef.current(content3, void 0, opts.traceId, turnMessages, reasoningParts);
39519
+ }
39520
+ }, []);
39521
+ const resetRunUiState = useCallback11(() => {
39522
+ setStreamingText("");
39523
+ setStreamingReasoning("");
39524
+ streamingChatIdRef.current = null;
39525
+ setExecutingTool(null);
39526
+ setLoading(false);
39527
+ }, []);
39336
39528
  const handleServerEvent = useCallback11(async (client, event) => {
39337
39529
  const ts = toolSystemRef.current;
39338
39530
  const strs = stringsRef.current;
39339
39531
  if (event.type === EventType.RUN_STARTED) {
39340
39532
  messageCountAtRunStartRef.current = client.messages.length;
39533
+ runIdAtRunStartRef.current = client.currentRunId ?? void 0;
39341
39534
  hasTextFromPriorStepRef.current = false;
39342
39535
  setStreamingReasoning("");
39343
39536
  } else if (event.type === EventType.REASONING_MESSAGE_START) {
@@ -39389,30 +39582,21 @@ function useServerEvents({
39389
39582
  setStreamingText((prev) => prev + contentEvent.delta);
39390
39583
  } else if (event.type === EventType.TEXT_MESSAGE_END) {
39391
39584
  } else if (event.type === EventType.RUN_FINISHED) {
39392
- const content3 = client.currentMessageContent;
39393
- if (content3) {
39394
- const finishedEvent = event;
39395
- const traceId = finishedEvent.runId;
39396
- const turnMessages = extractTurnMessages(client.messages, messageCountAtRunStartRef.current);
39397
- const reasoningParts = client.currentReasoningBlocks.length > 0 ? [...client.currentReasoningBlocks] : void 0;
39398
- saveAIResponseRef.current(content3, void 0, traceId, turnMessages, reasoningParts);
39399
- }
39400
- setStreamingText("");
39401
- setStreamingReasoning("");
39402
- streamingChatIdRef.current = null;
39403
- setExecutingTool(null);
39404
- setLoading(false);
39585
+ const finishedEvent = event;
39586
+ await persistFinalResponse(client, { aborted: false, traceId: finishedEvent.runId });
39587
+ resetRunUiState();
39405
39588
  } else if (event.type === EventType.RUN_ERROR) {
39406
39589
  const errorEvent = event;
39407
39590
  const errorCode = errorEvent.message;
39408
- console.error("[ServerEvents] Run error:", errorCode);
39409
- const userMessage = strs.errors[errorCode] || errorEvent.message || strs.errors[ErrorCode.UNKNOWN_ERROR];
39410
- saveAIResponseRef.current(userMessage, "error");
39411
- setStreamingText("");
39412
- setStreamingReasoning("");
39413
- streamingChatIdRef.current = null;
39414
- setExecutingTool(null);
39415
- setLoading(false);
39591
+ if (errorCode === ErrorCode.ABORTED) {
39592
+ client.finalizeRun({ aborted: true });
39593
+ await persistFinalResponse(client, { aborted: true, traceId: runIdAtRunStartRef.current });
39594
+ } else {
39595
+ console.error("[ServerEvents] Run error:", errorCode);
39596
+ const userMessage = strs.errors[errorCode] || errorEvent.message || strs.errors[ErrorCode.UNKNOWN_ERROR];
39597
+ saveAIResponseRef.current(userMessage, "error");
39598
+ }
39599
+ resetRunUiState();
39416
39600
  }
39417
39601
  }, []);
39418
39602
  const handleDisconnect = useCallback11(() => {
@@ -39582,6 +39766,8 @@ var noOpContextValue = {
39582
39766
  },
39583
39767
  delete: async () => {
39584
39768
  }
39769
+ },
39770
+ abortRun: () => {
39585
39771
  }
39586
39772
  };
39587
39773
  var DEFAULT_FILE_UPLOAD_CONFIG = {
@@ -39715,6 +39901,9 @@ function UseAIProvider({
39715
39901
  console.error("Failed to register tools:", err);
39716
39902
  }
39717
39903
  }, [toolSystem.hasTools, toolSystem.aggregatedTools, connected]);
39904
+ const abortRun = useCallback13(() => {
39905
+ clientRef.current?.abortRun();
39906
+ }, []);
39718
39907
  const handleSendMessage = useCallback13(async (message, attachments, messageForwardedProps) => {
39719
39908
  if (!clientRef.current) return;
39720
39909
  serverEvents.clearStreamingText();
@@ -39813,7 +40002,8 @@ function UseAIProvider({
39813
40002
  save: saveCommand,
39814
40003
  rename: renameCommand,
39815
40004
  delete: deleteCommand
39816
- }
40005
+ },
40006
+ abortRun
39817
40007
  };
39818
40008
  const effectiveStreamingText = serverEvents.streamingChatIdRef.current === chatManagement.displayedChatId ? serverEvents.streamingText : "";
39819
40009
  const effectiveStreamingReasoning = serverEvents.streamingChatIdRef.current === chatManagement.displayedChatId ? serverEvents.streamingReasoning : "";
@@ -39821,6 +40011,7 @@ function UseAIProvider({
39821
40011
  connected,
39822
40012
  loading: serverEvents.loading,
39823
40013
  sendMessage: handleSendMessage,
40014
+ abortRun,
39824
40015
  messages,
39825
40016
  streamingText: effectiveStreamingText,
39826
40017
  streamingReasoning: effectiveStreamingReasoning,
@@ -39870,6 +40061,7 @@ function UseAIProvider({
39870
40061
  const hasCustomChat = CustomChat !== void 0 && CustomChat !== null;
39871
40062
  const chatPanelProps = {
39872
40063
  onSendMessage: handleSendMessage,
40064
+ onAbort: abortRun,
39873
40065
  messages,
39874
40066
  loading: serverEvents.loading,
39875
40067
  connected,
@@ -39917,6 +40109,7 @@ function UseAIProvider({
39917
40109
  isOpen: isChatOpen,
39918
40110
  onClose: () => handleSetChatOpen(false),
39919
40111
  onSendMessage: handleSendMessage,
40112
+ onAbort: abortRun,
39920
40113
  messages,
39921
40114
  loading: serverEvents.loading,
39922
40115
  connected,