165 lines
5.4 KiB
TypeScript
165 lines
5.4 KiB
TypeScript
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
import {
|
|
IMAGE_WORKSPACE_UNAVAILABLE_CODE,
|
|
IMAGE_WORKSPACE_UNAVAILABLE_MESSAGE,
|
|
type ImageWorkspaceGenerationSettings,
|
|
type ImageWorkspaceReferenceUploadInput,
|
|
type ImageWorkspaceSendMessageInput,
|
|
type ImageWorkspaceSnapshot,
|
|
} from '../../../shared/image-workspace';
|
|
import { LocalImageWorkspaceError } from '../../image-workspace/local-workspace';
|
|
import type { HostApiContext } from '../context';
|
|
import { parseJsonBody, sendJson } from '../route-utils';
|
|
|
|
const IMAGE_WORKSPACE_ROUTE_PREFIX = '/api/works/image-workspace';
|
|
|
|
function sendWorkspace(res: ServerResponse, workspace: ImageWorkspaceSnapshot): void {
|
|
sendJson(res, 200, { success: true, status: 200, workspace });
|
|
}
|
|
|
|
function decodedSegments(pathname: string): string[] | null {
|
|
const suffix = pathname.slice(IMAGE_WORKSPACE_ROUTE_PREFIX.length).replace(/^\/+/, '');
|
|
if (!suffix) return [];
|
|
try {
|
|
return suffix.split('/').map((segment) => decodeURIComponent(segment));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function asString(value: unknown): string {
|
|
return typeof value === 'string' ? value : '';
|
|
}
|
|
|
|
function asStringArray(value: unknown): string[] {
|
|
return Array.isArray(value) && value.every((item) => typeof item === 'string')
|
|
? value
|
|
: [];
|
|
}
|
|
|
|
function asSettings(value: unknown): ImageWorkspaceGenerationSettings {
|
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) return {};
|
|
const source = value as Record<string, unknown>;
|
|
return {
|
|
modeId: asString(source.modeId) || undefined,
|
|
modelId: asString(source.modelId) || undefined,
|
|
aspectRatioId: asString(source.aspectRatioId) || undefined,
|
|
resolutionId: asString(source.resolutionId) || undefined,
|
|
outputCountId: asString(source.outputCountId) || undefined,
|
|
};
|
|
}
|
|
|
|
function sendRouteError(res: ServerResponse, error: unknown): void {
|
|
if (error instanceof LocalImageWorkspaceError) {
|
|
sendJson(res, error.status, {
|
|
success: false,
|
|
status: error.status,
|
|
code: error.code,
|
|
error: error.message,
|
|
});
|
|
return;
|
|
}
|
|
if (error instanceof SyntaxError) {
|
|
sendJson(res, 400, {
|
|
success: false,
|
|
status: 400,
|
|
code: 'IMAGE_WORKSPACE_INVALID_JSON',
|
|
error: '请求内容不是有效 JSON',
|
|
});
|
|
return;
|
|
}
|
|
sendJson(res, 500, {
|
|
success: false,
|
|
status: 500,
|
|
code: 'IMAGE_WORKSPACE_LOCAL_REQUEST_FAILED',
|
|
error: error instanceof Error ? error.message : '创作空间请求失败',
|
|
});
|
|
}
|
|
|
|
export async function handleImageWorkspaceRoutes(
|
|
req: IncomingMessage,
|
|
res: ServerResponse,
|
|
url: URL,
|
|
ctx: HostApiContext,
|
|
): Promise<boolean> {
|
|
if (url.pathname !== IMAGE_WORKSPACE_ROUTE_PREFIX
|
|
&& !url.pathname.startsWith(`${IMAGE_WORKSPACE_ROUTE_PREFIX}/`)) {
|
|
return false;
|
|
}
|
|
|
|
if (!ctx.imageWorkspace) {
|
|
sendJson(res, 501, {
|
|
success: false,
|
|
status: 501,
|
|
code: IMAGE_WORKSPACE_UNAVAILABLE_CODE,
|
|
error: IMAGE_WORKSPACE_UNAVAILABLE_MESSAGE,
|
|
});
|
|
return true;
|
|
}
|
|
|
|
const segments = decodedSegments(url.pathname);
|
|
if (!segments) {
|
|
sendJson(res, 400, { success: false, status: 400, error: '无效的绘画空间路径' });
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
if (segments.length === 0 && req.method === 'GET') {
|
|
sendWorkspace(res, await ctx.imageWorkspace.getSnapshot());
|
|
return true;
|
|
}
|
|
|
|
if (segments.length === 1 && segments[0] === 'projects' && req.method === 'POST') {
|
|
const body = await parseJsonBody<{ name?: unknown }>(req);
|
|
sendWorkspace(res, await ctx.imageWorkspace.createProject(asString(body.name)));
|
|
return true;
|
|
}
|
|
|
|
if (segments.length === 1 && segments[0] === 'local-data' && req.method === 'DELETE') {
|
|
sendWorkspace(res, await ctx.imageWorkspace.reset());
|
|
return true;
|
|
}
|
|
|
|
if (segments.length === 3 && segments[0] === 'projects' && segments[2] === 'agents' && req.method === 'POST') {
|
|
sendWorkspace(res, await ctx.imageWorkspace.addAgent(segments[1]));
|
|
return true;
|
|
}
|
|
|
|
if (segments.length === 3 && segments[0] === 'projects' && segments[2] === 'messages' && req.method === 'POST') {
|
|
const body = await parseJsonBody<Record<string, unknown>>(req);
|
|
const input: ImageWorkspaceSendMessageInput = {
|
|
projectId: segments[1],
|
|
agentId: asString(body.agentId),
|
|
prompt: asString(body.prompt),
|
|
referenceImageIds: asStringArray(body.referenceImageIds),
|
|
settings: asSettings(body.settings),
|
|
};
|
|
sendWorkspace(res, await ctx.imageWorkspace.sendMessage(input));
|
|
return true;
|
|
}
|
|
|
|
if (segments.length === 3 && segments[0] === 'projects' && segments[2] === 'references' && req.method === 'POST') {
|
|
const body = await parseJsonBody<Record<string, unknown>>(req);
|
|
const input: ImageWorkspaceReferenceUploadInput = {
|
|
projectId: segments[1],
|
|
fileName: asString(body.fileName),
|
|
mimeType: asString(body.mimeType),
|
|
contentBase64: asString(body.contentBase64),
|
|
};
|
|
const result = await ctx.imageWorkspace.uploadReference(input);
|
|
sendJson(res, 200, { success: true, status: 200, ...result });
|
|
return true;
|
|
}
|
|
|
|
sendJson(res, 404, {
|
|
success: false,
|
|
status: 404,
|
|
code: 'IMAGE_WORKSPACE_ROUTE_NOT_FOUND',
|
|
error: `没有对应的绘画空间接口:${req.method ?? 'GET'} ${url.pathname}`,
|
|
});
|
|
} catch (error) {
|
|
sendRouteError(res, error);
|
|
}
|
|
return true;
|
|
}
|