@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/index.js CHANGED
@@ -99,6 +99,11 @@ var defaultStrings = {
99
99
  /** Error for unknown/unexpected errors */
100
100
  UNKNOWN_ERROR: "An unexpected error occurred. Please try again."
101
101
  },
102
+ // Non-error notices shown as system-style bubbles
103
+ notices: {
104
+ /** Shown as a separate bubble after the user aborts generation */
105
+ aborted: "Generation stopped. You can continue the conversation."
106
+ },
102
107
  // Thinking/reasoning display
103
108
  thinking: {
104
109
  /** Label shown while thinking is in progress */
@@ -157,6 +162,8 @@ var defaultTheme = {
157
162
  activeBackground: "#f0f0ff",
158
163
  /** Disabled button background */
159
164
  buttonDisabledBackground: "#e5e7eb",
165
+ /** Stop (abort) button background — neutral so it doesn't read as a primary action */
166
+ stopButtonBackground: "#e5e7eb",
160
167
  // Text colors
161
168
  /** Primary text color */
162
169
  textColor: "#1f2937",
@@ -301,10 +308,29 @@ function mergeAssistantMessagesForDisplay(messages) {
301
308
  let pendingTexts = [];
302
309
  let pendingIds = [];
303
310
  let pendingReasoningParts = [];
311
+ const flushPending = () => {
312
+ if (pendingTexts.length > 0 || pendingReasoningParts.length > 0) {
313
+ result.push({
314
+ id: `merged-${pendingIds.join("-")}`,
315
+ role: "assistant",
316
+ content: pendingTexts.join("\n\n"),
317
+ createdAt: /* @__PURE__ */ new Date(),
318
+ ...pendingReasoningParts.length > 0 ? { reasoningParts: pendingReasoningParts } : {}
319
+ });
320
+ pendingTexts = [];
321
+ pendingIds = [];
322
+ pendingReasoningParts = [];
323
+ }
324
+ };
304
325
  for (const msg of messages) {
305
326
  if (msg.role === "tool") {
306
327
  continue;
307
328
  }
329
+ if (msg.displayMode === "info") {
330
+ flushPending();
331
+ result.push(msg);
332
+ continue;
333
+ }
308
334
  if (msg.role === "assistant") {
309
335
  const text = getTextFromContent(msg.content);
310
336
  if (msg.toolCalls && msg.toolCalls.length > 0) {
@@ -329,30 +355,11 @@ function mergeAssistantMessagesForDisplay(messages) {
329
355
  pendingReasoningParts = [];
330
356
  }
331
357
  } else {
332
- if (pendingTexts.length > 0 || pendingReasoningParts.length > 0) {
333
- result.push({
334
- id: `merged-${pendingIds.join("-")}`,
335
- role: "assistant",
336
- content: pendingTexts.join("\n\n"),
337
- createdAt: /* @__PURE__ */ new Date(),
338
- ...pendingReasoningParts.length > 0 ? { reasoningParts: pendingReasoningParts } : {}
339
- });
340
- pendingTexts = [];
341
- pendingIds = [];
342
- pendingReasoningParts = [];
343
- }
358
+ flushPending();
344
359
  result.push(msg);
345
360
  }
346
361
  }
347
- if (pendingTexts.length > 0 || pendingReasoningParts.length > 0) {
348
- result.push({
349
- id: `merged-${pendingIds.join("-")}`,
350
- role: "assistant",
351
- content: pendingTexts.join("\n\n"),
352
- createdAt: /* @__PURE__ */ new Date(),
353
- ...pendingReasoningParts.length > 0 ? { reasoningParts: pendingReasoningParts } : {}
354
- });
355
- }
362
+ flushPending();
356
363
  return result;
357
364
  }
358
365
 
@@ -2161,6 +2168,7 @@ function hasFileContent(content) {
2161
2168
  }
2162
2169
  function UseAIChatPanel({
2163
2170
  onSendMessage,
2171
+ onAbort,
2164
2172
  messages,
2165
2173
  loading,
2166
2174
  connected,
@@ -2718,177 +2726,207 @@ function UseAIChatPanel({
2718
2726
  ]
2719
2727
  }
2720
2728
  ),
2721
- displayMessages.map((message) => /* @__PURE__ */ jsxs9(
2722
- "div",
2723
- {
2724
- "data-testid": `chat-message-${message.role}`,
2725
- className: `chat-message chat-message-${message.role}`,
2726
- style: {
2727
- display: "flex",
2728
- flexDirection: "column",
2729
- alignItems: message.role === "user" ? "flex-end" : "flex-start"
2730
- },
2731
- onMouseEnter: () => message.role === "user" && setHoveredMessageId(message.id),
2732
- onMouseLeave: () => setHoveredMessageId(null),
2733
- children: [
2734
- /* @__PURE__ */ jsxs9(
2735
- "div",
2736
- {
2737
- style: {
2738
- position: "relative",
2739
- maxWidth: "80%"
2740
- },
2741
- children: [
2742
- message.role === "user" && hoveredMessageId === message.id && onSaveCommand && !slashCommands.isSavingCommand(message.id) && /* @__PURE__ */ jsx12(
2743
- "button",
2744
- {
2745
- "data-testid": "save-command-button",
2746
- onClick: (e) => {
2747
- e.stopPropagation();
2748
- const messageText = getDisplayTextFromContent(message.content);
2749
- slashCommands.startSavingCommand(message.id, messageText);
2750
- },
2751
- title: "Save as slash command",
2752
- style: {
2753
- position: "absolute",
2754
- top: "-8px",
2755
- right: "-8px",
2756
- width: "24px",
2757
- height: "24px",
2758
- borderRadius: "50%",
2759
- border: "none",
2760
- background: theme.backgroundColor,
2761
- boxShadow: "0 2px 6px rgba(0, 0, 0, 0.15)",
2762
- cursor: "pointer",
2763
- display: "flex",
2764
- alignItems: "center",
2765
- justifyContent: "center",
2766
- color: theme.primaryColor,
2767
- transition: "all 0.15s",
2768
- zIndex: 10
2769
- },
2770
- onMouseEnter: (e) => {
2771
- e.currentTarget.style.transform = "scale(1.1)";
2772
- e.currentTarget.style.boxShadow = "0 3px 8px rgba(0, 0, 0, 0.2)";
2773
- },
2774
- onMouseLeave: (e) => {
2775
- e.currentTarget.style.transform = "scale(1)";
2776
- e.currentTarget.style.boxShadow = "0 2px 6px rgba(0, 0, 0, 0.15)";
2777
- },
2778
- children: /* @__PURE__ */ jsxs9("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2779
- /* @__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" }),
2780
- /* @__PURE__ */ jsx12("polyline", { points: "17 21 17 13 7 13 7 21" }),
2781
- /* @__PURE__ */ jsx12("polyline", { points: "7 3 7 8 15 8" })
2782
- ] })
2783
- }
2784
- ),
2785
- /* @__PURE__ */ jsxs9(
2786
- "div",
2787
- {
2788
- "data-testid": "chat-message-content",
2789
- className: `chat-message-content${message.role === "assistant" ? " markdown-content" : ""}`,
2790
- style: {
2791
- padding: "10px 14px",
2792
- borderRadius: slashCommands.isSavingCommand(message.id) ? "12px 12px 0 0" : "12px",
2793
- background: message.displayMode === "error" ? theme.errorBackground : message.role === "user" ? theme.primaryGradient : theme.assistantMessageBackground,
2794
- color: message.displayMode === "error" ? theme.errorTextColor : message.role === "user" ? "white" : theme.textColor,
2795
- fontSize: "14px",
2796
- lineHeight: "1.5",
2797
- wordWrap: "break-word"
2798
- },
2799
- children: [
2800
- 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(
2801
- FilePlaceholder,
2802
- {
2803
- name: part.file.name,
2804
- size: part.file.size
2805
- },
2806
- idx
2807
- )) }),
2808
- message.role === "assistant" ? /* @__PURE__ */ jsxs9(Fragment, { children: [
2809
- message.reasoningParts && message.reasoningParts.length > 0 && /* @__PURE__ */ jsx12(
2810
- Reasoning,
2729
+ displayMessages.map((message) => {
2730
+ if (message.displayMode === "info") {
2731
+ return /* @__PURE__ */ jsx12(
2732
+ "div",
2733
+ {
2734
+ "data-testid": "chat-message-info",
2735
+ className: "chat-message chat-message-info",
2736
+ style: { display: "flex", justifyContent: "center", padding: "2px 0" },
2737
+ children: /* @__PURE__ */ jsx12(
2738
+ "div",
2739
+ {
2740
+ style: {
2741
+ maxWidth: "80%",
2742
+ padding: "6px 12px",
2743
+ borderRadius: "12px",
2744
+ background: theme.assistantMessageBackground,
2745
+ color: theme.secondaryTextColor,
2746
+ fontSize: "12px",
2747
+ lineHeight: "1.4",
2748
+ textAlign: "center",
2749
+ wordWrap: "break-word"
2750
+ },
2751
+ children: getDisplayTextFromContent(message.content)
2752
+ }
2753
+ )
2754
+ },
2755
+ message.id
2756
+ );
2757
+ }
2758
+ return /* @__PURE__ */ jsxs9(
2759
+ "div",
2760
+ {
2761
+ "data-testid": `chat-message-${message.role}`,
2762
+ className: `chat-message chat-message-${message.role}`,
2763
+ style: {
2764
+ display: "flex",
2765
+ flexDirection: "column",
2766
+ alignItems: message.role === "user" ? "flex-end" : "flex-start"
2767
+ },
2768
+ onMouseEnter: () => message.role === "user" && setHoveredMessageId(message.id),
2769
+ onMouseLeave: () => setHoveredMessageId(null),
2770
+ children: [
2771
+ /* @__PURE__ */ jsxs9(
2772
+ "div",
2773
+ {
2774
+ style: {
2775
+ position: "relative",
2776
+ maxWidth: "80%"
2777
+ },
2778
+ children: [
2779
+ message.role === "user" && hoveredMessageId === message.id && onSaveCommand && !slashCommands.isSavingCommand(message.id) && /* @__PURE__ */ jsx12(
2780
+ "button",
2781
+ {
2782
+ "data-testid": "save-command-button",
2783
+ onClick: (e) => {
2784
+ e.stopPropagation();
2785
+ const messageText = getDisplayTextFromContent(message.content);
2786
+ slashCommands.startSavingCommand(message.id, messageText);
2787
+ },
2788
+ title: "Save as slash command",
2789
+ style: {
2790
+ position: "absolute",
2791
+ top: "-8px",
2792
+ right: "-8px",
2793
+ width: "24px",
2794
+ height: "24px",
2795
+ borderRadius: "50%",
2796
+ border: "none",
2797
+ background: theme.backgroundColor,
2798
+ boxShadow: "0 2px 6px rgba(0, 0, 0, 0.15)",
2799
+ cursor: "pointer",
2800
+ display: "flex",
2801
+ alignItems: "center",
2802
+ justifyContent: "center",
2803
+ color: theme.primaryColor,
2804
+ transition: "all 0.15s",
2805
+ zIndex: 10
2806
+ },
2807
+ onMouseEnter: (e) => {
2808
+ e.currentTarget.style.transform = "scale(1.1)";
2809
+ e.currentTarget.style.boxShadow = "0 3px 8px rgba(0, 0, 0, 0.2)";
2810
+ },
2811
+ onMouseLeave: (e) => {
2812
+ e.currentTarget.style.transform = "scale(1)";
2813
+ e.currentTarget.style.boxShadow = "0 2px 6px rgba(0, 0, 0, 0.15)";
2814
+ },
2815
+ children: /* @__PURE__ */ jsxs9("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2816
+ /* @__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" }),
2817
+ /* @__PURE__ */ jsx12("polyline", { points: "17 21 17 13 7 13 7 21" }),
2818
+ /* @__PURE__ */ jsx12("polyline", { points: "7 3 7 8 15 8" })
2819
+ ] })
2820
+ }
2821
+ ),
2822
+ /* @__PURE__ */ jsxs9(
2823
+ "div",
2824
+ {
2825
+ "data-testid": "chat-message-content",
2826
+ className: `chat-message-content${message.role === "assistant" ? " markdown-content" : ""}`,
2827
+ style: {
2828
+ padding: "10px 14px",
2829
+ borderRadius: slashCommands.isSavingCommand(message.id) ? "12px 12px 0 0" : "12px",
2830
+ background: message.displayMode === "error" ? theme.errorBackground : message.role === "user" ? theme.primaryGradient : theme.assistantMessageBackground,
2831
+ color: message.displayMode === "error" ? theme.errorTextColor : message.role === "user" ? "white" : theme.textColor,
2832
+ fontSize: "14px",
2833
+ lineHeight: "1.5",
2834
+ wordWrap: "break-word"
2835
+ },
2836
+ children: [
2837
+ 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(
2838
+ FilePlaceholder,
2811
2839
  {
2812
- reasoningParts: message.reasoningParts,
2813
- theme,
2814
- strings
2815
- }
2816
- ),
2817
- /* @__PURE__ */ jsx12(MarkdownContent, { content: getTextFromContent(message.content) })
2818
- ] }) : (
2819
- // User/tool bubbles: display-only text so transformed_file
2820
- // (e.g. OCR body) isn't dumped into the chat bubble.
2821
- getDisplayTextFromContent(message.content)
2822
- )
2823
- ]
2824
- }
2825
- ),
2826
- slashCommands.renderInlineSaveUI({
2827
- messageId: message.id,
2828
- messageText: getDisplayTextFromContent(message.content)
2840
+ name: part.file.name,
2841
+ size: part.file.size
2842
+ },
2843
+ idx
2844
+ )) }),
2845
+ message.role === "assistant" ? /* @__PURE__ */ jsxs9(Fragment, { children: [
2846
+ message.reasoningParts && message.reasoningParts.length > 0 && /* @__PURE__ */ jsx12(
2847
+ Reasoning,
2848
+ {
2849
+ reasoningParts: message.reasoningParts,
2850
+ theme,
2851
+ strings
2852
+ }
2853
+ ),
2854
+ /* @__PURE__ */ jsx12(MarkdownContent, { content: getTextFromContent(message.content) })
2855
+ ] }) : (
2856
+ // User/tool bubbles: display-only text so transformed_file
2857
+ // (e.g. OCR body) isn't dumped into the chat bubble.
2858
+ getDisplayTextFromContent(message.content)
2859
+ )
2860
+ ]
2861
+ }
2862
+ ),
2863
+ slashCommands.renderInlineSaveUI({
2864
+ messageId: message.id,
2865
+ messageText: getDisplayTextFromContent(message.content)
2866
+ })
2867
+ ]
2868
+ }
2869
+ ),
2870
+ message.role === "assistant" && message.traceId && feedbackEnabled && onFeedback && /* @__PURE__ */ jsxs9(
2871
+ "div",
2872
+ {
2873
+ "data-testid": "feedback-buttons",
2874
+ style: {
2875
+ display: "flex",
2876
+ gap: "4px",
2877
+ marginTop: "4px",
2878
+ padding: "0 4px"
2879
+ },
2880
+ children: [
2881
+ /* @__PURE__ */ jsx12(
2882
+ FeedbackButton,
2883
+ {
2884
+ type: "upvote",
2885
+ isSelected: message.feedback === "upvote",
2886
+ onClick: () => {
2887
+ const newFeedback = message.feedback === "upvote" ? null : "upvote";
2888
+ onFeedback(message.id, message.traceId, newFeedback);
2889
+ },
2890
+ selectedColor: theme.primaryColor,
2891
+ unselectedColor: theme.secondaryTextColor
2892
+ }
2893
+ ),
2894
+ /* @__PURE__ */ jsx12(
2895
+ FeedbackButton,
2896
+ {
2897
+ type: "downvote",
2898
+ isSelected: message.feedback === "downvote",
2899
+ onClick: () => {
2900
+ const newFeedback = message.feedback === "downvote" ? null : "downvote";
2901
+ onFeedback(message.id, message.traceId, newFeedback);
2902
+ },
2903
+ selectedColor: theme.errorTextColor,
2904
+ unselectedColor: theme.secondaryTextColor
2905
+ }
2906
+ )
2907
+ ]
2908
+ }
2909
+ ),
2910
+ /* @__PURE__ */ jsx12(
2911
+ "div",
2912
+ {
2913
+ style: {
2914
+ fontSize: "11px",
2915
+ color: theme.secondaryTextColor,
2916
+ marginTop: "4px",
2917
+ padding: "0 4px"
2918
+ },
2919
+ children: message.createdAt.toLocaleTimeString([], {
2920
+ hour: "2-digit",
2921
+ minute: "2-digit"
2829
2922
  })
2830
- ]
2831
- }
2832
- ),
2833
- message.role === "assistant" && message.traceId && feedbackEnabled && onFeedback && /* @__PURE__ */ jsxs9(
2834
- "div",
2835
- {
2836
- "data-testid": "feedback-buttons",
2837
- style: {
2838
- display: "flex",
2839
- gap: "4px",
2840
- marginTop: "4px",
2841
- padding: "0 4px"
2842
- },
2843
- children: [
2844
- /* @__PURE__ */ jsx12(
2845
- FeedbackButton,
2846
- {
2847
- type: "upvote",
2848
- isSelected: message.feedback === "upvote",
2849
- onClick: () => {
2850
- const newFeedback = message.feedback === "upvote" ? null : "upvote";
2851
- onFeedback(message.id, message.traceId, newFeedback);
2852
- },
2853
- selectedColor: theme.primaryColor,
2854
- unselectedColor: theme.secondaryTextColor
2855
- }
2856
- ),
2857
- /* @__PURE__ */ jsx12(
2858
- FeedbackButton,
2859
- {
2860
- type: "downvote",
2861
- isSelected: message.feedback === "downvote",
2862
- onClick: () => {
2863
- const newFeedback = message.feedback === "downvote" ? null : "downvote";
2864
- onFeedback(message.id, message.traceId, newFeedback);
2865
- },
2866
- selectedColor: theme.errorTextColor,
2867
- unselectedColor: theme.secondaryTextColor
2868
- }
2869
- )
2870
- ]
2871
- }
2872
- ),
2873
- /* @__PURE__ */ jsx12(
2874
- "div",
2875
- {
2876
- style: {
2877
- fontSize: "11px",
2878
- color: theme.secondaryTextColor,
2879
- marginTop: "4px",
2880
- padding: "0 4px"
2881
- },
2882
- children: message.createdAt.toLocaleTimeString([], {
2883
- hour: "2-digit",
2884
- minute: "2-digit"
2885
- })
2886
- }
2887
- )
2888
- ]
2889
- },
2890
- message.id
2891
- )),
2923
+ }
2924
+ )
2925
+ ]
2926
+ },
2927
+ message.id
2928
+ );
2929
+ }),
2892
2930
  loading && /* @__PURE__ */ jsx12(
2893
2931
  "div",
2894
2932
  {
@@ -3113,33 +3151,65 @@ function UseAIChatPanel({
3113
3151
  ] })
3114
3152
  }
