@ash-cloud/ash-ui 0.2.3 → 0.2.5

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
@@ -423,11 +423,17 @@ function Conversation({
423
423
  children,
424
424
  className,
425
425
  autoScroll: initialAutoScroll = true,
426
- scrollThreshold = 100
426
+ scrollThreshold = 100,
427
+ onLoadMore,
428
+ hasMore = false,
429
+ isLoadingMore = false,
430
+ loadMoreThreshold = 100
427
431
  }) {
428
432
  const containerRef = react.useRef(null);
429
433
  const [isScrolledUp, setIsScrolledUp] = react.useState(false);
430
434
  const [autoScroll, setAutoScroll] = react.useState(initialAutoScroll);
435
+ const prevScrollHeightRef = react.useRef(0);
436
+ const wasLoadingMoreRef = react.useRef(false);
431
437
  const scrollToBottom = react.useCallback((behavior = "smooth") => {
432
438
  if (containerRef.current) {
433
439
  containerRef.current.scrollTo({
@@ -445,7 +451,26 @@ function Conversation({
445
451
  if (isAtBottom && !autoScroll) {
446
452
  setAutoScroll(true);
447
453
  }
448
- }, [scrollThreshold, autoScroll]);
454
+ if (scrollTop < loadMoreThreshold && hasMore && !isLoadingMore && onLoadMore) {
455
+ onLoadMore();
456
+ }
457
+ }, [scrollThreshold, autoScroll, loadMoreThreshold, hasMore, isLoadingMore, onLoadMore]);
458
+ react.useEffect(() => {
459
+ if (isLoadingMore && !wasLoadingMoreRef.current && containerRef.current) {
460
+ prevScrollHeightRef.current = containerRef.current.scrollHeight;
461
+ }
462
+ wasLoadingMoreRef.current = isLoadingMore;
463
+ }, [isLoadingMore]);
464
+ react.useLayoutEffect(() => {
465
+ if (!isLoadingMore && prevScrollHeightRef.current > 0 && containerRef.current) {
466
+ const newScrollHeight = containerRef.current.scrollHeight;
467
+ const scrollDelta = newScrollHeight - prevScrollHeightRef.current;
468
+ if (scrollDelta > 0) {
469
+ containerRef.current.scrollTop += scrollDelta;
470
+ }
471
+ prevScrollHeightRef.current = 0;
472
+ }
473
+ }, [isLoadingMore]);
449
474
  react.useEffect(() => {
450
475
  if (autoScroll && !isScrolledUp) {
451
476
  scrollToBottom("instant");
@@ -456,7 +481,9 @@ function Conversation({
456
481
  scrollToBottom,
457
482
  isScrolledUp,
458
483
  autoScroll,
459
- setAutoScroll
484
+ setAutoScroll,
485
+ hasMore,
486
+ isLoadingMore
460
487
  };
461
488
  return /* @__PURE__ */ jsxRuntime.jsx(ConversationContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
462
489
  "div",
@@ -554,6 +581,50 @@ function ConversationScrollButton({
554
581
  }
555
582
  );
556
583
  }
584
+ function ConversationLoadMore({
585
+ className
586
+ }) {
587
+ const { hasMore, isLoadingMore } = useConversation();
588
+ if (!hasMore && !isLoadingMore) return null;
589
+ return /* @__PURE__ */ jsxRuntime.jsx(
590
+ "div",
591
+ {
592
+ className: cn(
593
+ "ash-conversation-load-more flex items-center justify-center py-3",
594
+ className
595
+ ),
596
+ children: isLoadingMore && /* @__PURE__ */ jsxRuntime.jsxs(
597
+ "svg",
598
+ {
599
+ className: "w-5 h-5 animate-spin text-[var(--ash-text-muted,rgba(255,255,255,0.5))]",
600
+ fill: "none",
601
+ viewBox: "0 0 24 24",
602
+ children: [
603
+ /* @__PURE__ */ jsxRuntime.jsx(
604
+ "circle",
605
+ {
606
+ className: "opacity-25",
607
+ cx: "12",
608
+ cy: "12",
609
+ r: "10",
610
+ stroke: "currentColor",
611
+ strokeWidth: "4"
612
+ }
613
+ ),
614
+ /* @__PURE__ */ jsxRuntime.jsx(
615
+ "path",
616
+ {
617
+ className: "opacity-75",
618
+ fill: "currentColor",
619
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
620
+ }
621
+ )
622
+ ]
623
+ }
624
+ )
625
+ }
626
+ );
627
+ }
557
628
  var ReactMarkdown = react.lazy(() => import('react-markdown'));
558
629
  function LazyMarkdown({ children, fallback, components, className }) {
559
630
  const [mounted, setMounted] = react.useState(false);
@@ -3595,6 +3666,8 @@ function useAgentChat(options) {
3595
3666
  onSessionEnd,
3596
3667
  onError,
3597
3668
  onSandboxLog,
3669
+ canUseTool,
3670
+ resolveToolPermission,
3598
3671
  onReconnect,
3599
3672
  maxReconnectAttempts = 3,
3600
3673
  reconnectBaseDelay = 1e3,
@@ -3628,6 +3701,23 @@ function useAgentChat(options) {
3628
3701
  const emitStreamingEntries = react.useCallback((newEntries) => {
3629
3702
  setStreamingEntries([...newEntries]);
3630
3703
  }, []);
3704
+ const handleToolPermission = react.useCallback(async (event) => {
3705
+ if (event.type !== "tool_permission") return;
3706
+ if (!canUseTool) return;
3707
+ if (!event.requestId || !event.sessionId || !event.toolName) return;
3708
+ const request = {
3709
+ requestId: event.requestId,
3710
+ sessionId: event.sessionId,
3711
+ toolName: event.toolName,
3712
+ input: event.input
3713
+ };
3714
+ const allow = await canUseTool(request);
3715
+ if (!resolveToolPermission) {
3716
+ console.warn("[useAgentChat] resolveToolPermission not provided for tool permission response");
3717
+ return;
3718
+ }
3719
+ await resolveToolPermission(request, allow);
3720
+ }, [canUseTool, resolveToolPermission]);
3631
3721
  const createTextEntry = react.useCallback((id, content) => ({
3632
3722
  id,
3633
3723
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3786,6 +3876,7 @@ function useAgentChat(options) {
3786
3876
  for await (const event of stream) {
3787
3877
  if (controller.signal.aborted) break;
3788
3878
  eventCountRef.current++;
3879
+ await handleToolPermission(event);
3789
3880
  localStreamingEntries = processEvent(event, localStreamingEntries);
3790
3881
  emitStreamingEntries(localStreamingEntries);
3791
3882
  if (event.type === "complete" || event.type === "session_end" || event.type === "error") {
@@ -3819,7 +3910,7 @@ function useAgentChat(options) {
3819
3910
  }
3820
3911
  return false;
3821
3912
  }
3822
- }, [subscribeToSession, maxReconnectAttempts, reconnectBaseDelay, onReconnect, processEvent, emitStreamingEntries]);
3913
+ }, [subscribeToSession, maxReconnectAttempts, reconnectBaseDelay, onReconnect, processEvent, emitStreamingEntries, handleToolPermission]);
3823
3914
  const send = react.useCallback(async (prompt) => {
3824
3915
  if (isStreaming) return;
3825
3916
  let finalPrompt = prompt;
@@ -3898,6 +3989,7 @@ function useAgentChat(options) {
3898
3989
  console.error("[useAgentChat] onEvent error:", err);
3899
3990
  }
3900
3991
  }
3992
+ await handleToolPermission(event);
3901
3993
  let processedEvent = event;
3902
3994
  if (middleware?.length) {
3903
3995
  processedEvent = await applyEventMiddleware(middleware, event);
@@ -3947,7 +4039,7 @@ function useAgentChat(options) {
3947
4039
  abortControllerRef.current = null;
3948
4040
  resetStreamingState();
3949
4041
  }
3950
- }, [isStreaming, sessionId, historyEntries, streamingEntries, createStream, subscribeToSession, processEvent, emitStreamingEntries, resetStreamingState, onError, attemptReconnect, onBeforeSend, onEvent, middleware]);
4042
+ }, [isStreaming, sessionId, historyEntries, streamingEntries, createStream, subscribeToSession, processEvent, emitStreamingEntries, resetStreamingState, onError, attemptReconnect, onBeforeSend, onEvent, middleware, handleToolPermission]);
3951
4043
  const stop = react.useCallback(() => {
3952
4044
  reconnectAttemptsRef.current = maxReconnectAttempts + 1;
3953
4045
  setIsReconnecting(false);
@@ -3998,6 +4090,8 @@ function useChat(options) {
3998
4090
  initialSessionId,
3999
4091
  initialMessages = [],
4000
4092
  onToolCall,
4093
+ canUseTool,
4094
+ resolveToolPermission,
4001
4095
  onFinish,
4002
4096
  onError,
4003
4097
  onSessionStart,
@@ -4032,6 +4126,23 @@ function useChat(options) {
4032
4126
  return prev;
4033
4127
  });
4034
4128
  }, []);
4129
+ const handleToolPermission = react.useCallback(async (event) => {
4130
+ if (event.type !== "tool_permission") return;
4131
+ if (!canUseTool) return;
4132
+ if (!event.requestId || !event.sessionId || !event.toolName) return;
4133
+ const request = {
4134
+ requestId: event.requestId,
4135
+ sessionId: event.sessionId,
4136
+ toolName: event.toolName,
4137
+ input: event.input
4138
+ };
4139
+ const allow = await canUseTool(request);
4140
+ if (!resolveToolPermission) {
4141
+ console.warn("[useChat] resolveToolPermission not provided for tool permission response");
4142
+ return;
4143
+ }
4144
+ await resolveToolPermission(request, allow);
4145
+ }, [canUseTool, resolveToolPermission]);
4035
4146
  const processEvent = react.useCallback((event) => {
4036
4147
  switch (event.type) {
4037
4148
  case "session_start":
@@ -4151,6 +4262,7 @@ function useChat(options) {
4151
4262
  const stream = createStream(finalPrompt, sessionIdRef.current || void 0, streamOptions);
4152
4263
  for await (const event of stream) {
4153
4264
  if (controller.signal.aborted) break;
4265
+ await handleToolPermission(event);
4154
4266
  let processedEvent = event;
4155
4267
  if (middleware?.length) {
4156
4268
  processedEvent = await applyEventMiddleware(middleware, event);
@@ -4180,7 +4292,7 @@ function useChat(options) {
4180
4292
  abortControllerRef.current = null;
4181
4293
  currentAssistantMessageRef.current = null;
4182
4294
  }
4183
- }, [isLoading, createStream, processEvent, middleware, onError]);
4295
+ }, [isLoading, createStream, processEvent, middleware, onError, handleToolPermission]);
4184
4296
  const stop = react.useCallback(() => {
4185
4297
  reconnectAttemptsRef.current = maxReconnectAttempts + 1;
4186
4298
  setIsReconnecting(false);
@@ -4335,6 +4447,7 @@ exports.CodeIcon = CodeIcon;
4335
4447
  exports.Conversation = Conversation;
4336
4448
  exports.ConversationContent = ConversationContent;
4337
4449
  exports.ConversationEmptyState = ConversationEmptyState;
4450
+ exports.ConversationLoadMore = ConversationLoadMore;
4338
4451
  exports.ConversationScrollButton = ConversationScrollButton;
4339
4452
  exports.CopyIcon = CopyIcon;
4340
4453
  exports.DEFAULT_STYLE_CONFIG = DEFAULT_STYLE_CONFIG;