@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.cjs CHANGED
@@ -22,6 +22,7 @@ var Ansi = require('ansi-to-react');
22
22
  var tokenlens = require('tokenlens');
23
23
  var cmdk = require('cmdk');
24
24
  var nanoid = require('nanoid');
25
+ var dateFns = require('date-fns');
25
26
 
26
27
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
27
28
 
@@ -7927,32 +7928,31 @@ function mergeTreeNodes(groups) {
7927
7928
  }
7928
7929
  return Array.from(byId.values());
7929
7930
  }
7930
- function statusColor(status) {
7931
- switch (status) {
7932
- case "active":
7933
- return "default";
7934
- case "completed":
7935
- return "secondary";
7936
- case "failed":
7937
- return "destructive";
7938
- default:
7939
- return "outline";
7940
- }
7931
+ function formatSessionTime(value) {
7932
+ const date = new Date(value);
7933
+ if (Number.isNaN(date.getTime())) return "";
7934
+ return dateFns.formatDistanceToNow(date, { addSuffix: true });
7941
7935
  }
7942
7936
  function TreeNodeRow({
7943
7937
  entry,
7944
7938
  depth,
7945
7939
  activeSessionId,
7946
7940
  canStop,
7941
+ canDelete,
7942
+ deletingSessionId,
7947
7943
  onSelect,
7948
- onStop
7944
+ onStop,
7945
+ onDelete
7949
7946
  }) {
7950
7947
  const [expanded, setExpanded] = react.useState(true);
7951
7948
  const hasChildren = entry.children.length > 0;
7952
7949
  const isActive = entry.id === activeSessionId;
7953
7950
  const isRunning = entry.status === "active";
7954
- const messageCount = entry.messageCount ?? 0;
7955
- const toolCallCount = entry.toolCallCount ?? 0;
7951
+ const title = entry.title ?? "New session";
7952
+ const subtitle = [
7953
+ entry.model ?? "default",
7954
+ formatSessionTime(entry.updatedAt)
7955
+ ].filter(Boolean).join(" \xB7 ");
7956
7956
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7957
7957
  /* @__PURE__ */ jsxRuntime.jsxs(
7958
7958
  "div",
@@ -7964,16 +7964,16 @@ function TreeNodeRow({
7964
7964
  if (e.key === "Enter" || e.key === " ") onSelect(entry.id);
7965
7965
  },
7966
7966
  className: cn(
7967
- "group flex items-center gap-1.5 py-1.5 pr-1 rounded cursor-pointer text-sm transition-colors",
7968
- isActive ? "bg-line text-foreground" : "hover:bg-input text-subtle hover:text-foreground"
7967
+ "group flex items-center gap-2 w-full py-2 pr-2 text-left cursor-pointer transition-colors border-l-2",
7968
+ isActive ? "bg-line text-foreground border-l-primary" : "bg-transparent text-subtle hover:bg-input hover:text-foreground border-l-transparent"
7969
7969
  ),
7970
- style: { paddingLeft: `${depth * 12 + 4}px` },
7970
+ style: { paddingLeft: `${depth * 12 + 12}px` },
7971
7971
  children: [
7972
7972
  hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
7973
7973
  "button",
7974
7974
  {
7975
7975
  type: "button",
7976
- className: "p-0.5 rounded hover:bg-line",
7976
+ className: "p-0.5 rounded hover:bg-line shrink-0",
7977
7977
  onClick: (e) => {
7978
7978
  e.stopPropagation();
7979
7979
  setExpanded(!expanded);
@@ -7983,32 +7983,10 @@ function TreeNodeRow({
7983
7983
  children: expanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDownIcon, { className: "w-3.5 h-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "w-3.5 h-3.5" })
7984
7984
  }
7985
7985
  ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-4 h-4 shrink-0" }),
7986
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex items-center gap-1.5", children: [
7987
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: entry.title ?? entry.channel ?? "session" }),
7988
- entry.agentType && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "outline", className: "h-4 px-1.5 text-[10px]", children: entry.agentType }),
7989
- /* @__PURE__ */ jsxRuntime.jsx(
7990
- Badge,
7991
- {
7992
- variant: statusColor(entry.status),
7993
- className: "h-4 px-1.5 text-[10px]",
7994
- children: entry.status
7995
- }
7996
- )
7986
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
7987
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm truncate", children: title }),
7988
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-faint truncate", children: subtitle })
7997
7989
  ] }),
7998
- /* @__PURE__ */ jsxRuntime.jsxs(
7999
- "span",
8000
- {
8001
- className: "text-xs text-faint shrink-0 tabular-nums",
8002
- "aria-label": `${messageCount} messages, ${toolCallCount} tool calls`,
8003
- title: `${messageCount} messages, ${toolCallCount} tool calls`,
8004
- children: [
8005
- messageCount,
8006
- "m/",
8007
- toolCallCount,
8008
- "t"
8009
- ]
8010
- }
8011
- ),
8012
7990
  isRunning && canStop && /* @__PURE__ */ jsxRuntime.jsx(
8013
7991
  "button",
8014
7992
  {
@@ -8022,6 +8000,21 @@ function TreeNodeRow({
8022
8000
  title: "Stop sub-agent",
8023
8001
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SquareIcon, { className: "w-3 h-3", fill: "currentColor" })
8024
8002
  }
8003
+ ),
8004
+ canDelete && /* @__PURE__ */ jsxRuntime.jsx(
8005
+ "button",
8006
+ {
8007
+ type: "button",
8008
+ 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",
8009
+ onClick: (e) => {
8010
+ e.stopPropagation();
8011
+ onDelete(entry.id);
8012
+ },
8013
+ "aria-label": "Delete session",
8014
+ title: "Delete session",
8015
+ disabled: deletingSessionId === entry.id,
8016
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2Icon, { className: "w-3 h-3" })
8017
+ }
8025
8018
  )
8026
8019
  ]
8027
8020
  }
