@usecrow/ui 0.1.5 → 0.1.7

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
@@ -126,6 +126,7 @@ function useChat({
126
126
  async (message, botMsgId) => {
127
127
  let accumulatedText = "";
128
128
  let firstChunk = true;
129
+ const pendingClientTools = [];
129
130
  abortControllerRef.current = new AbortController();
130
131
  try {
131
132
  const identityToken = window.__crow_identity_token;
@@ -157,6 +158,14 @@ function useChat({
157
158
  const data = line.slice(6).trim();
158
159
  if (data === "[DONE]") {
159
160
  setIsLoading(false);
161
+ for (const tool of pendingClientTools) {
162
+ onToolCall?.({
163
+ type: "client_call",
164
+ toolName: tool.toolName,
165
+ toolCallId: tool.toolCallId,
166
+ arguments: tool.arguments
167
+ });
168
+ }
160
169
  return;
161
170
  }
162
171
  try {
@@ -253,9 +262,9 @@ function useChat({
253
262
  );
254
263
  break;
255
264
  case "client_tool_call":
256
- onToolCall?.({
257
- type: "client_call",
265
+ pendingClientTools.push({
258
266
  toolName: parsed.tool_name,
267
+ toolCallId: parsed.tool_call_id,
259
268
  arguments: parsed.arguments
260
269
  });
261
270
  break;
@@ -369,6 +378,102 @@ function useChat({
369
378
  const loadMessages = React3.useCallback((historyMessages) => {
370
379
  setMessages(historyMessages);
371
380
  }, []);
381
+ const submitToolResult = React3.useCallback(
382
+ async (toolCallId, toolName, result) => {
383
+ if (!conversationId) {
384
+ console.error("[Crow] Cannot submit tool result: no conversation ID");
385
+ return;
386
+ }
387
+ const botMsgId = generateMessageId("bot");
388
+ const pendingClientTools = [];
389
+ setMessages((prev) => [
390
+ ...prev,
391
+ {
392
+ id: botMsgId,
393
+ content: "",
394
+ isBot: true,
395
+ timestamp: /* @__PURE__ */ new Date()
396
+ }
397
+ ]);
398
+ setIsLoading(true);
399
+ try {
400
+ const identityToken = window.__crow_identity_token;
401
+ const response = await fetch(`${apiUrl}/api/chat/tool-result`, {
402
+ method: "POST",
403
+ headers: { "Content-Type": "application/json" },
404
+ body: JSON.stringify({
405
+ product_id: productId,
406
+ conversation_id: conversationId,
407
+ tool_call_id: toolCallId,
408
+ tool_name: toolName,
409
+ result,
410
+ identity_token: identityToken,
411
+ model: selectedModel
412
+ })
413
+ });
414
+ if (!response.ok) {
415
+ throw new Error(`HTTP error! status: ${response.status}`);
416
+ }
417
+ const reader = response.body?.getReader();
418
+ const decoder = new TextDecoder();
419
+ let accumulatedText = "";
420
+ if (reader) {
421
+ while (true) {
422
+ const { done, value } = await reader.read();
423
+ if (done) break;
424
+ const chunk = decoder.decode(value);
425
+ const lines = chunk.split("\n");
426
+ for (const line of lines) {
427
+ if (line.startsWith("data: ")) {
428
+ const data = line.slice(6).trim();
429
+ if (data === "[DONE]") {
430
+ setIsLoading(false);
431
+ for (const tool of pendingClientTools) {
432
+ onToolCall?.({
433
+ type: "client_call",
434
+ toolName: tool.toolName,
435
+ toolCallId: tool.toolCallId,
436
+ arguments: tool.arguments
437
+ });
438
+ }
439
+ return;
440
+ }
441
+ try {
442
+ const parsed = JSON.parse(data);
443
+ if (parsed.type === "content") {
444
+ accumulatedText += parsed.content;
445
+ setMessages(
446
+ (prev) => prev.map(
447
+ (msg) => msg.id === botMsgId ? { ...msg, content: accumulatedText } : msg
448
+ )
449
+ );
450
+ } else if (parsed.type === "client_tool_call") {
451
+ pendingClientTools.push({
452
+ toolName: parsed.tool_name,
453
+ toolCallId: parsed.tool_call_id,
454
+ arguments: parsed.arguments
455
+ });
456
+ }
457
+ } catch (e) {
458
+ console.error("[Crow] Parse error in tool result response:", e);
459
+ }
460
+ }
461
+ }
462
+ }
463
+ }
464
+ } catch (error) {
465
+ console.error("[Crow] Error submitting tool result:", error);
466
+ setMessages(
467
+ (prev) => prev.map(
468
+ (msg) => msg.id === botMsgId ? { ...msg, content: "Sorry, I encountered an error processing the tool result." } : msg
469
+ )
470
+ );
471
+ } finally {
472
+ setIsLoading(false);
473
+ }
474
+ },
475
+ [apiUrl, productId, conversationId, selectedModel]
476
+ );
372
477
  return {
373
478
  messages,
374
479
  isLoading,
@@ -380,7 +485,8 @@ function useChat({
380
485
  sendMessage,
381
486
  stopGeneration,
382
487
  resetMessages,
383
- loadMessages
488
+ loadMessages,
489
+ submitToolResult
384
490
  };
385
491
  }
386
492
  function useConversations({ productId, apiUrl = "" }) {
@@ -668,8 +774,8 @@ var DEFAULT_WIDGET_STYLES = {
668
774
  border: "#d1d5db",
669
775
  text: "#111827",
670
776
  // Bot messages
671
- botBubble: "#ffffff",
672
- botText: "#000000",
777
+ botBubble: "#000000",
778
+ botText: "#ffffff",
673
779
  // User messages
674
780
  userBubble: "#ffffff",
675
781
  userText: "#000000",
@@ -2114,6 +2220,7 @@ function CrowWidget({
2114
2220
  });
2115
2221
  const messagesContainerRef = React3.useRef(null);
2116
2222
  const executeClientToolRef = React3.useRef(null);
2223
+ const submitToolResultRef = React3.useRef(null);
2117
2224
  const [isCollapsed, setIsCollapsed] = React3.useState(variant === "floating");
2118
2225
  const [showConversationList, setShowConversationList] = React3.useState(false);
2119
2226
  const [isVerifiedUser, setIsVerifiedUser] = React3.useState(false);
@@ -2160,11 +2267,30 @@ function CrowWidget({
2160
2267
  break;
2161
2268
  }
2162
2269
  },
2163
- onToolCall: (event) => {
2164
- if (event.type === "client_call" && event.toolName) {
2165
- console.log("[Crow Widget] Executing client tool:", event.toolName, event.arguments);
2166
- const result = executeClientToolRef.current?.(event.toolName, event.arguments || {});
2167
- result?.then((r) => console.log("[Crow Widget] Tool result:", r)).catch((e) => console.error("[Crow Widget] Tool error:", e));
2270
+ onToolCall: async (event) => {
2271
+ if (event.type === "client_call" && event.toolName && event.toolCallId) {
2272
+ try {
2273
+ const result = await executeClientToolRef.current?.(
2274
+ event.toolName,
2275
+ event.arguments || {}
2276
+ );
2277
+ if (result && submitToolResultRef.current) {
2278
+ await submitToolResultRef.current(
2279
+ event.toolCallId,
2280
+ event.toolName,
2281
+ result
2282
+ );
2283
+ }
2284
+ } catch (e) {
2285
+ console.error("[Crow Widget] Tool error:", e);
2286
+ if (submitToolResultRef.current) {
2287
+ await submitToolResultRef.current(
2288
+ event.toolCallId,
2289
+ event.toolName,
2290
+ { success: false, error: String(e) }
2291
+ );
2292
+ }
2293
+ }
2168
2294
  }
2169
2295
  },
2170
2296
  onRestoredConversation: () => {
@@ -2210,6 +2336,7 @@ function CrowWidget({
2210
2336
  }
2211
2337
  });
2212
2338
  executeClientToolRef.current = executeClientTool;
2339
+ submitToolResultRef.current = chat.submitToolResult;
2213
2340
  React3.useEffect(() => {
2214
2341
  if (!isLoadingStyles) {
2215
2342
  onReady?.();