refactor: 标题生成重构、UI样式优化、测试增强
- 将标题生成逻辑提取为独立函数,提前到Agent调用前非阻塞执行 - 修复模型/供应商不存在时的HTTP状态码 500→400 - ChatPanel: 分离模型选择useEffect、CSS类替代内联样式、按钮样式统一 - use-conversations: fetchConversations/fetchMessages改用handleResponse去重 - 聊天面板滚动优化(scroll-behavior: smooth, overflow-anchor: auto) - 测试: mock支持onFinish回调,新增首次消息标题生成测试 - 移除未使用的SendMessageRequest接口
This commit is contained in:
@@ -79,12 +79,12 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
|
|||||||
const d = wrap(db);
|
const d = wrap(db);
|
||||||
const modelRow = d.select().from(models).where(eq(models.id, conversation.modelId)).get();
|
const modelRow = d.select().from(models).where(eq(models.id, conversation.modelId)).get();
|
||||||
if (!modelRow) {
|
if (!modelRow) {
|
||||||
return jsonResponse(createApiError(`模型不存在: ${conversation.modelId}`, 500), { mode, status: 500 });
|
return jsonResponse(createApiError(`模型不存在: ${conversation.modelId}`, 400), { mode, status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const providerRow = d.select().from(providers).where(eq(providers.id, modelRow.providerId)).get();
|
const providerRow = d.select().from(providers).where(eq(providers.id, modelRow.providerId)).get();
|
||||||
if (!providerRow) {
|
if (!providerRow) {
|
||||||
return jsonResponse(createApiError(`供应商不存在: ${modelRow.providerId}`, 500), { mode, status: 500 });
|
return jsonResponse(createApiError(`供应商不存在: ${modelRow.providerId}`, 400), { mode, status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const registry = buildProviderRegistry(db);
|
const registry = buildProviderRegistry(db);
|
||||||
@@ -95,6 +95,17 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const firstUserMsg = body.messages.find((m) => m.role === "user");
|
||||||
|
const firstUserText =
|
||||||
|
firstUserMsg?.parts
|
||||||
|
?.filter((p) => p.type === "text")
|
||||||
|
.map((p) => p.text)
|
||||||
|
.join("") ?? "";
|
||||||
|
|
||||||
|
if (conversation.title === "新会话" && firstUserText) {
|
||||||
|
generateConversationTitle(firstUserText, model, db, conversation.id, logger);
|
||||||
|
}
|
||||||
|
|
||||||
const agent = createAlfredAgent(model);
|
const agent = createAlfredAgent(model);
|
||||||
return await createAgentUIStreamResponse({
|
return await createAgentUIStreamResponse({
|
||||||
agent,
|
agent,
|
||||||
@@ -110,58 +121,6 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
|
|||||||
role: "assistant",
|
role: "assistant",
|
||||||
});
|
});
|
||||||
updateConversationTimestamp(db, conversation.id);
|
updateConversationTimestamp(db, conversation.id);
|
||||||
|
|
||||||
try {
|
|
||||||
if (conversation.title === "新会话") {
|
|
||||||
const firstUserText =
|
|
||||||
body.messages
|
|
||||||
?.find((m) => m.role === "user")
|
|
||||||
?.parts?.filter((p) => p.type === "text")
|
|
||||||
?.map((p) => p.text)
|
|
||||||
?.join("") ?? "";
|
|
||||||
|
|
||||||
if (firstUserText) {
|
|
||||||
if (firstUserText.length <= 5) {
|
|
||||||
updateConversation(db, conversation.id, { title: firstUserText });
|
|
||||||
} else {
|
|
||||||
void generateText({
|
|
||||||
model,
|
|
||||||
prompt: `请根据以下对话开头生成一个简短标题(不超过10个字):${firstUserText}`,
|
|
||||||
system: "你是一个标题生成助手,只返回标题文本,不要解释。",
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
const title = result.text.trim().slice(0, 10);
|
|
||||||
updateConversation(db, conversation.id, { title: title || firstUserText.slice(0, 10) });
|
|
||||||
})
|
|
||||||
.catch((titleError: unknown) => {
|
|
||||||
const titleMsg = titleError instanceof Error ? titleError.message : String(titleError);
|
|
||||||
logger.error({ conversationId: conversation.id, error: titleMsg }, "标题生成失败");
|
|
||||||
try {
|
|
||||||
updateConversation(db, conversation.id, { title: firstUserText.slice(0, 10) });
|
|
||||||
} catch {
|
|
||||||
logger.error({ conversationId: conversation.id }, "标题兜底更新失败");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (titleError: unknown) {
|
|
||||||
const titleMsg = titleError instanceof Error ? titleError.message : String(titleError);
|
|
||||||
logger.error({ conversationId: conversation.id, error: titleMsg }, "标题生成失败");
|
|
||||||
try {
|
|
||||||
const fallbackTitle =
|
|
||||||
body.messages
|
|
||||||
?.find((m) => m.role === "user")
|
|
||||||
?.parts?.filter((p) => p.type === "text")
|
|
||||||
?.map((p) => p.text)
|
|
||||||
?.join("")
|
|
||||||
?.slice(0, 10) ?? "新会话";
|
|
||||||
updateConversation(db, conversation.id, { title: fallbackTitle });
|
|
||||||
} catch (fallbackError: unknown) {
|
|
||||||
const fbMsg = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
||||||
logger.error({ conversationId: conversation.id, error: fbMsg }, "标题兜底更新失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
uiMessages: body.messages,
|
uiMessages: body.messages,
|
||||||
});
|
});
|
||||||
@@ -170,3 +129,35 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
|
|||||||
return jsonResponse(createApiError(`AI 调用失败:${msg}`, 500), { mode, status: 500 });
|
return jsonResponse(createApiError(`AI 调用失败:${msg}`, 500), { mode, status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateConversationTitle(
|
||||||
|
firstUserText: string,
|
||||||
|
model: ReturnType<ReturnType<typeof buildProviderRegistry>["languageModel"]>,
|
||||||
|
db: Database,
|
||||||
|
conversationId: string,
|
||||||
|
logger: Logger,
|
||||||
|
): void {
|
||||||
|
if (firstUserText.length <= 5) {
|
||||||
|
updateConversation(db, conversationId, { title: firstUserText });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateText({
|
||||||
|
model,
|
||||||
|
prompt: `请根据以下对话开头生成一个简短标题(不超过10个字):${firstUserText}`,
|
||||||
|
system: "你是一个标题生成助手,只返回标题文本,不要解释。",
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
const title = result.text.trim().slice(0, 10);
|
||||||
|
updateConversation(db, conversationId, { title: title || firstUserText.slice(0, 10) });
|
||||||
|
})
|
||||||
|
.catch((titleError: unknown) => {
|
||||||
|
const titleMsg = titleError instanceof Error ? titleError.message : String(titleError);
|
||||||
|
logger.error({ conversationId, error: titleMsg }, "标题生成失败");
|
||||||
|
try {
|
||||||
|
updateConversation(db, conversationId, { title: firstUserText.slice(0, 10) });
|
||||||
|
} catch {
|
||||||
|
logger.error({ conversationId }, "标题兜底更新失败");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -99,11 +99,6 @@ export type ModelCapability =
|
|||||||
| "video-generation"
|
| "video-generation"
|
||||||
| "video-recognition";
|
| "video-recognition";
|
||||||
|
|
||||||
export interface SendMessageRequest {
|
|
||||||
conversationId: string;
|
|
||||||
messages: Array<{ content: string; role: "assistant" | "system" | "user" }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateConversationRequest {
|
export interface UpdateConversationRequest {
|
||||||
modelId?: string;
|
modelId?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|||||||
@@ -70,21 +70,11 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
|||||||
setInput("");
|
setInput("");
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
try {
|
try {
|
||||||
const convPromise = fetchConversation(projectId, conversationId);
|
|
||||||
const msgPromise = fetchRef.current(projectId, conversationId);
|
const msgPromise = fetchRef.current(projectId, conversationId);
|
||||||
|
|
||||||
const conv = await convPromise;
|
|
||||||
const data = await msgPromise;
|
const data = await msgPromise;
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
|
|
||||||
const firstTextId = textModels[0]?.id;
|
|
||||||
if (firstTextId && textModels.every((m) => m.id !== conv.modelId)) {
|
|
||||||
setSelectedModelId(firstTextId);
|
|
||||||
void updateConversation(projectId, conversationId, { modelId: firstTextId });
|
|
||||||
} else {
|
|
||||||
setSelectedModelId(conv.modelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const history = data.items
|
const history = data.items
|
||||||
.filter((m: { role: string }) => m.role === "user" || m.role === "assistant")
|
.filter((m: { role: string }) => m.role === "user" || m.role === "assistant")
|
||||||
.reverse()
|
.reverse()
|
||||||
@@ -109,7 +99,22 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [conversationId, projectId, setMessages, message, textModels]);
|
}, [conversationId, projectId, setMessages, message]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!conversationId) return;
|
||||||
|
const firstTextId = textModels[0]?.id;
|
||||||
|
if (!firstTextId) return;
|
||||||
|
|
||||||
|
void fetchConversation(projectId, conversationId).then((conv) => {
|
||||||
|
if (textModels.every((m) => m.id !== conv.modelId)) {
|
||||||
|
setSelectedModelId(firstTextId);
|
||||||
|
void updateConversation(projectId, conversationId, { modelId: firstTextId });
|
||||||
|
} else {
|
||||||
|
setSelectedModelId(conv.modelId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [conversationId, textModels, projectId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollRef.current?.scrollTo({ behavior: "smooth", top: scrollRef.current.scrollHeight });
|
scrollRef.current?.scrollTo({ behavior: "smooth", top: scrollRef.current.scrollHeight });
|
||||||
@@ -213,18 +218,39 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
|||||||
const buttons: React.ReactNode[] = [];
|
const buttons: React.ReactNode[] = [];
|
||||||
|
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button icon={<CopyOutlined />} key="copy" onClick={() => handleCopy(msg)} size="small" type="text" />,
|
<Button
|
||||||
|
className="btn-dimmed"
|
||||||
|
icon={<CopyOutlined />}
|
||||||
|
key="copy"
|
||||||
|
onClick={() => handleCopy(msg)}
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLastUser && !isEditing) {
|
if (isLastUser && !isEditing) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button icon={<EditOutlined />} key="edit" onClick={() => handleEditStart(msg)} size="small" type="text" />,
|
<Button
|
||||||
|
className="btn-dimmed"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
key="edit"
|
||||||
|
onClick={() => handleEditStart(msg)}
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
/>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLastAssistant) {
|
if (isLastAssistant) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button icon={<RedoOutlined />} key="regenerate" onClick={handleRegenerate} size="small" type="text" />,
|
<Button
|
||||||
|
className="btn-dimmed"
|
||||||
|
icon={<RedoOutlined />}
|
||||||
|
key="regenerate"
|
||||||
|
onClick={handleRegenerate}
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
/>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,8 +264,8 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
|||||||
<div className="app-chat-panel">
|
<div className="app-chat-panel">
|
||||||
<div className="chat-welcome-area">
|
<div className="chat-welcome-area">
|
||||||
<Flex align="center" gap={12} vertical>
|
<Flex align="center" gap={12} vertical>
|
||||||
<RobotOutlined style={{ color: "var(--ant-color-primary)", fontSize: 48 }} />
|
<RobotOutlined className="welcome-icon" />
|
||||||
<Typography.Title level={3} style={{ margin: 0 }}>
|
<Typography.Title className="welcome-title" level={3}>
|
||||||
你好,我是阿福
|
你好,我是阿福
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
<Typography.Text type="secondary">有什么我可以帮助你的吗?</Typography.Text>
|
<Typography.Text type="secondary">有什么我可以帮助你的吗?</Typography.Text>
|
||||||
@@ -274,6 +300,7 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
|||||||
<Flex gap={8} vertical>
|
<Flex gap={8} vertical>
|
||||||
{messages.map((msg, idx) => (
|
{messages.map((msg, idx) => (
|
||||||
<Card
|
<Card
|
||||||
|
classNames={{ extra: "card-extra-actions" }}
|
||||||
extra={getCardExtra(msg, idx)}
|
extra={getCardExtra(msg, idx)}
|
||||||
key={msg.id}
|
key={msg.id}
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -29,20 +29,12 @@ export async function fetchConversation(projectId: string, conversationId: strin
|
|||||||
|
|
||||||
export async function fetchConversations(projectId: string): Promise<ConversationListResponse> {
|
export async function fetchConversations(projectId: string): Promise<ConversationListResponse> {
|
||||||
const response = await fetch(`/api/projects/${projectId}/conversations?pageSize=100`);
|
const response = await fetch(`/api/projects/${projectId}/conversations?pageSize=100`);
|
||||||
if (!response.ok) {
|
return handleResponse(response, (data) => data as ConversationListResponse);
|
||||||
const body = (await response.json().catch(() => null)) as null | { error?: string };
|
|
||||||
throw new Error(body?.error ?? `HTTP ${response.status}`);
|
|
||||||
}
|
|
||||||
return response.json() as Promise<ConversationListResponse>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchMessages(projectId: string, conversationId: string): Promise<MessageListResponse> {
|
export async function fetchMessages(projectId: string, conversationId: string): Promise<MessageListResponse> {
|
||||||
const response = await fetch(`/api/projects/${projectId}/conversations/${conversationId}/messages?pageSize=200`);
|
const response = await fetch(`/api/projects/${projectId}/conversations/${conversationId}/messages?pageSize=200`);
|
||||||
if (!response.ok) {
|
return handleResponse(response, (data) => data as MessageListResponse);
|
||||||
const body = (await response.json().catch(() => null)) as null | { error?: string };
|
|
||||||
throw new Error(body?.error ?? `HTTP ${response.status}`);
|
|
||||||
}
|
|
||||||
return response.json() as Promise<MessageListResponse>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateConversation(
|
export async function updateConversation(
|
||||||
|
|||||||
@@ -184,7 +184,9 @@ body {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
overflow-anchor: auto;
|
||||||
padding-left: var(--ant-padding-sm);
|
padding-left: var(--ant-padding-sm);
|
||||||
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-loading-indicator {
|
.chat-loading-indicator {
|
||||||
@@ -228,14 +230,23 @@ body {
|
|||||||
color: var(--ant-color-error);
|
color: var(--ant-color-error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.welcome-icon {
|
||||||
|
color: var(--ant-color-primary);
|
||||||
|
font-size: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-title {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-model-select {
|
.chat-model-select {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-extra .ant-btn-text {
|
.card-extra-actions .btn-dimmed {
|
||||||
color: var(--ant-color-text-quaternary);
|
color: var(--ant-color-text-quaternary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-extra .ant-btn-text:hover {
|
.card-extra-actions .btn-dimmed:hover {
|
||||||
color: var(--ant-color-text-secondary);
|
color: var(--ant-color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,19 +13,33 @@ import { createMigratedMemoryTestDatabase } from "../../helpers";
|
|||||||
const MODE: RuntimeMode = "test";
|
const MODE: RuntimeMode = "test";
|
||||||
|
|
||||||
void mock.module("ai", () => ({
|
void mock.module("ai", () => ({
|
||||||
createAgentUIStreamResponse: () =>
|
createAgentUIStreamResponse: (opts: {
|
||||||
Promise.resolve(
|
agent: unknown;
|
||||||
|
messages: unknown[];
|
||||||
|
onFinish:
|
||||||
|
| ((event: { finishReason?: string; responseMessage: { parts?: Array<{ text: string; type: string }> } }) => void)
|
||||||
|
| undefined;
|
||||||
|
}) => {
|
||||||
|
if (opts.onFinish) {
|
||||||
|
opts.onFinish({
|
||||||
|
responseMessage: {
|
||||||
|
parts: [{ text: "test reply from AI", type: "text" }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve(
|
||||||
new Response(
|
new Response(
|
||||||
'data: {"type":"start-step"}\n\ndata: {"type":"text-start","id":"txt-1"}\n\ndata: {"type":"text-delta","id":"txt-1","delta":"test reply from AI"}\n\ndata: {"type":"text-end","id":"txt-1"}\n\ndata: {"type":"finish-step"}\n\ndata: {"type":"finish"}\n\n',
|
'data: {"type":"start-step"}\n\ndata: {"type":"text-start","id":"txt-1"}\n\ndata: {"type":"text-delta","id":"txt-1","delta":"test reply from AI"}\n\ndata: {"type":"text-end","id":"txt-1"}\n\ndata: {"type":"finish-step"}\n\ndata: {"type":"finish"}\n\n',
|
||||||
{
|
{
|
||||||
headers: { "Content-Type": "text/event-stream" },
|
headers: { "Content-Type": "text/event-stream" },
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
createProviderRegistry: () => ({
|
createProviderRegistry: () => ({
|
||||||
languageModel: () => ({}),
|
languageModel: () => ({}),
|
||||||
}),
|
}),
|
||||||
generateText: () => Promise.resolve({ text: "", usage: {} }),
|
generateText: () => Promise.resolve({ text: "AI总结标题", usage: {} }),
|
||||||
stepCountIs: () => () => true,
|
stepCountIs: () => () => true,
|
||||||
tool: () => ({ execute: async () => await Promise.resolve({}) }),
|
tool: () => ({ execute: async () => await Promise.resolve({}) }),
|
||||||
ToolLoopAgent: function M() {
|
ToolLoopAgent: function M() {
|
||||||
@@ -593,8 +607,56 @@ describe("聊天 API 路由", () => {
|
|||||||
db,
|
db,
|
||||||
);
|
);
|
||||||
const msgBody = (await msgRes.json()) as { items: Message[] };
|
const msgBody = (await msgRes.json()) as { items: Message[] };
|
||||||
expect(msgBody.items.length).toBeGreaterThanOrEqual(1);
|
expect(msgBody.items.length).toBeGreaterThanOrEqual(2);
|
||||||
expect(msgBody.items.some((m) => m.role === "user")).toBe(true);
|
expect(msgBody.items.some((m) => m.role === "user")).toBe(true);
|
||||||
|
expect(msgBody.items.some((m) => m.role === "assistant")).toBe(true);
|
||||||
|
handle.close();
|
||||||
|
} finally {
|
||||||
|
handle.cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("首次发送消息时触发标题生成", async () => {
|
||||||
|
const handle = createMigratedMemoryTestDatabase("chat-send-title");
|
||||||
|
try {
|
||||||
|
const db = handle.db;
|
||||||
|
const projectId = seedProject(db);
|
||||||
|
const providerId = seedProvider(db);
|
||||||
|
const modelId = seedModel(db, providerId);
|
||||||
|
|
||||||
|
const createRes = await createConversationViaHandler(
|
||||||
|
new Request(`http://localhost/api/projects/${projectId}/conversations`, {
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
method: "POST",
|
||||||
|
}),
|
||||||
|
db,
|
||||||
|
);
|
||||||
|
const created = ((await createRes.json()) as { conversation: Conversation }).conversation;
|
||||||
|
expect(created.title).toBe("新会话");
|
||||||
|
|
||||||
|
await sendChatViaHandler(
|
||||||
|
new Request(`http://localhost/api/projects/${projectId}/chat`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
conversationId: created.id,
|
||||||
|
messages: [{ parts: [{ text: "请帮我分析一下这个项目的性能瓶颈", type: "text" }], role: "user" }],
|
||||||
|
modelDbId: modelId,
|
||||||
|
}),
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
method: "POST",
|
||||||
|
}),
|
||||||
|
db,
|
||||||
|
);
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
|
|
||||||
|
const getRes = await getConversationViaHandler(
|
||||||
|
new Request(`http://localhost/api/projects/${projectId}/conversations/${created.id}`),
|
||||||
|
db,
|
||||||
|
);
|
||||||
|
const body = (await getRes.json()) as { conversation: Conversation };
|
||||||
|
expect(body.conversation.title).not.toBe("新会话");
|
||||||
|
expect(body.conversation.title).toBe("AI总结标题");
|
||||||
handle.close();
|
handle.close();
|
||||||
} finally {
|
} finally {
|
||||||
handle.cleanup();
|
handle.cleanup();
|
||||||
|
|||||||
Reference in New Issue
Block a user