@invergent/agent-chat-react 1.4.9 → 1.4.11

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.d.cts CHANGED
@@ -371,7 +371,8 @@ interface SessionTreePanelProps {
371
371
  /** Treat the root as hidden, so its children appear as top-level rows. */
372
372
  hideRoot?: boolean;
373
373
  onSessionSelect?: (sessionId: string) => void;
374
+ onSessionDelete?: (sessionId: string) => void;
374
375
  }
375
- declare function SessionTreePanel({ adapter, sessionId, activeSessionId, agentId, title, sessionListLimit, hideRoot, onSessionSelect, }: SessionTreePanelProps): react_jsx_runtime.JSX.Element | null;
376
+ declare function SessionTreePanel({ adapter, sessionId, activeSessionId, agentId, title, sessionListLimit, hideRoot, onSessionSelect, onSessionDelete, }: SessionTreePanelProps): react_jsx_runtime.JSX.Element | null;
376
377
 
377
378
  export { AgentChat, type AgentChatAdapter, type AgentChatAdapterContextValue, AgentChatAdapterProvider, type AgentChatArtifactKind, type AgentChatArtifactMeta, type AgentChatArtifactPayload, type AgentChatChartArtifactSpec, type AgentChatClarifyAnswer, type AgentChatClarifyArgs, type AgentChatClarifyChoice, type AgentChatClarifyQuestion, type AgentChatErrorCategory, type AgentChatErrorInfo, type AgentChatEventStream, type AgentChatEventType, type AgentChatExpertFeedbackRating, type AgentChatHtmlArtifactSpec, type AgentChatMarkdownArtifactSpec, type AgentChatMessage, type AgentChatMessageStatus, type AgentChatProps, type AgentChatRole, type AgentChatRuntimeApi, type AgentChatRuntimeEvent, type AgentChatSession, type AgentChatSessionList, type AgentChatSessionTree, type AgentChatSessionTreeNode, type AgentChatSlashCommand, type AgentChatSseMessageEvent, type AgentChatState, type AgentChatSvgArtifactSpec, type AgentChatSystemKind, type AgentChatTableArtifactSpec, type AgentChatTokenUsage, type AgentChatToolCallInfo, type AgentChatWorkspaceEntry, type AgentChatWorkspaceFile, type AgentChatWorkspaceTree, type AgentChatWorkspaceUpload, type ArtifactKind, type ArtifactPayload, type ChartArtifactSpec, type ChatMessage, type ClarifyAnswer, type ClarifyArgs, type ClarifyChoice, type ClarifyQuestion, type ErrorInfo, type HtmlArtifactSpec, type MarkdownArtifactSpec, MessageResponse, type MessageResponseProps, type RetryIndicator, type SessionTree, type SessionTreeNode, SessionTreePanel, type SessionTreePanelProps, type SvgArtifactSpec, type TableArtifactSpec, type TokenUsage, type ToolCallInfo, type WorkspaceEntry, type WorkspaceFile, type WorkspaceTree, type WorkspaceUpload, useAgentChatAdapterContext, useAgentChatRuntime };
package/dist/index.d.ts CHANGED
@@ -371,7 +371,8 @@ interface SessionTreePanelProps {
371
371
  /** Treat the root as hidden, so its children appear as top-level rows. */
372
372
  hideRoot?: boolean;
373
373
  onSessionSelect?: (sessionId: string) => void;
374
+ onSessionDelete?: (sessionId: string) => void;
374
375
  }
375
- declare function SessionTreePanel({ adapter, sessionId, activeSessionId, agentId, title, sessionListLimit, hideRoot, onSessionSelect, }: SessionTreePanelProps): react_jsx_runtime.JSX.Element | null;
376
+ declare function SessionTreePanel({ adapter, sessionId, activeSessionId, agentId, title, sessionListLimit, hideRoot, onSessionSelect, onSessionDelete, }: SessionTreePanelProps): react_jsx_runtime.JSX.Element | null;
376
377
 
