@agentprojectcontext/apx 1.56.2 → 1.57.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.
- package/package.json +1 -1
- package/src/core/stores/conversations.js +10 -0
- package/src/core/stores/messages.js +37 -6
- package/src/host/daemon/api/conversations.js +20 -2
- package/src/interfaces/web/dist/assets/index-CEI8DfVg.css +1 -0
- package/src/interfaces/web/dist/assets/index-DJ-ocXOR.js +651 -0
- package/src/interfaces/web/dist/assets/{index-DaE_memX.js.map → index-DJ-ocXOR.js.map} +1 -1
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +6 -6
- package/src/interfaces/web/src/App.tsx +1 -1
- package/src/interfaces/web/src/components/chat/ChatList.tsx +86 -65
- package/src/interfaces/web/src/components/common/TabNav.tsx +1 -1
- package/src/interfaces/web/src/hooks/useChat.ts +39 -7
- package/src/interfaces/web/src/i18n/en.ts +9 -3
- package/src/interfaces/web/src/i18n/es.ts +9 -3
- package/src/interfaces/web/src/lib/api/conversations.ts +6 -0
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +136 -36
- package/src/interfaces/web/src/types/daemon.ts +5 -0
- package/src/interfaces/web/dist/assets/index-CAUezTBY.css +0 -1
- package/src/interfaces/web/dist/assets/index-DaE_memX.js +0 -651
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { useSearchParams } from "react-router-dom";
|
|
3
|
-
import useSWR from "swr";
|
|
4
|
-
import { Plus, Trash2 } from "lucide-react";
|
|
5
|
-
import { Agents } from "../../lib/api";
|
|
3
|
+
import useSWR, { mutate } from "swr";
|
|
4
|
+
import { Plus, RotateCcw, Trash2 } from "lucide-react";
|
|
5
|
+
import { Agents, Conversations } from "../../lib/api";
|
|
6
6
|
import { Badge, Button, Dialog, Empty, Field, Input, Loading, Switch } from "../../components/ui";
|
|
7
7
|
import { Composer } from "../../components/chat/Composer";
|
|
8
8
|
import { MessageList } from "../../components/chat/MessageList";
|
|
9
9
|
import { ContextBar } from "../../components/chat/ContextBar";
|
|
10
10
|
import { InlineAskPanel, pendingAskQuestions } from "../../components/chat/InlineAskPanel";
|
|
11
|
-
import { ChatList, type ChatKey } from "../../components/chat/ChatList";
|
|
11
|
+
import { ChatList, type ChatKey, type ChatSelectionMeta } from "../../components/chat/ChatList";
|
|
12
12
|
import { useChat } from "../../hooks/useChat";
|
|
13
13
|
import { useToast } from "../../components/Toast";
|
|
14
14
|
import { t } from "../../i18n";
|
|
@@ -22,24 +22,52 @@ const ROBY_SLUG = "__super_agent__";
|
|
|
22
22
|
|
|
23
23
|
export function ChatTab({ pid }: { pid: string }) {
|
|
24
24
|
const toast = useToast();
|
|
25
|
-
const [params] = useSearchParams();
|
|
25
|
+
const [params, setSearchParams] = useSearchParams();
|
|
26
26
|
const agents = useSWR(`/projects/${pid}/agents`, () => Agents.list(pid));
|
|
27
27
|
const [creating, setCreating] = useState(false);
|
|
28
28
|
const [model, setModel] = useState("");
|
|
29
29
|
const [dismissedAskKey, setDismissedAskKey] = useState<string | null>(null);
|
|
30
|
-
const { msgs, send: sendChat, stop, clear, load, loadThread, streaming
|
|
30
|
+
const { msgs, send: sendChat, stop, clear, load, loadThread, streaming } =
|
|
31
31
|
useChat(pid, (m) => toast.error(m));
|
|
32
32
|
const persona = usePersonaName();
|
|
33
33
|
|
|
34
34
|
// Selection state — drives both the sidebar highlight and the right-pane
|
|
35
|
-
// header.
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
const [selected, setSelected] = useState<ChatKey>(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
// header. Restored from the URL query on mount (so a chat is deep-linkable),
|
|
36
|
+
// defaulting to a live session with the super-agent so the chat works even on
|
|
37
|
+
// a brand-new project with zero agents and zero conversations.
|
|
38
|
+
const [selected, setSelected] = useState<ChatKey>(() => {
|
|
39
|
+
const agent = params.get("agent");
|
|
40
|
+
const conv = params.get("conv");
|
|
41
|
+
const channel = params.get("channel");
|
|
42
|
+
const thread = params.get("thread");
|
|
43
|
+
if (channel && thread) return { kind: "thread", channel, threadId: thread };
|
|
44
|
+
if (agent && conv) return { kind: "conv", agentSlug: agent, convId: conv };
|
|
45
|
+
if (agent) return { kind: "live", agentSlug: agent };
|
|
46
|
+
return { kind: "live", agentSlug: ROBY_SLUG };
|
|
47
|
+
});
|
|
48
|
+
// Display metadata for the current selection (channel/created date/title),
|
|
49
|
+
// carried from the sidebar so the header can show it without a second fetch.
|
|
50
|
+
const [selectedMeta, setSelectedMeta] = useState<ChatSelectionMeta | undefined>(undefined);
|
|
51
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
52
|
+
const [deleting, setDeleting] = useState(false);
|
|
53
|
+
|
|
54
|
+
// Select a chat and mirror its id into the URL query so the current chat is
|
|
55
|
+
// shareable/deep-linkable. `replace` keeps navigation history clean.
|
|
56
|
+
const selectChat = (key: ChatKey, meta?: ChatSelectionMeta) => {
|
|
57
|
+
setSelected(key);
|
|
58
|
+
setSelectedMeta(meta);
|
|
59
|
+
const next = new URLSearchParams();
|
|
60
|
+
if (key.kind === "conv") {
|
|
61
|
+
next.set("agent", key.agentSlug);
|
|
62
|
+
next.set("conv", key.convId);
|
|
63
|
+
} else if (key.kind === "thread") {
|
|
64
|
+
next.set("channel", key.channel);
|
|
65
|
+
next.set("thread", key.threadId);
|
|
66
|
+
} else {
|
|
67
|
+
next.set("agent", key.agentSlug);
|
|
68
|
+
}
|
|
69
|
+
setSearchParams(next, { replace: true });
|
|
70
|
+
};
|
|
43
71
|
|
|
44
72
|
const agentList = agents.data || [];
|
|
45
73
|
const isRoby = (slug: string | null | undefined) => slug === ROBY_SLUG;
|
|
@@ -95,27 +123,61 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
95
123
|
catch { /* ignore */ }
|
|
96
124
|
};
|
|
97
125
|
|
|
98
|
-
|
|
99
|
-
|
|
126
|
+
// "+ New" from the sidebar: start a fresh in-memory session with the picked
|
|
127
|
+
// agent (super-agent or a project agent). It materialises in the Web group
|
|
128
|
+
// once the first message is sent.
|
|
129
|
+
const onNewChat = (agentSlug: string) => {
|
|
130
|
+
selectChat({ kind: "live", agentSlug });
|
|
131
|
+
clear();
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// "New session" header button: reset the pane but stay with the current
|
|
135
|
+
// agent (Roby for channel threads / the super-agent, else the project agent).
|
|
136
|
+
const newSession = () => {
|
|
137
|
+
const agentSlug = activeIsRoby ? ROBY_SLUG : activeAgent?.slug ?? selected.agentSlug;
|
|
138
|
+
selectChat({ kind: "live", agentSlug });
|
|
100
139
|
clear();
|
|
101
140
|
};
|
|
102
141
|
|
|
142
|
+
// "Delete" header button: permanently remove the persisted conversation
|
|
143
|
+
// (agent `.md` file) or channel thread (ledger day-file), then reset the pane
|
|
144
|
+
// and revalidate the sidebar list so the entry disappears.
|
|
145
|
+
const doDelete = async () => {
|
|
146
|
+
setDeleting(true);
|
|
147
|
+
try {
|
|
148
|
+
if (selected.kind === "conv") {
|
|
149
|
+
await Conversations.remove(pid, selected.agentSlug, selected.convId);
|
|
150
|
+
void mutate(`/projects/${pid}/agents/${selected.agentSlug}/conversations`);
|
|
151
|
+
} else if (selected.kind === "thread") {
|
|
152
|
+
await Conversations.removeThread(pid, selected.channel, selected.threadId);
|
|
153
|
+
void mutate(`/projects/${pid}/super-agent/threads`);
|
|
154
|
+
}
|
|
155
|
+
toast.success(t("project.chat.deleted"));
|
|
156
|
+
setConfirmDelete(false);
|
|
157
|
+
newSession();
|
|
158
|
+
} catch (e) {
|
|
159
|
+
toast.error((e as Error)?.message || t("shared_ui.err_chat_failed"));
|
|
160
|
+
} finally {
|
|
161
|
+
setDeleting(false);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Header shows "Created {date} · {channel} · {agent}" (or "New chat · …" for a
|
|
166
|
+
// fresh session with no persisted date yet), per the sidebar redesign.
|
|
167
|
+
const agentLabel = activeIsRoby ? persona : activeAgent?.slug ?? selected.agentSlug;
|
|
168
|
+
const channelLabel =
|
|
169
|
+
selected.kind === "thread" ? selected.channel : selectedMeta?.channel || "web";
|
|
170
|
+
const createdIso =
|
|
171
|
+
selected.kind === "thread" ? selected.threadId : selectedMeta?.createdAt;
|
|
172
|
+
|
|
103
173
|
const headerTitle =
|
|
104
|
-
selected.kind === "
|
|
105
|
-
?
|
|
106
|
-
:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const headerSubtitle =
|
|
112
|
-
selected.kind === "thread"
|
|
113
|
-
? t("project.chat.thread_subtitle", { channel: selected.channel, persona })
|
|
114
|
-
: activeIsRoby
|
|
115
|
-
? t("project.chat.superagent_subtitle", { persona })
|
|
116
|
-
: selected.kind === "conv"
|
|
117
|
-
? t("project.chat.loaded_subtitle", { slug: selected.agentSlug })
|
|
118
|
-
: t("project.chat.subtitle");
|
|
174
|
+
selected.kind === "live"
|
|
175
|
+
? t("project.chat.live_title", { agent: agentLabel })
|
|
176
|
+
: selectedMeta?.title ||
|
|
177
|
+
(selected.kind === "thread" ? selected.threadId : selected.convId);
|
|
178
|
+
const headerSubtitle = createdIso
|
|
179
|
+
? t("project.chat.meta_created", { date: formatDate(createdIso), channel: channelLabel })
|
|
180
|
+
: t("project.chat.meta_new", { channel: channelLabel });
|
|
119
181
|
|
|
120
182
|
if (agents.isLoading) return <Loading />;
|
|
121
183
|
|
|
@@ -127,7 +189,7 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
127
189
|
superAgentSlug={ROBY_SLUG}
|
|
128
190
|
superAgentLabel={t("agents_ui.super_agent_label", { persona })}
|
|
129
191
|
selected={selected}
|
|
130
|
-
onSelect={
|
|
192
|
+
onSelect={selectChat}
|
|
131
193
|
onNewChat={onNewChat}
|
|
132
194
|
/>
|
|
133
195
|
|
|
@@ -141,9 +203,8 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
141
203
|
{activeIsRoby ? (
|
|
142
204
|
<Badge tone="success">{t("agents_ui.super_agent_badge")}</Badge>
|
|
143
205
|
) : (
|
|
144
|
-
|
|
206
|
+
<Badge tone="info">{agentLabel}</Badge>
|
|
145
207
|
)}
|
|
146
|
-
{selected.kind === "conv" && <Badge tone="info">{conversationId || "…"}</Badge>}
|
|
147
208
|
{!agentList.length && !activeIsRoby && (
|
|
148
209
|
<Button variant="primary" size="sm" onClick={() => setCreating(true)}>
|
|
149
210
|
<Plus size={14} /> {t("project.chat.create_agent")}
|
|
@@ -153,10 +214,20 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
153
214
|
variant="ghost"
|
|
154
215
|
size="sm"
|
|
155
216
|
disabled={streaming || msgs.length === 0}
|
|
156
|
-
onClick={
|
|
217
|
+
onClick={newSession}
|
|
157
218
|
>
|
|
158
|
-
<
|
|
219
|
+
<RotateCcw size={13} /> {t("project.chat.new_session")}
|
|
159
220
|
</Button>
|
|
221
|
+
{(selected.kind === "conv" || selected.kind === "thread") && (
|
|
222
|
+
<Button
|
|
223
|
+
variant="destructive"
|
|
224
|
+
size="sm"
|
|
225
|
+
disabled={streaming}
|
|
226
|
+
onClick={() => setConfirmDelete(true)}
|
|
227
|
+
>
|
|
228
|
+
<Trash2 size={13} /> {t("project.chat.delete")}
|
|
229
|
+
</Button>
|
|
230
|
+
)}
|
|
160
231
|
</div>
|
|
161
232
|
</header>
|
|
162
233
|
|
|
@@ -198,10 +269,39 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
198
269
|
onClose={() => setCreating(false)}
|
|
199
270
|
onCreated={() => { setCreating(false); agents.mutate(); }}
|
|
200
271
|
/>
|
|
272
|
+
|
|
273
|
+
<Dialog
|
|
274
|
+
open={confirmDelete}
|
|
275
|
+
onClose={() => setConfirmDelete(false)}
|
|
276
|
+
title={t("project.chat.delete_confirm_title")}
|
|
277
|
+
description={t("project.chat.delete_confirm_desc")}
|
|
278
|
+
size="sm"
|
|
279
|
+
footer={
|
|
280
|
+
<>
|
|
281
|
+
<Button variant="ghost" onClick={() => setConfirmDelete(false)} disabled={deleting}>
|
|
282
|
+
{t("common.cancel")}
|
|
283
|
+
</Button>
|
|
284
|
+
<Button variant="destructive" onClick={doDelete} loading={deleting}>
|
|
285
|
+
<Trash2 size={14} /> {t("project.chat.delete")}
|
|
286
|
+
</Button>
|
|
287
|
+
</>
|
|
288
|
+
}
|
|
289
|
+
>
|
|
290
|
+
<p className="text-sm text-muted-fg">{headerTitle}</p>
|
|
291
|
+
</Dialog>
|
|
201
292
|
</div>
|
|
202
293
|
);
|
|
203
294
|
}
|
|
204
295
|
|
|
296
|
+
// Localised short date for the header "Created {date}" line. Falls back to the
|
|
297
|
+
// raw string for anything Date can't parse.
|
|
298
|
+
function formatDate(iso?: string): string {
|
|
299
|
+
if (!iso) return "";
|
|
300
|
+
const d = new Date(iso);
|
|
301
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
302
|
+
return d.toLocaleDateString();
|
|
303
|
+
}
|
|
304
|
+
|
|
205
305
|
function CreateAgentDialog({
|
|
206
306
|
open,
|
|
207
307
|
onClose,
|
|
@@ -143,6 +143,11 @@ export interface ConversationMessage {
|
|
|
143
143
|
content: string;
|
|
144
144
|
ts?: string;
|
|
145
145
|
name?: string;
|
|
146
|
+
/** Present on role:"tool" rows from the global ledger — the tool name and its
|
|
147
|
+
* structured args/result, so the viewer can render a ToolCall part. */
|
|
148
|
+
tool?: string;
|
|
149
|
+
args?: Record<string, unknown>;
|
|
150
|
+
result?: unknown;
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
export interface ConversationDetail {
|