Files
ai-skill-chat/src/app/api/chat/route.ts
2026-07-30 08:42:55 +08:00

147 lines
4.3 KiB
TypeScript

import { apiError } from "@/lib/api";
import { startChatRun } from "@/lib/chat-runs";
import {
createChatRunWithMessages,
getActiveChatRun,
getConversation,
getConversationContinuation,
getDefaultConversation,
getSkill,
listMessages,
} from "@/lib/db";
import { hasDeepSeekApiKey } from "@/lib/deepseek";
import {
pinSkillsToSnapshots,
snapshotsForSkills,
} from "@/lib/skill-snapshots";
import { classifyContinuationReply } from "@/lib/skill-continuation";
import type { ChatMessage, Skill } from "@/lib/types";
import { createId } from "@/lib/utils";
import { chatRequestSchema } from "@/lib/validation";
export const runtime = "nodejs";
export const maxDuration = 120;
export async function GET() {
try {
const conversation = getDefaultConversation();
const continuation = getConversationContinuation(conversation.id);
return Response.json({
conversation,
messages: listMessages(conversation.id),
activeRun: getActiveChatRun(conversation.id),
retainedSkillIds: continuation.skillIds,
continuation,
apiConfigured: hasDeepSeekApiKey(),
});
} catch (error) {
return apiError(error, "无法读取聊天记录");
}
}
export async function POST(request: Request) {
try {
const input = chatRequestSchema.parse(await request.json());
if (!getConversation(input.conversationId)) {
return Response.json({ error: "会话不存在" }, { status: 404 });
}
const existingMessages = listMessages(input.conversationId);
const continuation = getConversationContinuation(
input.conversationId,
);
const currentSelectedSkills = [...new Set(input.selectedSkillIds)]
.map((id) => getSkill(id))
.filter(
(skill): skill is Skill => skill?.status === "active",
);
const replyKind = classifyContinuationReply(input.message);
const continuesExistingTask =
!["complete", "abandoned"].includes(continuation.state) &&
replyKind !== "new_topic" &&
replyKind !== "decline";
const retainedSnapshots = continuesExistingTask
? continuation.skillSnapshots
: [];
const selectedSkills = pinSkillsToSnapshots(
currentSelectedSkills,
retainedSnapshots,
);
const skillSnapshots = snapshotsForSkills(
selectedSkills,
retainedSnapshots,
);
const userMessageId = createId("message");
const assistantMessageId = createId("message");
const runId = createId("run");
const now = new Date().toISOString();
const creation = createChatRunWithMessages({
conversationId: input.conversationId,
userMessage: {
id: userMessageId,
role: "user",
content: input.message,
selectedSkillIds: selectedSkills.map((skill) => skill.id),
usedSkillIds: [],
skillSnapshots,
skillExecutions: [],
status: "complete",
createdAt: now,
},
assistantMessage: {
id: assistantMessageId,
role: "assistant",
content: "",
selectedSkillIds: selectedSkills.map((skill) => skill.id),
usedSkillIds: [],
skillSnapshots,
skillExecutions: [],
status: "streaming",
createdAt: new Date(Date.now() + 1).toISOString(),
},
run: {
id: runId,
clientRequestId: input.clientRequestId,
userMessageId,
assistantMessageId,
},
});
if (creation.outcome === "existing") {
return Response.json({ run: creation.run });
}
if (creation.outcome === "active_conflict") {
return Response.json(
{ error: "当前已有一轮回答正在进行", run: creation.run },
{ status: 409 },
);
}
const run = creation.run;
const history = existingMessages
.filter((message) => message.status !== "error")
.slice(-20)
.map((message) => ({
role: message.role,
content: message.content,
})) satisfies Array<{
role: ChatMessage["role"];
content: string;
}>;
startChatRun({
runId,
conversationId: input.conversationId,
userMessage: input.message,
selectedSkills,
skillSnapshots,
continuation,
history,
});
return Response.json({ run }, { status: 202 });
} catch (error) {
return apiError(error, "无法发送消息");
}
}