将图片画布入口改为 /editor/canvas 新增 editor_canvas 表并关联 editor_project 默认画布 更新 project API 响应中的 canvas 快照兼容层 统一图片画布侧栏列表项和图标按钮组件 同步前端测试、SpacetimeDB bindings、技术文档和 TRACKING 记录
222 lines
5.5 KiB
TypeScript
222 lines
5.5 KiB
TypeScript
import { requestJson } from '../apiClient';
|
|
|
|
const EDITOR_PROJECT_API_BASE = '/api/editor/projects';
|
|
const EDITOR_IMAGE_GENERATION_API = '/api/editor/images/generations';
|
|
const EDITOR_IMAGE_EDIT_API = '/api/editor/images/edits';
|
|
const DEFAULT_PROJECT_TITLE = '未命名画布';
|
|
const EDITOR_PROJECT_REQUEST_OPTIONS = {
|
|
clearAuthOnUnauthorized: false,
|
|
notifyAuthStateChange: false,
|
|
};
|
|
|
|
export type EditorCanvasViewport = {
|
|
x: number;
|
|
y: number;
|
|
scale: number;
|
|
};
|
|
|
|
export type EditorProjectLayerSnapshot = Record<string, unknown> & {
|
|
layerId: string;
|
|
resourceId: string;
|
|
};
|
|
|
|
export type EditorProjectResourceSourceType =
|
|
| 'uploaded'
|
|
| 'generated'
|
|
| 'mock_generated';
|
|
|
|
export type EditorProjectResourceSnapshot = {
|
|
resourceId: string;
|
|
projectId: string;
|
|
imageSrc: string;
|
|
objectKey?: string | null;
|
|
assetObjectId?: string | null;
|
|
width: number;
|
|
height: number;
|
|
sourceType: EditorProjectResourceSourceType;
|
|
prompt?: string | null;
|
|
actualPrompt?: string | null;
|
|
model?: string | null;
|
|
provider?: string | null;
|
|
taskId?: string | null;
|
|
sourceResourceId?: string | null;
|
|
createdAt?: string;
|
|
updatedAt?: string;
|
|
};
|
|
|
|
export type EditorImageGenerationInput = {
|
|
prompt: string;
|
|
};
|
|
|
|
export type EditorImageEditInput = {
|
|
prompt: string;
|
|
sourceImageSrc: string;
|
|
};
|
|
|
|
export type EditorImageGenerationResult = {
|
|
imageSrc: string;
|
|
width: number;
|
|
height: number;
|
|
sourceType: 'generated';
|
|
prompt: string;
|
|
actualPrompt?: string | null;
|
|
model: string;
|
|
provider: string;
|
|
taskId: string;
|
|
};
|
|
|
|
export type EditorProjectSnapshot = {
|
|
projectId: string;
|
|
title: string;
|
|
canvas?: EditorCanvasSnapshot;
|
|
viewport: EditorCanvasViewport;
|
|
layers: EditorProjectLayerSnapshot[];
|
|
resources: EditorProjectResourceSnapshot[];
|
|
updatedAt: string;
|
|
};
|
|
|
|
export type EditorCanvasSnapshot = {
|
|
canvasId: string;
|
|
projectId: string;
|
|
title: string;
|
|
viewport: EditorCanvasViewport;
|
|
layers: EditorProjectLayerSnapshot[];
|
|
createdAt?: string;
|
|
updatedAt: string;
|
|
};
|
|
|
|
export type EditorProjectCreateInput = {
|
|
title?: string;
|
|
};
|
|
|
|
export type EditorProjectLayoutSaveInput = {
|
|
viewport: EditorCanvasViewport;
|
|
layers: EditorProjectLayerSnapshot[];
|
|
};
|
|
|
|
export type EditorProjectResourceCreateInput = {
|
|
imageSrc: string;
|
|
objectKey?: string | null;
|
|
assetObjectId?: string | null;
|
|
width: number;
|
|
height: number;
|
|
sourceType: EditorProjectResourceSourceType;
|
|
prompt?: string | null;
|
|
actualPrompt?: string | null;
|
|
model?: string | null;
|
|
provider?: string | null;
|
|
taskId?: string | null;
|
|
sourceResourceId?: string | null;
|
|
};
|
|
|
|
type EditorProjectResponse = {
|
|
project: EditorProjectSnapshot;
|
|
};
|
|
|
|
type EditorProjectRecentResponse = {
|
|
project: EditorProjectSnapshot | null;
|
|
};
|
|
|
|
type EditorProjectResourceResponse = {
|
|
resource: EditorProjectResourceSnapshot;
|
|
};
|
|
|
|
type EditorImageGenerationResponse = EditorImageGenerationResult;
|
|
|
|
function jsonRequest(method: 'POST' | 'PATCH', body: Record<string, unknown>) {
|
|
return {
|
|
method,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
};
|
|
}
|
|
|
|
export async function loadRecentEditorProject() {
|
|
return requestJson<EditorProjectRecentResponse>(
|
|
`${EDITOR_PROJECT_API_BASE}/recent`,
|
|
{ method: 'GET' },
|
|
'读取图片画布工程失败',
|
|
EDITOR_PROJECT_REQUEST_OPTIONS,
|
|
);
|
|
}
|
|
|
|
export async function createEditorProject(input: EditorProjectCreateInput = {}) {
|
|
const response = await requestJson<EditorProjectResponse>(
|
|
EDITOR_PROJECT_API_BASE,
|
|
jsonRequest('POST', { title: input.title?.trim() || DEFAULT_PROJECT_TITLE }),
|
|
'创建图片画布工程失败',
|
|
EDITOR_PROJECT_REQUEST_OPTIONS,
|
|
);
|
|
return response.project;
|
|
}
|
|
|
|
export async function loadOrCreateRecentEditorProject() {
|
|
const response = await loadRecentEditorProject();
|
|
if (response.project) {
|
|
return response.project;
|
|
}
|
|
return createEditorProject({ title: DEFAULT_PROJECT_TITLE });
|
|
}
|
|
|
|
export async function saveEditorProjectLayout(
|
|
projectId: string,
|
|
input: EditorProjectLayoutSaveInput,
|
|
) {
|
|
const response = await requestJson<EditorProjectResponse>(
|
|
`${EDITOR_PROJECT_API_BASE}/${encodeURIComponent(projectId)}`,
|
|
jsonRequest('PATCH', {
|
|
viewport: input.viewport,
|
|
layers: input.layers,
|
|
}),
|
|
'保存图片画布工程失败',
|
|
EDITOR_PROJECT_REQUEST_OPTIONS,
|
|
);
|
|
return response.project;
|
|
}
|
|
|
|
export async function createEditorProjectResource(
|
|
projectId: string,
|
|
input: EditorProjectResourceCreateInput,
|
|
) {
|
|
const response = await requestJson<EditorProjectResourceResponse>(
|
|
`${EDITOR_PROJECT_API_BASE}/${encodeURIComponent(projectId)}/resources`,
|
|
jsonRequest('POST', { ...input }),
|
|
'创建图片画布资源失败',
|
|
EDITOR_PROJECT_REQUEST_OPTIONS,
|
|
);
|
|
return response.resource;
|
|
}
|
|
|
|
export async function generateEditorImage(input: EditorImageGenerationInput) {
|
|
return requestJson<EditorImageGenerationResponse>(
|
|
EDITOR_IMAGE_GENERATION_API,
|
|
jsonRequest('POST', { prompt: input.prompt }),
|
|
'生成图片失败',
|
|
{
|
|
...EDITOR_PROJECT_REQUEST_OPTIONS,
|
|
timeoutMs: 1_200_000,
|
|
retry: {
|
|
maxRetries: 0,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function editEditorImage(input: EditorImageEditInput) {
|
|
return requestJson<EditorImageGenerationResponse>(
|
|
EDITOR_IMAGE_EDIT_API,
|
|
jsonRequest('POST', {
|
|
prompt: input.prompt,
|
|
sourceImageSrc: input.sourceImageSrc,
|
|
}),
|
|
'修改图片失败',
|
|
{
|
|
...EDITOR_PROJECT_REQUEST_OPTIONS,
|
|
timeoutMs: 1_200_000,
|
|
retry: {
|
|
maxRetries: 0,
|
|
},
|
|
},
|
|
);
|
|
}
|