@agent-native/core 0.217.0 → 0.219.0

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.
@@ -1,3 +1,4 @@
1
+ import { appPath } from "@agent-native/core/client/api-path";
1
2
  import { useT } from "@agent-native/core/client/i18n";
2
3
  import {
3
4
  navigateWithAgentChatViewTransition,
@@ -5,14 +6,15 @@ import {
5
6
  useChatThreads,
6
7
  type ChatThreadSummary,
7
8
  } from "@agent-native/toolkit/app/chat/agentkit-chat/rail";
9
+ import {
10
+ ownDraftRows,
11
+ OwnershipChatHistoryRail,
12
+ useOwnershipChatHistory,
13
+ type OwnDraftRow,
14
+ } from "@agent-native/toolkit/app/chat/ownership-history";
8
15
  import { OrgSwitcher } from "@agent-native/toolkit/app/org";
9
16
  import { openCommandMenu } from "@agent-native/toolkit/app/shared";
10
17
  import { AgentNativeIcon } from "@agent-native/toolkit/app/shared/AgentNativeIcon";
11
- import {
12
- ChatHistoryList,
13
- type ChatHistoryItem,
14
- type ChatHistorySection,
15
- } from "@agent-native/toolkit/chat-history/ChatHistoryList";
16
18
  import {
17
19
  IconApps,
18
20
  IconClock,
@@ -25,7 +27,7 @@ import {
25
27
  IconPin,
26
28
  IconSearch,
27
29
  } from "@tabler/icons-react";
28
- import { useEffect, useMemo, useRef, useState } from "react";
30
+ import { useEffect, useRef, useState } from "react";
29
31
  import { Link, useLocation, useNavigate } from "react-router";
30
32
  import { toast } from "sonner";
31
33
 
@@ -41,7 +43,6 @@ import {
41
43
  } from "@/components/ui/tooltip";
42
44
  import { APP_TITLE } from "@/lib/app-config";
43
45
  import { chatThreadPath, HOME_PATH, threadIdFromPath } from "@/lib/chat-paths";
44
- import { visibleChatThreads } from "@/lib/sidebar-thread-state";
45
46
  import { cn } from "@/lib/utils";
46
47
 
47
48
  const CHAT_STORAGE_KEY = "chat";
@@ -53,11 +54,9 @@ interface SidebarProps {
53
54
  onCollapsedChange?: (collapsed: boolean) => void;
54
55
  }
55
56
 
56
- function threadTitle(thread: ChatThreadSummary, untitledLabel: string) {
57
- return thread.title || untitledLabel;
58
- }
59
-
60
- function threadUpdatedAt(thread: ChatThreadSummary) {
57
+ function threadUpdatedAt(
58
+ thread: Pick<ChatThreadSummary, "createdAt" | "updatedAt">,
59
+ ) {
61
60
  return Number.isFinite(thread.updatedAt)
62
61
  ? thread.updatedAt
63
62
  : Number.isFinite(thread.createdAt)
@@ -65,14 +64,6 @@ function threadUpdatedAt(thread: ChatThreadSummary) {
65
64
  : 0;
66
65
  }
67
66
 
68
- function compareRecentThreads(a: ChatThreadSummary, b: ChatThreadSummary) {
69
- return threadUpdatedAt(b) - threadUpdatedAt(a);
70
- }
71
-
72
- function comparePinnedThreads(a: ChatThreadSummary, b: ChatThreadSummary) {
73
- return (b.pinnedAt ?? 0) - (a.pinnedAt ?? 0);
74
- }
75
-
76
67
  function formatRelativeTime(timestamp: number) {
77
68
  const elapsed = timestamp - Date.now();
78
69
  const intervals = [
@@ -106,7 +97,10 @@ function ThreadHistoryTitle({
106
97
  title,
107
98
  running,
108
99
  }: {
109
- thread: ChatThreadSummary;
100
+ thread: OwnDraftRow &
101
+ Partial<
102
+ Pick<ChatThreadSummary, "preview" | "scope" | "source" | "pinnedAt">
103
+ >;
110
104
  title: string;
111
105
  running: boolean;
112
106
  }) {
@@ -287,7 +281,7 @@ function ChatThreadsSection({ collapsed }: { collapsed: boolean }) {
287
281
  pinThread,
288
282
  archiveThread,
289
283
  renameThread,
290
- refreshThreads,
284
+ isNewThread,
291
285
  } = useChatThreads(undefined, CHAT_STORAGE_KEY, undefined, {
292
286
  autoCreate: false,
293
287
  restoreActiveThread: false,
@@ -295,66 +289,46 @@ function ChatThreadsSection({ collapsed }: { collapsed: boolean }) {
295
289
  const { workingThreadIds, observedThreadStarts } =
296
290
  useAgentChatRunningThreads();
297
291
 
298
- const visibleThreads = useMemo(
299
- () => visibleChatThreads(threads, observedThreadStarts),
300
- [observedThreadStarts, threads],
301
- );
302
- const displayedActiveThreadId =
303
- threadIdFromPath(location.pathname) ??
304
- activeThreadId ??
305
- persistedActiveThreadId();
306
-
307
- const historySections = useMemo<ChatHistorySection[]>(() => {
308
- const untitledLabel = t("chat.untitledChat");
309
- const toItem = (thread: ChatThreadSummary): ChatHistoryItem => {
310
- const title = threadTitle(thread, untitledLabel);
292
+ const localMineRows = ownDraftRows({
293
+ threads,
294
+ isNewThread,
295
+ observedThreadStarts,
296
+ });
297
+ const history = useOwnershipChatHistory({
298
+ getThreadUrl: (threadId) => appPath(chatThreadPath(threadId)),
299
+ activeId:
300
+ threadIdFromPath(location.pathname) ??
301
+ activeThreadId ??
302
+ persistedActiveThreadId(),
303
+ localMineRows,
304
+ pinned: { label: t("chat.pinned") },
305
+ mutations: {
306
+ pin: pinThread,
307
+ rename: renameThread,
308
+ archive: archiveThread,
309
+ },
310
+ onMutationFailed: (kind) => {
311
+ if (kind === "archive") toast.error(t("chat.archiveFailed"));
312
+ if (kind === "rename") toast.error(t("chat.renameFailed"));
313
+ },
314
+ decorateItem: (item, row) => {
315
+ const title = row.title || row.preview || t("chat.untitledChat");
311
316
  return {
312
- id: thread.id,
317
+ ...item,
313
318
  title: (
314
319
  <ThreadHistoryTitle
315
- thread={thread}
320
+ thread={row}
316
321
  title={title}
317
- running={workingThreadIds.has(thread.id)}
322
+ running={workingThreadIds.has(row.id)}
318
323
  />
319
324
  ),
320
- titleText: title,
321
- pinned: Boolean(thread.pinnedAt),
322
325
  };
323
- };
324
- const pinned = visibleThreads
325
- .filter((thread) => Boolean(thread.pinnedAt))
326
- .sort(comparePinnedThreads)
327
- .map(toItem);
328
- const recent = visibleThreads
329
- .filter((thread) => !thread.pinnedAt)
330
- .sort(compareRecentThreads)
331
- .slice(0, 30)
332
- .map(toItem);
333
-
334
- return [
335
- { id: "pinned", label: t("chat.pinned"), items: pinned },
336
- { id: "recents", label: t("chat.recents"), items: recent },
337
- ];
338
- }, [t, visibleThreads, workingThreadIds]);
339
-
340
- useEffect(() => {
341
- const refresh = () => refreshThreads();
342
- const handleRunning = (event: Event) => {
343
- const detail = (event as CustomEvent).detail as
344
- | { isRunning?: unknown }
345
- | undefined;
346
- if (typeof detail?.isRunning === "boolean") refreshThreads();
347
- };
348
-
349
- window.addEventListener("agent-chat:threads-updated", refresh);
350
- window.addEventListener("agentNative.chatRunning", handleRunning);
351
- window.addEventListener("focus", refresh);
352
- return () => {
353
- window.removeEventListener("agent-chat:threads-updated", refresh);
354
- window.removeEventListener("agentNative.chatRunning", handleRunning);
355
- window.removeEventListener("focus", refresh);
356
- };
357
- }, [refreshThreads]);
326
+ },
327
+ });
328
+ const displayedActiveThreadId =
329
+ threadIdFromPath(location.pathname) ??
330
+ activeThreadId ??
331
+ persistedActiveThreadId();
358
332
 
359
333
  function openThread(threadId: string, options?: { isNew?: boolean }) {
360
334
  switchThread(threadId);
@@ -382,23 +356,6 @@ function ChatThreadsSection({ collapsed }: { collapsed: boolean }) {
382
356
  if (threadId) openThread(threadId, { isNew: true });
383
357
  }
384
358
 
385
- async function handleArchiveThread(threadId: string) {
386
- const wasActive =
387
- threadId === activeThreadId || threadId === persistedActiveThreadId();
388
- const archived = await archiveThread(threadId);
389
- if (!archived) {
390
- toast.error(t("chat.archiveFailed"));
391
- return;
392
- }
393
- if (wasActive) await handleNewChat();
394
- }
395
-
396
- function handleRenameThread(threadId: string, title: string) {
397
- void renameThread(threadId, title).then((renamed) => {
398
- if (!renamed) toast.error(t("chat.renameFailed"));
399
- });
400
- }
401
-
402
359
  const newChatButton = (
403
360
  <button
404
361
  type="button"
@@ -430,17 +387,20 @@ function ChatThreadsSection({ collapsed }: { collapsed: boolean }) {
430
387
  return (
431
388
  <div className="flex min-h-0 flex-1 flex-col">
432
389
  <div className="px-2 pb-3">{newChatButton}</div>
433
- <ChatHistoryList
434
- sections={historySections}
390
+ <OwnershipChatHistoryRail
391
+ {...history}
392
+ onNewChat={() => void handleNewChat()}
393
+ showNewChat={false}
435
394
  activeId={displayedActiveThreadId}
436
395
  onSelect={openThread}
437
- onTogglePin={(threadId) => {
438
- const thread = visibleThreads.find((item) => item.id === threadId);
439
- if (thread) void pinThread(threadId, !thread.pinnedAt);
396
+ onDelete={async (threadId) => {
397
+ const wasActive =
398
+ threadId === activeThreadId ||
399
+ threadId === persistedActiveThreadId();
400
+ const archived = await history.listProps.onDelete?.(threadId);
401
+ if (archived && wasActive) await handleNewChat();
440
402
  }}
441
- onRename={handleRenameThread}
442
403
  renameMaxLength={160}
443
- onDelete={(threadId) => void handleArchiveThread(threadId)}
444
404
  labels={{
445
405
  options: (item) =>
446
406
  t("chat.optionsFor", { title: item.titleText ?? "" }),
@@ -451,8 +411,9 @@ function ChatThreadsSection({ collapsed }: { collapsed: boolean }) {
451
411
  unpin: t("chat.unpinChat"),
452
412
  delete: t("chat.archiveChat"),
453
413
  }}
454
- variant="rail"
455
- emptyLabel={null}
414
+ railLabels={{
415
+ newChat: t("chat.newChat"),
416
+ }}
456
417
  className="chat-sidebar-history min-h-0 flex-1"
457
418
  listClassName="pb-4"
458
419
  />
@@ -1,29 +0,0 @@
1
- import type { ChatThreadSummary } from "@agent-native/toolkit/app/chat/agentkit-chat/index";
2
-
3
- export function visibleChatThreads(
4
- threads: ChatThreadSummary[],
5
- observedThreadStarts: ReadonlyMap<string, number>,
6
- ): ChatThreadSummary[] {
7
- const knownIds = new Set(threads.map((thread) => thread.id));
8
- const optimistic: ChatThreadSummary[] = [...observedThreadStarts].flatMap(
9
- ([id, startedAt]) =>
10
- knownIds.has(id)
11
- ? []
12
- : [
13
- {
14
- id,
15
- title: "",
16
- preview: "",
17
- messageCount: 0,
18
- createdAt: startedAt,
19
- updatedAt: startedAt,
20
- scope: null,
21
- },
22
- ],
23
- );
24
- return [...threads, ...optimistic].filter(
25
- (thread) =>
26
- !thread.archivedAt &&
27
- (thread.messageCount > 0 || observedThreadStarts.has(thread.id)),
28
- );
29
- }
@@ -1,29 +0,0 @@
1
- import type { ChatThreadSummary } from "@agent-native/toolkit/app/chat/agentkit-chat/index";
2
-
3
- export function visibleChatThreads(
4
- threads: ChatThreadSummary[],
5
- observedThreadStarts: ReadonlyMap<string, number>,
6
- ): ChatThreadSummary[] {
7
- const knownIds = new Set(threads.map((thread) => thread.id));
8
- const optimistic: ChatThreadSummary[] = [...observedThreadStarts].flatMap(
9
- ([id, startedAt]) =>
10
- knownIds.has(id)
11
- ? []
12
- : [
13
- {
14
- id,
15
- title: "",
16
- preview: "",
17
- messageCount: 0,
18
- createdAt: startedAt,
19
- updatedAt: startedAt,
20
- scope: null,
21
- },
22
- ],
23
- );
24
- return [...threads, ...optimistic].filter(
25
- (thread) =>
26
- !thread.archivedAt &&
27
- (thread.messageCount > 0 || observedThreadStarts.has(thread.id)),
28
- );
29
- }