refactor: migrate from legacy session API to new rooms API for multi-agent mention support

main
sp mac bookpro 2605 2026-06-08 00:34:56 +08:00
parent b6d1c9e08d
commit 8a85c675ef
8 changed files with 76 additions and 67 deletions

View File

@ -42,21 +42,20 @@ export interface ChatHistoryResp {
}
export const ChatAPI = {
history: (agentId: string, sessionId?: string) =>
api.get<ChatHistoryResp>(`/chat/${agentId}/messages`, { params: sessionId ? { sessionId } : {} }).then((r) => r.data),
send: (agentId: string, content: string, sessionId?: string, model?: string, imageUrls?: string[]) =>
history: (roomId: string) =>
api.get<ChatHistoryResp>(`/rooms/${roomId}/messages`).then((r) => r.data),
send: (roomId: string, content: string, targetAgentId: string, model?: string, model_id?: string, imageUrls?: string[]) =>
api
.post<{ user: ChatMessage; assistant: ChatMessage }>(`/chat/${agentId}/messages`, {
.post<{ user: ChatMessage; assistant: ChatMessage }>(`/rooms/${roomId}/messages`, {
content,
sessionId,
targetAgentId,
model,
model_id,
imageUrls
})
.then((r) => r.data),
clear: (agentId: string, sessionId?: string) =>
api.delete(`/chat/${agentId}/messages`, { params: sessionId ? { sessionId } : {} }).then((r) => r.data),
switchBranch: (agentId: string, userMsgId: string, branchId: string) =>
api.post(`/chat/${agentId}/messages/${userMsgId}/switch-branch`, { branchId }).then((r) => r.data)
clear: (roomId: string) =>
api.delete(`/rooms/${roomId}/messages`).then((r) => r.data)
};
export interface ChatAttachment {

View File

@ -30,18 +30,18 @@ export interface SearchResult {
export const SessionAPI = {
list: (agentId: string, archived: '0' | '1' | 'all' = '0') =>
api.get<ChatSession[]>(`/agents/${agentId}/sessions`, { params: { archived } }).then((r) => r.data),
create: (agentId: string, title?: string) => api.post<ChatSession>(`/agents/${agentId}/sessions`, { title }).then((r) => r.data),
rename: (agentId: string, sessionId: string, title: string) => api.put(`/agents/${agentId}/sessions/${sessionId}`, { title }).then((r) => r.data),
remove: (agentId: string, sessionId: string) => api.delete(`/agents/${agentId}/sessions/${sessionId}`).then((r) => r.data),
api.get<ChatSession[]>(`/agents/${agentId}/rooms`, { params: { archived } }).then((r) => r.data),
create: (agentId: string, title?: string) => api.post<ChatSession>(`/agents/${agentId}/rooms`, { title: title || '新会话' }).then((r) => r.data),
rename: (agentId: string, sessionId: string, title: string) => api.put(`/agents/${agentId}/rooms/${sessionId}`, { title }).then((r) => r.data),
remove: (agentId: string, sessionId: string) => api.delete(`/agents/${agentId}/rooms/${sessionId}`).then((r) => r.data),
archive: (agentId: string, sessionId: string, archived: boolean) =>
api.post(`/agents/${agentId}/sessions/${sessionId}/archive`, { archived }).then((r) => r.data),
api.post(`/agents/${agentId}/rooms/${sessionId}/archive`, { archived }).then((r) => r.data),
share: (agentId: string, sessionId: string, ttlHours = 0) =>
api.post<{ token: string; expiresAt?: number }>(`/agents/${agentId}/sessions/${sessionId}/share`, { ttlHours }).then((r) => r.data),
revokeShare: (agentId: string, sessionId: string) => api.delete(`/agents/${agentId}/sessions/${sessionId}/share`).then((r) => r.data),
api.post<{ token: string; expiresAt?: number }>(`/agents/${agentId}/rooms/${sessionId}/share`, { ttlHours }).then((r) => r.data),
revokeShare: (agentId: string, sessionId: string) => api.delete(`/agents/${agentId}/rooms/${sessionId}/share`).then((r) => r.data),
search: (agentId: string, q: string, opts: { includeArchived?: boolean; limit?: number } = {}) =>
api
.get<SearchResult>(`/agents/${agentId}/sessions/search`, {
.get<SearchResult>(`/agents/${agentId}/rooms/search`, {
params: {
q,
includeArchived: opts.includeArchived ? '1' : '0',
@ -50,7 +50,7 @@ export const SessionAPI = {
})
.then((r) => r.data),
exportSession: (agentId: string, sessionId: string, format: 'md' | 'json' = 'md') => {
const url = withApiBase(`/agents/${agentId}/sessions/${sessionId}/export?format=${format}`);
const url = withApiBase(`/agents/${agentId}/rooms/${sessionId}/export?format=${format}`);
const a = document.createElement('a');
a.href = url;
a.rel = 'noopener';

View File

@ -21,21 +21,21 @@ export interface ModelOverrides {
}
export async function streamChat(
agentId: string,
roomId: string,
targetAgentId: string,
content: string,
handlers: StreamEvents,
signal?: AbortSignal,
sessionId?: string,
model?: string,
modelId?: string,
imageUrls?: string[]
) {
const resp = await fetch(`https://api.hoyidata.com/aura/v1/chat/${agentId}/messages/stream`, {
const resp = await fetch(`https://api.hoyidata.com/aura/v1/rooms/${roomId}/messages/stream`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
body: JSON.stringify({
content,
sessionId,
targetAgentId,
model,
model_id: modelId,
imageUrls: imageUrls ?? []

View File

@ -75,8 +75,11 @@ export default function ChatPreview({ agent, agentId }: Props) {
setSessionId(sid);
}
const model = String(agent?.model || '').split(',')[0]?.trim() || undefined;
const modelId = model ? undefined : undefined;
const targetAgentId = agentId;
await streamChat(
agentId,
sid,
targetAgentId,
text,
{
onMeta: (m) => setStreaming((s) => ({ ...s, retrieved: m.retrieved || [] })),
@ -104,7 +107,7 @@ export default function ChatPreview({ agent, agentId }: Props) {
}),
onDone: (data) => {
setMessages((m) => [...m.filter((x) => x.id !== tempUser.id), data.user, data.assistant]);
setStreaming({ active: false, text: '', retrieved: [], toolCalls: [] });
setStreaming({ active: false, text: '', retrieved: [], toolCalls: [] });
scrollBottom();
},
onError: (errMsg) => {
@ -114,8 +117,8 @@ export default function ChatPreview({ agent, agentId }: Props) {
}
},
ctrl.signal,
sid,
model
model,
modelId
);
} catch (e: any) {
if (e?.name !== 'AbortError') {

View File

@ -14,7 +14,7 @@ import { useChatData } from './hooks/useChatData';
import { useChatSender } from './hooks/useChatSender';
import { markdownToPlainText } from './utils/copy';
const lastSessionKey = (agentId: string) => `chat:lastSession:${agentId}`;
const lastRoomKey = (agentId: string) => `chat:lastRoom:${agentId}`;
export default function ChatPage() {
const { id } = useParams();
@ -22,7 +22,7 @@ export default function ChatPage() {
const [searchParams, setSearchParams] = useSearchParams();
const { message } = AntApp.useApp();
const [sessionId, setSessionId] = useState<string | null>(null);
const [roomId, setRoomId] = useState<string | null>(null);
const [highlightId, setHighlightId] = useState<string | null>(null);
const [overrides, setOverrides] = useState<ModelOverrides>({});
@ -41,9 +41,9 @@ export default function ChatPage() {
const m = searchParams.get('msg');
if (s) {
setSessionId(s);
setRoomId(s);
try {
localStorage.setItem(lastSessionKey(id), s);
localStorage.setItem(lastRoomKey(id), s);
} catch {
// ignore
}
@ -58,13 +58,13 @@ export default function ChatPage() {
const saved = (() => {
try {
return localStorage.getItem(lastSessionKey(id));
return localStorage.getItem(lastRoomKey(id));
} catch {
return null;
}
})();
if (saved) {
setSessionId(saved);
setRoomId(saved);
return;
}
@ -72,37 +72,37 @@ export default function ChatPage() {
try {
const list = await SessionAPI.list(id, '0');
if (list.length > 0) {
setSessionId(list[0].id);
setRoomId(list[0].id);
return;
}
const created = await SessionAPI.create(id);
setSessionId(created.id);
setRoomId(created.id);
} catch (e: any) {
message.error('加载会话失败:' + (e?.message ?? e));
message.error('加载房间失败:' + (e?.message ?? e));
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);
useEffect(() => {
if (!id || !sessionId) return;
if (!id || !roomId) return;
try {
localStorage.setItem(lastSessionKey(id), sessionId);
localStorage.setItem(lastRoomKey(id), roomId);
} catch {
// ignore
}
const cur = searchParams.get('session');
if (cur === sessionId) return;
if (cur === roomId) return;
const next = new URLSearchParams(searchParams);
next.set('session', sessionId);
next.set('session', roomId);
next.delete('msg');
setSearchParams(next, { replace: true });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, sessionId]);
}, [id, roomId]);
const { agent, agentList, messages, setMessages, branches, setBranches, loadMessages } = useChatData({
agentId: id,
sessionId,
roomId,
highlightId,
setHighlightId,
scrollBottom,
@ -115,7 +115,7 @@ export default function ChatPage() {
agentId: id,
agent,
agentList,
sessionId,
roomId,
overrides,
setOverrides: (updater) => setOverrides(updater),
messages,
@ -213,9 +213,9 @@ export default function ChatPage() {
setHighlightId(null);
setMessages(() => []);
setBranches({});
setSessionId(created.id);
setRoomId(created.id);
} catch (e: any) {
message.error('创建会话失败:' + (e?.message ?? e));
message.error('创建房间失败:' + (e?.message ?? e));
}
}}
agentList={agentList}
@ -232,8 +232,8 @@ export default function ChatPage() {
setInput={(updater) => sender.setInput(updater(sender.input))}
overrides={overrides}
setOverrides={(updater) => setOverrides(updater)}
sessionId={sessionId}
setSessionId={setSessionId}
roomId={roomId}
setRoomId={setRoomId}
setHighlightId={setHighlightId}
sessionRefresh={sender.sessionRefresh}
mcpDrawerOpen={mcpDrawerOpen}

View File

@ -11,8 +11,8 @@ export default function ChatDrawers(props: {
setInput: (updater: (prev: string) => string) => void;
overrides: ModelOverrides;
setOverrides: (updater: (prev: ModelOverrides) => ModelOverrides) => void;
sessionId: string | null;
setSessionId: (v: string | null) => void;
roomId: string | null;
setRoomId: (v: string | null) => void;
setHighlightId: (v: string | null) => void;
sessionRefresh: number;
mcpDrawerOpen: boolean;
@ -31,8 +31,8 @@ export default function ChatDrawers(props: {
setInput,
overrides,
setOverrides,
sessionId,
setSessionId,
roomId,
setRoomId,
setHighlightId,
sessionRefresh,
mcpDrawerOpen,
@ -165,9 +165,9 @@ export default function ChatDrawers(props: {
{agentId ? (
<SessionSidebar
agentId={agentId}
activeSessionId={sessionId}
activeSessionId={roomId}
onChange={(sid, opts) => {
setSessionId(sid);
setRoomId(sid);
setHighlightId(opts?.highlightMessageId || null);
setHistoryDrawerOpen(false);
}}

View File

@ -5,7 +5,7 @@ import { parseAgentModels } from '../utils/agentModels';
export function useChatData(args: {
agentId?: string;
sessionId: string | null;
roomId: string | null;
highlightId: string | null;
setHighlightId: (v: string | null) => void;
scrollBottom: (force?: boolean) => void;
@ -13,7 +13,7 @@ export function useChatData(args: {
setOverrides: (updater: (prev: ModelOverrides) => ModelOverrides) => void;
abort: () => void;
}) {
const { agentId, sessionId, highlightId, setHighlightId, scrollBottom, initialScrollDoneRef, setOverrides, abort } = args;
const { agentId, roomId, highlightId, setHighlightId, scrollBottom, initialScrollDoneRef, setOverrides, abort } = args;
const [agent, setAgent] = useState<Agent | null>(null);
const [agentList, setAgentList] = useState<Agent[]>([]);
const [messages, setMessages] = useState<ChatMessage[]>([]);
@ -48,8 +48,8 @@ export function useChatData(args: {
};
const loadMessages = async () => {
if (!agentId || !sessionId) return;
const his = await ChatAPI.history(agentId, sessionId || undefined);
if (!roomId) return;
const his = await ChatAPI.history(roomId);
setMessages(Array.isArray(his.messages) ? his.messages : []);
setBranches(his.branches || {});
requestAnimationFrame(() => {
@ -92,10 +92,10 @@ export function useChatData(args: {
}, [agentId]);
useEffect(() => {
if (!agentId || !sessionId) return;
if (!roomId) return;
loadMessages();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sessionId, highlightId, agentId]);
}, [roomId, highlightId, agentId]);
return {
agent,

View File

@ -84,7 +84,7 @@ export function useChatSender(args: {
agentId?: string;
agent: Agent | null;
agentList: Agent[];
sessionId: string | null;
roomId: string | null;
overrides: ModelOverrides;
setOverrides: (updater: (prev: ModelOverrides) => ModelOverrides) => void;
messages: ChatMessage[];
@ -95,7 +95,7 @@ export function useChatSender(args: {
notify: { success: (t: string) => void; error: (t: string) => void };
abortRef: { current: AbortController | null };
}) {
const { agentId, agent, agentList, sessionId, overrides, setOverrides, setBranches, loadMessages, scrollBottom, notify, abortRef } = args;
const { agentId, agent, agentList, roomId, overrides, setOverrides, setBranches, loadMessages, scrollBottom, notify, abortRef } = args;
const [input, setInput] = useState('');
const [sending, setSending] = useState(false);
const [useStream, setUseStream] = useState(true);
@ -153,7 +153,12 @@ export function useChatSender(args: {
const content = attText ? `${text}\n\n${attText}` : text;
try {
if (!roomId) {
notify.error('房间未初始化');
return;
}
await streamChat(
roomId,
targetAgentId,
content,
{
@ -230,7 +235,6 @@ export function useChatSender(args: {
}
},
ctrl.signal,
sessionId,
targetModel,
targetModelId,
imageUrls
@ -253,8 +257,8 @@ export function useChatSender(args: {
const handleSendNonStream = async (text: string) => {
if (!agentId) return;
if (!sessionId) {
notify.error('会话未初始化,请稍后重试');
if (!roomId) {
notify.error('房间未初始化,请稍后重试');
return;
}
const tempUser: ChatMessage = { id: 'tmp-' + Date.now(), role: 'user', content: text, createdAt: Date.now() };
@ -267,17 +271,20 @@ export function useChatSender(args: {
// 如果提及了其他 Agent使用该 Agent 的第一个模型
let targetModel: string;
let targetModelId: string;
if (targetAgent && targetAgent.id !== agentId) {
const models = parseAgentModels(targetAgent.model);
targetModel = models[0]?.name || '';
targetModelId = models[0]?.id || '';
} else {
targetModel = overrides.model || agentModels[0]?.name || '';
targetModelId = overrides.model_id || agentModels[0]?.id || '';
}
const attText = buildAttachmentsText(attachments);
const content = attText ? `${text}\n\n${attText}` : text;
try {
const res = await ChatAPI.send(targetAgentId, content, sessionId, targetModel, imageUrls);
const res = await ChatAPI.send(roomId, content, targetAgentId, targetModel, targetModelId, imageUrls);
args.setMessages((m) => [...(m || []).filter((x) => x.id !== tempUser.id), res.user, res.assistant]);
setSessionRefresh((t) => t + 1);
setAttachments([]);
@ -309,11 +316,11 @@ export function useChatSender(args: {
const handleClear = async () => {
if (!agentId) return;
if (!sessionId) {
notify.error('会话未初始化,请稍后重试');
if (!roomId) {
notify.error('房间未初始化,请稍后重试');
return;
}
await ChatAPI.clear(agentId, sessionId);
await ChatAPI.clear(roomId);
args.setMessages(() => []);
setBranches({});
notify.success('对话已清空');