This commit is contained in:
2026-04-10 15:37:02 +08:00
parent 161cd32277
commit f19e482c8f
233 changed files with 43987 additions and 5127 deletions

View File

@@ -0,0 +1,78 @@
import { fetchJson, parseApiErrorMessage, saveJsonObject } from './jsonClient';
export const EDITOR_API_BASE_PATH = '/api/editor';
export const ASSETS_API_BASE_PATH = '/api/assets';
export const EDITOR_JSON_RESOURCE_IDS = {
itemOverrides: 'item-overrides',
npcVisualOverrides: 'npc-visual-overrides',
npcLayoutConfig: 'npc-layout-config',
characterOverrides: 'character-overrides',
monsterOverrides: 'monster-overrides',
sceneOverrides: 'scene-overrides',
sceneNpcOverrides: 'scene-npc-overrides',
stateFunctionOverrides: 'state-function-overrides',
} as const;
export type EditorJsonResourceId =
(typeof EDITOR_JSON_RESOURCE_IDS)[keyof typeof EDITOR_JSON_RESOURCE_IDS];
export const ASSET_API_PATHS = {
characterVisualGenerate: `${ASSETS_API_BASE_PATH}/character-visual/generate`,
characterVisualPublish: `${ASSETS_API_BASE_PATH}/character-visual/publish`,
characterVisualJobs: `${ASSETS_API_BASE_PATH}/character-visual/jobs`,
characterAnimationGenerate: `${ASSETS_API_BASE_PATH}/character-animation/generate`,
characterAnimationPublish: `${ASSETS_API_BASE_PATH}/character-animation/publish`,
characterAnimationJobs: `${ASSETS_API_BASE_PATH}/character-animation/jobs`,
characterAnimationImportVideo: `${ASSETS_API_BASE_PATH}/character-animation/import-video`,
characterAnimationTemplates: `${ASSETS_API_BASE_PATH}/character-animation/templates`,
qwenSpriteMaster: `${ASSETS_API_BASE_PATH}/qwen-sprite/master`,
qwenSpriteSheet: `${ASSETS_API_BASE_PATH}/qwen-sprite/sheet`,
qwenSpriteFrameRepair: `${ASSETS_API_BASE_PATH}/qwen-sprite/frame-repair`,
qwenSpriteSave: `${ASSETS_API_BASE_PATH}/qwen-sprite/save`,
} as const;
export const EDITOR_ITEM_CATALOG_API_PATH =
`${EDITOR_API_BASE_PATH}/catalog/items`;
export function buildEditorJsonApiPath(resourceId: EditorJsonResourceId) {
return `${EDITOR_API_BASE_PATH}/json/${resourceId}`;
}
export function fetchEditorJsonResource<T>(
resourceId: EditorJsonResourceId,
fallbackMessage = '读取失败',
) {
return fetchJson<T>(buildEditorJsonApiPath(resourceId), fallbackMessage);
}
export function saveEditorJsonResource(
resourceId: EditorJsonResourceId,
payload: Record<string, unknown>,
fallbackMessage = '保存失败',
) {
return saveJsonObject(
buildEditorJsonApiPath(resourceId),
payload,
fallbackMessage,
);
}
export async function postApiJson<T>(
url: string,
payload: Record<string, unknown>,
fallbackMessage: string,
) {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const responseText = await response.text();
if (!response.ok) {
throw new Error(parseApiErrorMessage(responseText, fallbackMessage));
}
return responseText ? (JSON.parse(responseText) as T) : ({} as T);
}

View File

@@ -1,43 +1,28 @@
type ApiErrorPayload = {
error?: {
message?: string;
};
message?: string;
};
export function parseApiErrorMessage(responseText: string, fallbackMessage: string) {
if (!responseText) {
return fallbackMessage;
}
try {
const parsed = JSON.parse(responseText) as ApiErrorPayload;
if (parsed.error?.message) {
return parsed.error.message;
}
if (typeof parsed.message === 'string' && parsed.message.trim()) {
return parsed.message;
}
} catch {
// Fall through to the raw response text below.
}
return responseText;
}
import {
API_RESPONSE_ENVELOPE_HEADER,
API_RESPONSE_ENVELOPE_VERSION,
parseApiErrorMessage,
unwrapApiResponse,
} from '../../../packages/shared/src/http';
export async function fetchJson<T>(
url: string,
fallbackMessage = '请求失败',
): Promise<T> {
const response = await fetch(url);
const response = await fetch(url, {
headers: {
[API_RESPONSE_ENVELOPE_HEADER]: API_RESPONSE_ENVELOPE_VERSION,
},
});
const responseText = await response.text();
if (!response.ok) {
throw new Error(parseApiErrorMessage(responseText, `${fallbackMessage}: ${response.status}`));
}
return responseText ? (JSON.parse(responseText) as T) : ({} as T);
return responseText
? unwrapApiResponse<T>(JSON.parse(responseText) as T)
: ({} as T);
}
export async function saveJsonObject(
@@ -47,7 +32,10 @@ export async function saveJsonObject(
) {
const response = await fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
headers: {
'Content-Type': 'application/json',
[API_RESPONSE_ENVELOPE_HEADER]: API_RESPONSE_ENVELOPE_VERSION,
},
body: JSON.stringify(payload, null, 2),
});
const responseText = await response.text();
@@ -56,3 +44,5 @@ export async function saveJsonObject(
throw new Error(parseApiErrorMessage(responseText, fallbackMessage));
}
}
export { parseApiErrorMessage };

View File

@@ -1,9 +1,12 @@
import { useState } from 'react';
import { saveJsonObject } from './jsonClient';
import {
saveEditorJsonResource,
type EditorJsonResourceId,
} from './editorApiClient';
type UseJsonSaveOptions = {
endpoint: string;
resourceId: EditorJsonResourceId;
payload: Record<string, unknown>;
validate?: () => string[];
successMessage: string;
@@ -11,7 +14,7 @@ type UseJsonSaveOptions = {
};
export function useJsonSave({
endpoint,
resourceId,
payload,
validate,
successMessage,
@@ -32,7 +35,7 @@ export function useJsonSave({
}
try {
await saveJsonObject(endpoint, payload);
await saveEditorJsonResource(resourceId, payload, errorMessage);
setSaveMessage(successMessage);
} catch (error) {
setSaveMessage(error instanceof Error ? error.message : errorMessage);