147 lines
4.3 KiB
TypeScript
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, "无法发送消息");
|
|
}
|
|
}
|