@meetsmore-oss/use-ai-client 1.3.0 → 1.5.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/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  generateChatId,
3
3
  generateMessageId
4
- } from "./chunk-QTQR7MAU.js";
4
+ } from "./chunk-STF3H6F5.js";
5
5
 
6
6
  // src/useAI.ts
7
- import { useState as useState11, useEffect as useEffect10, useRef as useRef11, useCallback as useCallback10, useMemo as useMemo6 } from "react";
7
+ import { useState as useState12, useEffect as useEffect11, useRef as useRef12, useCallback as useCallback11, useMemo as useMemo6 } from "react";
8
8
 
9
9
  // src/providers/useAIProvider.tsx
10
- import { createContext as createContext4, useContext as useContext4, useState as useState10, useEffect as useEffect9, useCallback as useCallback9, useRef as useRef9 } from "react";
10
+ import { createContext as createContext4, useContext as useContext4, useState as useState11, useEffect as useEffect10, useCallback as useCallback10, useRef as useRef10 } from "react";
11
11
 
12
12
  // src/types.ts
13
13
  import { EventType, ErrorCode } from "@meetsmore-oss/use-ai-core";
@@ -51,7 +51,9 @@ var defaultStrings = {
51
51
  /** Input placeholder when connecting */
52
52
  connectingPlaceholder: "Connecting...",
53
53
  /** Loading indicator text */
54
- thinking: "Thinking"
54
+ thinking: "Thinking",
55
+ /** File processing indicator text (shown during file transformation like OCR) */
56
+ processingFile: "Processing file..."
55
57
  },
56
58
  // File upload
57
59
  fileUpload: {
@@ -102,6 +104,11 @@ var defaultStrings = {
102
104
  RATE_LIMITED: "Too many requests. Please wait a moment before trying again.",
103
105
  /** Error for unknown/unexpected errors */
104
106
  UNKNOWN_ERROR: "An unexpected error occurred. Please try again."
107
+ },
108
+ // Tool execution status
109
+ toolExecution: {
110
+ /** Fallback messages when no tool title is provided (one randomly selected) */
111
+ fallbackMessages: ["Working", "Processing", "Thinking"]
105
112
  }
106
113
  };
107
114
  var StringsContext = createContext(defaultStrings);
