修复重新生成功能:改用重新发送消息链路,并确保 model_id 在请求顶层
parent
44dba476c4
commit
b5d40e876b
|
|
@ -13,6 +13,14 @@ export interface ToolCallTrace {
|
||||||
result: any;
|
result: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ModelOverrides {
|
||||||
|
model?: string;
|
||||||
|
model_id?: string;
|
||||||
|
temperature?: number;
|
||||||
|
topP?: number;
|
||||||
|
maxTokens?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChatMessage {
|
export interface ChatMessage {
|
||||||
id: string;
|
id: string;
|
||||||
role: 'user' | 'assistant' | 'agent' | 'system';
|
role: 'user' | 'assistant' | 'agent' | 'system';
|
||||||
|
|
@ -48,13 +56,12 @@ export interface ChatHistoryResp {
|
||||||
export const ChatAPI = {
|
export const ChatAPI = {
|
||||||
history: (roomId: string) =>
|
history: (roomId: string) =>
|
||||||
api.get<ChatHistoryResp>(`/rooms/${roomId}/messages`).then((r) => r.data),
|
api.get<ChatHistoryResp>(`/rooms/${roomId}/messages`).then((r) => r.data),
|
||||||
send: (roomId: string, content: string, targetAgentId: string, model?: string, model_id?: string, imageUrls?: string[]) =>
|
send: (roomId: string, content: string, targetAgentId: string, overrides?: ModelOverrides, imageUrls?: string[]) =>
|
||||||
api
|
api
|
||||||
.post<{ user: ChatMessage; assistant: ChatMessage }>(`/rooms/${roomId}/messages`, {
|
.post<{ user: ChatMessage; assistant: ChatMessage }>(`/rooms/${roomId}/messages`, {
|
||||||
content,
|
content,
|
||||||
targetAgentId,
|
targetAgentId,
|
||||||
model,
|
...overrides,
|
||||||
model_id,
|
|
||||||
imageUrls
|
imageUrls
|
||||||
})
|
})
|
||||||
.then((r) => r.data),
|
.then((r) => r.data),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { ChatMessage } from './chat';
|
import type { ChatMessage, ModelOverrides } from './chat';
|
||||||
import { API_BASE_URL } from './http';
|
import { API_BASE_URL } from './http';
|
||||||
|
|
||||||
export interface StreamEvents {
|
export interface StreamEvents {
|
||||||
|
|
@ -13,22 +13,13 @@ export interface StreamEvents {
|
||||||
onError?: (msg: string) => void;
|
onError?: (msg: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelOverrides {
|
|
||||||
model?: string;
|
|
||||||
model_id?: string;
|
|
||||||
temperature?: number;
|
|
||||||
topP?: number;
|
|
||||||
maxTokens?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function streamChat(
|
export async function streamChat(
|
||||||
roomId: string,
|
roomId: string,
|
||||||
targetAgentId: string,
|
targetAgentId: string,
|
||||||
content: string,
|
content: string,
|
||||||
handlers: StreamEvents,
|
handlers: StreamEvents,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
model?: string,
|
overrides?: ModelOverrides,
|
||||||
modelId?: string,
|
|
||||||
imageUrls?: string[]
|
imageUrls?: string[]
|
||||||
) {
|
) {
|
||||||
const resp = await fetch(`${API_BASE_URL}rooms/${roomId}/messages/stream`, {
|
const resp = await fetch(`${API_BASE_URL}rooms/${roomId}/messages/stream`, {
|
||||||
|
|
@ -37,8 +28,7 @@ export async function streamChat(
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
content,
|
content,
|
||||||
targetAgentId,
|
targetAgentId,
|
||||||
model,
|
...overrides,
|
||||||
model_id: modelId,
|
|
||||||
imageUrls: imageUrls ?? []
|
imageUrls: imageUrls ?? []
|
||||||
}),
|
}),
|
||||||
signal,
|
signal,
|
||||||
|
|
@ -47,24 +37,6 @@ export async function streamChat(
|
||||||
return await consumeSSE(resp, handlers, signal);
|
return await consumeSSE(resp, handlers, signal);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function regenerateMessage(
|
|
||||||
agentId: string,
|
|
||||||
messageId: string,
|
|
||||||
handlers: StreamEvents,
|
|
||||||
signal?: AbortSignal,
|
|
||||||
overrides?: ModelOverrides,
|
|
||||||
attachmentsText?: string
|
|
||||||
) {
|
|
||||||
const resp = await fetch(`${API_BASE_URL}chat/${agentId}/messages/${messageId}/regenerate`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
|
|
||||||
body: JSON.stringify({ overrides, attachmentsText }),
|
|
||||||
signal,
|
|
||||||
credentials: 'include'
|
|
||||||
});
|
|
||||||
return await consumeSSE(resp, handlers, signal);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function consumeSSE(resp: Response, h: StreamEvents, signal?: AbortSignal) {
|
async function consumeSSE(resp: Response, h: StreamEvents, signal?: AbortSignal) {
|
||||||
if (!resp.ok || !resp.body) {
|
if (!resp.ok || !resp.body) {
|
||||||
const txt = await resp.text().catch(() => '');
|
const txt = await resp.text().catch(() => '');
|
||||||
|
|
@ -150,4 +122,3 @@ async function consumeSSE(resp: Response, h: StreamEvents, signal?: AbortSignal)
|
||||||
reader.cancel().catch(() => {});
|
reader.cancel().catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,8 +117,7 @@ export default function ChatPreview({ agent, agentId }: Props) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ctrl.signal,
|
ctrl.signal,
|
||||||
model,
|
{ model, model_id: modelId }
|
||||||
modelId
|
|
||||||
);
|
);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ export default function MessageItem(props: {
|
||||||
<Button size="small" className='actions-btn' type="text" icon={<CopyOutlined />} />
|
<Button size="small" className='actions-btn' type="text" icon={<CopyOutlined />} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<Tooltip title="重新生成(开新分支)">
|
<Tooltip title="重新生成">
|
||||||
<Button size="small" className='actions-btn' type="text" icon={<SyncOutlined />} disabled={busy} onClick={() => onRegenerate?.(message.id)} />
|
<Button size="small" className='actions-btn' type="text" icon={<SyncOutlined />} disabled={busy} onClick={() => onRegenerate?.(message.id)} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import type { Agent, BranchInfo, ChatAttachment, ChatMessage, ModelOverrides, RetrievedSnippet, ToolCallTrace } from '../../../api';
|
import type { Agent, BranchInfo, ChatAttachment, ChatMessage, ModelOverrides, RetrievedSnippet, ToolCallTrace } from '../../../api';
|
||||||
import { ChatAPI, ChatAttachmentsAPI, ImageAPI, regenerateMessage, streamChat } from '../../../api';
|
import { ChatAPI, ChatAttachmentsAPI, ImageAPI, streamChat } from '../../../api';
|
||||||
import { buildAttachmentsText } from '../utils/attachments';
|
import { buildAttachmentsText } from '../utils/attachments';
|
||||||
import { parseAgentModels } from '../utils/agentModels';
|
import { parseAgentModels } from '../utils/agentModels';
|
||||||
|
|
||||||
|
|
@ -235,8 +235,7 @@ export function useChatSender(args: {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ctrl.signal,
|
ctrl.signal,
|
||||||
targetModel,
|
{ ...overrides, model: targetModel, model_id: targetModelId },
|
||||||
targetModelId,
|
|
||||||
imageUrls
|
imageUrls
|
||||||
);
|
);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|
@ -284,7 +283,7 @@ export function useChatSender(args: {
|
||||||
const attText = buildAttachmentsText(attachments);
|
const attText = buildAttachmentsText(attachments);
|
||||||
const content = attText ? `${text}\n\n${attText}` : text;
|
const content = attText ? `${text}\n\n${attText}` : text;
|
||||||
try {
|
try {
|
||||||
const res = await ChatAPI.send(roomId, content, targetAgentId, targetModel, targetModelId, imageUrls);
|
const res = await ChatAPI.send(roomId, content, targetAgentId, { ...overrides, model: targetModel, model_id: targetModelId }, imageUrls);
|
||||||
args.setMessages((m) => [...(m || []).filter((x) => x.id !== tempUser.id), res.user, res.assistant]);
|
args.setMessages((m) => [...(m || []).filter((x) => x.id !== tempUser.id), res.user, res.assistant]);
|
||||||
setSessionRefresh((t) => t + 1);
|
setSessionRefresh((t) => t + 1);
|
||||||
setAttachments([]);
|
setAttachments([]);
|
||||||
|
|
@ -327,65 +326,25 @@ export function useChatSender(args: {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRegenerate = async (assistantId: string) => {
|
const handleRegenerate = async (assistantId: string) => {
|
||||||
if (!agentId || sending) return;
|
if (!agentId || sending || !roomId) return;
|
||||||
|
|
||||||
|
// 找到当前要重新生成的助手消息,并回溯找到它的父消息(用户消息)
|
||||||
|
const assistantMsg = args.messages.find((m) => m.id === assistantId);
|
||||||
|
const userMsgId = assistantMsg?.parentId;
|
||||||
|
const userMsg = args.messages.find((m) => m.id === userMsgId);
|
||||||
|
|
||||||
|
// 如果找不到父消息,则尝试找最后一条用户消息
|
||||||
|
const lastUserMsg = [...args.messages].reverse().find((m) => m.role === 'user');
|
||||||
|
const targetUserMsg = userMsg || lastUserMsg;
|
||||||
|
|
||||||
|
if (!targetUserMsg) {
|
||||||
|
notify.error('找不到上一条用户消息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setSending(true);
|
setSending(true);
|
||||||
setStreaming({ active: true, reasoningText: '', answerText: '', errorMessage: null, retryInfo: null, retrieved: [], toolCalls: [] });
|
|
||||||
abortRef.current?.abort();
|
|
||||||
const ctrl = new AbortController();
|
|
||||||
abortRef.current = ctrl;
|
|
||||||
try {
|
try {
|
||||||
await regenerateMessage(
|
await handleSendStream(targetUserMsg.content);
|
||||||
agentId,
|
|
||||||
assistantId,
|
|
||||||
{
|
|
||||||
onMeta: (m) => setStreaming((s) => ({ ...s, retrieved: m.retrieved || [] })),
|
|
||||||
onRetry: (data) => {
|
|
||||||
setStreaming((s) => ({ ...s, retryInfo: data }));
|
|
||||||
if (data?.stage === 'fallback_model' && data?.toModel) {
|
|
||||||
setOverrides((o) => ({ ...o, model: String(data.toModel) }));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onReasoningDelta: (chunk) =>
|
|
||||||
setStreaming((s) => {
|
|
||||||
const next = { ...s, reasoningText: s.reasoningText + chunk };
|
|
||||||
scrollBottom();
|
|
||||||
return next;
|
|
||||||
}),
|
|
||||||
onDelta: (chunk) =>
|
|
||||||
setStreaming((s) => {
|
|
||||||
const next = { ...s, answerText: s.answerText + chunk };
|
|
||||||
scrollBottom();
|
|
||||||
return next;
|
|
||||||
}),
|
|
||||||
onToolCall: (data) => setStreaming((s) => ({ ...s, toolCalls: [...s.toolCalls, { name: data.name, args: data.args, result: { pending: true } }] })),
|
|
||||||
onToolResult: (data) =>
|
|
||||||
setStreaming((s) => {
|
|
||||||
const list = [...s.toolCalls];
|
|
||||||
for (let i = list.length - 1; i >= 0; i--) {
|
|
||||||
if (list[i].name === data.name && (list[i].result as any)?.pending) {
|
|
||||||
list[i] = { ...list[i], result: data.result };
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { ...s, toolCalls: list };
|
|
||||||
}),
|
|
||||||
onDone: () => {
|
|
||||||
setStreaming({ active: false, reasoningText: '', answerText: '', errorMessage: null, retryInfo: null, retrieved: [], toolCalls: [] });
|
|
||||||
loadMessages();
|
|
||||||
},
|
|
||||||
onAborted: () => {
|
|
||||||
setStreaming({ active: false, reasoningText: '', answerText: '', errorMessage: null, retryInfo: null, retrieved: [], toolCalls: [] });
|
|
||||||
loadMessages();
|
|
||||||
},
|
|
||||||
onError: (errMsg) => {
|
|
||||||
notify.error('重新生成失败:' + errMsg);
|
|
||||||
setStreaming({ active: false, reasoningText: '', answerText: '', errorMessage: errMsg, retryInfo: null, retrieved: [], toolCalls: [] });
|
|
||||||
loadMessages();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ctrl.signal,
|
|
||||||
overrides
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
setSending(false);
|
setSending(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue