fix: restore safe top actions and home group chat entry
This commit is contained in:
29
src/app/api/v1/group-chats/route.ts
Normal file
29
src/app/api/v1/group-chats/route.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { requireRequestSession } from "@/lib/boss-auth";
|
||||
import { createIndependentGroupChat } from "@/lib/boss-data";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const session = await requireRequestSession(request);
|
||||
if (!session) {
|
||||
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = (await request.json()) as {
|
||||
memberProjectIds?: string[];
|
||||
};
|
||||
|
||||
try {
|
||||
const project = await createIndependentGroupChat({
|
||||
memberProjectIds: Array.isArray(body.memberProjectIds)
|
||||
? body.memberProjectIds.filter((memberProjectId) => typeof memberProjectId === "string")
|
||||
: [],
|
||||
createdBy: session.account,
|
||||
});
|
||||
return NextResponse.json({ ok: true, project });
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4411,84 +4411,129 @@ export async function createProjectGroupChat(input: {
|
||||
const project = await mutateState((state) => {
|
||||
const source = state.projects.find((item) => item.id === input.sourceProjectId);
|
||||
if (!source) throw new Error("GROUP_CHAT_SOURCE_NOT_FOUND");
|
||||
|
||||
const requestedProjectIds = [input.sourceProjectId, ...input.memberProjectIds];
|
||||
const memberProjects: Project[] = [];
|
||||
const seenProjectIds = new Set<string>();
|
||||
for (const projectId of requestedProjectIds) {
|
||||
if (seenProjectIds.has(projectId)) {
|
||||
continue;
|
||||
}
|
||||
seenProjectIds.add(projectId);
|
||||
|
||||
const memberProject = state.projects.find((item) => item.id === projectId);
|
||||
if (!memberProject) {
|
||||
throw new Error("GROUP_CHAT_MEMBER_NOT_FOUND");
|
||||
}
|
||||
memberProjects.push(memberProject);
|
||||
}
|
||||
if (memberProjects.length < 2) {
|
||||
throw new Error("GROUP_CHAT_REQUIRES_AT_LEAST_TWO_THREADS");
|
||||
}
|
||||
|
||||
const now = nowIso();
|
||||
const projectId = randomToken("project");
|
||||
const threadId = randomToken("thread");
|
||||
const threadDisplayName = source.threadMeta.threadDisplayName ?? source.name;
|
||||
const folderName = source.threadMeta.folderName ?? (source.isGroup ? "群聊" : source.name);
|
||||
const groupMembers = memberProjects.map((memberProject) => ({
|
||||
projectId: memberProject.id,
|
||||
deviceId: memberProject.deviceIds[0] ?? memberProject.id,
|
||||
threadId: memberProject.threadMeta.threadId,
|
||||
threadDisplayName: memberProject.threadMeta.threadDisplayName,
|
||||
folderName: memberProject.threadMeta.folderName,
|
||||
}));
|
||||
const nextProject = normalizeProject({
|
||||
id: projectId,
|
||||
name: threadDisplayName,
|
||||
pinned: false,
|
||||
systemPinned: false,
|
||||
deviceIds: dedupeStrings(groupMembers.map((member) => member.deviceId)),
|
||||
preview: `已创建群聊《${threadDisplayName}》`,
|
||||
updatedAt: now,
|
||||
lastMessageAt: now,
|
||||
isGroup: true,
|
||||
unreadCount: 0,
|
||||
riskLevel: source.riskLevel,
|
||||
threadMeta: {
|
||||
projectId,
|
||||
threadId,
|
||||
threadDisplayName,
|
||||
folderName,
|
||||
activityIconCount: Math.max(1, memberProjects.length),
|
||||
updatedAt: now,
|
||||
},
|
||||
groupMembers,
|
||||
createdByAgent: true,
|
||||
collaborationMode: "development",
|
||||
approvalState: "not_required",
|
||||
messages: [
|
||||
{
|
||||
id: randomToken("msg"),
|
||||
sender: "master",
|
||||
senderLabel: input.createdBy || "群聊创建",
|
||||
body: `已由 ${input.createdBy || "系统"} 创建群聊《${threadDisplayName}》。`,
|
||||
sentAt: now,
|
||||
kind: "text",
|
||||
},
|
||||
],
|
||||
goals: [],
|
||||
versions: [],
|
||||
return createGroupChatFromProjectIds(state, {
|
||||
requestedProjectIds: [input.sourceProjectId, ...input.memberProjectIds],
|
||||
createdBy: input.createdBy,
|
||||
defaultRiskLevel: source.riskLevel,
|
||||
});
|
||||
|
||||
state.projects.unshift(nextProject);
|
||||
return nextProject;
|
||||
});
|
||||
publishBossEvent("project.messages.updated", { projectId: project.id });
|
||||
publishBossEvent("conversation.updated", { projectId: project.id });
|
||||
return project;
|
||||
}
|
||||
|
||||
export async function createIndependentGroupChat(input: {
|
||||
memberProjectIds: string[];
|
||||
createdBy: string;
|
||||
}) {
|
||||
const project = await mutateState((state) =>
|
||||
createGroupChatFromProjectIds(state, {
|
||||
requestedProjectIds: input.memberProjectIds,
|
||||
createdBy: input.createdBy,
|
||||
}),
|
||||
);
|
||||
publishBossEvent("project.messages.updated", { projectId: project.id });
|
||||
publishBossEvent("conversation.updated", { projectId: project.id });
|
||||
return project;
|
||||
}
|
||||
|
||||
function createGroupChatFromProjectIds(
|
||||
state: BossState,
|
||||
input: {
|
||||
requestedProjectIds: string[];
|
||||
createdBy: string;
|
||||
defaultRiskLevel?: Project["riskLevel"];
|
||||
},
|
||||
) {
|
||||
const memberProjects: Project[] = [];
|
||||
const seenProjectIds = new Set<string>();
|
||||
for (const projectId of input.requestedProjectIds) {
|
||||
if (!projectId || seenProjectIds.has(projectId)) {
|
||||
continue;
|
||||
}
|
||||
seenProjectIds.add(projectId);
|
||||
|
||||
const memberProject = state.projects.find((item) => item.id === projectId);
|
||||
if (!memberProject) {
|
||||
throw new Error("GROUP_CHAT_MEMBER_NOT_FOUND");
|
||||
}
|
||||
memberProjects.push(memberProject);
|
||||
}
|
||||
if (memberProjects.length < 2) {
|
||||
throw new Error("GROUP_CHAT_REQUIRES_AT_LEAST_TWO_THREADS");
|
||||
}
|
||||
|
||||
const now = nowIso();
|
||||
const projectId = randomToken("project");
|
||||
const threadId = randomToken("thread");
|
||||
const threadDisplayName = buildAutoGroupChatName(memberProjects);
|
||||
const folderName = "群聊";
|
||||
const groupMembers = memberProjects.map((memberProject) => ({
|
||||
projectId: memberProject.id,
|
||||
deviceId: memberProject.deviceIds[0] ?? memberProject.id,
|
||||
threadId: memberProject.threadMeta.threadId,
|
||||
threadDisplayName: memberProject.threadMeta.threadDisplayName,
|
||||
folderName: memberProject.threadMeta.folderName,
|
||||
}));
|
||||
const seedProject = memberProjects[0];
|
||||
const nextProject = normalizeProject({
|
||||
id: projectId,
|
||||
name: threadDisplayName,
|
||||
pinned: false,
|
||||
systemPinned: false,
|
||||
deviceIds: dedupeStrings(groupMembers.map((member) => member.deviceId)),
|
||||
preview: `已创建群聊《${threadDisplayName}》`,
|
||||
updatedAt: now,
|
||||
lastMessageAt: now,
|
||||
isGroup: true,
|
||||
unreadCount: 0,
|
||||
riskLevel: input.defaultRiskLevel ?? seedProject?.riskLevel ?? "normal",
|
||||
threadMeta: {
|
||||
projectId,
|
||||
threadId,
|
||||
threadDisplayName,
|
||||
folderName,
|
||||
activityIconCount: Math.max(1, memberProjects.length),
|
||||
updatedAt: now,
|
||||
},
|
||||
groupMembers,
|
||||
createdByAgent: true,
|
||||
collaborationMode: "development",
|
||||
approvalState: "not_required",
|
||||
messages: [
|
||||
{
|
||||
id: randomToken("msg"),
|
||||
sender: "master",
|
||||
senderLabel: input.createdBy || "群聊创建",
|
||||
body: `已由 ${input.createdBy || "系统"} 创建群聊《${threadDisplayName}》。`,
|
||||
sentAt: now,
|
||||
kind: "text",
|
||||
},
|
||||
],
|
||||
goals: [],
|
||||
versions: [],
|
||||
});
|
||||
|
||||
state.projects.unshift(nextProject);
|
||||
return nextProject;
|
||||
}
|
||||
|
||||
function buildAutoGroupChatName(memberProjects: Project[]) {
|
||||
const titles = memberProjects
|
||||
.map((project) => project.threadMeta.threadDisplayName || project.name)
|
||||
.filter((title) => typeof title === "string" && title.trim().length > 0);
|
||||
if (titles.length === 0) {
|
||||
return "新群聊";
|
||||
}
|
||||
if (titles.length === 1) {
|
||||
return titles[0];
|
||||
}
|
||||
if (titles.length === 2) {
|
||||
return `${titles[0]}、${titles[1]}`;
|
||||
}
|
||||
return `${titles[0]}、${titles[1]}等${titles.length}个线程`;
|
||||
}
|
||||
|
||||
export async function appendProjectMessage(payload: {
|
||||
projectId: string;
|
||||
sender?: MessageSender;
|
||||
|
||||
Reference in New Issue
Block a user