377
378
  export { AgentChat, type AgentChatAdapter, type AgentChatAdapterContextValue, AgentChatAdapterProvider, type AgentChatArtifactKind, type AgentChatArtifactMeta, type AgentChatArtifactPayload, type AgentChatChartArtifactSpec, type AgentChatClarifyAnswer, type AgentChatClarifyArgs, type AgentChatClarifyChoice, type AgentChatClarifyQuestion, type AgentChatErrorCategory, type AgentChatErrorInfo, type AgentChatEventStream, type AgentChatEventType, type AgentChatExpertFeedbackRating, type AgentChatHtmlArtifactSpec, type AgentChatMarkdownArtifactSpec, type AgentChatMessage, type AgentChatMessageStatus, type AgentChatProps, type AgentChatRole, type AgentChatRuntimeApi, type AgentChatRuntimeEvent, type AgentChatSession, type AgentChatSessionList, type AgentChatSessionTree, type AgentChatSessionTreeNode, type AgentChatSlashCommand, type AgentChatSseMessageEvent, type AgentChatState, type AgentChatSvgArtifactSpec, type AgentChatSystemKind, type AgentChatTableArtifactSpec, type AgentChatTokenUsage, type AgentChatToolCallInfo, type AgentChatWorkspaceEntry, type AgentChatWorkspaceFile, type AgentChatWorkspaceTree, type AgentChatWorkspaceUpload, type ArtifactKind, type ArtifactPayload, type ChartArtifactSpec, type ChatMessage, type ClarifyAnswer, type ClarifyArgs, type ClarifyChoice, type ClarifyQuestion, type ErrorInfo, type HtmlArtifactSpec, type MarkdownArtifactSpec, MessageResponse, type MessageResponseProps, type RetryIndicator, type SessionTree, type SessionTreeNode, SessionTreePanel, type SessionTreePanelProps, type SvgArtifactSpec, type TableArtifactSpec, type TokenUsage, type ToolCallInfo, type WorkspaceEntry, type WorkspaceFile, type WorkspaceTree, type WorkspaceUpload, useAgentChatAdapterContext, useAgentChatRuntime };
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import Ansi from 'ansi-to-react';
18
18
  import { getUsage } from 'tokenlens';
19
19
  import { Command as Command$1 } from 'cmdk';
20
20
  import { nanoid } from 'nanoid';
21
+ import { formatDistanceToNow } from 'date-fns';
21
22
 
22
23
  // src/agent-chat.tsx
23
24
  var AgentChatAdapterContext = createContext(null);
@@ -7822,32 +7823,31 @@ function mergeTreeNodes(groups) {
7822
7823
  }
7823
7824
  return Array.from(byId.values());
7824
7825
  }