@@ -8033,8 +8026,11 @@ function TreeNodeRow({
8033
8026
  depth: depth + 1,
8034
8027
  activeSessionId,
8035
8028
  canStop,
8029
+ canDelete,
8030
+ deletingSessionId,
8036
8031
  onSelect,
8037
- onStop
8032
+ onStop,
8033
+ onDelete
8038
8034
  },
8039
8035
  child.id
8040
8036
  ))
@@ -8048,15 +8044,20 @@ function SessionTreePanel({
8048
8044
  title = "Running",
8049
8045
  sessionListLimit = DEFAULT_SESSION_LIST_LIMIT,
8050
8046
  hideRoot = false,
8051
- onSessionSelect
8047
+ onSessionSelect,
8048
+ onSessionDelete
8052
8049
  }) {
8053
8050
  const [nodes, setNodes] = react.useState([]);
8054
8051
  const [loading, setLoading] = react.useState(false);
8055
8052
  const [error, setError] = react.useState(null);
8056
8053
  const [hasEverLoaded, setHasEverLoaded] = react.useState(false);
8054
+ const [deletingSessionId, setDeletingSessionId] = react.useState(
8055
+ null
8056
+ );
8057
8057
  const mounted = react.useRef(true);
8058
8058
  const requestId = react.useRef(0);
8059
8059
  const lastFingerprint = react.useRef("");
8060
+ const resetContext = react.useRef(null);
8060
8061
  const refetch = react.useCallback(
8061
8062
  async (opts) => {
8062
8063
  const canLoadSessionList = Boolean(agentId && adapter.listSessions);
@@ -8111,12 +8112,17 @@ function SessionTreePanel({
8111
8112
  };
8112
8113
  }, []);
8113
8114
  react.useEffect(() => {
8114
- setNodes([]);
8115
+ const previous = resetContext.current;
8116
+ const shouldReset = !previous || previous.adapter !== adapter || previous.agentId !== agentId || previous.sessionListLimit !== sessionListLimit;
8117
+ resetContext.current = { adapter, agentId, sessionListLimit };
8118
+ if (shouldReset) {
8119
+ setNodes([]);
8120
+ setHasEverLoaded(false);
8121
+ lastFingerprint.current = "";
8122
+ }
8115
8123
  setError(null);
8116
- setHasEverLoaded(false);
8117
- lastFingerprint.current = "";
8118
8124
  void refetch();
8119
- }, [refetch, sessionId]);
8125
+ }, [adapter, agentId, refetch, sessionListLimit]);
8120
8126
  const runningCount = react.useMemo(
8121
8127
  () => nodes.filter((n) => n.status === "active").length,
8122
8128
  [nodes]
@@ -8147,17 +8153,44 @@ function SessionTreePanel({
8147
8153
  },
8148
8154
  [adapter, refetch]
8149
8155
  );
8156
+ const handleDelete = react.useCallback(
8157
+ async (id) => {
8158
+ if (!adapter.deleteSession || deletingSessionId) return;
8159
+ setDeletingSessionId(id);
8160
+ try {
8161
+ await adapter.deleteSession({ sessionId: id });
8162
+ onSessionDelete?.(id);
8163
+ await refetch({ silent: true });
8164
+ } catch (e) {
8165
+ setError(e instanceof Error ? e.message : "Failed to delete session");
8166
+ } finally {
8167
+ if (mounted.current) setDeletingSessionId(null);
8168
+ }
8169
+ },
8170
+ [adapter, deletingSessionId, onSessionDelete, refetch]
8171
+ );
8150
8172
  if (!hasEverLoaded) return null;
8151
8173
  if (nodes.length === 0) return null;
8152
8174
  const topLevel = hideRoot ? roots.flatMap((r) => r.children) : roots;
8153
8175
  if (topLevel.length === 0) return null;
8154
8176
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-line", children: [
8155
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 px-3 py-2 text-xs font-semibold uppercase tracking-wide text-faint", children: [
8177
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 px-3 py-2 text-xs font-semibold uppercase tracking-wide", children: [
8156
8178
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UsersIcon, { className: "w-3.5 h-3.5" }),
8157
8179
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: title }),
8158
8180
  runningCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", className: "h-4 px-1.5 text-[10px] ml-auto", children: runningCount })
8159
8181
  ] }),
8160
- loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-xs text-faint", children: "Loading..." }),
8182
+ loading && /* @__PURE__ */ jsxRuntime.jsx(
8183
+ "div",
8184
+ {
8185
+ className: "px-3 py-2",
8186
+ role: "status",
8187
+ "aria-label": "Loading sessions",
8188
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
8189
+ /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "h-3.5 w-28" }),
8190
+ /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "h-3 w-20" })
8191
+ ] })
8192
+ }
8193
+ ),
8161
8194
  error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-xs text-destructive", children: error }),
8162
8195
  !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 pb-2", children: topLevel.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
8163
8196
  TreeNodeRow,
@@ -8166,8 +8199,11 @@ function SessionTreePanel({
8166
8199
  depth: 0,
8167
8200
  activeSessionId: activeSessionId ?? "",
8168
8201
  canStop: Boolean(adapter.stopSession),
8202
+ canDelete: Boolean(adapter.deleteSession),
8203
+ deletingSessionId,
8169
8204
  onSelect: handleSelect,
8170
- onStop: handleStop
8205
+ onStop: handleStop,
8206
+ onDelete: handleDelete
8171
8207
  },
8172
8208
  entry.id
8173
8209
  )) })