@meetsmore-oss/use-ai-client 1.13.1 → 1.14.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
@@ -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;
@@ -23869,38 +23945,10 @@ var UseAIClient = class {
23869
23945
  this._currentReasoningBlocks = [];
23870
23946
  }
23871
23947
  } 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
- }
23948
+ this.finalizeRun({ aborted: false });
23949
+ }
23950
+ if (event.type === EventType.RUN_FINISHED || event.type === EventType.RUN_ERROR) {
23951
+ this._currentRunId = null;
23904
23952
  }
23905
23953
  this.eventHandlers.forEach((handler) => handler(event));
23906
23954
  }
@@ -23964,10 +24012,12 @@ var UseAIClient = class {
23964
24012
  // Type cast needed for Message type compatibility
23965
24013
  };
23966
24014
  this._messages.push(userMessage);
24015
+ const runId = v4_default();
24016
+ this._currentRunId = runId;
23967
24017
  const runInput = {
23968
24018
  threadId: this.threadId,
23969
24019
  // Use getter to ensure non-null
23970
- runId: v4_default(),
24020
+ runId,
23971
24021
  messages: this._messages,
23972
24022
  tools: this._tools.map((t) => ({
23973
24023
  name: t.name,
@@ -24022,6 +24072,115 @@ var UseAIClient = class {
24022
24072
  this._pendingToolResults.push(toolResultMsg);
24023
24073
  this.send(toolResultMessage);
24024
24074
  }
24075
+ /**
24076
+ * Aborts the in-flight run, if any.
24077
+ * Sends an `abort_run` message to the server which cancels the AI stream
24078
+ * and rejects any pending tool/approval waits. The server then emits
24079
+ * `RUN_ERROR` with `ErrorCode.ABORTED`, which the client handles by
24080
+ * persisting the partial response.
24081
+ *
24082
+ * No-op when no run is in flight.
24083
+ */
24084
+ abortRun() {
24085
+ const runId = this._currentRunId;
24086
+ if (!runId) return;
24087
+ this.send({
24088
+ type: "abort_run",
24089
+ data: { runId }
24090
+ });
24091
+ }
24092
+ /**
24093
+ * Flushes the final in-progress step into `_messages` when a run terminates.
24094
+ *
24095
+ * Only two terminations reach here. Truncation (maxOutputTokens / finish
24096
+ * reason 'length') and tool-execution errors never do — the server absorbs
24097
+ * them: truncation continues via a fallback step and ends as a normal
24098
+ * RUN_FINISHED, and tool errors come back as tool_results that keep the run
24099
+ * going. So the dispatch is binary:
24100
+ * - `aborted: false` (RUN_FINISHED): every tool-call step was already flushed
24101
+ * at STEP_FINISHED, so the in-progress step is always text-only.
24102
+ * - `aborted: true` (RUN_ERROR / ABORTED): the run may have been cut
24103
+ * mid-tool-call or mid-reasoning, so extra repair is needed.
24104
+ *
24105
+ * After this returns, `currentMessageContent` and `currentReasoningBlocks`
24106
+ * remain readable for the persistence helper. The next RUN_STARTED clears them.
24107
+ */
24108
+ finalizeRun(opts) {
24109
+ if (opts.aborted) {
24110
+ this.finalizeAbortedRun();
24111
+ } else {
24112
+ this.finalizeCompletedRun();
24113
+ }
24114
+ }
24115
+ /**
24116
+ * Normal completion (RUN_FINISHED). The in-progress step is text-only —
24117
+ * tool-call steps were already flushed at STEP_FINISHED — so just push the
24118
+ * trailing assistant text with its reasoning.
24119
+ */
24120
+ finalizeCompletedRun() {
24121
+ this._currentReasoningBlockText = "";
24122
+ if (!this._currentAssistantMessage) return;
24123
+ if (this._currentMessageContent) {
24124
+ const stepReasoning = this._currentReasoningBlocks.length > 0 ? [...this._currentReasoningBlocks] : void 0;
24125
+ this._messages.push({
24126
+ id: this._currentAssistantMessage.id || v4_default(),
24127
+ role: "assistant",
24128
+ content: this._currentMessageContent,
24129
+ ...stepReasoning ? { reasoningParts: stepReasoning } : {}
24130
+ });
24131
+ }
24132
+ this._currentAssistantMessage = null;
24133
+ this._currentAssistantToolCalls = [];
24134
+ this._pendingToolResults = [];
24135
+ }
24136
+ /**
24137
+ * User-initiated abort (RUN_ERROR / ABORTED).
24138
+ *
24139
+ * Drops the in-progress step's reasoning blocks. A block gets its encrypted
24140
+ * signature on REASONING_ENCRYPTED_VALUE, which arrives after
24141
+ * REASONING_MESSAGE_END — so a mid-stream abort can leave signature-less
24142
+ * blocks. Persisting those would corrupt the next turn. Reasoning from
24143
+ * already-completed prior steps lives on STEP_FINISHED-flushed assistant
24144
+ * messages and is untouched. Aborted-step messages therefore never carry
24145
+ * reasoningParts.
24146
+ */
24147
+ finalizeAbortedRun() {
24148
+ this._currentReasoningBlocks = [];
24149
+ this._currentReasoningBlockText = "";
24150
+ if (!this._currentAssistantMessage) return;
24151
+ if (this._currentAssistantToolCalls.length > 0) {
24152
+ this._messages.push({
24153
+ id: this._currentAssistantMessage.id || v4_default(),
24154
+ role: "assistant",
24155
+ content: this._currentMessageContent || "",
24156
+ toolCalls: [...this._currentAssistantToolCalls]
24157
+ });
24158
+ this._messages.push(...this._pendingToolResults);
24159
+ const respondedIds = new Set(
24160
+ this._pendingToolResults.map((m) => "toolCallId" in m ? m.toolCallId : void 0).filter((id) => typeof id === "string")
24161
+ );
24162
+ for (const tc of this._currentAssistantToolCalls) {
24163
+ if (!respondedIds.has(tc.id)) {
24164
+ this._messages.push({
24165
+ id: v4_default(),
24166
+ role: "tool",
24167
+ content: JSON.stringify({ aborted: true, reason: "Cancelled by user before tool finished" }),
24168
+ toolCallId: tc.id
24169
+ });
24170
+ }
24171
+ }
24172
+ this._currentMessageContent = "";
24173
+ } else if (this._currentMessageContent) {
24174
+ this._messages.push({
24175
+ id: this._currentAssistantMessage.id || v4_default(),
24176
+ role: "assistant",
24177
+ content: this._currentMessageContent
24178
+ });
24179
+ }
24180
+ this._currentAssistantMessage = null;
24181
+ this._currentAssistantToolCalls = [];
24182
+ this._pendingToolResults = [];
24183
+ }
24025
24184
  /**
24026
24185
  * Sends a tool approval response back to the server.
24027
24186
  *
@@ -24116,6 +24275,12 @@ var UseAIClient = class {
24116
24275
  get currentMessageContent() {
24117
24276
  return this._currentMessageContent;
24118
24277
  }
24278
+ /**
24279
+ * Gets the runId of the in-flight run, or null when no run is active.
24280
+ */
24281
+ get currentRunId() {
24282
+ return this._currentRunId;
24283
+ }
24119
24284
  /**
24120
24285
  * Gets the current reasoning blocks collected during the current run.
24121
24286
  */
@@ -38348,7 +38513,7 @@ import { useState as useState7, useCallback as useCallback5, useRef as useRef5,
38348
38513
 
38349
38514
  // src/utils/messageConversion.ts
38350
38515
  function transformMessagesToClientFormat(persistedMessages) {
38351
- return persistedMessages.map((msg) => {
38516
+ return persistedMessages.filter((msg) => msg.displayMode !== "info").map((msg) => {
38352
38517
  switch (msg.role) {
38353
38518
  case "tool":
38354
38519
  return {
@@ -39321,6 +39486,7 @@ function useServerEvents({
39321
39486
  const loadingRef = useRef10(loading);
39322
39487
  loadingRef.current = loading;
39323
39488
  const messageCountAtRunStartRef = useRef10(0);
39489
+ const runIdAtRunStartRef = useRef10(void 0);
39324
39490
  const hasTextFromPriorStepRef = useRef10(false);
39325
39491
  const [executingToolRaw, setExecutingTool] = useState13(null);
39326
39492
  const executingToolFallbackRef = useRef10(null);
@@ -39333,11 +39499,37 @@ function useServerEvents({
39333
39499
  saveAIResponseRef.current = saveAIResponse;
39334
39500
  const stringsRef = useRef10(strings);
39335
39501
  stringsRef.current = strings;
39502
+ const persistFinalResponse = useCallback11(async (client, opts) => {
39503
+ const content3 = client.currentMessageContent;
39504
+ const reasoningParts = client.currentReasoningBlocks.length > 0 ? [...client.currentReasoningBlocks] : void 0;
39505
+ const turnMessages = extractTurnMessages(client.messages, messageCountAtRunStartRef.current);
39506
+ if (opts.aborted) {
39507
+ const notice = stringsRef.current.notices.aborted;
39508
+ if (content3) {
39509
+ await saveAIResponseRef.current(content3, void 0, opts.traceId, turnMessages, reasoningParts);
39510
+ await saveAIResponseRef.current(notice, "info");
39511
+ } else {
39512
+ await saveAIResponseRef.current(notice, "info", opts.traceId, turnMessages);
39513
+ }
39514
+ return;
39515
+ }
39516
+ if (content3) {
39517
+ await saveAIResponseRef.current(content3, void 0, opts.traceId, turnMessages, reasoningParts);
39518
+ }
39519
+ }, []);
39520
+ const resetRunUiState = useCallback11(() => {
39521
+ setStreamingText("");
39522
+ setStreamingReasoning("");
39523
+ streamingChatIdRef.current = null;
39524
+ setExecutingTool(null);
39525
+ setLoading(false);
39526
+ }, []);
39336
39527
  const handleServerEvent = useCallback11(async (client, event) => {
39337
39528
  const ts = toolSystemRef.current;
39338
39529
  const strs = stringsRef.current;
39339
39530
  if (event.type === EventType.RUN_STARTED) {
39340
39531
  messageCountAtRunStartRef.current = client.messages.length;
39532
+ runIdAtRunStartRef.current = client.currentRunId ?? void 0;
39341
39533
  hasTextFromPriorStepRef.current = false;
39342
39534
  setStreamingReasoning("");
39343
39535
  } else if (event.type === EventType.REASONING_MESSAGE_START) {
@@ -39389,30 +39581,21 @@ function useServerEvents({
39389
39581
  setStreamingText((prev) => prev + contentEvent.delta);
39390
39582
  } else if (event.type === EventType.TEXT_MESSAGE_END) {
39391
39583
  } 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);
39584
+ const finishedEvent = event;
39585
+ await persistFinalResponse(client, { aborted: false, traceId: finishedEvent.runId });
39586
+ resetRunUiState();
39405
39587
  } else if (event.type === EventType.RUN_ERROR) {
39406
39588
  const errorEvent = event;
39407
39589
  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);
39590
+ if (errorCode === ErrorCode.ABORTED) {
39591
+ client.finalizeRun({ aborted: true });
39592
+ await persistFinalResponse(client, { aborted: true, traceId: runIdAtRunStartRef.current });
39593
+ } else {
39594
+ console.error("[ServerEvents] Run error:", errorCode);
39595
+ const userMessage = strs.errors[errorCode] || errorEvent.message || strs.errors[ErrorCode.UNKNOWN_ERROR];
39596
+ saveAIResponseRef.current(userMessage, "error");
39597
+ }
39598
+ resetRunUiState();
39416
39599
  }
39417
39600
  }, []);
39418
39601
  const handleDisconnect = useCallback11(() => {
@@ -39582,6 +39765,8 @@ var noOpContextValue = {
39582
39765
  },
39583
39766
  delete: async () => {
39584
39767
  }
39768
+ },
39769
+ abortRun: () => {
39585
39770
  }
39586
39771
  };
39587
39772
  var DEFAULT_FILE_UPLOAD_CONFIG = {
@@ -39715,6 +39900,9 @@ function UseAIProvider({
39715
39900
  console.error("Failed to register tools:", err);
39716
39901
  }
39717
39902
  }, [toolSystem.hasTools, toolSystem.aggregatedTools, connected]);
39903
+ const abortRun = useCallback13(() => {
39904
+ clientRef.current?.abortRun();
39905
+ }, []);
39718
39906
  const handleSendMessage = useCallback13(async (message, attachments, messageForwardedProps) => {
39719
39907
  if (!clientRef.current) return;
39720
39908
  serverEvents.clearStreamingText();
@@ -39813,7 +40001,8 @@ function UseAIProvider({
39813
40001
  save: saveCommand,
39814
40002
  rename: renameCommand,
39815
40003
  delete: deleteCommand
39816
- }
40004
+ },
40005
+ abortRun
39817
40006
  };
39818
40007
  const effectiveStreamingText = serverEvents.streamingChatIdRef.current === chatManagement.displayedChatId ? serverEvents.streamingText : "";
39819
40008
  const effectiveStreamingReasoning = serverEvents.streamingChatIdRef.current === chatManagement.displayedChatId ? serverEvents.streamingReasoning : "";
@@ -39821,6 +40010,7 @@ function UseAIProvider({
39821
40010
  connected,
39822
40011
  loading: serverEvents.loading,
39823
40012
  sendMessage: handleSendMessage,
40013
+ abortRun,
39824
40014
  messages,
39825
40015
  streamingText: effectiveStreamingText,
39826
40016
  streamingReasoning: effectiveStreamingReasoning,
@@ -39870,6 +40060,7 @@ function UseAIProvider({
39870
40060
  const hasCustomChat = CustomChat !== void 0 && CustomChat !== null;
39871
40061
  const chatPanelProps = {
39872
40062
  onSendMessage: handleSendMessage,
40063
+ onAbort: abortRun,
39873
40064
  messages,
39874
40065
  loading: serverEvents.loading,
39875
40066
  connected,
@@ -39917,6 +40108,7 @@ function UseAIProvider({
39917
40108
  isOpen: isChatOpen,
39918
40109
  onClose: () => handleSetChatOpen(false),
39919
40110
  onSendMessage: handleSendMessage,
40111
+ onAbort: abortRun,
39920
40112
  messages,
39921
40113
  loading: serverEvents.loading,
39922
40114
  connected,