7825
- function statusColor(status) {
7826
- switch (status) {
7827
- case "active":
7828
- return "default";
7829
- case "completed":
7830
- return "secondary";
7831
- case "failed":
7832
- return "destructive";
7833
- default:
7834
- return "outline";
7835
- }
7826
+ function formatSessionTime(value) {
7827
+ const date = new Date(value);
7828
+ if (Number.isNaN(date.getTime())) return "";
7829
+ return formatDistanceToNow(date, { addSuffix: true });
7836
7830
  }
7837
7831
  function TreeNodeRow({
7838
7832
  entry,
7839
7833
  depth,
7840
7834
  activeSessionId,
7841
7835
  canStop,
7836
+ canDelete,
7837
+ deletingSessionId,
7842
7838
  onSelect,
7843
- onStop
7839
+ onStop,
7840
+ onDelete
7844
7841
  }) {
7845
7842
  const [expanded, setExpanded] = useState(true);
7846
7843
  const hasChildren = entry.children.length > 0;
7847
7844
  const isActive = entry.id === activeSessionId;
7848
7845
  const isRunning = entry.status === "active";
7849
- const messageCount = entry.messageCount ?? 0;
7850
- const toolCallCount = entry.toolCallCount ?? 0;
7846
+ const title = entry.title ?? "New session";
7847
+ const subtitle = [
7848
+ entry.model ?? "default",
7849
+ formatSessionTime(entry.updatedAt)
7850
+ ].filter(Boolean).join(" \xB7 ");
7851
7851
  return /* @__PURE__ */ jsxs(Fragment, { children: [
7852
7852
  /* @__PURE__ */ jsxs(
7853
7853
  "div",
@@ -7859,16 +7859,16 @@ function TreeNodeRow({
7859
7859
  if (e.key === "Enter" || e.key === " ") onSelect(entry.id);
7860
7860
  },
7861
7861
  className: cn(
7862
- "group flex items-center gap-1.5 py-1.5 pr-1 rounded cursor-pointer text-sm transition-colors",
7863
- isActive ? "bg-line text-foreground" : "hover:bg-input text-subtle hover:text-foreground"
7862
+ "group flex items-center gap-2 w-full py-2 pr-2 text-left cursor-pointer transition-colors border-l-2",
7863
+ isActive ? "bg-line text-foreground border-l-primary" : "bg-transparent text-subtle hover:bg-input hover:text-foreground border-l-transparent"
7864
7864
  ),
7865
- style: { paddingLeft: `${depth * 12 + 4}px` },
7865
+ style: { paddingLeft: `${depth * 12 + 12}px` },
7866
7866
  children: [
7867
7867
  hasChildren ? /* @__PURE__ */ jsx(
7868
7868
  "button",
7869
7869
  {
7870
7870
  type: "button",
7871
- className: "p-0.5 rounded hover:bg-line",
7871
+ className: "p-0.5 rounded hover:bg-line shrink-0",
7872
7872
  onClick: (e) => {
7873
7873
  e.stopPropagation();
7874
7874
  setExpanded(!expanded);
@@ -7878,32 +7878,10 @@ function TreeNodeRow({
7878
7878
  children: expanded ? /* @__PURE__ */ jsx(ChevronDownIcon, { className: "w-3.5 h-3.5" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "w-3.5 h-3.5" })
7879
7879
  }
7880
7880
  ) : /* @__PURE__ */ jsx("span", { className: "w-4 h-4 shrink-0" }),
7881
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 flex items-center gap-1.5", children: [
7882
- /* @__PURE__ */ jsx("span", { className: "truncate", children: entry.title ?? entry.channel ?? "session" }),
7883
- entry.agentType && /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "h-4 px-1.5 text-[10px]", children: entry.agentType }),
7884
- /* @__PURE__ */ jsx(
7885
- Badge,
7886
- {
7887
- variant: statusColor(entry.status),
7888
- className: "h-4 px-1.5 text-[10px]",
7889
- children: entry.status
7890
- }
7891
- )
7881
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
7882
+ /* @__PURE__ */ jsx("div", { className: "text-sm truncate", children: title }),
7883
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-faint truncate", children: subtitle })
7892
7884
  ] }),
7893
- /* @__PURE__ */ jsxs(
7894
- "span",
7895
- {
7896
- className: "text-xs text-faint shrink-0 tabular-nums",
7897
- "aria-label": `${messageCount} messages, ${toolCallCount} tool calls`,
7898
- title: `${messageCount} messages, ${toolCallCount} tool calls`,
7899
- children: [
7900
- messageCount,
7901
- "m/",
7902
- toolCallCount,
7903
- "t"
7904
- ]
7905
- }
7906
- ),
7907
7885
  isRunning && canStop && /* @__PURE__ */ jsx(
7908
7886
  "button",
7909
7887
  {
@@ -7917,6 +7895,21 @@ function TreeNodeRow({
7917
7895
  title: "Stop sub-agent",
7918
7896
  children: /* @__PURE__ */ jsx(SquareIcon, { className: "w-3 h-3", fill: "currentColor" })
7919
7897
  }
7898
+ ),
7899
+ canDelete && /* @__PURE__ */ jsx(
7900
+ "button",
7901
+ {
7902
+ type: "button",
7903
+ className: "p-1 rounded opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-destructive/10 hover:text-destructive disabled:pointer-events-none disabled:opacity-50 transition-all",
7904
+ onClick: (e) => {
7905
+ e.stopPropagation();
7906
+ onDelete(entry.id);
7907
+ },
7908
+ "aria-label": "Delete session",
7909
+ title: "Delete session",
7910
+ disabled: deletingSessionId === entry.id,
7911
+ children: /* @__PURE__ */ jsx(Trash2Icon, { className: "w-3 h-3" })
7912
+ }
7920
7913
  )
7921
7914
  ]
7922
7915
  }
@@ -7928,8 +7921,11 @@ function TreeNodeRow({
7928
7921
  depth: depth + 1,
7929
7922
  activeSessionId,
7930
7923
  canStop,
7924
+ canDelete,
7925
+ deletingSessionId,
7931
7926
  onSelect,
7932
- onStop
7927
+ onStop,
7928
+ onDelete
7933
7929
  },
7934
7930
  child.id
7935
7931
  ))
@@ -7943,15 +7939,20 @@ function SessionTreePanel({
7943
7939
  title = "Running",
7944
7940
  sessionListLimit = DEFAULT_SESSION_LIST_LIMIT,
7945
7941
  hideRoot = false,
7946
- onSessionSelect
7942
+ onSessionSelect,
7943
+ onSessionDelete
7947
7944
  }) {
7948
7945
  const [nodes, setNodes] = useState([]);
7949
7946
  const [loading, setLoading] = useState(false);
7950
7947
  const [error, setError] = useState(null);
7951
7948
  const [hasEverLoaded, setHasEverLoaded] = useState(false);
7949
+ const [deletingSessionId, setDeletingSessionId] = useState(
7950
+ null
7951
+ );
7952
7952
  const mounted = useRef(true);
7953
7953
  const requestId = useRef(0);
7954
7954
  const lastFingerprint = useRef("");
7955
+ const resetContext = useRef(null);
7955
7956
  const refetch = useCallback(
7956
7957
  async (opts) => {
7957
7958
  const canLoadSessionList = Boolean(agentId && adapter.listSessions);
@@ -8006,12 +8007,17 @@ function SessionTreePanel({
8006
8007
  };
8007
8008
  }, []);
8008
8009
  useEffect(() => {
8009
- setNodes([]);
8010
+ const previous = resetContext.current;
8011
+ const shouldReset = !previous || previous.adapter !== adapter || previous.agentId !== agentId || previous.sessionListLimit !== sessionListLimit;
8012
+ resetContext.current = { adapter, agentId, sessionListLimit };
8013
+ if (shouldReset) {
8014
+ setNodes([]);
8015
+ setHasEverLoaded(false);
8016
+ lastFingerprint.current = "";
8017
+ }
8010
8018
  setError(null);
8011
- setHasEverLoaded(false);
8012
- lastFingerprint.current = "";
8013
8019
  void refetch();
8014
- }, [refetch, sessionId]);
8020
+ }, [adapter, agentId, refetch, sessionListLimit]);
8015
8021
  const runningCount = useMemo(
8016
8022
  () => nodes.filter((n) => n.status === "active").length,
8017
8023
  [nodes]
@@ -8042,17 +8048,44 @@ function SessionTreePanel({
8042
8048
  },
8043
8049
  [adapter, refetch]
8044
8050
  );
8051
+ const handleDelete = useCallback(
8052
+ async (id) => {
8053
+ if (!adapter.deleteSession || deletingSessionId) return;
8054
+ setDeletingSessionId(id);
8055
+ try {
8056
+ await adapter.deleteSession({ sessionId: id });
8057
+ onSessionDelete?.(id);
8058
+ await refetch({ silent: true });
8059
+ } catch (e) {
8060
+ setError(e instanceof Error ? e.message : "Failed to delete session");
8061
+ } finally {
8062
+ if (mounted.current) setDeletingSessionId(null);
8063
+ }
8064
+ },
8065
+ [adapter, deletingSessionId, onSessionDelete, refetch]
8066
+ );
8045
8067
  if (!hasEverLoaded) return null;
8046
8068
  if (nodes.length === 0) return null;
8047
8069
  const topLevel = hideRoot ? roots.flatMap((r) => r.children) : roots;
8048
8070
  if (topLevel.length === 0) return null;
8049
8071
  return /* @__PURE__ */ jsxs("div", { className: "border-t border-line", children: [
8050
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-3 py-2 text-xs font-semibold uppercase tracking-wide text-faint", children: [
8072
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-3 py-2 text-xs font-semibold uppercase tracking-wide", children: [
8051
8073
  /* @__PURE__ */ jsx(UsersIcon, { className: "w-3.5 h-3.5" }),
8052
8074
  /* @__PURE__ */ jsx("span", { children: title }),
8053
8075
  runningCount > 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", className: "h-4 px-1.5 text-[10px] ml-auto", children: runningCount })
8054
8076
  ] }),
8055
- loading && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-faint", children: "Loading..." }),
8077
+ loading && /* @__PURE__ */ jsx(
8078
+ "div",
8079
+ {
8080
+ className: "px-3 py-2",
8081
+ role: "status",
8082
+ "aria-label": "Loading sessions",
8083
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
8084
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-28" }),
8085
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-20" })
8086
+ ] })
8087
+ }
8088
+ ),
8056
8089
  error && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-destructive", children: error }),
8057
8090
  !error && /* @__PURE__ */ jsx("div", { className: "px-1 pb-2", children: topLevel.map((entry) => /* @__PURE__ */ jsx(
8058
8091
  TreeNodeRow,
@@ -8061,8 +8094,11 @@ function SessionTreePanel({
8061
8094
  depth: 0,
8062
8095
  activeSessionId: activeSessionId ?? "",
8063
8096
  canStop: Boolean(adapter.stopSession),
8097
+ canDelete: Boolean(adapter.deleteSession),
8098
+ deletingSessionId,
8064
8099
  onSelect: handleSelect,
8065
- onStop: handleStop
8100
+ onStop: handleStop,
8101
+ onDelete: handleDelete
8066
8102
  },
8067
8103
  entry.id
8068
8104
  )) })