@agentprojectcontext/apx 1.35.0 → 1.36.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 +27 -2
- package/src/host/daemon/api/exec.js +2 -0
- package/src/interfaces/web/dist/assets/index-Cm0KyPoZ.css +1 -0
- package/src/interfaces/web/dist/assets/index-DJKA763h.js +628 -0
- package/src/interfaces/web/dist/assets/index-DJKA763h.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/App.tsx +0 -1
- package/src/interfaces/web/src/components/chat/ChatList.tsx +412 -0
- package/src/interfaces/web/src/components/settings/AppearancePanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +1 -1
- package/src/interfaces/web/src/hooks/useChat.ts +35 -2
- package/src/interfaces/web/src/i18n/en.ts +11 -1
- package/src/interfaces/web/src/i18n/es.ts +11 -1
- package/src/interfaces/web/src/lib/api/agents.ts +1 -1
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +3 -5
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +6 -4
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +120 -87
- package/src/interfaces/web/dist/assets/index-C0fm31dY.js +0 -618
- package/src/interfaces/web/dist/assets/index-C0fm31dY.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-UcAqlBO6.css +0 -1
- package/src/interfaces/web/src/screens/project/ThreadsTab.tsx +0 -100
|
@@ -120,7 +120,7 @@ export function VoiceScreen() {
|
|
|
120
120
|
};
|
|
121
121
|
|
|
122
122
|
return (
|
|
123
|
-
<div className="
|
|
123
|
+
<div className="p-6" data-testid="screen-voice">
|
|
124
124
|
<div className="grid gap-6 xl:grid-cols-2">
|
|
125
125
|
{/* Left: TTS providers */}
|
|
126
126
|
<Section
|
|
@@ -4,11 +4,11 @@ import useSWR from "swr";
|
|
|
4
4
|
import { Plus, Trash2 } from "lucide-react";
|
|
5
5
|
import { Agents } from "../../lib/api";
|
|
6
6
|
import { Badge, Button, Dialog, Empty, Field, Input, Loading, Switch } from "../../components/ui";
|
|
7
|
-
import { UiSelect } from "../../components/UiSelect";
|
|
8
7
|
import { Composer } from "../../components/chat/Composer";
|
|
9
8
|
import { MessageList } from "../../components/chat/MessageList";
|
|
10
9
|
import { ContextBar } from "../../components/chat/ContextBar";
|
|
11
10
|
import { InlineAskPanel, pendingAskQuestions } from "../../components/chat/InlineAskPanel";
|
|
11
|
+
import { ChatList, type ChatKey } from "../../components/chat/ChatList";
|
|
12
12
|
import { useChat } from "../../hooks/useChat";
|
|
13
13
|
import { useToast } from "../../components/Toast";
|
|
14
14
|
import { t } from "../../i18n";
|
|
@@ -24,48 +24,56 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
24
24
|
const toast = useToast();
|
|
25
25
|
const [params] = useSearchParams();
|
|
26
26
|
const agents = useSWR(`/projects/${pid}/agents`, () => Agents.list(pid));
|
|
27
|
-
const [activeSlug, setActiveSlug] = useState(params.get("agent") || "");
|
|
28
27
|
const [creating, setCreating] = useState(false);
|
|
29
28
|
const [model, setModel] = useState("");
|
|
30
29
|
const [dismissedAskKey, setDismissedAskKey] = useState<string | null>(null);
|
|
31
|
-
const { msgs, send: sendChat, stop, clear, streaming } =
|
|
30
|
+
const { msgs, send: sendChat, stop, clear, load, streaming, conversationId } =
|
|
31
|
+
useChat(pid, (m) => toast.error(m));
|
|
32
32
|
const persona = usePersonaName();
|
|
33
33
|
|
|
34
|
+
// Selection state — drives both the sidebar highlight and the right-pane
|
|
35
|
+
// header. Defaults to a live session with the super-agent so the chat works
|
|
36
|
+
// even on a brand-new project with zero agents and zero conversations.
|
|
37
|
+
const initialFromUrl = params.get("agent");
|
|
38
|
+
const [selected, setSelected] = useState<ChatKey>(
|
|
39
|
+
initialFromUrl
|
|
40
|
+
? { kind: "live", agentSlug: initialFromUrl }
|
|
41
|
+
: { kind: "live", agentSlug: ROBY_SLUG },
|
|
42
|
+
);
|
|
43
|
+
|
|
34
44
|
const agentList = agents.data || [];
|
|
35
|
-
// Virtual options shown in the dropdown — the super-agent is always first,
|
|
36
|
-
// then the real project agents. It works on every project (calls
|
|
37
|
-
// /projects/:pid/super-agent/chat) so we expose it everywhere, not just /base.
|
|
38
45
|
const isRoby = (slug: string | null | undefined) => slug === ROBY_SLUG;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{ value: ROBY_SLUG, label: `${persona} (super-agent)` },
|
|
42
|
-
...agentList.map((a) => ({ value: a.slug, label: a.slug })),
|
|
43
|
-
],
|
|
44
|
-
[agentList, persona],
|
|
45
|
-
);
|
|
46
|
+
|
|
47
|
+
// The agent whose dropdown badge / model we show on the right header.
|
|
46
48
|
const activeAgent = useMemo(
|
|
47
|
-
() =>
|
|
48
|
-
|
|
49
|
+
() =>
|
|
50
|
+
selected.kind === "live"
|
|
51
|
+
? agentList.find((a) => a.slug === selected.agentSlug)
|
|
52
|
+
: agentList.find((a) => a.slug === selected.agentSlug),
|
|
53
|
+
[agentList, selected],
|
|
49
54
|
);
|
|
50
|
-
|
|
51
|
-
// real agent, or Roby when the project has no agents at all.
|
|
52
|
-
const effectiveSlug = isRoby(activeSlug)
|
|
53
|
-
? ROBY_SLUG
|
|
54
|
-
: (activeAgent?.slug || ROBY_SLUG);
|
|
55
|
-
const activeIsRoby = effectiveSlug === ROBY_SLUG;
|
|
55
|
+
const activeIsRoby = isRoby(selected.agentSlug);
|
|
56
56
|
|
|
57
|
+
// Whenever the user picks a stored conversation, reload the in-memory chat
|
|
58
|
+
// with its persisted history. The hook itself binds the conversation_id so
|
|
59
|
+
// subsequent sends append to the same file.
|
|
57
60
|
useEffect(() => {
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
if (selected.kind === "conv") {
|
|
62
|
+
void load(selected.agentSlug, selected.convId);
|
|
63
|
+
} else {
|
|
64
|
+
// Switching to a live session = drop any previously bound conversation.
|
|
65
|
+
if (conversationId) clear();
|
|
66
|
+
}
|
|
67
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
68
|
+
}, [selected.kind, selected.kind === "conv" ? selected.convId : selected.agentSlug]);
|
|
62
69
|
|
|
63
70
|
const send = async (text: string) => {
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
if (activeIsRoby) {
|
|
72
|
+
await sendChat(text, { model: model || undefined });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!activeAgent) return;
|
|
76
|
+
await sendChat(text, { model: model || undefined, agentSlug: activeAgent.slug });
|
|
69
77
|
};
|
|
70
78
|
|
|
71
79
|
const copyToClipboard = async (text: string) => {
|
|
@@ -73,72 +81,97 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
73
81
|
catch { /* ignore */ }
|
|
74
82
|
};
|
|
75
83
|
|
|
76
|
-
|
|
84
|
+
const onNewChat = () => {
|
|
85
|
+
setSelected({ kind: "live", agentSlug: ROBY_SLUG });
|
|
86
|
+
clear();
|
|
87
|
+
};
|
|
77
88
|
|
|
89
|
+
const headerTitle = activeIsRoby
|
|
90
|
+
? t("project.chat.superagent_title", { persona })
|
|
91
|
+
: selected.kind === "conv"
|
|
92
|
+
? selected.convId
|
|
93
|
+
: t("project.chat.title");
|
|
78
94
|
const headerSubtitle = activeIsRoby
|
|
79
95
|
? t("project.chat.superagent_subtitle", { persona })
|
|
80
|
-
:
|
|
96
|
+
: selected.kind === "conv"
|
|
97
|
+
? t("project.chat.loaded_subtitle", { slug: selected.agentSlug })
|
|
98
|
+
: t("project.chat.subtitle");
|
|
99
|
+
|
|
100
|
+
if (agents.isLoading) return <Loading />;
|
|
81
101
|
|
|
82
102
|
return (
|
|
83
|
-
<div className="flex h-full
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
103
|
+
<div className="flex h-full overflow-hidden rounded-xl border border-border bg-card/40">
|
|
104
|
+
<ChatList
|
|
105
|
+
pid={pid}
|
|
106
|
+
agents={agentList}
|
|
107
|
+
superAgentSlug={ROBY_SLUG}
|
|
108
|
+
superAgentLabel={`${persona} (super-agent)`}
|
|
109
|
+
selected={selected}
|
|
110
|
+
onSelect={setSelected}
|
|
111
|
+
onNewChat={onNewChat}
|
|
112
|
+
/>
|
|
113
|
+
|
|
114
|
+
<section className="flex min-w-0 flex-1 flex-col">
|
|
115
|
+
<header className="flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3">
|
|
116
|
+
<div className="min-w-0">
|
|
117
|
+
<h2 className="truncate text-sm font-semibold">{headerTitle}</h2>
|
|
118
|
+
<p className="truncate text-[11px] text-muted-fg">{headerSubtitle}</p>
|
|
98
119
|
</div>
|
|
99
|
-
|
|
100
|
-
?
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
<div className="flex items-center gap-2">
|
|
121
|
+
{activeIsRoby ? (
|
|
122
|
+
<Badge tone="success">super-agent</Badge>
|
|
123
|
+
) : (
|
|
124
|
+
activeAgent?.model && <Badge tone="info">{activeAgent.model}</Badge>
|
|
125
|
+
)}
|
|
126
|
+
{selected.kind === "conv" && <Badge tone="info">{conversationId || "…"}</Badge>}
|
|
127
|
+
{!agentList.length && !activeIsRoby && (
|
|
128
|
+
<Button variant="primary" size="sm" onClick={() => setCreating(true)}>
|
|
129
|
+
<Plus size={14} /> {t("project.chat.create_agent")}
|
|
130
|
+
</Button>
|
|
131
|
+
)}
|
|
132
|
+
<Button
|
|
133
|
+
variant="ghost"
|
|
134
|
+
size="sm"
|
|
135
|
+
disabled={streaming || msgs.length === 0}
|
|
136
|
+
onClick={onNewChat}
|
|
137
|
+
>
|
|
138
|
+
<Trash2 size={13} /> {t("project.chat.clear")}
|
|
105
139
|
</Button>
|
|
140
|
+
</div>
|
|
141
|
+
</header>
|
|
142
|
+
|
|
143
|
+
<div className="flex-1 overflow-y-auto">
|
|
144
|
+
{msgs.length ? (
|
|
145
|
+
<MessageList msgs={msgs} onCopy={copyToClipboard} />
|
|
146
|
+
) : (
|
|
147
|
+
<div className="flex h-full items-center justify-center p-8">
|
|
148
|
+
<p className="text-sm text-muted-fg">{t("project.chat.empty")}</p>
|
|
149
|
+
</div>
|
|
106
150
|
)}
|
|
107
|
-
<Button variant="ghost" size="sm" disabled={streaming || msgs.length === 0} onClick={resetConversation}>
|
|
108
|
-
<Trash2 size={13} /> {t("project.chat.clear")}
|
|
109
|
-
</Button>
|
|
110
151
|
</div>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
})()}
|
|
135
|
-
<Composer
|
|
136
|
-
onSend={send}
|
|
137
|
-
onStop={stop}
|
|
138
|
-
streaming={streaming}
|
|
139
|
-
model={model}
|
|
140
|
-
onModelChange={setModel}
|
|
141
|
-
/>
|
|
152
|
+
<ContextBar msgs={msgs} />
|
|
153
|
+
{(() => {
|
|
154
|
+
const pending = !streaming ? pendingAskQuestions(msgs) : null;
|
|
155
|
+
if (!pending || pending.turnKey === dismissedAskKey) return null;
|
|
156
|
+
return (
|
|
157
|
+
<InlineAskPanel
|
|
158
|
+
turnKey={pending.turnKey}
|
|
159
|
+
questions={pending.questions}
|
|
160
|
+
onSubmit={(compiled) => void send(compiled)}
|
|
161
|
+
onDismiss={() => setDismissedAskKey(pending.turnKey)}
|
|
162
|
+
disabled={streaming}
|
|
163
|
+
/>
|
|
164
|
+
);
|
|
165
|
+
})()}
|
|
166
|
+
<Composer
|
|
167
|
+
onSend={send}
|
|
168
|
+
onStop={stop}
|
|
169
|
+
streaming={streaming}
|
|
170
|
+
model={model}
|
|
171
|
+
onModelChange={setModel}
|
|
172
|
+
/>
|
|
173
|
+
</section>
|
|
174
|
+
|
|
142
175
|
<CreateAgentDialog
|
|
143
176
|
open={creating}
|
|
144
177
|
pid={pid}
|