3115
3153
  ) }),
3116
- /* @__PURE__ */ jsx12(
3117
- "button",
3118
- {
3119
- "data-testid": "chat-send-button",
3120
- className: "chat-send-button",
3121
- onClick: handleSend,
3122
- disabled: !connected || loading || pendingApprovals.length > 0 || !input.trim() && attachments.length === 0,
3123
- style: {
3124
- padding: "6px",
3125
- background: connected && !loading && pendingApprovals.length === 0 && (input.trim() || attachments.length > 0) ? theme.primaryGradient : theme.buttonDisabledBackground,
3126
- color: connected && !loading && pendingApprovals.length === 0 && (input.trim() || attachments.length > 0) ? "white" : theme.secondaryTextColor,
3127
- border: "none",
3128
- borderRadius: "50%",
3129
- cursor: connected && !loading && pendingApprovals.length === 0 && (input.trim() || attachments.length > 0) ? "pointer" : "not-allowed",
3130
- display: "flex",
3131
- alignItems: "center",
3132
- justifyContent: "center",
3133
- width: "32px",
3134
- height: "32px",
3135
- transition: "all 0.2s"
3136
- },
3137
- 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: [
3138
- /* @__PURE__ */ jsx12("line", { x1: "12", y1: "19", x2: "12", y2: "5" }),
3139
- /* @__PURE__ */ jsx12("polyline", { points: "5 12 12 5 19 12" })
3140
- ] })
3154
+ (() => {
3155
+ const canAbort = loading && !!onAbort;
3156
+ const canSend = connected && !loading && pendingApprovals.length === 0 && (input.trim() || attachments.length > 0);
3157
+ if (loading && onAbort) {
3158
+ return /* @__PURE__ */ jsx12(
3159
+ "button",
3160
+ {
3161
+ "data-testid": "chat-stop-button",
3162
+ className: "chat-stop-button",
3163
+ onClick: onAbort,
3164
+ disabled: !canAbort,
3165
+ title: canAbort ? "Stop generating" : "Cannot stop while a tool is running",
3166
+ "aria-label": "Stop generating",
3167
+ style: {
3168
+ padding: "6px",
3169
+ background: canAbort ? theme.stopButtonBackground : theme.buttonDisabledBackground,
3170
+ color: theme.secondaryTextColor,
3171
+ border: "none",
3172
+ borderRadius: "50%",
3173
+ cursor: canAbort ? "pointer" : "not-allowed",
3174
+ display: "flex",
3175
+ alignItems: "center",
3176
+ justifyContent: "center",
3177
+ width: "32px",
3178
+ height: "32px",
3179
+ transition: "all 0.2s"
3180
+ },
3181
+ 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" }) })
3182
+ }
3183
+ );
3141
3184
  }
3142
- )
3185
+ return /* @__PURE__ */ jsx12(
3186
+ "button",
3187
+ {
3188
+ "data-testid": "chat-send-button",
3189
+ className: "chat-send-button",
3190
+ onClick: handleSend,
3191
+ disabled: !canSend,
3192
+ style: {
3193
+ padding: "6px",
3194
+ background: canSend ? theme.primaryGradient : theme.buttonDisabledBackground,
3195
+ color: canSend ? "white" : theme.secondaryTextColor,
3196
+ border: "none",
3197
+ borderRadius: "50%",
3198
+ cursor: canSend ? "pointer" : "not-allowed",
3199
+ display: "flex",
3200
+ alignItems: "center",
3201
+ justifyContent: "center",
3202
+ width: "32px",
3203
+ height: "32px",
3204
+ transition: "all 0.2s"
3205
+ },
3206
+ 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: [
3207
+ /* @__PURE__ */ jsx12("line", { x1: "12", y1: "19", x2: "12", y2: "5" }),
3208
+ /* @__PURE__ */ jsx12("polyline", { points: "5 12 12 5 19 12" })
3209
+ ] })
3210
+ }
3211
+ );
3212
+ })()
3143
3213
  ]