@@ -1244,7 +1251,6 @@ async function processAttachments(attachments, config) {
1244
1251
  const chat = await getCurrentChat();
1245
1252
  const context = { chat };
1246
1253
  for (const attachment of attachments) {
1247
- onFileProgress?.(attachment.id, { status: "processing" });
1248
1254
  try {
1249
1255
  if (attachment.transformedContent !== void 0) {
1250
1256
  contentParts.push({
@@ -1256,11 +1262,11 @@ async function processAttachments(attachments, config) {
1256
1262
  size: attachment.file.size
1257
1263
  }
1258
1264
  });
1259
- onFileProgress?.(attachment.id, { status: "done" });
1260
1265
  continue;
1261
1266
  }
1262
1267
  const transformer = findTransformer(attachment.file.type, transformers);
1263
1268
  if (transformer) {
1269
+ onFileProgress?.(attachment.id, { status: "processing" });
1264
1270
  const transformedText = await getTransformedContent(
1265
1271
  attachment.file,
1266
1272
  transformer,
@@ -1278,6 +1284,7 @@ async function processAttachments(attachments, config) {
1278
1284
  size: attachment.file.size
1279
1285
  }
1280
1286
  });
1287
+ onFileProgress?.(attachment.id, { status: "done" });
1281
1288
  } else {
1282
1289
  const url = await backend.prepareForSend(attachment.file);
1283
1290
  if (attachment.file.type.startsWith("image/")) {
@@ -1291,7 +1298,6 @@ async function processAttachments(attachments, config) {
1291
1298
  });
1292
1299
  }
1293
1300
  }
1294
- onFileProgress?.(attachment.id, { status: "done" });
1295
1301
  } catch (error) {
1296
1302
  onFileProgress?.(attachment.id, { status: "error" });
1297
1303
  throw error;
@@ -1570,6 +1576,71 @@ function useDropdownState(options = {}) {
1570
1576
 
1571
1577
  // src/components/UseAIChatPanel.tsx
1572
1578
  import { Fragment, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1579
+ function FeedbackButton({ type, isSelected, onClick, selectedColor, unselectedColor }) {
1580
+ const buttonRef = useRef4(null);
1581
+ const handleClick = () => {
1582
+ if (!isSelected && buttonRef.current) {
1583
+ buttonRef.current.style.transform = "scale(1.3)";
1584
+ setTimeout(() => {
1585
+ if (buttonRef.current) {
1586
+ buttonRef.current.style.transform = "scale(1)";
1587
+ }
1588
+ }, 150);
1589
+ }
1590
+ onClick();
1591
+ };
1592
+ const thumbsUpPath = "M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3";
1593
+ const thumbsDownPath = "M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17";
1594
+ return /* @__PURE__ */ jsx10(
1595
+ "button",
1596
+ {
1597
+ ref: buttonRef,
1598
+ "data-testid": `feedback-${type}`,
1599
+ onClick: handleClick,
1600
+ title: type === "upvote" ? "Good response" : "Poor response",
1601
+ style: {
1602
+ background: "transparent",
1603
+ border: "none",
1604
+ padding: "4px",
1605
+ cursor: "pointer",
1606
+ color: isSelected ? selectedColor : unselectedColor,
1607
+ opacity: isSelected ? 1 : 0.5,
1608
+ transition: "all 0.15s",
1609
+ display: "flex",
1610
+ alignItems: "center",
1611
+ justifyContent: "center",
1612
+ borderRadius: "4px",
1613
+ transform: "scale(1)"
1614
+ },
1615
+ onMouseEnter: (e) => {
1616
+ if (!isSelected) {
1617
+ e.currentTarget.style.opacity = "0.8";
1618
+ e.currentTarget.style.color = selectedColor;
1619
+ }
1620
+ },
1621
+ onMouseLeave: (e) => {
1622
+ if (!isSelected) {
1623
+ e.currentTarget.style.opacity = "0.5";
1624
+ e.currentTarget.style.color = unselectedColor;
1625
+ }
1626
+ },
1627
+ children: /* @__PURE__ */ jsx10(
1628
+ "svg",
1629
+ {
1630
+ width: "14",
1631
+ height: "14",
1632
+ viewBox: "0 0 24 24",
1633
+ fill: isSelected ? "currentColor" : "none",
1634
+ stroke: "currentColor",
1635
+ strokeWidth: "2",
1636
+ strokeLinecap: "round",
1637
+ strokeLinejoin: "round",
1638
+ children: /* @__PURE__ */ jsx10("path", { d: type === "upvote" ? thumbsUpPath : thumbsDownPath })
1639
+ }
1640
+ )
1641
+ }
1642
+ );
1643
+ }
1573
1644
  function getTextContent(content) {
1574
1645
  if (typeof content === "string") {
1575
1646
  return content;
@@ -1597,11 +1668,15 @@ function UseAIChatPanel({
1597
1668
  selectedAgent,
1598
1669
  onAgentChange,
1599
1670
  fileUploadConfig,
1671
+ fileProcessing,
1600
1672
  commands = [],
1601
1673
  onSaveCommand,
1602
1674
  onRenameCommand,
1603
1675
  onDeleteCommand,
1604
- closeButton
1676
+ closeButton,
1677
+ executingTool,
1678
+ feedbackEnabled,
1679
+ onFeedback
1605
1680
  }) {
1606
1681
  const strings = useStrings();
1607
1682
  const theme = useTheme();
@@ -2226,6 +2301,46 @@ function UseAIChatPanel({
2226
2301
  ]
2227
2302
  }
2228
2303
  ),
2304
+ message.role === "assistant" && message.traceId && feedbackEnabled && onFeedback && /* @__PURE__ */ jsxs7(
2305
+ "div",
2306
+ {
2307
+ "data-testid": "feedback-buttons",
2308
+ style: {
2309
+ display: "flex",
2310
+ gap: "4px",
2311
+ marginTop: "4px",
2312
+ padding: "0 4px"
2313
+ },
2314
+ children: [
2315
+ /* @__PURE__ */ jsx10(
2316
+ FeedbackButton,
2317
+ {
2318
+ type: "upvote",
2319
+ isSelected: message.feedback === "upvote",
2320
+ onClick: () => {
2321
+ const newFeedback = message.feedback === "upvote" ? null : "upvote";
2322
+ onFeedback(message.id, message.traceId, newFeedback);
2323
+ },
2324
+ selectedColor: theme.primaryColor,
2325
+ unselectedColor: theme.secondaryTextColor
2326
+ }
2327
+ ),
2328
+ /* @__PURE__ */ jsx10(
2329
+ FeedbackButton,
2330
+ {
2331
+ type: "downvote",
2332
+ isSelected: message.feedback === "downvote",
2333
+ onClick: () => {
2334
+ const newFeedback = message.feedback === "downvote" ? null : "downvote";
2335
+ onFeedback(message.id, message.traceId, newFeedback);
2336
+ },
2337
+ selectedColor: theme.errorTextColor,
2338
+ unselectedColor: theme.secondaryTextColor
2339
+ }
2340
+ )
2341
+ ]
2342
+ }
2343
+ ),
2229
2344
  /* @__PURE__ */ jsx10(
2230
2345
  "div",
2231
2346
  {
@@ -2265,10 +2380,29 @@ function UseAIChatPanel({
2265
2380
  color: theme.textColor,
2266
2381
  maxWidth: "80%"
2267
2382
  },
2268
- children: streamingText ? /* @__PURE__ */ jsx10(MarkdownContent, { content: streamingText }) : /* @__PURE__ */ jsxs7(Fragment, { children: [
2269
- /* @__PURE__ */ jsx10("span", { style: { opacity: 0.6 }, children: strings.input.thinking }),
2270
- /* @__PURE__ */ jsx10("span", { className: "dots", style: { marginLeft: "4px" }, children: "..." })
2271
- ] })
2383
+ children: streamingText ? /* @__PURE__ */ jsx10(MarkdownContent, { content: streamingText }) : fileProcessing && fileProcessing.status === "processing" ? /* @__PURE__ */ jsxs7("div", { children: [
2384
+ /* @__PURE__ */ jsx10("span", { style: { opacity: 0.6 }, children: strings.input.processingFile }),
2385
+ fileProcessing.progress != null && /* @__PURE__ */ jsxs7(Fragment, { children: [
2386
+ /* @__PURE__ */ jsxs7("span", { style: { opacity: 0.6, marginLeft: "4px" }, children: [
2387
+ Math.round(fileProcessing.progress),
2388
+ "%"
2389
+ ] }),
2390
+ /* @__PURE__ */ jsx10("div", { style: {
2391
+ marginTop: "6px",
2392
+ height: "4px",
2393
+ borderRadius: "2px",
2394
+ background: theme.borderColor,
2395
+ overflow: "hidden"
2396
+ }, children: /* @__PURE__ */ jsx10("div", { style: {
2397
+ height: "100%",
2398
+ width: `${fileProcessing.progress}%`,
2399
+ borderRadius: "2px",
2400
+ background: theme.primaryColor,
2401
+ transition: "width 0.3s ease"
2402
+ } }) })
2403
+ ] }),
2404
+ fileProcessing.progress == null && /* @__PURE__ */ jsx10("span", { className: "dots", style: { marginLeft: "4px" }, children: "..." })
2405
+ ] }) : /* @__PURE__ */ jsx10("span", { className: "dots", style: { opacity: 0.6 }, children: "..." })
2272
2406
  }
2273
2407
  )
2274
2408
  }
@@ -2355,7 +2489,7 @@ function UseAIChatPanel({
2355
2489
  value: input,
2356
2490
  onChange: handleInputChange,
2357
2491
  onKeyDown: handleKeyDown,
2358
- placeholder: connected ? strings.input.placeholder : strings.input.connectingPlaceholder,
2492
+ placeholder: !connected ? strings.input.connectingPlaceholder : loading ? `${executingTool?.displayText ?? strings.input.thinking}...` : strings.input.placeholder,
2359
2493
  disabled: !connected || loading,
2360
2494
  rows: 1,
2361
2495
  style: {
@@ -2613,10 +2747,14 @@ function UseAIChat({ floating = false }) {
2613
2747
  selectedAgent: ctx.agents.selected,
2614
2748
  onAgentChange: ctx.agents.set,
2615
2749
  fileUploadConfig: ctx.fileUploadConfig,
2750
+ fileProcessing: ctx.fileProcessing,
2616
2751
  commands: ctx.commands.list,
2617
2752
  onSaveCommand: ctx.commands.save,
2618
2753
  onRenameCommand: ctx.commands.rename,
2619
- onDeleteCommand: ctx.commands.delete
2754
+ onDeleteCommand: ctx.commands.delete,
2755
+ executingTool: ctx.executingTool,
2756
+ feedbackEnabled: ctx.feedback?.enabled,
2757
+ onFeedback: ctx.feedback?.submit
2620
2758
  };
2621
2759
  if (floating) {
2622
2760
  return /* @__PURE__ */ jsx12(
@@ -2677,6 +2815,9 @@ var UseAIClient = class {
2677
2815
  _currentAssistantToolCalls = [];
2678
2816
  // Tool call assembly
2679
2817
  currentToolCalls = /* @__PURE__ */ new Map();
2818
+ // Feedback tracking
2819
+ _langfuseEnabled = false;
2820
+ langfuseConfigHandlers = /* @__PURE__ */ new Set();
2680
2821
  /**
2681
2822
  * Establishes a Socket.IO connection to the server.
2682
2823
  * Connection state changes are notified via onConnectionStateChange().
@@ -2719,6 +2860,11 @@ var UseAIClient = class {
2719
2860
  this._defaultAgent = data.defaultAgent;
2720
2861
  this.agentsChangeHandlers.forEach((handler) => handler(data.agents, data.defaultAgent));
2721
2862
  });
2863
+ this.socket.on("config", (data) => {
2864
+ console.log("[Client] Received server config:", data);
2865
+ this._langfuseEnabled = data.langfuseEnabled ?? false;
2866
+ this.langfuseConfigHandlers.forEach((handler) => handler(this._langfuseEnabled));
2867
+ });
2722
2868
  this.socket.on("connect_error", (error) => {
2723
2869
  console.warn("[UseAI] Connection error:", error.message);
2724
2870
  });
@@ -3135,6 +3281,41 @@ var UseAIClient = class {
3135
3281
  isConnected() {
3136
3282
  return this.socket !== null && this.socket.connected;
3137
3283
  }
3284
+ /**
3285
+ * Subscribes to Langfuse config changes.
3286
+ *
3287
+ * @param handler - Callback function receiving langfuse enabled status
3288
+ * @returns Cleanup function to unsubscribe
3289
+ */
3290
+ onLangfuseConfigChange(handler) {
3291
+ this.langfuseConfigHandlers.add(handler);
3292
+ handler(this._langfuseEnabled);
3293
+ return () => {
3294
+ this.langfuseConfigHandlers.delete(handler);
3295
+ };
3296
+ }
3297
+ /**
3298
+ * Submits feedback for an assistant message.
3299
+ * Sends feedback to the server, which forwards it to Langfuse.
3300
+ *
3301
+ * @param messageId - The client-side message ID
3302
+ * @param traceId - The Langfuse trace ID (runId from RUN_FINISHED)
3303
+ * @param feedback - 'upvote' for positive, 'downvote' for negative, null to remove
3304
+ */
3305
+ submitFeedback(messageId, traceId, feedback) {
3306
+ if (!this.socket?.connected) {
3307
+ console.warn("[UseAI] Cannot submit feedback: not connected");
3308
+ return;
3309
+ }
3310
+ if (!this._langfuseEnabled) {
3311
+ console.warn("[UseAI] Cannot submit feedback: Langfuse not enabled on server");
3312
+ return;
3313
+ }
3314
+ this.send({
3315
+ type: "message_feedback",
3316
+ data: { messageId, traceId, feedback }
3317
+ });
3318
+ }
3138
3319
  };
3139
3320
 
3140
3321
  // src/defineTool.ts
@@ -3174,8 +3355,8 @@ function defineTool(description, schemaOrFn, fnOrOptions, options) {
3174
3355
  description,
3175
3356
  parameters
3176
3357
  };
3177
- if (this._options.confirmationRequired) {
3178
- toolDef.confirmationRequired = true;
3358
+ if (this._options.annotations) {
3359
+ toolDef.annotations = this._options.annotations;
3179
3360
  }
3180
3361
  return toolDef;
3181
3362
  },
@@ -3373,7 +3554,9 @@ function transformMessagesToUI(storageMessages) {
3373
3554
  role: msg.role,
3374
3555
  content: msg.content,
3375
3556
  timestamp: msg.createdAt,
3376
- displayMode: msg.displayMode
3557
+ displayMode: msg.displayMode,
3558
+ traceId: msg.traceId,
3559
+ feedback: msg.feedback
3377
3560
  }));
3378
3561
  }
3379
3562
  function transformMessagesToClientFormat(uiMessages) {
@@ -3389,6 +3572,8 @@ function transformMessagesToClientFormat(uiMessages) {
3389
3572
  function useChatManagement({
3390
3573
  repository,
3391
3574
  clientRef,
3575
+ messages,
3576
+ setMessages,
3392
3577
  onSendMessage,
3393
3578
  setOpen,
3394
3579
  connected,
@@ -3396,7 +3581,6 @@ function useChatManagement({
3396
3581
  }) {
3397
3582
  const [currentChatId, setCurrentChatId] = useState5(null);
3398
3583
  const [pendingChatId, setPendingChatId] = useState5(null);
3399
- const [messages, setMessages] = useState5([]);
3400
3584
  const currentChatIdSnapshot = useRef5(null);
3401
3585
  const pendingChatIdSnapshot = useRef5(null);
3402
3586
  useEffect5(() => {
@@ -3515,7 +3699,7 @@ function useChatManagement({
3515
3699
  console.error("[ChatManagement] Chat not found:", chatId);
3516
3700
  return false;
3517
3701
  }
3518
- const { generateMessageId: generateMessageId2 } = await import("./types-64CH2HXY.js");
3702
+ const { generateMessageId: generateMessageId2 } = await import("./types-GWPQMSYT.js");
3519
3703
  chat.messages.push({
3520
3704
  id: generateMessageId2(),
3521
3705
  role: "user",
@@ -3537,7 +3721,7 @@ function useChatManagement({
3537
3721
  return false;
3538
3722
  }
3539
3723
  }, [repository, reloadMessages]);
3540
- const saveAIResponse = useCallback4(async (content, displayMode) => {
3724
+ const saveAIResponse = useCallback4(async (content, displayMode, traceId) => {
3541
3725
  const currentChatIdValue = currentChatIdSnapshot.current;
3542
3726
  const pendingChatIdValue = pendingChatIdSnapshot.current;
3543
3727
  const displayedChatId2 = pendingChatIdValue || currentChatIdValue;
@@ -3551,13 +3735,14 @@ function useChatManagement({
3551
3735
  console.error("[ChatManagement] Chat not found:", currentChatIdValue);
3552
3736
  return;
3553
3737
  }
3554
- const { generateMessageId: generateMessageId2 } = await import("./types-64CH2HXY.js");
3738
+ const { generateMessageId: generateMessageId2 } = await import("./types-GWPQMSYT.js");
3555
3739
  chat.messages.push({
3556
3740
  id: generateMessageId2(),
3557
3741
  role: "assistant",
3558
3742
  content,
3559
3743
  createdAt: /* @__PURE__ */ new Date(),
3560
- displayMode
3744
+ displayMode,
3745
+ traceId
3561
3746
  });
3562
3747
  if (!chat.title) {
3563
3748
  const firstUserMessage = chat.messages.find((msg) => msg.role === "user");
@@ -3675,7 +3860,6 @@ function useChatManagement({
3675
3860
  return {
3676
3861
  currentChatId,
3677
3862
  pendingChatId,
3678
- messages,
3679
3863
  displayedChatId,
3680
3864
  createNewChat,
3681
3865
  loadChat,
@@ -4069,6 +4253,68 @@ function usePromptState({
4069
4253
  };
4070
4254
  }
4071
4255
 
4256
+ // src/hooks/useFeedback.ts
4257
+ import { useState as useState10, useEffect as useEffect9, useRef as useRef9, useCallback as useCallback9 } from "react";
4258
+ function useFeedback({
4259
+ clientRef,
4260
+ repository,
4261
+ getDisplayedChatId,
4262
+ setMessages
4263
+ }) {
4264
+ const [enabled, setEnabled] = useState10(false);
4265
+ const enabledRef = useRef9(false);
4266
+ useEffect9(() => {
4267
+ enabledRef.current = enabled;
4268
+ }, [enabled]);
4269
+ useEffect9(() => {
4270
+ const client = clientRef.current;
4271
+ if (!client) return;
4272
+ const unsubscribe = client.onLangfuseConfigChange((isEnabled) => {
4273
+ setEnabled(isEnabled);
4274
+ });
4275
+ return unsubscribe;
4276
+ }, [clientRef.current]);
4277
+ const updateFeedbackInStorage = useCallback9(async (messageId, feedback) => {
4278
+ const displayedChatId = getDisplayedChatId();
4279
+ if (!displayedChatId) {
4280
+ console.warn("[useFeedback] No chat ID, cannot update feedback");
4281
+ return;
4282
+ }
4283
+ try {
4284
+ const chat = await repository.loadChat(displayedChatId);
4285
+ if (!chat) {
4286
+ console.error("[useFeedback] Chat not found:", displayedChatId);
4287
+ return;
4288
+ }
4289
+ const message = chat.messages.find((msg) => msg.id === messageId);
4290
+ if (message) {
4291
+ message.feedback = feedback;
4292
+ await repository.saveChat(chat);
4293
+ setMessages(
4294
+ (prevMessages) => prevMessages.map(
4295
+ (msg) => msg.id === messageId ? { ...msg, feedback } : msg
4296
+ )
4297
+ );
4298
+ } else {
4299
+ console.warn("[useFeedback] Message not found:", messageId);
4300
+ }
4301
+ } catch (error) {
4302
+ console.error("[useFeedback] Failed to update feedback:", error);
4303
+ }
4304
+ }, [repository, getDisplayedChatId, setMessages]);
4305
+ const submitFeedback = useCallback9((messageId, traceId, feedback) => {
4306
+ updateFeedbackInStorage(messageId, feedback);
4307
+ const client = clientRef.current;
4308
+ if (client && enabledRef.current) {
4309
+ client.submitFeedback(messageId, traceId, feedback);
4310
+ }
4311
+ }, [clientRef, updateFeedbackInStorage]);
4312
+ return {
4313
+ enabled,
4314
+ submitFeedback
4315
+ };
4316
+ }
4317
+
4072
4318
  // src/providers/useAIProvider.tsx
4073
4319
  import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
4074
4320
  var __UseAIContext = createContext4(null);
@@ -4150,20 +4396,24 @@ function UseAIProvider({
4150
4396
  const fileUploadConfig = fileUploadConfigProp === false ? void 0 : fileUploadConfigProp ?? DEFAULT_FILE_UPLOAD_CONFIG;
4151
4397
  const theme = { ...defaultTheme, ...customTheme };
4152
4398
  const strings = { ...defaultStrings, ...customStrings };
4153
- const [connected, setConnected] = useState10(false);
4154
- const [isChatOpen, setIsChatOpen] = useState10(false);
4155
- const [loading, setLoading] = useState10(false);
4156
- const handleSetChatOpen = useCallback9((open) => {
4399
+ const [connected, setConnected] = useState11(false);
4400
+ const [isChatOpen, setIsChatOpen] = useState11(false);
4401
+ const [loading, setLoading] = useState11(false);
4402
+ const [messages, setMessages] = useState11([]);
4403
+ const [fileProcessingState, setFileProcessingState] = useState11(null);
4404
+ const handleSetChatOpen = useCallback10((open) => {
4157
4405
  setIsChatOpen(open);
4158
4406
  onOpenChange?.(open);
4159
4407
  }, [onOpenChange]);
4160
- const [streamingText, setStreamingText] = useState10("");
4161
- const streamingChatIdRef = useRef9(null);
4162
- const clientRef = useRef9(null);
4163
- const repositoryRef = useRef9(
4408
+ const [streamingText, setStreamingText] = useState11("");
4409
+ const streamingChatIdRef = useRef10(null);
4410
+ const [executingTool, setExecutingTool] = useState11(null);
4411
+ const executingToolFallbackRef = useRef10(null);
4412
+ const clientRef = useRef10(null);
4413
+ const repositoryRef = useRef10(
4164
4414
  chatRepository || new LocalStorageChatRepository()
4165
4415
  );
4166
- const handleSendMessageRef = useRef9(null);
4416
+ const handleSendMessageRef = useRef10(null);
4167
4417
  const {
4168
4418
  registerTools,
4169
4419
  unregisterTools,
@@ -4185,7 +4435,7 @@ function UseAIProvider({
4185
4435
  clientRef,
4186
4436
  connected
4187
4437
  });
4188
- const stableSendMessage = useCallback9(async (message, attachments) => {
4438
+ const stableSendMessage = useCallback10(async (message, attachments) => {
4189
4439
  if (handleSendMessageRef.current) {
4190
4440
  await handleSendMessageRef.current(message, attachments);
4191
4441
  }
@@ -4193,6 +4443,8 @@ function UseAIProvider({
4193
4443
  const chatManagement = useChatManagement({
4194
4444
  repository: repositoryRef.current,
4195
4445
  clientRef,
4446
+ messages,
4447
+ setMessages,
4196
4448
  onSendMessage: stableSendMessage,
4197
4449
  setOpen: handleSetChatOpen,
4198
4450
  connected,
@@ -4201,7 +4453,6 @@ function UseAIProvider({
4201
4453
  const {
4202
4454
  currentChatId,
4203
4455
  pendingChatId,
4204
- messages,
4205
4456
  displayedChatId,
4206
4457
  createNewChat,
4207
4458
  loadChat,
@@ -4215,6 +4466,12 @@ function UseAIProvider({
4215
4466
  getCurrentChat,
4216
4467
  updateMetadata
4217
4468
  } = chatManagement;
4469
+ const feedback = useFeedback({
4470
+ clientRef,
4471
+ repository: repositoryRef.current,
4472
+ getDisplayedChatId: () => displayedChatId,
4473
+ setMessages
4474
+ });
4218
4475
  const {
4219
4476
  availableAgents,
4220
4477
  defaultAgent,
@@ -4228,7 +4485,7 @@ function UseAIProvider({
4228
4485
  renameCommand,
4229
4486
  deleteCommand
4230
4487
  } = useCommandManagement({ repository: commandRepository });
4231
- useEffect9(() => {
4488
+ useEffect10(() => {
4232
4489
  console.log("[UseAIProvider] Initializing client with serverUrl:", serverUrl);
4233
4490
  const client = new UseAIClient(serverUrl);
4234
4491
  if (mcpHeadersProvider) {
@@ -4241,9 +4498,19 @@ function UseAIProvider({
4241
4498
  console.log("[UseAIProvider] Connecting...");
4242
4499
  client.connect();
4243
4500
  const unsubscribe = client.onEvent("globalChat", async (event) => {
4244
- if (event.type === EventType.TOOL_CALL_END) {
4501
+ if (event.type === EventType.TOOL_CALL_START) {
4502
+ const e = event;
4503
+ const tool = aggregatedToolsRef.current[e.toolCallName];
4504
+ const title = e.annotations?.title ?? tool?._options?.annotations?.title ?? null;
4505
+ if (!title) {
4506
+ const fallbacks = strings.toolExecution.fallbackMessages;
4507
+ executingToolFallbackRef.current = fallbacks[Math.floor(Math.random() * fallbacks.length)];
4508
+ }
4509
+ setExecutingTool({ toolCallId: e.toolCallId, title });
4510
+ } else if (event.type === EventType.TOOL_CALL_END) {
4245
4511
  const toolCallEnd = event;
4246
4512
  const toolCallId = toolCallEnd.toolCallId;
4513
+ setExecutingTool((prev) => prev?.toolCallId === toolCallId ? null : prev);
4247
4514
  const toolCallData = client["currentToolCalls"].get(toolCallId);
4248
4515
  if (!toolCallData) {
4249
4516
  console.error(`[Provider] Tool call ${toolCallId} not found`);
@@ -4293,14 +4560,16 @@ function UseAIProvider({
4293
4560
  const contentEvent = event;
4294
4561
  setStreamingText((prev) => prev + contentEvent.delta);
4295
4562
  } else if (event.type === EventType.TEXT_MESSAGE_END) {
4563
+ setStreamingText("");
4564
+ streamingChatIdRef.current = null;
4565
+ } else if (event.type === EventType.RUN_FINISHED) {
4296
4566
  const content = client.currentMessageContent;
4297
4567
  if (content) {
4298
- console.log("[Provider] Received text message:", content.substring(0, 100));
4299
- saveAIResponse(content);
4300
- setStreamingText("");
4301
- streamingChatIdRef.current = null;
4302
- setLoading(false);
4568
+ const finishedEvent = event;
4569
+ const traceId = finishedEvent.runId;
4570
+ saveAIResponse(content, void 0, traceId);
4303
4571
  }
4572
+ setLoading(false);
4304
4573
  } else if (event.type === EventType.RUN_ERROR) {
4305
4574
  const errorEvent = event;
4306
4575
  const errorCode = errorEvent.message;
@@ -4319,15 +4588,15 @@ function UseAIProvider({
4319
4588
  client.disconnect();
4320
4589
  };
4321
4590
  }, [serverUrl]);
4322
- useEffect9(() => {
4591
+ useEffect10(() => {
4323
4592
  const client = clientRef.current;
4324
4593
  if (!client) return;
4325
4594
  if (mcpHeadersProvider) {
4326
4595
  client.setMcpHeadersProvider(mcpHeadersProvider);
4327
4596
  }
4328
4597
  }, [mcpHeadersProvider]);
4329
- const lastRegisteredToolsRef = useRef9("");
4330
- useEffect9(() => {
4598
+ const lastRegisteredToolsRef = useRef10("");
4599
+ useEffect10(() => {
4331
4600
  const client = clientRef.current;
4332
4601
  if (!client || !client.isConnected() || !hasTools) return;
4333
4602
  const toolKeys = Object.keys(aggregatedTools).sort().join(",");
@@ -4345,7 +4614,7 @@ function UseAIProvider({
4345
4614
  console.error("Failed to register tools:", err);
4346
4615
  }
4347
4616
  }, [hasTools, aggregatedTools, connected]);
4348
- const handleSendMessage = useCallback9(async (message, attachments) => {
4617
+ const handleSendMessage = useCallback10(async (message, attachments) => {
4349
4618
  if (!clientRef.current) return;
4350
4619
  setStreamingText("");
4351
4620
  const activatedChatId = activatePendingChat();
@@ -4369,21 +4638,36 @@ function UseAIProvider({
4369
4638
  });
4370
4639
  }
4371
4640
  persistedContent = persistedParts;
4372
- const fileContent = await processAttachments(attachments, {
4373
- getCurrentChat,
4374
- backend: fileUploadConfig?.backend,
4375
- transformers: fileUploadConfig?.transformers
4376
- });
4377
- multimodalContent = [];
4378
- if (message.trim()) {
4379
- multimodalContent.push({ type: "text", text: message });
4641
+ if (activeChatId) {
4642
+ await saveUserMessage(activeChatId, persistedContent);
4380
4643
  }
4381
- multimodalContent.push(...fileContent);
4382
- }
4383
- if (activeChatId) {
4384
- await saveUserMessage(activeChatId, persistedContent);
4644
+ setLoading(true);
4645
+ try {
4646
+ const fileContent = await processAttachments(attachments, {
4647
+ getCurrentChat,
4648
+ backend: fileUploadConfig?.backend,
4649
+ transformers: fileUploadConfig?.transformers,
4650
+ onFileProgress: (_fileId, state) => {
4651
+ setFileProcessingState(state);
4652
+ }
4653
+ });
4654
+ multimodalContent = [];
4655
+ if (message.trim()) {
4656
+ multimodalContent.push({ type: "text", text: message });
4657
+ }
4658
+ multimodalContent.push(...fileContent);
4659
+ } catch (error) {
4660
+ setLoading(false);
4661
+ throw error;
4662
+ } finally {
4663
+ setFileProcessingState(null);
4664
+ }
4665
+ } else {
4666
+ if (activeChatId) {
4667
+ await saveUserMessage(activeChatId, persistedContent);
4668
+ }
4669
+ setLoading(true);
4385
4670
  }
4386
- setLoading(true);
4387
4671
  await clientRef.current.sendPrompt(message, multimodalContent);
4388
4672
  }, [activatePendingChat, currentChatId, saveUserMessage, fileUploadConfig, getCurrentChat]);
4389
4673
  handleSendMessageRef.current = handleSendMessage;
@@ -4426,6 +4710,9 @@ function UseAIProvider({
4426
4710
  }
4427
4711
  };
4428
4712
  const effectiveStreamingText = streamingChatIdRef.current === displayedChatId ? streamingText : "";
4713
+ const executingToolDisplay = executingTool ? {
4714
+ displayText: executingTool.title ?? executingToolFallbackRef.current ?? strings.toolExecution.fallbackMessages[0]
4715
+ } : null;
4429
4716
  const chatUIContextValue = {
4430
4717
  connected,
4431
4718
  loading,
@@ -4434,6 +4721,7 @@ function UseAIProvider({
4434
4721
  streamingText: effectiveStreamingText,
4435
4722
  suggestions: aggregatedSuggestions,
4436
4723
  fileUploadConfig,
4724
+ fileProcessing: fileProcessingState,
4437
4725
  history: {
4438
4726
  currentId: displayedChatId,
4439
4727
  create: createNewChat,
@@ -4457,6 +4745,11 @@ function UseAIProvider({
4457
4745
  ui: {
4458
4746
  isOpen: isChatOpen,
4459
4747
  setOpen: handleSetChatOpen
4748
+ },
4749
+ executingTool: executingToolDisplay,
4750
+ feedback: {
4751
+ enabled: feedback.enabled,
4752
+ submit: feedback.submitFeedback
4460
4753
  }
4461
4754
  };
4462
4755
  const isUIDisabled = CustomButton === null || CustomChat === null;
@@ -4479,10 +4772,14 @@ function UseAIProvider({
4479
4772
  selectedAgent,
4480
4773
  onAgentChange: setAgent,
4481
4774
  fileUploadConfig,
4775
+ fileProcessing: fileProcessingState,
4482
4776
  commands,
4483
4777
  onSaveCommand: saveCommand,
4484
4778
  onRenameCommand: renameCommand,
4485
- onDeleteCommand: deleteCommand
4779
+ onDeleteCommand: deleteCommand,
4780
+ executingTool: executingToolDisplay,
4781
+ feedbackEnabled: feedback.enabled,
4782
+ onFeedback: feedback.submitFeedback
4486
4783
  };
4487
4784
  const renderDefaultChat = () => {
4488
4785
  if (isUIDisabled) return null;
@@ -4546,11 +4843,11 @@ function useAIContext() {
4546
4843
  }
4547
4844
 
4548
4845
  // src/hooks/useStableTools.ts
4549
- import { useRef as useRef10 } from "react";
4846
+ import { useRef as useRef11 } from "react";
4550
4847
  function useStableTools(tools) {
4551
- const latestToolsRef = useRef10({});
4552
- const stableToolsRef = useRef10({});
4553
- const prevToolNamesRef = useRef10("");
4848
+ const latestToolsRef = useRef11({});
4849
+ const stableToolsRef = useRef11({});
4850
+ const prevToolNamesRef = useRef11("");
4554
4851
  if (!tools) {
4555
4852
  latestToolsRef.current = {};
4556
4853
  return void 0;
@@ -4620,25 +4917,25 @@ function useAI(options = {}) {
4620
4917
  const { connected, tools, client, prompts } = useAIContext();
4621
4918
  const { register: registerTools, unregister: unregisterTools } = tools;
4622
4919
  const { update: updatePrompt, registerWaiter, unregisterWaiter } = prompts;
4623
- const [response, setResponse] = useState11(null);
4624
- const [loading, setLoading] = useState11(false);
4625
- const [error, setError] = useState11(null);
4626
- const hookId = useRef11(`useAI-${Math.random().toString(36).substr(2, 9)}`);
4627
- const toolsRef = useRef11({});
4628
- const componentRef = useRef11(null);
4629
- const promptChangeResolvers = useRef11([]);
4920
+ const [response, setResponse] = useState12(null);
4921
+ const [loading, setLoading] = useState12(false);
4922
+ const [error, setError] = useState12(null);
4923
+ const hookId = useRef12(`useAI-${Math.random().toString(36).substr(2, 9)}`);
4924
+ const toolsRef = useRef12({});
4925
+ const componentRef = useRef12(null);
4926
+ const promptChangeResolvers = useRef12([]);
4630
4927
  const stableTools = useStableTools(options.tools);
4631
4928
  const toolsKey = useMemo6(() => {
4632
4929
  if (!options.tools) return "";
4633
4930
  return Object.keys(options.tools).sort().join(",");
4634
4931
  }, [options.tools]);
4635
4932
  const memoizedSuggestions = useMemo6(() => options.suggestions, [options.suggestions]);
4636
- useEffect10(() => {
4933
+ useEffect11(() => {
4637
4934
  if (componentRef.current) {
4638
4935
  componentRef.current.setAttribute("data-useai-context", "true");
4639
4936
  }
4640
4937
  }, []);
4641
- const waitForPromptChange = useCallback10(() => {
4938
+ const waitForPromptChange = useCallback11(() => {
4642
4939
  return new Promise((resolve) => {
4643
4940
  const timeoutMs = 100;
4644
4941
  const timeoutId = setTimeout(() => {
@@ -4655,14 +4952,14 @@ function useAI(options = {}) {
4655
4952
  promptChangeResolvers.current.push(resolveAndCleanup);
4656
4953
  });
4657
4954
  }, []);
4658
- useEffect10(() => {
4955
+ useEffect11(() => {
4659
4956
  if (!enabled || options.invisible) return;
4660
4957
  registerWaiter(hookId.current, waitForPromptChange);
4661
4958
  return () => {
4662
4959
  unregisterWaiter(hookId.current);
4663
4960
  };
4664
4961
  }, [enabled, options.invisible, registerWaiter, unregisterWaiter, waitForPromptChange]);
4665
- useEffect10(() => {
4962
+ useEffect11(() => {
4666
4963
  if (!enabled) return;
4667
4964
  updatePrompt(hookId.current, options.prompt, memoizedSuggestions);
4668
4965
  if (promptChangeResolvers.current.length > 0) {
@@ -4670,15 +4967,15 @@ function useAI(options = {}) {
4670
4967
  promptChangeResolvers.current = [];
4671
4968
  }
4672
4969
  }, [enabled, options.prompt, memoizedSuggestions, updatePrompt]);
4673
- const updatePromptRef = useRef11(updatePrompt);
4970
+ const updatePromptRef = useRef12(updatePrompt);
4674
4971
  updatePromptRef.current = updatePrompt;
4675
- useEffect10(() => {
4972
+ useEffect11(() => {
4676
4973
  const id = hookId.current;
4677
4974
  return () => {
4678
4975
  updatePromptRef.current(id, void 0, void 0);
4679
4976
  };
4680
4977
  }, []);
4681
- useEffect10(() => {
4978
+ useEffect11(() => {
4682
4979
  if (!enabled) return;
4683
4980
  if (stableTools) {
4684
4981
  const componentId = options.id || componentRef.current?.id;
@@ -4692,7 +4989,7 @@ function useAI(options = {}) {
4692
4989
  }
4693
4990
  };
4694
4991
  }, [enabled, toolsKey, stableTools, options.id, options.invisible, registerTools, unregisterTools]);
4695
- useEffect10(() => {
4992
+ useEffect11(() => {
4696
4993
  if (!enabled || !client) return;
4697
4994
  const unsubscribe = client.onEvent(hookId.current, (event) => {
4698
4995
  handleAGUIEvent(event);
@@ -4701,7 +4998,7 @@ function useAI(options = {}) {
4701
4998
  unsubscribe();
4702
4999
  };
4703
5000
  }, [enabled, client]);
4704
- const handleAGUIEvent = useCallback10(async (event) => {
5001
+ const handleAGUIEvent = useCallback11(async (event) => {
4705
5002
  switch (event.type) {
4706
5003
  case EventType.TEXT_MESSAGE_END: {
4707
5004
  const content = client?.currentMessageContent;
@@ -4721,7 +5018,7 @@ function useAI(options = {}) {
4721
5018
  }
4722
5019
  }
4723
5020
  }, [client, options.onError]);
4724
- const generate = useCallback10(async (prompt) => {
5021
+ const generate = useCallback11(async (prompt) => {
4725
5022
  if (!enabled) {
4726
5023
  const error2 = new Error("AI features are disabled");
4727
5024
  setError(error2);
@@ -4757,17 +5054,17 @@ function useAI(options = {}) {
4757
5054
  }
4758
5055
 
4759
5056
  // src/useAIWorkflow.ts
4760
- import { useState as useState12, useCallback as useCallback11, useRef as useRef12, useEffect as useEffect11 } from "react";
5057
+ import { useState as useState13, useCallback as useCallback12, useRef as useRef13, useEffect as useEffect12 } from "react";
4761
5058
  import { EventType as EventType3 } from "@meetsmore-oss/use-ai-core";
4762
5059
  import { v4 as uuidv43 } from "uuid";
4763
5060
  function useAIWorkflow(runner, workflowId) {
4764
5061
  const { connected, client } = useAIContext();
4765
- const [status, setStatus] = useState12("idle");
4766
- const [text, setText] = useState12(null);
4767
- const [error, setError] = useState12(null);
4768
- const currentWorkflowRef = useRef12(null);
4769
- const eventListenerIdRef = useRef12(`useAIWorkflow-${Math.random().toString(36).substr(2, 9)}`);
4770
- const handleWorkflowEvent = useCallback11(async (event) => {
5062
+ const [status, setStatus] = useState13("idle");
5063
+ const [text, setText] = useState13(null);
5064
+ const [error, setError] = useState13(null);
5065
+ const currentWorkflowRef = useRef13(null);
5066
+ const eventListenerIdRef = useRef13(`useAIWorkflow-${Math.random().toString(36).substr(2, 9)}`);
5067
+ const handleWorkflowEvent = useCallback12(async (event) => {
4771
5068
  const currentWorkflow = currentWorkflowRef.current;
4772
5069
  if (!currentWorkflow) return;
4773
5070
  if (event.type === EventType3.RUN_STARTED) {
@@ -4852,14 +5149,14 @@ function useAIWorkflow(runner, workflowId) {
4852
5149
  }
4853
5150
  }
4854
5151
  }, [client]);
4855
- useEffect11(() => {
5152
+ useEffect12(() => {
4856
5153
  if (!client) return;
4857
5154
  const unsubscribe = client.onEvent(eventListenerIdRef.current, handleWorkflowEvent);
4858
5155
  return () => {
4859
5156
  unsubscribe();
4860
5157
  };
4861
5158
  }, [client, handleWorkflowEvent]);
4862
- const trigger = useCallback11(async (options) => {
5159
+ const trigger = useCallback12(async (options) => {
4863
5160
  if (!client?.isConnected()) {
4864
5161
  const err = new Error("Not connected to server");
4865
5162
  setError(err);
@@ -4948,6 +5245,7 @@ export {
4948
5245
  useChatManagement,
4949
5246
  useCommandManagement,
4950
5247
  useDropdownState,
5248
+ useFeedback,
4951
5249
  useFileUpload,
4952
5250
  usePromptState,
4953
5251
  useSlashCommands,