@ai-matrx/chat 0.6.5 → 0.6.6
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/CHANGELOG.md +55 -0
- package/dist/agents/components/chat/ChatHistorySidebar.d.ts +4 -4
- package/dist/agents/components/chat/ChatHistorySidebar.js +2 -2
- package/dist/agents/components/chat/ChatSidebarMenu.js +1 -1
- package/dist/agents/components/conversation-actions/conversationActionRegistry.d.ts +8 -0
- package/dist/agents/components/conversation-actions/conversationActionRegistry.js +11 -4
- package/dist/agents/components/conversation-history/ConversationArchiveSection.d.ts +7 -0
- package/dist/agents/components/conversation-history/{ConversationTrashSection.js → ConversationArchiveSection.js} +20 -20
- package/dist/agents/components/conversation-history/ConversationHistorySidebar.d.ts +29 -10
- package/dist/agents/components/conversation-history/ConversationHistorySidebar.js +75 -76
- package/dist/agents/components/conversation-history/ConversationPickerWindow.js +1 -2
- package/dist/agents/components/inputs/smart-input/RunControlsTabPanel.d.ts +2 -0
- package/dist/agents/components/inputs/smart-input/RunControlsTabPanel.js +8 -1
- package/dist/agents/components/inputs/smart-input/RunToolPicker.js +2 -1
- package/dist/agents/components/run/AgentRunHeader.js +11 -5
- package/dist/agents/components/shared/AgentHeaderMobile.js +1 -0
- package/dist/agents/components/shared/AgentModeController.js +90 -27
- package/dist/agents/components/shared/AgentSelectorIsland.js +2 -1
- package/dist/agents/redux/agent-definition/run-tier-record.js +1 -0
- package/dist/agents/redux/conversation-history/history-filters.d.ts +6 -0
- package/dist/agents/redux/conversation-history/history-filters.js +4 -1
- package/dist/agents/redux/conversation-history/selectors.js +2 -3
- package/dist/agents/redux/conversation-history/slice.d.ts +6 -9
- package/dist/agents/redux/conversation-history/slice.js +14 -23
- package/dist/agents/redux/conversation-history/thunks.js +1 -23
- package/dist/agents/redux/conversation-history/types.d.ts +12 -12
- package/dist/agents/redux/conversation-history/types.js +4 -6
- package/dist/agents/redux/conversation-list/conversation-list.selectors.d.ts +6 -0
- package/dist/agents/redux/conversation-list/conversation-list.selectors.js +10 -0
- package/dist/agents/redux/conversation-list/conversation-list.types.d.ts +6 -0
- package/dist/agents/redux/conversation-list/conversation-row-actions.thunks.js +11 -2
- package/dist/agents/redux/conversation-list/conversation-trash.thunks.d.ts +11 -4
- package/dist/agents/redux/conversation-list/conversation-trash.thunks.js +41 -20
- package/dist/agents/redux/execution-system/message-crud/soft-delete-conversation.thunk.js +5 -2
- package/dist/window-panels/windows/agents/AgentRunWindow.d.ts +1 -1
- package/dist/window-panels/windows/agents/AgentRunWindow.js +27 -21
- package/dist/window-panels/windows/agents/RunControlsWindow.js +1 -0
- package/package.json +5 -5
- package/dist/agents/components/conversation-history/ConversationTrashSection.d.ts +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.6.6
|
|
4
|
+
### Unreleased
|
|
5
|
+
|
|
6
|
+
- **One "Archived" place for every conversation list** (Arman, 2026-10-08). The separate "Archived (N)" toggle
|
|
7
|
+
and the "Trash" disclosure are gone; `ConversationArchiveSection` (was `ConversationTrashSection`) is the one
|
|
8
|
+
closed-by-default section at the foot of `ConversationHistorySidebar`, listing archived AND deleted
|
|
9
|
+
conversations with Restore on each (restore picks the right door: un-delete RPC, unarchive, or both). Live lists
|
|
10
|
+
never show archived rows. Removed: scope fields `archiveView` / `archivedCount`, actions `setScopeArchiveView` /
|
|
11
|
+
`setScopeArchivedCount`, `rowMatchesArchiveView` (→ `isLiveConversationRow`); `fetchTrashedConversations` →
|
|
12
|
+
`fetchArchivedConversations`. `ConversationListItem.deletedAt` added.
|
|
13
|
+
- Consumer header is ONE row: an always-visible search field + refresh + filter. No heading text: prop
|
|
14
|
+
`historyLabel` removed; `initialSearchOpen` → `autoFocusSearch`.
|
|
15
|
+
- `AgentRunWindow`: title is the open chat's title (else the agent's name) with no "Run" prefix; New chat is a
|
|
16
|
+
quiet theme-token icon beside the sidebar toggle (`actionsLeft`), not a primary disc on the right.
|
|
17
|
+
|
|
18
|
+
Consumer action: required if you pass `historyLabel` (delete it) or `initialSearchOpen` (rename to `autoFocusSearch`).
|
|
19
|
+
|
|
20
|
+
- `ConversationHistorySidebar` takes `keepLoaded`: a host that freezes and wakes the list (a board tile's
|
|
21
|
+
Activity re-runs every effect on wake) no longer re-reads it. The mount read is skipped while the scope
|
|
22
|
+
was last read for the same agents, source filter and lanes; a changed filter, Refresh, search and
|
|
23
|
+
"load more" still read. Default false (the list refreshes on every mount, as before).
|
|
24
|
+
|
|
25
|
+
- `ConversationHistorySidebar` takes `onlyConversationIds`: a list of "the conversations that belong to THIS
|
|
26
|
+
thing" (a board's). The read is `id in (...)` (a `[]` set reads nothing), the archived count follows, a
|
|
27
|
+
conversation minted elsewhere is not live-upserted into it, and a changed set reads again. New scope field
|
|
28
|
+
`onlyConversationIds` + action `setScopeOnlyConversationIds`. Pair with `serverSearch={false}`.
|
|
29
|
+
|
|
30
|
+
- `ConversationHistorySidebar` takes `removeFromList` ({ label, onRemove }): a row-menu entry for a scoped list that
|
|
31
|
+
takes the conversation OUT of the list (never out of existence); the host performs the removal.
|
|
32
|
+
|
|
33
|
+
Consumer action: optional — pass `keepLoaded` where the list can sleep and wake; `onlyConversationIds` (+ `removeFromList`) for a scoped list.
|
|
3
34
|
## 0.6.5
|
|
35
|
+
|
|
36
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
37
|
+
`git diff npm/chat/v0.6.4..npm/chat/v0.6.5 -- apps/shared/chat`).
|
|
38
|
+
No source changes intended and no consumer action required.
|
|
39
|
+
|
|
40
|
+
## 0.6.4
|
|
41
|
+
|
|
42
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
43
|
+
`git diff npm/chat/v0.6.3..npm/chat/v0.6.4 -- apps/shared/chat`).
|
|
44
|
+
No source changes intended and no consumer action required.
|
|
45
|
+
|
|
46
|
+
## 0.6.3
|
|
47
|
+
|
|
48
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
49
|
+
`git diff npm/chat/v0.6.2..npm/chat/v0.6.3 -- apps/shared/chat`).
|
|
50
|
+
No source changes intended and no consumer action required.
|
|
51
|
+
|
|
52
|
+
## 0.6.2
|
|
53
|
+
|
|
54
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
55
|
+
`git diff npm/chat/v0.6.1..npm/chat/v0.6.2 -- apps/shared/chat`).
|
|
56
|
+
No source changes intended and no consumer action required.
|
|
57
|
+
|
|
58
|
+
## 0.6.1
|
|
4
59
|
### rendered-output P2 WP4: every artifact type attaches
|
|
5
60
|
|
|
6
61
|
- `RenderedArtifactSource` carries `artifact_type`, the type's own `options` (first = default,
|
|
@@ -42,11 +42,11 @@ export interface ChatHistorySidebarProps {
|
|
|
42
42
|
* (ALLOW-list). Still honored and AND-ed with the surface filter.
|
|
43
43
|
*/
|
|
44
44
|
excludeSourceFeatures?: string[];
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
/** Suppress the built-in inline
|
|
45
|
+
/** Put the caret in the search field on mount. */
|
|
46
|
+
autoFocusSearch?: boolean;
|
|
47
|
+
/** Suppress the built-in inline search field. */
|
|
48
48
|
hideSearchAffordance?: boolean;
|
|
49
49
|
className?: string;
|
|
50
50
|
}
|
|
51
|
-
export declare function ChatHistorySidebar({ scopeId, activeConversationId, onOpenConversation, openInPlace, headerSlot, topSlot, surfaceId, excludeSourceFeatures,
|
|
51
|
+
export declare function ChatHistorySidebar({ scopeId, activeConversationId, onOpenConversation, openInPlace, headerSlot, topSlot, surfaceId, excludeSourceFeatures, autoFocusSearch, hideSearchAffordance, className, }: ChatHistorySidebarProps): React.JSX.Element;
|
|
52
52
|
export default ChatHistorySidebar;
|
|
@@ -10,7 +10,7 @@ function ChatHistorySidebar({
|
|
|
10
10
|
topSlot,
|
|
11
11
|
surfaceId = "chat",
|
|
12
12
|
excludeSourceFeatures,
|
|
13
|
-
|
|
13
|
+
autoFocusSearch = false,
|
|
14
14
|
hideSearchAffordance = false,
|
|
15
15
|
className
|
|
16
16
|
}) {
|
|
@@ -27,7 +27,7 @@ function ChatHistorySidebar({
|
|
|
27
27
|
headerSlot,
|
|
28
28
|
topSlot,
|
|
29
29
|
excludeSourceFeatures,
|
|
30
|
-
|
|
30
|
+
autoFocusSearch,
|
|
31
31
|
hideSearchAffordance,
|
|
32
32
|
serverSearch: true,
|
|
33
33
|
className
|
|
@@ -54,6 +54,14 @@ export interface ConversationMenuContext {
|
|
|
54
54
|
onShowOnly?: () => void;
|
|
55
55
|
/** Remove this conversation's source from the current scope's view. */
|
|
56
56
|
onHide?: () => void;
|
|
57
|
+
/**
|
|
58
|
+
* A list that holds only what belongs to one thing (a board's chats) offers
|
|
59
|
+
* to take THIS conversation out of it — never out of existence.
|
|
60
|
+
*/
|
|
61
|
+
removeFromList?: {
|
|
62
|
+
label: string;
|
|
63
|
+
run: () => void;
|
|
64
|
+
};
|
|
57
65
|
};
|
|
58
66
|
/**
|
|
59
67
|
* Inline rename belongs to ItemRow. Set false when a non-row host consumes
|
|
@@ -144,6 +144,14 @@ function buildConversationMenu(ctx) {
|
|
|
144
144
|
hidden: !ctx.onAddToProject,
|
|
145
145
|
onSelect: () => ctx.onAddToProject?.()
|
|
146
146
|
},
|
|
147
|
+
{
|
|
148
|
+
id: "remove-from-list",
|
|
149
|
+
label: src?.removeFromList?.label ?? "Remove from this list",
|
|
150
|
+
icon: FilterX,
|
|
151
|
+
description: "The conversation itself is kept",
|
|
152
|
+
hidden: !src?.removeFromList,
|
|
153
|
+
onSelect: () => src?.removeFromList?.run()
|
|
154
|
+
},
|
|
147
155
|
{
|
|
148
156
|
id: "duplicate",
|
|
149
157
|
label: "Duplicate",
|
|
@@ -249,9 +257,8 @@ function buildConversationMenu(ctx) {
|
|
|
249
257
|
const ok = await confirm({
|
|
250
258
|
title: "Delete conversation",
|
|
251
259
|
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
252
|
-
"This moves ",
|
|
253
260
|
/* @__PURE__ */ jsx("b", { children: displayTitle(ctx.title) }),
|
|
254
|
-
"
|
|
261
|
+
" moves to Archived at the bottom of the conversation list. You can restore it there."
|
|
255
262
|
] }),
|
|
256
263
|
confirmLabel: "Delete",
|
|
257
264
|
variant: "destructive"
|
|
@@ -264,8 +271,8 @@ function buildConversationMenu(ctx) {
|
|
|
264
271
|
toast.error(result.payload?.message ?? "Delete failed");
|
|
265
272
|
} else {
|
|
266
273
|
ctx.onMutationSuccess?.();
|
|
267
|
-
toast.success("
|
|
268
|
-
description: "Restore it from
|
|
274
|
+
toast.success("Moved to Archived", {
|
|
275
|
+
description: "Restore it from Archived at the bottom of the list."
|
|
269
276
|
});
|
|
270
277
|
}
|
|
271
278
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
interface ConversationArchiveSectionProps {
|
|
2
|
+
/** `"consumer"` matches the comfortable /chat sidebar; `"dense"` the rest. */
|
|
3
|
+
variant?: "dense" | "consumer";
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function ConversationArchiveSection({ variant, className, }: ConversationArchiveSectionProps): import("react").JSX.Element;
|
|
7
|
+
export default ConversationArchiveSection;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback,
|
|
4
|
-
import {
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { Archive, Loader2, Undo2 } from "lucide-react";
|
|
5
5
|
import { cn } from "@ai-matrx/design-system";
|
|
6
6
|
import { toast } from "../../../host/notify.js";
|
|
7
7
|
import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
|
|
8
8
|
import {
|
|
9
|
-
|
|
9
|
+
fetchArchivedConversations,
|
|
10
10
|
restoreConversation
|
|
11
11
|
} from "../../redux/conversation-list/conversation-trash.thunks.js";
|
|
12
12
|
import { ErrorAlchemyMenu } from "../../../host/ui-slots.js";
|
|
13
13
|
import { Button, DisclosureHeader } from "@ai-matrx/design-system/controls";
|
|
14
|
-
function
|
|
14
|
+
function ConversationArchiveSection({
|
|
15
15
|
variant = "dense",
|
|
16
16
|
className
|
|
17
17
|
}) {
|
|
@@ -26,11 +26,11 @@ function ConversationTrashSection({
|
|
|
26
26
|
);
|
|
27
27
|
const status = useAppSelector((state) => state.conversationList.trashStatus);
|
|
28
28
|
const error = useAppSelector((state) => state.conversationList.trashError);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
29
|
+
const toggle = () => {
|
|
30
|
+
const next = !open;
|
|
31
|
+
setOpen(next);
|
|
32
|
+
if (next) void dispatch(fetchArchivedConversations());
|
|
33
|
+
};
|
|
34
34
|
const handleRestore = useCallback(
|
|
35
35
|
async (conversationId) => {
|
|
36
36
|
setRestoringId(conversationId);
|
|
@@ -51,22 +51,22 @@ function ConversationTrashSection({
|
|
|
51
51
|
{
|
|
52
52
|
className: "w-full",
|
|
53
53
|
open,
|
|
54
|
-
onClick:
|
|
55
|
-
icon: /* @__PURE__ */ jsx(
|
|
56
|
-
title: "
|
|
54
|
+
onClick: toggle,
|
|
55
|
+
icon: /* @__PURE__ */ jsx(Archive, {}),
|
|
56
|
+
title: "Archived",
|
|
57
57
|
end: open && status === "succeeded" ? ids.length : void 0
|
|
58
58
|
}
|
|
59
59
|
),
|
|
60
|
-
open && /* @__PURE__ */ jsxs("div", { className: "pb-1", children: [
|
|
61
|
-
status === "loading" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-[11px] text-muted-foreground", children: [
|
|
60
|
+
open && /* @__PURE__ */ jsxs("div", { className: "max-h-64 overflow-y-auto pb-1", children: [
|
|
61
|
+
status === "loading" && ids.length === 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-[11px] text-muted-foreground", children: [
|
|
62
62
|
/* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }),
|
|
63
|
-
"Opening
|
|
63
|
+
"Opening\u2026"
|
|
64
64
|
] }),
|
|
65
65
|
status === "failed" && /* @__PURE__ */ jsxs("p", { className: "px-3 py-2 text-[11px] text-destructive", children: [
|
|
66
|
-
error ?? "
|
|
66
|
+
error ?? "Archived chats could not be read.",
|
|
67
67
|
/* @__PURE__ */ jsx(ErrorAlchemyMenu, { error })
|
|
68
68
|
] }),
|
|
69
|
-
status === "succeeded" && ids.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-2 text-[11px] text-muted-foreground", children: "Nothing
|
|
69
|
+
status === "succeeded" && ids.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-2 text-[11px] text-muted-foreground", children: "Nothing archived." }),
|
|
70
70
|
ids.map((id) => {
|
|
71
71
|
const conv = bodies[id];
|
|
72
72
|
if (!conv) return null;
|
|
@@ -99,8 +99,8 @@ function ConversationTrashSection({
|
|
|
99
99
|
] })
|
|
100
100
|
] });
|
|
101
101
|
}
|
|
102
|
-
var
|
|
102
|
+
var ConversationArchiveSection_default = ConversationArchiveSection;
|
|
103
103
|
export {
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
ConversationArchiveSection,
|
|
105
|
+
ConversationArchiveSection_default as default
|
|
106
106
|
};
|
|
@@ -97,20 +97,12 @@ export interface ConversationHistorySidebarProps {
|
|
|
97
97
|
topSlot?: React.ReactNode;
|
|
98
98
|
/** Extra header actions (shown next to grouping + search toggles). */
|
|
99
99
|
headerActions?: React.ReactNode;
|
|
100
|
-
/**
|
|
101
|
-
* Consumer variant only: the heading over the list. Default "Filtered Chats"
|
|
102
|
-
* (the /chat sidebar, which really does filter by source). A surface that
|
|
103
|
-
* lists one agent's conversations names what the list IS instead.
|
|
104
|
-
*/
|
|
105
|
-
historyLabel?: string;
|
|
106
100
|
/** Controls whether the built-in search input is shown. Default: true. */
|
|
107
101
|
showSearch?: boolean;
|
|
108
102
|
/** Controls whether the grouping toggle is shown. Default: true (dense). */
|
|
109
103
|
showGroupingToggle?: boolean;
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
*/
|
|
113
|
-
initialSearchOpen?: boolean;
|
|
104
|
+
/** Consumer variant only: put the caret in the search field on mount. */
|
|
105
|
+
autoFocusSearch?: boolean;
|
|
114
106
|
/**
|
|
115
107
|
* Consumer variant only: suppress the built-in inline search affordance
|
|
116
108
|
* (when surrounding chrome already provides a search entry point).
|
|
@@ -124,6 +116,33 @@ export interface ConversationHistorySidebarProps {
|
|
|
124
116
|
* list is not a conversation library.
|
|
125
117
|
*/
|
|
126
118
|
serverSearch?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Show only these conversations — a surface whose list is "what belongs to
|
|
121
|
+
* THIS thing" (a board's own conversations). `null`/omitted = no restriction;
|
|
122
|
+
* `[]` = an empty list with no read. The list reads again when the set
|
|
123
|
+
* changes. Pair with `serverSearch={false}`: the ranked search spans the
|
|
124
|
+
* person's whole library, so a restricted list filters what it holds.
|
|
125
|
+
*/
|
|
126
|
+
onlyConversationIds?: string[] | null;
|
|
127
|
+
/**
|
|
128
|
+
* With `onlyConversationIds`: a row menu entry that takes the conversation OUT
|
|
129
|
+
* of the list (e.g. "Remove from this board") without touching the
|
|
130
|
+
* conversation itself. The host does the removal; the list then shrinks when
|
|
131
|
+
* the host passes the smaller set.
|
|
132
|
+
*/
|
|
133
|
+
removeFromList?: {
|
|
134
|
+
label: string;
|
|
135
|
+
onRemove: (conv: ConversationListItem) => void;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* A host that FREEZES and wakes this component (a board tile's Activity,
|
|
139
|
+
* which re-runs every effect on wake) sets this so a wake never re-reads the
|
|
140
|
+
* list: the mount fetch is skipped while the scope already holds a first page
|
|
141
|
+
* that was read for the same agents, source filter and lanes. A changed
|
|
142
|
+
* filter, Refresh, search and "load more" still read. Default false — the
|
|
143
|
+
* list refreshes each time it mounts.
|
|
144
|
+
*/
|
|
145
|
+
keepLoaded?: boolean;
|
|
127
146
|
className?: string;
|
|
128
147
|
/** Extra classes for the built-in search input (dense). */
|
|
129
148
|
searchInputClassName?: string;
|
|
@@ -16,8 +16,7 @@ import {
|
|
|
16
16
|
Loader2,
|
|
17
17
|
Search,
|
|
18
18
|
Star,
|
|
19
|
-
StarOff
|
|
20
|
-
X
|
|
19
|
+
StarOff
|
|
21
20
|
} from "lucide-react";
|
|
22
21
|
import { cn } from "@ai-matrx/design-system";
|
|
23
22
|
import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
|
|
@@ -38,8 +37,8 @@ import { selectIsStreaming } from "../../redux/execution-system/selectors/aggreg
|
|
|
38
37
|
import {
|
|
39
38
|
seedScopeSourceFilter,
|
|
40
39
|
setScopeAgentIds,
|
|
41
|
-
setScopeArchiveView,
|
|
42
40
|
setScopeLanes,
|
|
41
|
+
setScopeOnlyConversationIds,
|
|
43
42
|
setScopeGrouping,
|
|
44
43
|
setScopeSearch,
|
|
45
44
|
setScopeSourceFilter
|
|
@@ -51,7 +50,8 @@ import {
|
|
|
51
50
|
sourceKey
|
|
52
51
|
} from "../../redux/conversation-history/source-registry.js";
|
|
53
52
|
import {
|
|
54
|
-
|
|
53
|
+
CONVERSATION_HISTORY_TTL_MS,
|
|
54
|
+
isLiveConversationRow
|
|
55
55
|
} from "../../redux/conversation-history/types.js";
|
|
56
56
|
import { useSurfaceSourceFilter } from "../../redux/conversation-history/useSurfaceSourceFilter.js";
|
|
57
57
|
import {
|
|
@@ -65,9 +65,8 @@ import { selectAgentById } from "../../redux/agent-definition/selectors.js";
|
|
|
65
65
|
import { EntityDoorControls } from "../../../host/ui-slots.js";
|
|
66
66
|
import { ConversationSourceFilterTree } from "./ConversationSourceFilterTree.js";
|
|
67
67
|
import { AllLanesOffNotice } from "./ConversationLaneToggles.js";
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
70
|
-
import { SegmentedControl, Button, DisclosureHeader } from "@ai-matrx/design-system/controls";
|
|
68
|
+
import { ConversationArchiveSection } from "./ConversationArchiveSection.js";
|
|
69
|
+
import { SegmentedControl, Button, DisclosureHeader, SearchField } from "@ai-matrx/design-system/controls";
|
|
71
70
|
import { ItemRow } from "../../../host/ui-slots.js";
|
|
72
71
|
import { toast } from "../../../host/notify.js";
|
|
73
72
|
import {
|
|
@@ -89,9 +88,13 @@ function useConversationHistoryController(props) {
|
|
|
89
88
|
isFavorite,
|
|
90
89
|
onToggleFavorite,
|
|
91
90
|
getConversationHref,
|
|
92
|
-
serverSearch = true
|
|
91
|
+
serverSearch = true,
|
|
92
|
+
keepLoaded = false,
|
|
93
|
+
onlyConversationIds = null,
|
|
94
|
+
removeFromList
|
|
93
95
|
} = props;
|
|
94
96
|
const dispatch = useAppDispatch();
|
|
97
|
+
const readFor = useRef(null);
|
|
95
98
|
const selectScope = useMemo(
|
|
96
99
|
() => makeSelectConversationHistoryScope(scopeId),
|
|
97
100
|
[scopeId]
|
|
@@ -120,6 +123,7 @@ function useConversationHistoryController(props) {
|
|
|
120
123
|
[agentIds]
|
|
121
124
|
);
|
|
122
125
|
const excludeKey = JSON.stringify(excludeSourceFeatures ?? []);
|
|
126
|
+
const onlyKey = onlyConversationIds ? onlyConversationIds.slice().sort().join(",") : null;
|
|
123
127
|
const resolvedKey = surfaceId ? JSON.stringify(resolvedSource) : "";
|
|
124
128
|
const enabledLanes = useAppSelector(selectConversationLanes);
|
|
125
129
|
const lanesKey = surfaceId ? enabledLanes.join(",") : "none";
|
|
@@ -136,6 +140,12 @@ function useConversationHistoryController(props) {
|
|
|
136
140
|
});
|
|
137
141
|
useEffect(() => {
|
|
138
142
|
dispatch(setScopeAgentIds({ scopeId, agentIds: agentIds.slice() }));
|
|
143
|
+
dispatch(
|
|
144
|
+
setScopeOnlyConversationIds({
|
|
145
|
+
scopeId,
|
|
146
|
+
ids: onlyConversationIds ? onlyConversationIds.slice() : null
|
|
147
|
+
})
|
|
148
|
+
);
|
|
139
149
|
dispatch(
|
|
140
150
|
setScopeLanes({ scopeId, lanes: surfaceId ? enabledLanes : null })
|
|
141
151
|
);
|
|
@@ -159,6 +169,19 @@ function useConversationHistoryController(props) {
|
|
|
159
169
|
if (excludeSourceFeatures !== void 0) {
|
|
160
170
|
fetchArgs.excludeSourceFeatures = excludeSourceFeatures;
|
|
161
171
|
}
|
|
172
|
+
const readKey = [scopeId, agentIdsKey, pageSize, excludeKey, resolvedKey, lanesKey, onlyKey].join("|");
|
|
173
|
+
if (keepLoaded) {
|
|
174
|
+
if (readFor.current === readKey) return;
|
|
175
|
+
const fresh = dispatch((_d, getState) => {
|
|
176
|
+
const held = getState().conversationHistory.scopes[scopeId];
|
|
177
|
+
return !!held && held.status === "succeeded" && held.lastFetchedAt !== null && Date.now() - held.lastFetchedAt < CONVERSATION_HISTORY_TTL_MS;
|
|
178
|
+
});
|
|
179
|
+
if (fresh) {
|
|
180
|
+
readFor.current = readKey;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
readFor.current = readKey;
|
|
162
185
|
void dispatch(fetchConversationHistory(fetchArgs));
|
|
163
186
|
}, [
|
|
164
187
|
dispatch,
|
|
@@ -168,7 +191,9 @@ function useConversationHistoryController(props) {
|
|
|
168
191
|
excludeKey,
|
|
169
192
|
resolvedKey,
|
|
170
193
|
lanesKey,
|
|
171
|
-
surfaceId
|
|
194
|
+
surfaceId,
|
|
195
|
+
keepLoaded,
|
|
196
|
+
onlyKey
|
|
172
197
|
]);
|
|
173
198
|
const onSearchChange = useCallback(
|
|
174
199
|
(value) => {
|
|
@@ -186,12 +211,6 @@ function useConversationHistoryController(props) {
|
|
|
186
211
|
if (!hasMore || status === "loading" || status === "loading-more") return;
|
|
187
212
|
void dispatch(fetchConversationHistory({ scopeId, replace: false }));
|
|
188
213
|
}, [dispatch, scopeId, hasMore, status]);
|
|
189
|
-
const onArchiveViewChange = (open) => {
|
|
190
|
-
dispatch(
|
|
191
|
-
setScopeArchiveView({ scopeId, view: open ? "archived" : "active" })
|
|
192
|
-
);
|
|
193
|
-
void dispatch(fetchConversationHistory({ scopeId, replace: true }));
|
|
194
|
-
};
|
|
195
214
|
const onRefresh = () => {
|
|
196
215
|
void dispatch(fetchConversationHistory({ scopeId, replace: true }));
|
|
197
216
|
if (surfaceId) {
|
|
@@ -228,9 +247,9 @@ function useConversationHistoryController(props) {
|
|
|
228
247
|
);
|
|
229
248
|
const favorites = useMemo(() => {
|
|
230
249
|
return scope.items.filter(
|
|
231
|
-
(i) => isFavoriteResolved(i.conversationId) &&
|
|
250
|
+
(i) => isFavoriteResolved(i.conversationId) && isLiveConversationRow(i.status)
|
|
232
251
|
);
|
|
233
|
-
}, [scope.items,
|
|
252
|
+
}, [scope.items, isFavoriteResolved]);
|
|
234
253
|
const resolveHref = useCallback(
|
|
235
254
|
(conv) => getConversationHref ? getConversationHref(conv) : `/chat/${conv.conversationId}`,
|
|
236
255
|
[getConversationHref]
|
|
@@ -252,7 +271,7 @@ function useConversationHistoryController(props) {
|
|
|
252
271
|
},
|
|
253
272
|
[dispatch, scopeId]
|
|
254
273
|
);
|
|
255
|
-
const
|
|
274
|
+
const getSourceMenuBase = useCallback(
|
|
256
275
|
(conv) => {
|
|
257
276
|
if (!surfaceId) {
|
|
258
277
|
return {
|
|
@@ -309,6 +328,20 @@ function useConversationHistoryController(props) {
|
|
|
309
328
|
},
|
|
310
329
|
[surfaceId, scope, facets, commitSourceFilter]
|
|
311
330
|
);
|
|
331
|
+
const getSourceMenuCtx = useCallback(
|
|
332
|
+
(conv) => {
|
|
333
|
+
const base = getSourceMenuBase(conv);
|
|
334
|
+
if (!removeFromList) return base;
|
|
335
|
+
return {
|
|
336
|
+
...base,
|
|
337
|
+
removeFromList: {
|
|
338
|
+
label: removeFromList.label,
|
|
339
|
+
run: () => removeFromList.onRemove(conv)
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
},
|
|
343
|
+
[getSourceMenuBase, removeFromList]
|
|
344
|
+
);
|
|
312
345
|
return {
|
|
313
346
|
scope,
|
|
314
347
|
byDate,
|
|
@@ -323,7 +356,6 @@ function useConversationHistoryController(props) {
|
|
|
323
356
|
onGroupingChange,
|
|
324
357
|
onLoadMore,
|
|
325
358
|
onRefresh,
|
|
326
|
-
onArchiveViewChange,
|
|
327
359
|
isFavoriteResolved,
|
|
328
360
|
onToggleFavoriteResolved,
|
|
329
361
|
favorites,
|
|
@@ -383,7 +415,7 @@ const DenseView = ({
|
|
|
383
415
|
const cachedSearchItems = byDate.flatMap((bucket) => bucket.items);
|
|
384
416
|
const hasAuthoritativeResults = serverSearchState.isSettled && (serverSearchState.status === "succeeded" || serverSearchState.status === "loading-more" || serverSearchState.status === "failed" && serverSearchState.items.length > 0);
|
|
385
417
|
const searchItems = hasAuthoritativeResults ? serverSearchState.items.filter(
|
|
386
|
-
(item) =>
|
|
418
|
+
(item) => isLiveConversationRow(item.status)
|
|
387
419
|
) : cachedSearchItems;
|
|
388
420
|
const empty = !allLanesOff && status !== "loading" && status !== "idle" && !error && count === 0 && !searchTerm.trim() && emptyState;
|
|
389
421
|
const showControls = showSearch || showGroupingToggle || !!headerActions || !!surfaceId;
|
|
@@ -596,8 +628,7 @@ const DenseView = ({
|
|
|
596
628
|
count === 1 ? "" : "s"
|
|
597
629
|
] })
|
|
598
630
|
] }),
|
|
599
|
-
/* @__PURE__ */ jsx(
|
|
600
|
-
/* @__PURE__ */ jsx(ConversationTrashSection, { variant: "dense" })
|
|
631
|
+
/* @__PURE__ */ jsx(ConversationArchiveSection, { variant: "dense" })
|
|
601
632
|
] });
|
|
602
633
|
};
|
|
603
634
|
const ConsumerView = ({
|
|
@@ -609,13 +640,11 @@ const ConsumerView = ({
|
|
|
609
640
|
openInPlace = false,
|
|
610
641
|
headerSlot,
|
|
611
642
|
topSlot,
|
|
612
|
-
|
|
643
|
+
autoFocusSearch = false,
|
|
613
644
|
hideSearchAffordance = false,
|
|
614
|
-
historyLabel = "Filtered Chats",
|
|
615
645
|
pageSize = 30,
|
|
616
646
|
className
|
|
617
647
|
}) => {
|
|
618
|
-
const dispatch = useAppDispatch();
|
|
619
648
|
const {
|
|
620
649
|
byDate,
|
|
621
650
|
status,
|
|
@@ -636,27 +665,19 @@ const ConsumerView = ({
|
|
|
636
665
|
const cachedSearchItems = byDate.flatMap((bucket) => bucket.items);
|
|
637
666
|
const hasAuthoritativeResults = serverSearchState.isSettled && (serverSearchState.status === "succeeded" || serverSearchState.status === "loading-more" || serverSearchState.status === "failed" && serverSearchState.items.length > 0);
|
|
638
667
|
const searchItems = hasAuthoritativeResults ? serverSearchState.items.filter(
|
|
639
|
-
(item) =>
|
|
668
|
+
(item) => isLiveConversationRow(item.status)
|
|
640
669
|
) : cachedSearchItems;
|
|
641
670
|
const favoriteIds = useMemo(
|
|
642
671
|
() => new Set(favorites.map((f) => f.conversationId)),
|
|
643
672
|
[favorites]
|
|
644
673
|
);
|
|
645
|
-
const [searchOpen, setSearchOpen] = useState(initialSearchOpen);
|
|
646
674
|
const searchInputRef = useRef(null);
|
|
647
|
-
const openSearch = useCallback(() => {
|
|
648
|
-
setSearchOpen(true);
|
|
649
|
-
requestAnimationFrame(() => searchInputRef.current?.focus());
|
|
650
|
-
}, []);
|
|
651
675
|
useEffect(() => {
|
|
652
|
-
if (
|
|
676
|
+
if (autoFocusSearch) {
|
|
653
677
|
requestAnimationFrame(() => searchInputRef.current?.focus());
|
|
654
678
|
}
|
|
655
|
-
}, [
|
|
656
|
-
const
|
|
657
|
-
setSearchOpen(false);
|
|
658
|
-
dispatch(setScopeSearch({ scopeId, searchTerm: "" }));
|
|
659
|
-
}, [dispatch, scopeId]);
|
|
679
|
+
}, [autoFocusSearch]);
|
|
680
|
+
const showSearch = !hideSearchAffordance;
|
|
660
681
|
return /* @__PURE__ */ jsxs(
|
|
661
682
|
"div",
|
|
662
683
|
{
|
|
@@ -666,9 +687,22 @@ const ConsumerView = ({
|
|
|
666
687
|
),
|
|
667
688
|
children: [
|
|
668
689
|
headerSlot,
|
|
669
|
-
surfaceId && /* @__PURE__ */ jsxs("div", { className: "flex h-
|
|
670
|
-
/* @__PURE__ */ jsx(
|
|
671
|
-
|
|
690
|
+
(showSearch || surfaceId) && /* @__PURE__ */ jsxs("div", { className: "flex h-9 shrink-0 items-center gap-1 px-2", children: [
|
|
691
|
+
showSearch ? /* @__PURE__ */ jsx(
|
|
692
|
+
SearchField,
|
|
693
|
+
{
|
|
694
|
+
ref: searchInputRef,
|
|
695
|
+
value: searchTerm,
|
|
696
|
+
onChange: (e) => onSearchChange(e.target.value),
|
|
697
|
+
onKeyDown: (e) => {
|
|
698
|
+
if (e.key === "Escape") onSearchChange("");
|
|
699
|
+
},
|
|
700
|
+
placeholder: "Search",
|
|
701
|
+
"aria-label": "Search conversations",
|
|
702
|
+
className: "min-w-0 flex-1"
|
|
703
|
+
}
|
|
704
|
+
) : /* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
705
|
+
surfaceId && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center", children: [
|
|
672
706
|
status === "loading" ? /* @__PURE__ */ jsx(
|
|
673
707
|
LoadingTapButton,
|
|
674
708
|
{
|
|
@@ -699,25 +733,6 @@ const ConsumerView = ({
|
|
|
699
733
|
)
|
|
700
734
|
] })
|
|
701
735
|
] }),
|
|
702
|
-
!hideSearchAffordance && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-1 px-1 pb-1", children: searchOpen ? /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 items-center", children: [
|
|
703
|
-
/* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 h-3.5 w-3.5 text-muted-foreground" }),
|
|
704
|
-
/* @__PURE__ */ jsx(
|
|
705
|
-
"input",
|
|
706
|
-
{
|
|
707
|
-
ref: searchInputRef,
|
|
708
|
-
type: "search",
|
|
709
|
-
value: searchTerm,
|
|
710
|
-
onChange: (e) => onSearchChange(e.target.value),
|
|
711
|
-
onKeyDown: (e) => {
|
|
712
|
-
if (e.key === "Escape") closeSearch();
|
|
713
|
-
},
|
|
714
|
-
placeholder: "Search chats",
|
|
715
|
-
className: "h-8 w-full rounded-lg border border-border bg-background pl-8 pr-8 text-sm text-foreground placeholder:text-muted-foreground/70 outline-none focus:border-ring/40",
|
|
716
|
-
"aria-label": "Search conversations"
|
|
717
|
-
}
|
|
718
|
-
),
|
|
719
|
-
/* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(X, {}), onClick: closeSearch, "aria-label": "Close search", className: "absolute right-1.5" })
|
|
720
|
-
] }) : /* @__PURE__ */ jsx(Button, { variant: "quiet", icon: /* @__PURE__ */ jsx(Search, {}), onClick: openSearch, "aria-label": "Search conversations", className: "flex-1", children: "Search chats" }) }),
|
|
721
736
|
topSlot,
|
|
722
737
|
/* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto pt-1", children: [
|
|
723
738
|
error && !searchActive && /* @__PURE__ */ jsx(ReadFailure, { error, what: "your conversations", onRetry: onRefresh, className: "m-2" }),
|
|
@@ -794,27 +809,11 @@ const ConsumerView = ({
|
|
|
794
809
|
}
|
|
795
810
|
) })
|
|
796
811
|
] }),
|
|
797
|
-
/* @__PURE__ */ jsx(
|
|
798
|
-
/* @__PURE__ */ jsx(ConversationTrashSection, { variant: "consumer" })
|
|
812
|
+
/* @__PURE__ */ jsx(ConversationArchiveSection, { variant: "consumer" })
|
|
799
813
|
]
|
|
800
814
|
}
|
|
801
815
|
);
|
|
802
816
|
};
|
|
803
|
-
function ConversationArchivedToggle({ ctl }) {
|
|
804
|
-
const open = ctl.scope.archiveView === "archived";
|
|
805
|
-
const count = ctl.scope.archivedCount;
|
|
806
|
-
return /* @__PURE__ */ jsx(
|
|
807
|
-
ArchivedDisclosure,
|
|
808
|
-
{
|
|
809
|
-
count: count ?? 0,
|
|
810
|
-
countLabel: count === null ? null : void 0,
|
|
811
|
-
open,
|
|
812
|
-
onOpenChange: ctl.onArchiveViewChange,
|
|
813
|
-
keepWhileOpen: true,
|
|
814
|
-
className: "px-2 py-1"
|
|
815
|
-
}
|
|
816
|
-
);
|
|
817
|
-
}
|
|
818
817
|
const GroupingToggle = ({ value, onChange }) => /* @__PURE__ */ jsx(
|
|
819
818
|
SegmentedControl,
|
|
820
819
|
{
|
|
@@ -61,8 +61,7 @@ function ConversationPickerWindowInner({
|
|
|
61
61
|
activeConversationId: activeConversationId ?? null,
|
|
62
62
|
onOpenConversation: handleOpenConversation,
|
|
63
63
|
openInPlace: true,
|
|
64
|
-
|
|
65
|
-
initialSearchOpen: true,
|
|
64
|
+
autoFocusSearch: true,
|
|
66
65
|
className: "h-full bg-transparent"
|
|
67
66
|
}
|
|
68
67
|
)
|
|
@@ -22,6 +22,8 @@ export declare function useRunControlsState(conversationId: string, includeAttac
|
|
|
22
22
|
resolveTab: (tab: RunControlsTab) => RunControlsTab;
|
|
23
23
|
tabTrailing: (tabId: RunControlsTab) => ReactNode;
|
|
24
24
|
addedCount: number;
|
|
25
|
+
addedToolsCount: number;
|
|
26
|
+
addedSkillsCount: number;
|
|
25
27
|
hasModelOverride: boolean;
|
|
26
28
|
hasActiveContext: boolean;
|
|
27
29
|
anyDocActive: boolean;
|