Files
makelore/electron/api/routes/coding-projects.ts

178 lines
6.4 KiB
TypeScript

import type { IncomingMessage, ServerResponse } from 'node:http';
import type { ProjectType } from '../../../shared/project-config';
import type { ProjectIdentityChoice } from '../../../shared/coding-project-contracts';
import type { CodingProjectConfigSnapshot } from '../../coding-projects/project-service';
import type { CodingProject } from '../../coding-projects/project-store';
import type { HostApiContext } from '../context';
import { parseJsonBody, sendJson, sendNoContent } from '../route-utils';
import { sendCodingRouteError } from './coding-route-errors';
function isProjectRoute(pathname: string): boolean {
return pathname === '/api/coding/projects'
|| pathname.startsWith('/api/coding/projects/');
}
function publicProject(project: CodingProject | null): Omit<CodingProject, 'path'> | null {
if (!project) return null;
const { path: _path, ...safe } = project;
return safe;
}
function publicProjectSnapshot(snapshot: CodingProjectConfigSnapshot) {
return {
...snapshot,
project: publicProject(snapshot.project),
};
}
export async function handleCodingProjectRoutes(
req: IncomingMessage,
res: ServerResponse,
url: URL,
ctx: HostApiContext,
): Promise<boolean> {
if (!isProjectRoute(url.pathname)) return false;
const projects = ctx.codingProducts?.projects;
const conversations = ctx.codingProducts?.conversations;
if (!projects || !conversations) {
sendJson(res, 503, {
success: false,
code: 'CODING_CORE_UNAVAILABLE',
error: '本地编程服务暂时不可用。',
});
return true;
}
try {
if (url.pathname === '/api/coding/projects' && req.method === 'GET') {
const [items, activeProject] = await Promise.all([
projects.listProjects(),
projects.getActiveProject(),
]);
sendJson(res, 200, {
projects: items.map((project) => publicProject(project)),
activeProjectId: activeProject?.id ?? null,
});
return true;
}
if (url.pathname === '/api/coding/projects/open' && req.method === 'POST') {
const body = await parseJsonBody<{ projectPath?: string }>(req);
sendJson(res, 200, { project: publicProject(await projects.openProject(body.projectPath ?? '')) });
return true;
}
if (url.pathname === '/api/coding/projects/create' && req.method === 'POST') {
const body = await parseJsonBody<{
projectPath?: string;
parentPath?: string;
projectName?: string;
projectType?: ProjectType;
identity: ProjectIdentityChoice;
}>(req);
sendJson(res, 201, { snapshot: publicProjectSnapshot(await projects.createProject(body)) });
return true;
}
if (url.pathname === '/api/coding/projects/identity' && req.method === 'POST') {
const body = await parseJsonBody<{
localProjectId?: string;
identity: ProjectIdentityChoice;
}>(req);
sendJson(res, 200, {
snapshot: publicProjectSnapshot(
await projects.resolveProjectIdentity(
body.localProjectId ?? '',
body.identity,
),
),
});
return true;
}
if (url.pathname === '/api/coding/projects/identity/independent-copy' && req.method === 'POST') {
const body = await parseJsonBody<{
localProjectId?: string;
confirmed: unknown;
}>(req);
sendJson(res, 200, {
snapshot: publicProjectSnapshot(
await projects.makeProjectIndependentCopy(
body.localProjectId ?? '',
body.confirmed as true,
),
),
});
return true;
}
if (url.pathname === '/api/coding/projects/remove' && req.method === 'POST') {
const body = await parseJsonBody<{ projectId?: string }>(req);
await projects.removeProject(body.projectId ?? '');
sendNoContent(res);
return true;
}
if (url.pathname === '/api/coding/projects/active' && req.method === 'GET') {
sendJson(res, 200, { project: publicProject(await projects.getActiveProject()) });
return true;
}
if (url.pathname === '/api/coding/projects/active' && req.method === 'POST') {
const body = await parseJsonBody<{ projectId?: string }>(req);
sendJson(res, 200, { project: publicProject(await projects.setActiveProject(body.projectId ?? '')) });
return true;
}
if (url.pathname === '/api/coding/projects/config' && req.method === 'GET') {
sendJson(res, 200, {
snapshot: publicProjectSnapshot(
await projects.getConfig(url.searchParams.get('projectId')?.trim() || undefined),
),
});
return true;
}
if (url.pathname === '/api/coding/projects/config' && req.method === 'PUT') {
const body = await parseJsonBody<{ projectId?: string; config?: unknown }>(req);
sendJson(res, 200, {
snapshot: publicProjectSnapshot(await projects.saveConfig(body.projectId ?? '', body.config)),
});
return true;
}
if (url.pathname === '/api/coding/projects/knowledge' && req.method === 'POST') {
const body = await parseJsonBody<{
projectId?: string;
fileName?: string;
contentBase64?: string;
}>(req);
sendJson(res, 201, {
knowledgeFiles: await projects.addKnowledgeFile({
projectId: body.projectId ?? '',
fileName: body.fileName ?? '',
contentBase64: body.contentBase64 ?? '',
}),
});
return true;
}
if (url.pathname === '/api/coding/projects/legacy-conversation-notice/acknowledge'
&& req.method === 'POST') {
const body = await parseJsonBody<{ projectId?: string }>(req);
sendJson(res, 200, {
snapshot: publicProjectSnapshot(
await projects.acknowledgeLegacyConversationNotice(body.projectId ?? ''),
),
});
return true;
}
if (url.pathname === '/api/coding/projects/conversations' && req.method === 'GET') {
sendJson(res, 200, {
conversations: await conversations.listConversations(
url.searchParams.get('projectId')?.trim() || undefined,
),
});
return true;
}
if (url.pathname === '/api/coding/projects/conversations' && req.method === 'POST') {
const body = await parseJsonBody<{ projectId?: string; agentId?: unknown; title?: unknown }>(req);
sendJson(res, 201, { conversation: await conversations.createConversation(body) });
return true;
}
} catch (error) {
sendCodingRouteError(res, error);
return true;
}
return false;
}