@usecrow/ui 0.1.50 → 0.1.52

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.cjs CHANGED
@@ -70,6 +70,7 @@ function useChat({
70
70
  persistAnonymousConversations,
71
71
  welcomeMessage,
72
72
  selectedModel: initialSelectedModel,
73
+ toolConsentSettings,
73
74
  onVerificationStatus,
74
75
  onConversationId,
75
76
  onWorkflowEvent,
@@ -94,6 +95,8 @@ function useChat({
94
95
  const abortControllerRef = React3.useRef(null);
95
96
  const hasCheckedPersistRef = React3.useRef(false);
96
97
  const streamingToolCallsRef = React3.useRef([]);
98
+ const toolConsentSettingsRef = React3.useRef(toolConsentSettings);
99
+ toolConsentSettingsRef.current = toolConsentSettings;
97
100
  React3.useEffect(() => {
98
101
  if (initialSelectedModel) {
99
102
  setSelectedModel((prev) => prev !== initialSelectedModel ? initialSelectedModel : prev);
@@ -345,26 +348,43 @@ function useChat({
345
348
  }
346
349
  break;
347
350
  case "client_tool_call":
348
- onToolCall?.({
349
- type: "start",
350
- toolName: parsed.tool_name,
351
- arguments: parsed.arguments
352
- });
353
- const clientToolCall = {
354
- id: parsed.tool_call_id || `tool-${Date.now()}`,
355
- name: parsed.tool_name,
356
- displayName: parsed.display_name || void 0,
357
- arguments: parsed.arguments || {},
358
- status: "executing",
359
- timestamp: /* @__PURE__ */ new Date()
360
- };
361
- streamingToolCallsRef.current = [...streamingToolCallsRef.current, clientToolCall];
362
- setActiveToolCalls((prev) => [...prev, clientToolCall]);
363
- pendingClientTools.push({
364
- toolName: parsed.tool_name,
365
- toolCallId: parsed.tool_call_id,
366
- arguments: parsed.arguments
367
- });
351
+ {
352
+ const needsConsent = toolConsentSettingsRef.current?.[parsed.tool_name]?.requires_consent === true;
353
+ onToolCall?.({
354
+ type: "start",
355
+ toolName: parsed.tool_name,
356
+ arguments: parsed.arguments
357
+ });
358
+ if (needsConsent) {
359
+ const consentClientTc = {
360
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
361
+ name: parsed.tool_name,
362
+ displayName: parsed.display_name || void 0,
363
+ arguments: parsed.arguments || {},
364
+ status: "awaiting_consent",
365
+ requiresConsent: true,
366
+ timestamp: /* @__PURE__ */ new Date()
367
+ };
368
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, consentClientTc];
369
+ setActiveToolCalls((prev) => [...prev, consentClientTc]);
370
+ } else {
371
+ const clientToolCall = {
372
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
373
+ name: parsed.tool_name,
374
+ displayName: parsed.display_name || void 0,
375
+ arguments: parsed.arguments || {},
376
+ status: "executing",
377
+ timestamp: /* @__PURE__ */ new Date()
378
+ };
379
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, clientToolCall];
380
+ setActiveToolCalls((prev) => [...prev, clientToolCall]);
381
+ pendingClientTools.push({
382
+ toolName: parsed.tool_name,
383
+ toolCallId: parsed.tool_call_id,
384
+ arguments: parsed.arguments
385
+ });
386
+ }
387
+ }
368
388
  break;
369
389
  case "tool_consent_required":
370
390
  onToolCall?.({
@@ -746,21 +766,36 @@ function useChat({
746
766
  break;
747
767
  case "client_tool_call":
748
768
  {
749
- const toolCallEntry = {
750
- id: parsed.tool_call_id || `tool-${Date.now()}`,
751
- name: parsed.tool_name,
752
- displayName: parsed.display_name || void 0,
753
- arguments: parsed.arguments || {},
754
- status: "executing",
755
- timestamp: /* @__PURE__ */ new Date()
756
- };
757
- streamingToolCallsRef.current = [...streamingToolCallsRef.current, toolCallEntry];
758
- setActiveToolCalls((prev) => [...prev, toolCallEntry]);
759
- pendingClientTools.push({
760
- toolName: parsed.tool_name,
761
- toolCallId: parsed.tool_call_id,
762
- arguments: parsed.arguments
763
- });
769
+ const needsConsent2 = toolConsentSettingsRef.current?.[parsed.tool_name]?.requires_consent === true;
770
+ if (needsConsent2) {
771
+ const consentEntry2 = {
772
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
773
+ name: parsed.tool_name,
774
+ displayName: parsed.display_name || void 0,
775
+ arguments: parsed.arguments || {},
776
+ status: "awaiting_consent",
777
+ requiresConsent: true,
778
+ timestamp: /* @__PURE__ */ new Date()
779
+ };
780
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, consentEntry2];
781
+ setActiveToolCalls((prev) => [...prev, consentEntry2]);
782
+ } else {
783
+ const toolCallEntry = {
784
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
785
+ name: parsed.tool_name,
786
+ displayName: parsed.display_name || void 0,
787
+ arguments: parsed.arguments || {},
788
+ status: "executing",
789
+ timestamp: /* @__PURE__ */ new Date()
790
+ };
791
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, toolCallEntry];
792
+ setActiveToolCalls((prev) => [...prev, toolCallEntry]);
793
+ pendingClientTools.push({
794
+ toolName: parsed.tool_name,
795
+ toolCallId: parsed.tool_call_id,
796
+ arguments: parsed.arguments
797
+ });
798
+ }
764
799
  }
765
800
  break;
766
801
  case "tool_consent_required":
@@ -1527,6 +1562,9 @@ function useWidgetStyles({
1527
1562
  const [initialSuggestions, setInitialSuggestions] = React3.useState(
1528
1563
  styleCache.get(key)?.initialSuggestions || []
1529
1564
  );
1565
+ const [toolConsentSettings, setToolConsentSettings] = React3.useState(
1566
+ styleCache.get(key)?.toolConsentSettings || {}
1567
+ );
1530
1568
  const [agentName, setAgentName] = React3.useState(
1531
1569
  styleCache.get(key)?.agentName || "Assistant"
1532
1570
  );
@@ -1567,6 +1605,7 @@ function useWidgetStyles({
1567
1605
  setWelcomeMessage(config.welcomeMessage ?? void 0);
1568
1606
  setSelectedModel(config.model ?? void 0);
1569
1607
  setInitialSuggestions(config.initialSuggestions || []);
1608
+ setToolConsentSettings(config.toolConsentSettings || {});
1570
1609
  } catch (err) {
1571
1610
  console.error("[CrowWidget] Failed to fetch styles:", err);
1572
1611
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -1605,6 +1644,7 @@ function useWidgetStyles({
1605
1644
  welcomeMessage,
1606
1645
  selectedModel,
1607
1646
  initialSuggestions,
1647
+ toolConsentSettings,
1608
1648
  refetch: fetchStyles
1609
1649
  };
1610
1650
  }
@@ -1640,6 +1680,9 @@ function useCopilotStyles({
1640
1680
  const [selectedModel, setSelectedModel] = React3.useState(
1641
1681
  styleCache.get(key)?.model ?? void 0
1642
1682
  );
1683
+ const [toolConsentSettings, setToolConsentSettings] = React3.useState(
1684
+ styleCache.get(key)?.toolConsentSettings || {}
1685
+ );
1643
1686
  const hasFetchedRef = React3.useRef(false);
1644
1687
  const fetchStyles = async () => {
1645
1688
  if (skip) return;
@@ -1656,6 +1699,7 @@ function useCopilotStyles({
1656
1699
  setPersistAnonymousConversations(config.persistAnonymousConversations ?? true);
1657
1700
  setWelcomeMessage(config.welcomeMessage ?? void 0);
1658
1701
  setSelectedModel(config.model ?? void 0);
1702
+ setToolConsentSettings(config.toolConsentSettings || {});
1659
1703
  } catch (err) {
1660
1704
  console.error("[CrowCopilot] Failed to fetch styles:", err);
1661
1705
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -1693,6 +1737,7 @@ function useCopilotStyles({
1693
1737
  persistAnonymousConversations,
1694
1738
  welcomeMessage,
1695
1739
  selectedModel,
1740
+ toolConsentSettings,
1696
1741
  refetch: fetchStyles
1697
1742
  };
1698
1743
  }
@@ -1937,7 +1982,6 @@ function WidgetHeader({
1937
1982
  onNewChat,
1938
1983
  onToggleHistory,
1939
1984
  showMinimize = false,
1940
- isMinimized = false,
1941
1985
  onToggleMinimize
1942
1986
  }) {
1943
1987
  const { agentName, styles } = useWidgetStyleContext();
@@ -1959,7 +2003,7 @@ function WidgetHeader({
1959
2003
  }
1960
2004
  ) }),
1961
2005
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "crow-flex crow-items-center crow-gap-1", children: [
1962
- /* @__PURE__ */ jsxRuntime.jsx(
2006
+ isVerifiedUser ? /* @__PURE__ */ jsxRuntime.jsx(
1963
2007
  "button",
1964
2008
  {
1965
2009
  onClick: onNewChat,
@@ -1968,6 +2012,15 @@ function WidgetHeader({
1968
2012
  title: "New Chat",
1969
2013
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: 18, className: "crow-text-gray-700" })
1970
2014
  }
2015
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2016
+ "button",
2017
+ {
2018
+ onClick: onNewChat,
2019
+ className: "crow-p-1.5 hover:crow-bg-gray-200 crow-rounded crow-transition-colors",
2020
+ "aria-label": "Restart Chat",
2021
+ title: "Restart Chat",
2022
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { size: 16, className: "crow-text-gray-700" })
2023
+ }
1971
2024
  ),
1972
2025
  isVerifiedUser && /* @__PURE__ */ jsxRuntime.jsx(
1973
2026
  "button",
@@ -1983,9 +2036,10 @@ function WidgetHeader({
1983
2036
  "button",
1984
2037
  {
1985
2038
  onClick: onToggleMinimize,
1986
- className: "crow-p-1 hover:crow-bg-gray-200 crow-rounded crow-transition-colors",
1987
- "aria-label": isMinimized ? "Expand" : "Minimize",
1988
- children: isMinimized ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { size: 18, className: "crow-text-gray-900" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: 18, className: "crow-text-gray-900" })
2039
+ className: "crow-p-1.5 hover:crow-bg-gray-200 crow-rounded crow-transition-colors",
2040
+ "aria-label": "Close chat",
2041
+ title: "Close chat",
2042
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 18, className: "crow-text-gray-700" })
1989
2043
  }
1990
2044
  )
1991
2045
  ] })
@@ -3348,7 +3402,8 @@ function CrowWidget({
3348
3402
  persistAnonymousConversations,
3349
3403
  welcomeMessage: welcomeMessageFromAPI,
3350
3404
  selectedModel: selectedModelFromAPI,
3351
- initialSuggestions
3405
+ initialSuggestions,
3406
+ toolConsentSettings
3352
3407
  } = useWidgetStyles({
3353
3408
  productId,
3354
3409
  apiUrl,
@@ -3391,6 +3446,7 @@ function CrowWidget({
3391
3446
  persistAnonymousConversations,
3392
3447
  welcomeMessage,
3393
3448
  selectedModel,
3449
+ toolConsentSettings,
3394
3450
  onVerificationStatus: (isVerified) => {
3395
3451
  setIsVerifiedUser(isVerified);
3396
3452
  },
@@ -3693,14 +3749,47 @@ function CrowWidget({
3693
3749
  const handleToolConsent = async (toolCallId, approved) => {
3694
3750
  const toolCall = chat.activeToolCalls.find((tc) => tc.id === toolCallId) || chat.messages.flatMap((m) => m.toolCalls || []).find((tc) => tc.id === toolCallId);
3695
3751
  if (!toolCall) return;
3752
+ const isClientSide = !toolCall.serverSideExecution;
3696
3753
  if (approved) {
3697
3754
  chat.updateToolCallStatus(toolCallId, "executing");
3698
- if (submitToolResultRef.current) {
3699
- await submitToolResultRef.current(
3700
- toolCallId,
3701
- toolCall.name,
3702
- { consent_approved: true, tool_arguments: toolCall.arguments || {} }
3703
- );
3755
+ if (isClientSide) {
3756
+ try {
3757
+ const result = await executeClientToolRef.current?.(
3758
+ toolCall.name,
3759
+ toolCall.arguments || {}
3760
+ );
3761
+ const resultObj = result;
3762
+ const dataObj = resultObj?.data;
3763
+ const wasUserCancelled = dataObj?.declined === true || typeof resultObj?.error === "string" && resultObj.error.includes("cancelled by user") || typeof resultObj?.error === "string" && resultObj.error.includes("declined");
3764
+ if (wasUserCancelled) {
3765
+ console.log("[Crow Widget] Tool was cancelled by user after consent");
3766
+ return;
3767
+ }
3768
+ if (result && submitToolResultRef.current) {
3769
+ await submitToolResultRef.current(
3770
+ toolCallId,
3771
+ toolCall.name,
3772
+ result
3773
+ );
3774
+ }
3775
+ } catch (e) {
3776
+ console.error("[Crow Widget] Tool error after consent:", e);
3777
+ if (submitToolResultRef.current) {
3778
+ await submitToolResultRef.current(
3779
+ toolCallId,
3780
+ toolCall.name,
3781
+ { success: false, error: String(e) }
3782
+ );
3783
+ }
3784
+ }
3785
+ } else {
3786
+ if (submitToolResultRef.current) {
3787
+ await submitToolResultRef.current(
3788
+ toolCallId,
3789
+ toolCall.name,
3790
+ { consent_approved: true, tool_arguments: toolCall.arguments || {} }
3791
+ );
3792
+ }
3704
3793
  }
3705
3794
  } else {
3706
3795
  chat.updateToolCallStatus(toolCallId, "denied");
@@ -3748,7 +3837,9 @@ function CrowWidget({
3748
3837
  isVerifiedUser,
3749
3838
  showConversationList,
3750
3839
  onNewChat: handleNewChat,
3751
- onToggleHistory: handleToggleHistory
3840
+ onToggleHistory: handleToggleHistory,
3841
+ showMinimize: variant === "floating",
3842
+ onToggleMinimize: () => setIsCollapsed(true)
3752
3843
  }
3753
3844
  ),
3754
3845
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: showConversationList && isVerifiedUser && /* @__PURE__ */ jsxRuntime.jsx(
@@ -4129,7 +4220,8 @@ function CrowCopilot({
4129
4220
  pageNavigationRoutes,
4130
4221
  persistAnonymousConversations,
4131
4222
  welcomeMessage: welcomeMessageFromAPI,
4132
- selectedModel
4223
+ selectedModel,
4224
+ toolConsentSettings
4133
4225
  } = useCopilotStyles({
4134
4226
  productId,
4135
4227
  apiUrl,
@@ -4320,6 +4412,7 @@ function CrowCopilot({
4320
4412
  persistAnonymousConversations,
4321
4413
  welcomeMessage,
4322
4414
  selectedModel,
4415
+ toolConsentSettings,
4323
4416
  onVerificationStatus: (isVerified) => {
4324
4417
  setIsVerifiedUser(isVerified);
4325
4418
  },
@@ -4518,14 +4611,54 @@ function CrowCopilot({
4518
4611
  const handleToolConsent = async (toolCallId, approved) => {
4519
4612
  const toolCall = chat.activeToolCalls.find((tc) => tc.id === toolCallId) || chat.messages.flatMap((m) => m.toolCalls || []).find((tc) => tc.id === toolCallId);
4520
4613
  if (!toolCall) return;
4614
+ const isClientSide = !toolCall.serverSideExecution;
4521
4615
  if (approved) {
4522
4616
  chat.updateToolCallStatus(toolCallId, "executing");
4523
- if (submitToolResultRef.current) {
4524
- await submitToolResultRef.current(
4525
- toolCallId,
4526
- toolCall.name,
4527
- { consent_approved: true, tool_arguments: toolCall.arguments || {} }
4528
- );
4617
+ if (isClientSide) {
4618
+ try {
4619
+ const result = await executeClientToolRef.current?.(
4620
+ toolCall.name,
4621
+ toolCall.arguments || {}
4622
+ );
4623
+ const resultObj = result;
4624
+ const dataObj = resultObj?.data;
4625
+ const wasUserCancelled = dataObj?.declined === true || typeof resultObj?.error === "string" && resultObj.error.includes("cancelled by user") || typeof resultObj?.error === "string" && resultObj.error.includes("declined");
4626
+ if (wasUserCancelled) {
4627
+ console.log("[Crow Copilot] Tool was cancelled by user after consent");
4628
+ if (submitToolResultRef.current) {
4629
+ await submitToolResultRef.current(
4630
+ toolCallId,
4631
+ toolCall.name,
4632
+ { success: false, cancelled: true, error: "Action was cancelled by the user." }
4633
+ );
4634
+ }
4635
+ return;
4636
+ }
4637
+ if (result && submitToolResultRef.current) {
4638
+ await submitToolResultRef.current(
4639
+ toolCallId,
4640
+ toolCall.name,
4641
+ result
4642
+ );
4643
+ }
4644
+ } catch (e) {
4645
+ console.error("[Crow Copilot] Tool error after consent:", e);
4646
+ if (submitToolResultRef.current) {
4647
+ await submitToolResultRef.current(
4648
+ toolCallId,
4649
+ toolCall.name,
4650
+ { success: false, error: String(e) }
4651
+ );
4652
+ }
4653
+ }
4654
+ } else {
4655
+ if (submitToolResultRef.current) {
4656
+ await submitToolResultRef.current(
4657
+ toolCallId,
4658
+ toolCall.name,
4659
+ { consent_approved: true, tool_arguments: toolCall.arguments || {} }
4660
+ );
4661
+ }
4529
4662
  }
4530
4663
  } else {
4531
4664
  chat.updateToolCallStatus(toolCallId, "denied");