@datum-cloud/datum-ui 2.9.1 → 2.10.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,9 +1,11 @@
1
1
  import { t as cn } from "../utils-Bu32wN-x.mjs";
2
+ import { t as Button } from "../button-DoMqfQLu.mjs";
2
3
  import { t as Icon } from "../icon-wrapper-DKfJlJd0.mjs";
4
+ import { t as Dialog } from "../dialog-Gu70fban.mjs";
3
5
  import { a as DropdownMenuRadioGroup, c as DropdownMenuTrigger, i as DropdownMenuLabel, o as DropdownMenuRadioItem, r as DropdownMenuContent, s as DropdownMenuSeparator, t as DropdownMenu } from "../dropdown-menu-b8zY5Cs6.mjs";
4
6
  import { t as Tooltip } from "../tooltip-DhbN1BDK.mjs";
5
7
  import { n as EASE, t as DURATION } from "../motion-DvGRWdsm.mjs";
6
- import { ArrowDown, ArrowRight, ArrowUp, Brain, Check, ChevronDown, Copy, Download, MessagesSquare, Mic, MicOff, PanelLeft, Plus, RotateCw, Square, Trash2 } from "lucide-react";
8
+ import { Archive, ArchiveRestore, ArrowDown, ArrowRight, ArrowUp, Brain, Check, ChevronDown, Copy, Download, MessagesSquare, Mic, MicOff, PanelLeft, Plus, RotateCw, Square, Trash2 } from "lucide-react";
7
9
  import { Fragment, createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
8
10
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
11
  import { AnimatePresence, motion } from "motion/react";
@@ -842,12 +844,32 @@ function downloadChat(chat) {
842
844
  a.click();
843
845
  URL.revokeObjectURL(url);
844
846
  }
845
- function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, header }) {
847
+ const ROW_ACTION_CLASS = "text-muted-foreground/40 shrink-0 rounded p-0.5 opacity-0 transition-colors group-hover:opacity-100";
848
+ function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete = false, header }) {
849
+ const archiveEnabled = Boolean(onArchiveChat);
846
850
  const [query, setQuery] = useState("");
851
+ const [showArchived, setShowArchived] = useState(false);
852
+ const [pendingDelete, setPendingDelete] = useState(null);
853
+ const inArchivedView = archiveEnabled && showArchived;
847
854
  const filtered = useMemo(() => {
855
+ const inView = archiveEnabled ? chatList.filter((c) => Boolean(c.archived) === showArchived) : chatList;
848
856
  const q = query.trim().toLowerCase();
849
- return q ? chatList.filter((c) => c.title.toLowerCase().includes(q)) : chatList;
850
- }, [chatList, query]);
857
+ return q ? inView.filter((c) => c.title.toLowerCase().includes(q)) : inView;
858
+ }, [
859
+ archiveEnabled,
860
+ chatList,
861
+ query,
862
+ showArchived
863
+ ]);
864
+ const emptyText = inArchivedView ? query ? "No matching archived chats" : "No archived chats" : query ? "No matching chats" : "No saved chats";
865
+ const handleDelete = (e, chat) => {
866
+ if (!confirmDelete) {
867
+ onDeleteChat(e, chat.id);
868
+ return;
869
+ }
870
+ e.stopPropagation();
871
+ setPendingDelete(chat);
872
+ };
851
873
  return /* @__PURE__ */ jsxs("div", {
852
874
  className: "bg-background flex h-full w-64 shrink-0 flex-col border-r",
853
875
  children: [
@@ -855,36 +877,90 @@ function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, heade
855
877
  className: "shrink-0 border-b px-3 py-2",
856
878
  children: header
857
879
  }),
858
- /* @__PURE__ */ jsx("div", {
859
- className: "flex h-12 items-center border-b px-2",
860
- children: /* @__PURE__ */ jsx("input", {
880
+ /* @__PURE__ */ jsxs("div", {
881
+ className: "flex h-12 items-center gap-1 border-b px-2",
882
+ children: [/* @__PURE__ */ jsx("input", {
861
883
  value: query,
862
884
  onChange: (e) => setQuery(e.target.value),
863
- placeholder: "Search chats…",
885
+ placeholder: inArchivedView ? "Search archived chats…" : "Search chats…",
864
886
  className: "bg-muted text-foreground placeholder:text-muted-foreground/70 focus:ring-primary/40 w-full rounded-md px-2.5 py-1.5 text-xs focus:ring-1 focus:outline-none"
865
- })
887
+ }), archiveEnabled && /* @__PURE__ */ jsx(Tooltip, {
888
+ message: showArchived ? "Show active chats" : "Show archived chats",
889
+ side: "bottom",
890
+ children: /* @__PURE__ */ jsx("button", {
891
+ type: "button",
892
+ "aria-label": "Archived",
893
+ "aria-pressed": showArchived,
894
+ onClick: () => setShowArchived((v) => !v),
895
+ className: cn("shrink-0 rounded-md p-1.5 transition-colors", showArchived ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground hover:bg-accent"),
896
+ children: /* @__PURE__ */ jsx(Icon, {
897
+ icon: Archive,
898
+ className: "size-3.5"
899
+ })
900
+ })
901
+ })]
866
902
  }),
867
903
  /* @__PURE__ */ jsx("div", {
868
904
  className: "flex flex-1 flex-col gap-0.5 overflow-y-auto p-2",
869
905
  children: filtered.length === 0 ? /* @__PURE__ */ jsx("p", {
870
906
  className: "text-muted-foreground/60 px-2 py-1 text-xs",
871
- children: query ? "No matching chats" : "No saved chats"
907
+ children: emptyText
872
908
  }) : filtered.map((chat) => /* @__PURE__ */ jsxs("button", {
873
909
  type: "button",
874
910
  onClick: () => onLoadChat(chat),
875
911
  className: cn("group w-full rounded-lg px-2 py-1.5 text-left transition-colors", chat.id === currentChatId ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground hover:bg-accent/50"),
876
912
  children: [/* @__PURE__ */ jsxs("span", {
877
913
  className: "flex items-center gap-1",
878
- children: [/* @__PURE__ */ jsx("span", {
879
- className: "min-w-0 flex-1 truncate text-xs font-medium",
880
- children: chat.title
881
- }), /* @__PURE__ */ jsx("span", {
882
- role: "button",
883
- onClick: (e) => onDeleteChat(e, chat.id),
884
- "aria-label": "Delete chat",
885
- className: "text-muted-foreground/40 hover:text-destructive shrink-0 rounded p-0.5 opacity-0 transition-colors group-hover:opacity-100",
886
- children: /* @__PURE__ */ jsx(Trash2, { className: "size-3" })
887
- })]
914
+ children: [
915
+ /* @__PURE__ */ jsx("span", {
916
+ className: "min-w-0 flex-1 truncate text-xs font-medium",
917
+ children: chat.title
918
+ }),
919
+ archiveEnabled && !inArchivedView && /* @__PURE__ */ jsx(Tooltip, {
920
+ message: "Archive chat",
921
+ side: "top",
922
+ children: /* @__PURE__ */ jsx("span", {
923
+ role: "button",
924
+ onClick: (e) => {
925
+ e.stopPropagation();
926
+ onArchiveChat?.(e, chat.id);
927
+ },
928
+ "aria-label": "Archive chat",
929
+ className: cn(ROW_ACTION_CLASS, "hover:text-foreground"),
930
+ children: /* @__PURE__ */ jsx(Icon, {
931
+ icon: Archive,
932
+ className: "size-3"
933
+ })
934
+ })
935
+ }),
936
+ inArchivedView && onUnarchiveChat && /* @__PURE__ */ jsx(Tooltip, {
937
+ message: "Unarchive chat",
938
+ side: "top",
939
+ children: /* @__PURE__ */ jsx("span", {
940
+ role: "button",
941
+ onClick: (e) => {
942
+ e.stopPropagation();
943
+ onUnarchiveChat(e, chat.id);
944
+ },
945
+ "aria-label": "Unarchive chat",
946
+ className: cn(ROW_ACTION_CLASS, "hover:text-foreground"),
947
+ children: /* @__PURE__ */ jsx(Icon, {
948
+ icon: ArchiveRestore,
949
+ className: "size-3"
950
+ })
951
+ })
952
+ }),
953
+ /* @__PURE__ */ jsx("span", {
954
+ role: "button",
955
+ onClick: (e) => handleDelete(e, chat),
956
+ "aria-label": "Delete chat",
957
+ className: cn(ROW_ACTION_CLASS, "hover:text-destructive"),
958
+ children: /* @__PURE__ */ jsx(Icon, {
959
+ icon: Trash2,
960
+ className: "size-3"
961
+ })
962
+ })
963
+ ]
888
964
  }), /* @__PURE__ */ jsxs("span", {
889
965
  className: "flex items-center gap-1",
890
966
  children: [/* @__PURE__ */ jsx("span", {
@@ -900,8 +976,11 @@ function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, heade
900
976
  downloadChat(chat);
901
977
  },
902
978
  "aria-label": "Download chat as Markdown",
903
- className: "text-muted-foreground/40 hover:text-foreground shrink-0 rounded p-0.5 opacity-0 transition-colors group-hover:opacity-100",
904
- children: /* @__PURE__ */ jsx(Download, { className: "size-3" })
979
+ className: cn(ROW_ACTION_CLASS, "hover:text-foreground"),
980
+ children: /* @__PURE__ */ jsx(Icon, {
981
+ icon: Download,
982
+ className: "size-3"
983
+ })
905
984
  })
906
985
  })]
907
986
  })]
@@ -910,13 +989,40 @@ function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, heade
910
989
  /* @__PURE__ */ jsx("p", {
911
990
  className: "text-muted-foreground mt-auto shrink-0 border-t px-3 py-2 text-[10px]",
912
991
  children: "Chats are saved to your browser's local storage."
992
+ }),
993
+ confirmDelete && /* @__PURE__ */ jsx(Dialog, {
994
+ open: pendingDelete !== null,
995
+ onOpenChange: (open) => !open && setPendingDelete(null),
996
+ children: /* @__PURE__ */ jsxs(Dialog.Content, {
997
+ className: "sm:max-w-md",
998
+ children: [/* @__PURE__ */ jsx(Dialog.Header, {
999
+ title: "Delete chat?",
1000
+ description: pendingDelete ? `"${pendingDelete.title}" will be permanently deleted. This can't be undone.` : void 0
1001
+ }), /* @__PURE__ */ jsxs(Dialog.Footer, {
1002
+ className: "border-t-0",
1003
+ children: [/* @__PURE__ */ jsx(Button, {
1004
+ type: "tertiary",
1005
+ theme: "outline",
1006
+ onClick: () => setPendingDelete(null),
1007
+ children: "Cancel"
1008
+ }), /* @__PURE__ */ jsx(Button, {
1009
+ type: "danger",
1010
+ theme: "solid",
1011
+ onClick: (e) => {
1012
+ if (pendingDelete) onDeleteChat(e, pendingDelete.id);
1013
+ setPendingDelete(null);
1014
+ },
1015
+ children: "Delete"
1016
+ })]
1017
+ })]
1018
+ })
913
1019
  })
914
1020
  ]
915
1021
  });
916
1022
  }
917
1023
  //#endregion
918
1024
  //#region src/components/features/assistant/components/assistant-workspace.tsx
919
- function AssistantWorkspace({ config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onSuggestion, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory }) {
1025
+ function AssistantWorkspace({ config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete, onSuggestion, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory }) {
920
1026
  const hasMessages = messages.length > 0;
921
1027
  const promptCard = /* @__PURE__ */ jsx(PromptCard, {
922
1028
  editor,
@@ -961,7 +1067,10 @@ function AssistantWorkspace({ config, userName, title, messages, status, error,
961
1067
  currentChatId,
962
1068
  header: sidebarHeader,
963
1069
  onLoadChat,
964
- onDeleteChat
1070
+ onDeleteChat,
1071
+ onArchiveChat,
1072
+ onUnarchiveChat,
1073
+ confirmDelete
965
1074
  })
966
1075
  })
967
1076
  }),
@@ -38,6 +38,16 @@ export interface AssistantWorkspaceProps {
38
38
  onNewChat: () => void;
39
39
  onLoadChat: (chat: ChatSummary) => void;
40
40
  onDeleteChat: (e: ReactMouseEvent, chatId: string) => void;
41
+ /**
42
+ * Opt-in archive support. When provided, `chatList` may mix archived and
43
+ * active chats; the history panel lists active chats by default and adds an
44
+ * "Archived" view toggle plus per-row archive actions.
45
+ */
46
+ onArchiveChat?: (e: ReactMouseEvent, chatId: string) => void;
47
+ /** Restores an archived chat from the history panel's archived view. */
48
+ onUnarchiveChat?: (e: ReactMouseEvent, chatId: string) => void;
49
+ /** Confirm (deletion can't be undone) before calling `onDeleteChat`. */
50
+ confirmDelete?: boolean;
41
51
  onSuggestion: (text: string) => void;
42
52
  modelId: string;
43
53
  effortId: EffortId;
@@ -50,4 +60,4 @@ export interface AssistantWorkspaceProps {
50
60
  historyOpen: boolean;
51
61
  onToggleHistory: () => void;
52
62
  }
53
- export declare function AssistantWorkspace({ config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onSuggestion, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory, }: AssistantWorkspaceProps): import("react").JSX.Element;
63
+ export declare function AssistantWorkspace({ config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete, onSuggestion, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory, }: AssistantWorkspaceProps): import("react").JSX.Element;
@@ -1,10 +1,24 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { ChatSummary } from '../../types';
3
- interface HistoryPanelProps {
3
+ export interface HistoryPanelProps {
4
4
  chatList: ChatSummary[];
5
5
  currentChatId: string;
6
6
  onLoadChat: (chat: ChatSummary) => void;
7
7
  onDeleteChat: (e: React.MouseEvent, chatId: string) => void;
8
+ /**
9
+ * Opt-in archive support. When provided, `chatList` may mix archived and
10
+ * active chats: the panel lists active chats by default, adds an "Archived"
11
+ * view toggle next to the search input, and gives active rows an archive
12
+ * action. When omitted, the `archived` flag is ignored and no archive UI renders.
13
+ */
14
+ onArchiveChat?: (e: React.MouseEvent, chatId: string) => void;
15
+ /** Restores an archived chat. Rendered on rows in the archived view. */
16
+ onUnarchiveChat?: (e: React.MouseEvent, chatId: string) => void;
17
+ /**
18
+ * Ask for confirmation (deletion can't be undone) before calling
19
+ * `onDeleteChat`. Defaults to false, which deletes immediately.
20
+ */
21
+ confirmDelete?: boolean;
8
22
  /**
9
23
  * Optional host-supplied block pinned above the search input, naming the scope
10
24
  * the listed chats belong to — cloud-portal shows the current project. Hosts
@@ -12,5 +26,4 @@ interface HistoryPanelProps {
12
26
  */
13
27
  header?: ReactNode;
14
28
  }
15
- export declare function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, header, }: HistoryPanelProps): import("react").JSX.Element;
16
- export {};
29
+ export declare function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete, header, }: HistoryPanelProps): import("react").JSX.Element;
@@ -63,4 +63,9 @@ export interface ChatSummary {
63
63
  title: string;
64
64
  updatedAt: number;
65
65
  messages: UIMessage[];
66
+ /**
67
+ * Archived (hidden but restorable). Only honoured when the host passes
68
+ * `onArchiveChat`; otherwise every chat is listed regardless of this flag.
69
+ */
70
+ archived?: boolean;
66
71
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datum-cloud/datum-ui",
3
3
  "type": "module",
4
- "version": "2.9.1",
4
+ "version": "2.10.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "url": "https://github.com/datum-cloud/datum-ui"