3144
3214
  }
3145
3215
  )
@@ -3317,7 +3387,8 @@ function UseAIChat({ floating = false, submitMode }) {
3317
3387
  onFeedback: ctx.feedback?.submit,
3318
3388
  pendingApprovals: ctx.tools.pending.tools,
3319
3389
  onApproveToolCall: ctx.tools.pending.tools.length > 0 ? ctx.tools.pending.approveAll : void 0,
3320
- onRejectToolCall: ctx.tools.pending.tools.length > 0 ? ctx.tools.pending.rejectAll : void 0
3390
+ onRejectToolCall: ctx.tools.pending.tools.length > 0 ? ctx.tools.pending.rejectAll : void 0,
3391
+ onAbort: ctx.abortRun
3321
3392
  };
3322
3393
  if (floating) {
3323
3394
  return /* @__PURE__ */ jsx14(
@@ -3364,6 +3435,9 @@ var UseAIClient = class {
3364
3435
  _tools = [];
3365
3436
  _messages = [];
3366
3437
  _state = null;
3438
+ // Tracks the in-flight run so abortRun() can target it. Set by sendPrompt
3439
+ // and cleared at RUN_FINISHED / RUN_ERROR.
3440
+ _currentRunId = null;
3367
3441
  // Agent selection
3368
3442
  _availableAgents = [];
3369
3443
  _defaultAgent = null;
@@ -3453,6 +3527,7 @@ var UseAIClient = class {
3453
3527
  this._pendingToolResults = [];
3454
3528
  this._currentReasoningBlocks = [];
3455
3529
  this._currentReasoningBlockText = "";
3530
+ this._currentMessageContent = "";
3456
3531
  }
3457
3532
  if (event.type === EventType.TEXT_MESSAGE_START) {
3458
3533
  const e = event;
@@ -3541,40 +3616,13 @@ var UseAIClient = class {
3541
3616
  this._currentAssistantToolCalls = [];
3542
3617
  this._pendingToolResults = [];
3543
3618
  this._currentReasoningBlocks = [];
3619
+ this._currentMessageContent = "";
3544
3620
  }
3545
3621
  } else if (event.type === EventType.RUN_FINISHED) {
3546
- if (this._currentAssistantMessage) {
3547
- const reasoningParts = this._currentReasoningBlocks.length > 0 ? [...this._currentReasoningBlocks] : void 0;
3548
- if (this._currentAssistantToolCalls.length > 0) {
3549
- const toolCallMessage = {
3550
- id: uuidv42(),
3551
- role: "assistant",
3552
- content: "",
3553
- toolCalls: this._currentAssistantToolCalls,
3554
- ...reasoningParts ? { reasoningParts } : {}
3555
- };
3556
- this._messages.push(toolCallMessage);
3557
- this._messages.push(...this._pendingToolResults);
3558
- const textMessage = {
3559
- id: this._currentAssistantMessage.id,
3560
- role: "assistant",
3561
- content: this._currentAssistantMessage.content || ""
3562
- };
3563
- this._messages.push(textMessage);
3564
- } else {
3565
- const assistantMessage = {
3566
- id: this._currentAssistantMessage.id,
3567
- role: "assistant",
3568
- content: this._currentAssistantMessage.content || "",
3569
- ...reasoningParts ? { reasoningParts } : {}
3570
- };
3571
- this._messages.push(assistantMessage);
3572
- }
3573
- this._currentAssistantMessage = null;
3574
- this._currentAssistantToolCalls = [];
3575
- this._pendingToolResults = [];
3576
- this._currentReasoningBlockText = "";
3577
- }
3622
+ this.finalizeRun({ aborted: false });
3623
+ }
3624
+ if (event.type === EventType.RUN_FINISHED || event.type === EventType.RUN_ERROR) {
3625
+ this._currentRunId = null;
3578
3626
  }
3579
3627
  this.eventHandlers.forEach((handler) => handler(event));
3580
3628
  }
@@ -3638,10 +3686,12 @@ var UseAIClient = class {
3638
3686
  // Type cast needed for Message type compatibility
3639
3687
  };
3640
3688
  this._messages.push(userMessage);
3689
+ const runId = uuidv42();
3690
+ this._currentRunId = runId;
3641
3691
  const runInput = {
3642
3692
  threadId: this.threadId,
3643
3693
  // Use getter to ensure non-null
3644
- runId: uuidv42(),
3694
+ runId,
3645
3695
  messages: this._messages,
3646
3696
  tools: this._tools.map((t) => ({
3647
3697
  name: t.name,
@@ -3696,6 +3746,115 @@ var UseAIClient = class {
3696
3746
  this._pendingToolResults.push(toolResultMsg);
3697
3747
  this.send(toolResultMessage);
3698
3748
  }
3749
+ /**
3750
+ * Aborts the in-flight run, if any.
3751
+ * Sends an `abort_run` message to the server which cancels the AI stream
3752
+ * and rejects any pending tool/approval waits. The server then emits
3753
+ * `RUN_ERROR` with `ErrorCode.ABORTED`, which the client handles by
3754
+ * persisting the partial response.
3755
+ *
3756
+ * No-op when no run is in flight.
3757
+ */
3758
+ abortRun() {
3759
+ const runId = this._currentRunId;
3760
+ if (!runId) return;
3761
+ this.send({
3762
+ type: "abort_run",
3763
+ data: { runId }
3764
+ });
3765
+ }
3766
+ /**
3767
+ * Flushes the final in-progress step into `_messages` when a run terminates.
3768
+ *
3769
+ * Only two terminations reach here. Truncation (maxOutputTokens / finish
3770
+ * reason 'length') and tool-execution errors never do — the server absorbs
3771
+ * them: truncation continues via a fallback step and ends as a normal
3772
+ * RUN_FINISHED, and tool errors come back as tool_results that keep the run
3773
+ * going. So the dispatch is binary:
3774
+ * - `aborted: false` (RUN_FINISHED): every tool-call step was already flushed
3775
+ * at STEP_FINISHED, so the in-progress step is always text-only.
3776
+ * - `aborted: true` (RUN_ERROR / ABORTED): the run may have been cut
3777
+ * mid-tool-call or mid-reasoning, so extra repair is needed.
3778
+ *
3779
+ * After this returns, `currentMessageContent` and `currentReasoningBlocks`
3780
+ * remain readable for the persistence helper. The next RUN_STARTED clears them.
3781
+ */
3782
+ finalizeRun(opts) {
3783
+ if (opts.aborted) {
3784
+ this.finalizeAbortedRun();
3785
+ } else {
3786
+ this.finalizeCompletedRun();
3787
+ }
3788
+ }
3789
+ /**
3790
+ * Normal completion (RUN_FINISHED). The in-progress step is text-only —
3791
+ * tool-call steps were already flushed at STEP_FINISHED — so just push the
3792
+ * trailing assistant text with its reasoning.
3793
+ */
3794
+ finalizeCompletedRun() {
3795
+ this._currentReasoningBlockText = "";
3796
+ if (!this._currentAssistantMessage) return;
3797
+ if (this._currentMessageContent) {
3798
+ const stepReasoning = this._currentReasoningBlocks.length > 0 ? [...this._currentReasoningBlocks] : void 0;
3799
+ this._messages.push({
3800
+ id: this._currentAssistantMessage.id || uuidv42(),
3801
+ role: "assistant",
3802
+ content: this._currentMessageContent,
3803
+ ...stepReasoning ? { reasoningParts: stepReasoning } : {}
3804
+ });
3805
+ }
3806
+ this._currentAssistantMessage = null;
3807
+ this._currentAssistantToolCalls = [];
3808
+ this._pendingToolResults = [];
3809
+ }
3810
+ /**
3811
+ * User-initiated abort (RUN_ERROR / ABORTED).
3812
+ *
3813
+ * Drops the in-progress step's reasoning blocks. A block gets its encrypted
3814
+ * signature on REASONING_ENCRYPTED_VALUE, which arrives after
3815
+ * REASONING_MESSAGE_END — so a mid-stream abort can leave signature-less
3816
+ * blocks. Persisting those would corrupt the next turn. Reasoning from
3817
+ * already-completed prior steps lives on STEP_FINISHED-flushed assistant
3818
+ * messages and is untouched. Aborted-step messages therefore never carry
3819
+ * reasoningParts.
3820
+ */
3821
+ finalizeAbortedRun() {
3822
+ this._currentReasoningBlocks = [];
3823
+ this._currentReasoningBlockText = "";
3824
+ if (!this._currentAssistantMessage) return;
3825
+ if (this._currentAssistantToolCalls.length > 0) {
3826
+ this._messages.push({
3827
+ id: this._currentAssistantMessage.id || uuidv42(),
3828
+ role: "assistant",
3829
+ content: this._currentMessageContent || "",
3830
+ toolCalls: [...this._currentAssistantToolCalls]
3831
+ });
3832
+ this._messages.push(...this._pendingToolResults);
3833
+ const respondedIds = new Set(
3834
+ this._pendingToolResults.map((m) => "toolCallId" in m ? m.toolCallId : void 0).filter((id) => typeof id === "string")
3835
+ );
3836
+ for (const tc of this._currentAssistantToolCalls) {
3837
+ if (!respondedIds.has(tc.id)) {
3838
+ this._messages.push({
3839
+ id: uuidv42(),
3840
+ role: "tool",
3841
+ content: JSON.stringify({ aborted: true, reason: "Cancelled by user before tool finished" }),
3842
+ toolCallId: tc.id
3843
+ });
3844
+ }
3845
+ }
3846
+ this._currentMessageContent = "";
3847
+ } else if (this._currentMessageContent) {
3848
+ this._messages.push({
3849
+ id: this._currentAssistantMessage.id || uuidv42(),
3850
+ role: "assistant",
3851
+ content: this._currentMessageContent
3852
+ });
3853
+ }
3854
+ this._currentAssistantMessage = null;
3855
+ this._currentAssistantToolCalls = [];
3856
+ this._pendingToolResults = [];
3857
+ }
3699
3858
  /**
3700
3859
  * Sends a tool approval response back to the server.
3701
3860
  *
@@ -3790,6 +3949,12 @@ var UseAIClient = class {
3790
3949
  get currentMessageContent() {
3791
3950
  return this._currentMessageContent;
3792
3951
  }
3952
+ /**
3953
+ * Gets the runId of the in-flight run, or null when no run is active.
3954
+ */
3955
+ get currentRunId() {
3956
+ return this._currentRunId;
3957
+ }
3793
3958
  /**
3794
3959
  * Gets the current reasoning blocks collected during the current run.
3795
3960
  */
@@ -4255,7 +4420,7 @@ import { useState as useState7, useCallback as useCallback5, useRef as useRef5,
4255
4420
 
4256
4421
  // src/utils/messageConversion.ts
4257
4422
  function transformMessagesToClientFormat(persistedMessages) {
4258
- return persistedMessages.map((msg) => {
4423
+ return persistedMessages.filter((msg) => msg.displayMode !== "info").map((msg) => {
4259
4424
  switch (msg.role) {
4260
4425
  case "tool":
4261
4426
  return {
@@ -5233,6 +5398,7 @@ function useServerEvents({
5233
5398
  const loadingRef = useRef10(loading);
5234
5399
  loadingRef.current = loading;
5235
5400
  const messageCountAtRunStartRef = useRef10(0);
5401
+ const runIdAtRunStartRef = useRef10(void 0);
5236
5402
  const hasTextFromPriorStepRef = useRef10(false);
5237
5403
  const [executingToolRaw, setExecutingTool] = useState13(null);
5238
5404
  const executingToolFallbackRef = useRef10(null);
@@ -5245,11 +5411,37 @@ function useServerEvents({
5245
5411
  saveAIResponseRef.current = saveAIResponse;
5246
5412
  const stringsRef = useRef10(strings);
5247
5413
  stringsRef.current = strings;
5414
+ const persistFinalResponse = useCallback11(async (client, opts) => {
5415
+ const content = client.currentMessageContent;
5416
+ const reasoningParts = client.currentReasoningBlocks.length > 0 ? [...client.currentReasoningBlocks] : void 0;
5417
+ const turnMessages = extractTurnMessages(client.messages, messageCountAtRunStartRef.current);
5418
+ if (opts.aborted) {
5419
+ const notice = stringsRef.current.notices.aborted;
5420
+ if (content) {
5421
+ await saveAIResponseRef.current(content, void 0, opts.traceId, turnMessages, reasoningParts);
5422
+ await saveAIResponseRef.current(notice, "info");
5423
+ } else {
5424
+ await saveAIResponseRef.current(notice, "info", opts.traceId, turnMessages);
5425
+ }
5426
+ return;
5427
+ }
5428
+ if (content) {
5429
+ await saveAIResponseRef.current(content, void 0, opts.traceId, turnMessages, reasoningParts);
5430
+ }
5431
+ }, []);
5432
+ const resetRunUiState = useCallback11(() => {
5433
+ setStreamingText("");
5434
+ setStreamingReasoning("");
5435
+ streamingChatIdRef.current = null;
5436
+ setExecutingTool(null);
5437
+ setLoading(false);
5438
+ }, []);
5248
5439
  const handleServerEvent = useCallback11(async (client, event) => {
5249
5440
  const ts = toolSystemRef.current;
5250
5441
  const strs = stringsRef.current;
5251
5442
  if (event.type === EventType2.RUN_STARTED) {
5252
5443
  messageCountAtRunStartRef.current = client.messages.length;
5444
+ runIdAtRunStartRef.current = client.currentRunId ?? void 0;
5253
5445
  hasTextFromPriorStepRef.current = false;
5254
5446
  setStreamingReasoning("");
5255
5447
  } else if (event.type === EventType2.REASONING_MESSAGE_START) {
@@ -5301,30 +5493,21 @@ function useServerEvents({
5301
5493
  setStreamingText((prev) => prev + contentEvent.delta);
5302
5494
  } else if (event.type === EventType2.TEXT_MESSAGE_END) {
5303
5495
  } else if (event.type === EventType2.RUN_FINISHED) {
5304
- const content = client.currentMessageContent;
5305
- if (content) {
5306
- const finishedEvent = event;
5307
- const traceId = finishedEvent.runId;
5308
- const turnMessages = extractTurnMessages(client.messages, messageCountAtRunStartRef.current);
5309
- const reasoningParts = client.currentReasoningBlocks.length > 0 ? [...client.currentReasoningBlocks] : void 0;
5310
- saveAIResponseRef.current(content, void 0, traceId, turnMessages, reasoningParts);
5311
- }
5312
- setStreamingText("");
5313
- setStreamingReasoning("");
5314
- streamingChatIdRef.current = null;
5315
- setExecutingTool(null);
5316
- setLoading(false);
5496
+ const finishedEvent = event;
5497
+ await persistFinalResponse(client, { aborted: false, traceId: finishedEvent.runId });
5498
+ resetRunUiState();
5317
5499
  } else if (event.type === EventType2.RUN_ERROR) {
5318
5500
  const errorEvent = event;
5319
5501
  const errorCode = errorEvent.message;
5320
- console.error("[ServerEvents] Run error:", errorCode);
5321
- const userMessage = strs.errors[errorCode] || errorEvent.message || strs.errors[ErrorCode.UNKNOWN_ERROR];
5322
- saveAIResponseRef.current(userMessage, "error");
5323
- setStreamingText("");
5324
- setStreamingReasoning("");
5325
- streamingChatIdRef.current = null;
5326
- setExecutingTool(null);
5327
- setLoading(false);
5502
+ if (errorCode === ErrorCode.ABORTED) {
5503
+ client.finalizeRun({ aborted: true });
5504
+ await persistFinalResponse(client, { aborted: true, traceId: runIdAtRunStartRef.current });
5505
+ } else {
5506
+ console.error("[ServerEvents] Run error:", errorCode);
5507
+ const userMessage = strs.errors[errorCode] || errorEvent.message || strs.errors[ErrorCode.UNKNOWN_ERROR];
5508
+ saveAIResponseRef.current(userMessage, "error");
5509
+ }
5510
+ resetRunUiState();
5328
5511
  }
5329
5512
  }, []);
5330
5513
  const handleDisconnect = useCallback11(() => {
@@ -5494,6 +5677,8 @@ var noOpContextValue = {
5494
5677
  },
5495
5678
  delete: async () => {
5496
5679
  }
5680
+ },
5681
+ abortRun: () => {
5497
5682
  }
5498
5683
  };
5499
5684
  var DEFAULT_FILE_UPLOAD_CONFIG = {
@@ -5627,6 +5812,9 @@ function UseAIProvider({
5627
5812
  console.error("Failed to register tools:", err);
5628
5813
  }
5629
5814
  }, [toolSystem.hasTools, toolSystem.aggregatedTools, connected]);
5815
+ const abortRun = useCallback13(() => {
5816
+ clientRef.current?.abortRun();
5817
+ }, []);
5630
5818
  const handleSendMessage = useCallback13(async (message, attachments, messageForwardedProps) => {
5631
5819
  if (!clientRef.current) return;
5632
5820
  serverEvents.clearStreamingText();
@@ -5725,7 +5913,8 @@ function UseAIProvider({
5725
5913
  save: saveCommand,
5726
5914
  rename: renameCommand,
5727
5915
  delete: deleteCommand
5728
- }
5916
+ },
5917
+ abortRun
5729
5918
  };
5730
5919
  const effectiveStreamingText = serverEvents.streamingChatIdRef.current === chatManagement.displayedChatId ? serverEvents.streamingText : "";
5731
5920
  const effectiveStreamingReasoning = serverEvents.streamingChatIdRef.current === chatManagement.displayedChatId ? serverEvents.streamingReasoning : "";
@@ -5733,6 +5922,7 @@ function UseAIProvider({
5733
5922
  connected,
5734
5923
  loading: serverEvents.loading,
5735
5924
  sendMessage: handleSendMessage,
5925
+ abortRun,
5736
5926
  messages,
5737
5927
  streamingText: effectiveStreamingText,
5738
5928
  streamingReasoning: effectiveStreamingReasoning,
@@ -5782,6 +5972,7 @@ function UseAIProvider({
5782
5972
  const hasCustomChat = CustomChat !== void 0 && CustomChat !== null;
5783
5973
  const chatPanelProps = {
5784
5974
  onSendMessage: handleSendMessage,
5975
+ onAbort: abortRun,
5785
5976
  messages,
5786
5977
  loading: serverEvents.loading,
5787
5978
  connected,
@@ -5829,6 +6020,7 @@ function UseAIProvider({
5829
6020
  isOpen: isChatOpen,
5830
6021
  onClose: () => handleSetChatOpen(false),
5831
6022
  onSendMessage: handleSendMessage,
6023
+ onAbort: abortRun,
5832
6024
  messages,
5833
6025
  loading: serverEvents.loading,
5834
6026
  connected,