feat: restore wechat thread ui and group chat

This commit is contained in:
kris
2026-03-28 05:21:44 +08:00
parent afa7e79ad2
commit f0735b31e5
41 changed files with 4091 additions and 578 deletions

View File

@@ -0,0 +1,34 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { createProjectGroupChat } from "@/lib/boss-data";
export async function POST(
request: NextRequest,
context: { params: Promise<{ projectId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { projectId } = await context.params;
const body = (await request.json()) as {
memberProjectIds?: string[];
};
try {
const project = await createProjectGroupChat({
sourceProjectId: projectId,
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 },
);
}
}

View File

@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { appendProjectMessage } from "@/lib/boss-data";
import { appendProjectMessage, readState } from "@/lib/boss-data";
import { replyToMasterAgentUserMessage } from "@/lib/boss-master-agent";
export async function POST(
@@ -38,7 +38,24 @@ export async function POST(
});
}
return NextResponse.json({ ok: true, message, masterReply });
const state = await readState();
const project = state.projects.find((item) => item.id === projectId);
const collaborationGate = project
? {
isGroup: project.isGroup,
collaborationMode: project.collaborationMode,
requiresMasterAgentApproval:
project.isGroup && project.collaborationMode === "approval_required",
approvalState: project.approvalState,
}
: {
isGroup: false,
collaborationMode: "development" as const,
requiresMasterAgentApproval: false,
approvalState: "not_required" as const,
};
return NextResponse.json({ ok: true, message, masterReply, collaborationGate });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },

View File

@@ -0,0 +1,102 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { readState } from "@/lib/boss-data";
type ConversationParticipant = {
projectId: string;
deviceId: string;
threadId: string;
threadDisplayName: string;
folderName: string;
avatar: string;
isSourceProject: boolean;
};
function getFallbackAvatar(label: string) {
const trimmed = label.trim();
if (!trimmed) return "A";
return trimmed.slice(0, 1).toUpperCase();
}
function buildParticipant(
projectId: string,
deviceId: string,
threadId: string,
threadDisplayName: string,
folderName: string,
avatar?: string,
isSourceProject = false,
): ConversationParticipant {
return {
projectId,
deviceId,
threadId,
threadDisplayName,
folderName,
avatar: avatar?.trim() || getFallbackAvatar(threadDisplayName),
isSourceProject,
};
}
export async function GET(
request: NextRequest,
context: { params: Promise<{ projectId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { projectId } = await context.params;
const state = await readState();
const project = state.projects.find((item) => item.id === projectId);
if (!project) {
return NextResponse.json({ ok: false, message: "PROJECT_NOT_FOUND" }, { status: 404 });
}
const participants = project.isGroup
? (project.groupMembers.length > 0
? project.groupMembers.map((member) => {
const device = state.devices.find((item) => item.id === member.deviceId);
return buildParticipant(
member.projectId,
member.deviceId,
member.threadId,
member.threadDisplayName,
member.folderName,
device?.avatar,
member.projectId === project.id,
);
})
: [
buildParticipant(
project.id,
project.deviceIds[0] ?? project.id,
project.threadMeta.threadId,
project.threadMeta.threadDisplayName,
project.threadMeta.folderName,
state.devices.find((item) => item.id === project.deviceIds[0])?.avatar,
true,
),
])
: [
buildParticipant(
project.id,
project.deviceIds[0] ?? project.id,
project.threadMeta.threadId,
project.threadMeta.threadDisplayName,
project.threadMeta.folderName,
state.devices.find((item) => item.id === project.deviceIds[0])?.avatar,
true,
),
];
return NextResponse.json({
ok: true,
projectId: project.id,
isGroup: project.isGroup,
threadMeta: project.threadMeta,
participants,
});
}

View File

@@ -0,0 +1,45 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { renameGroupChat, renameProjectThread } from "@/lib/boss-data";
export async function POST(
request: NextRequest,
context: { params: Promise<{ projectId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const { projectId } = await context.params;
const body = (await request.json()) as {
mode?: "thread" | "group";
name?: string;
};
const name = body.name?.trim();
if (!name) {
return NextResponse.json({ ok: false, message: "EMPTY_NAME" }, { status: 400 });
}
try {
const project =
body.mode === "group"
? await renameGroupChat({
projectId,
name,
requestedBy: session.account,
})
: await renameProjectThread({
projectId,
threadDisplayName: name,
requestedBy: 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 },
);
}
}