Files
Genarrative/apps/ai-game-creator-shell/src/view/project-development/resourceEditModel.ts
T
menghao 03a4410272
Project CI / Repository checks (push) Successful in 1m5s
Project CI / Frontend tests (push) Successful in 3m20s
Project CI / Backend tests (push) Successful in 4m1s
Project CI / Native shell tests (push) Failing after 11m56s
game agent无限画布开发 (#136)
主要工作是共享同一套“画布内核与通用 UI 源码”,网站和 Tauri 只分别实现宿主适配层。

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Co-authored-by: kdletters <kdletters@qq.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/136
Co-authored-by: menghao <mh18530625731@163.com>
Co-committed-by: menghao <mh18530625731@163.com>
2026-08-12 10:54:16 +08:00

217 lines
5.4 KiB
TypeScript

import type { ProjectResource } from './resourceProjectionModel';
export type LocalProjectResourceEditKind =
| 'image-reference'
| 'svg'
| 'video'
| 'sound-effect'
| 'background-music'
| 'text'
| 'agent-result'
| 'version';
export type ProjectResourceEditCapability =
| {
route: 'image-canvas';
sourceAssetId: string;
normalizeSource: false;
mediaType: 'image/png' | 'image/jpeg' | 'image/webp';
}
| {
route: 'image-canvas';
sourceAssetId: null;
normalizeSource: true;
mediaType: 'image/png' | 'image/jpeg' | 'image/webp';
}
| {
route: 'derive';
editKind: LocalProjectResourceEditKind;
sourceMediaType: string;
semanticNotice: string | null;
};
const extensionMediaTypes: Record<string, string> = {
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
webp: 'image/webp',
gif: 'image/gif',
svg: 'image/svg+xml',
avif: 'image/avif',
bmp: 'image/bmp',
mp4: 'video/mp4',
webm: 'video/webm',
mov: 'video/quicktime',
mp3: 'audio/mpeg',
wav: 'audio/wav',
ogg: 'audio/ogg',
m4a: 'audio/mp4',
aac: 'audio/aac',
flac: 'audio/flac',
opus: 'audio/opus',
md: 'text/markdown',
markdown: 'text/markdown',
mdx: 'application/mdx',
txt: 'text/plain',
json: 'application/json',
yaml: 'application/yaml',
yml: 'application/yaml',
toml: 'application/toml',
html: 'text/html',
htm: 'text/html',
css: 'text/css',
scss: 'text/css',
less: 'text/css',
js: 'text/javascript',
jsx: 'text/javascript',
mjs: 'text/javascript',
cjs: 'text/javascript',
ts: 'text/typescript',
tsx: 'text/typescript',
rs: 'text/x-rust',
py: 'text/x-python',
go: 'text/x-go',
java: 'text/plain',
kt: 'text/plain',
kts: 'text/plain',
c: 'text/plain',
cc: 'text/plain',
cpp: 'text/plain',
h: 'text/plain',
hpp: 'text/plain',
cs: 'text/plain',
swift: 'text/plain',
php: 'text/plain',
rb: 'text/plain',
lua: 'text/plain',
sh: 'text/plain',
bash: 'text/plain',
zsh: 'text/plain',
sql: 'text/plain',
graphql: 'text/plain',
gql: 'text/plain',
xml: 'text/plain',
csv: 'text/csv',
ini: 'text/plain',
conf: 'text/plain',
vue: 'text/plain',
svelte: 'text/plain',
};
function pathExtension(path: string) {
return path.split(/[?#]/u, 1)[0]?.split('.').pop()?.toLowerCase() ?? '';
}
export function canonicalProjectedResourceMediaType(resource: ProjectResource) {
const declared = resource.mediaType.trim().toLowerCase();
if (/^[a-z0-9.+-]+\/[a-z0-9.+-]+$/u.test(declared)) {
return declared;
}
return extensionMediaTypes[pathExtension(resource.path)] ?? declared;
}
function isBackgroundMusic(resource: ProjectResource) {
const semanticSource = `${resource.subtype} ${resource.path}`.toLowerCase();
return ['background', 'bgm', 'music', 'theme'].some((marker) =>
semanticSource.includes(marker),
);
}
export function resolveProjectResourceEditCapability(
resource: ProjectResource,
): ProjectResourceEditCapability {
if (resource.version) {
return {
route: 'derive',
editKind: 'version',
sourceMediaType: 'application/vnd.genarrative.project-version+json',
semanticNotice: null,
};
}
if (resource.subtype === 'agent-result') {
return {
route: 'derive',
editKind: 'agent-result',
sourceMediaType: 'text/markdown',
semanticNotice: null,
};
}
const mediaType = canonicalProjectedResourceMediaType(resource);
if (['image/png', 'image/jpeg', 'image/webp'].includes(mediaType)) {
return resource.manifestAssetId
? {
route: 'image-canvas',
sourceAssetId: resource.manifestAssetId,
normalizeSource: false,
mediaType: mediaType as 'image/png' | 'image/jpeg' | 'image/webp',
}
: {
route: 'image-canvas',
sourceAssetId: null,
normalizeSource: true,
mediaType: mediaType as 'image/png' | 'image/jpeg' | 'image/webp',
};
}
if (mediaType === 'image/svg+xml') {
return {
route: 'derive',
editKind: 'svg',
sourceMediaType: mediaType,
semanticNotice: null,
};
}
if (mediaType.startsWith('image/')) {
return {
route: 'derive',
editKind: 'image-reference',
sourceMediaType: mediaType,
semanticNotice: null,
};
}
if (mediaType.startsWith('video/')) {
return {
route: 'derive',
editKind: 'video',
sourceMediaType: mediaType,
semanticNotice: null,
};
}
if (resource.category === 'audio' || mediaType.startsWith('audio/')) {
return {
route: 'derive',
editKind: isBackgroundMusic(resource)
? 'background-music'
: 'sound-effect',
sourceMediaType: mediaType,
semanticNotice: '音频将基于原资源语义派生重制,不会修改原音频波形。',
};
}
return {
route: 'derive',
editKind: 'text',
sourceMediaType: mediaType,
semanticNotice: null,
};
}
export function defaultDerivedResourceName(resource: ProjectResource) {
const baseName = resource.label.replace(/\.[^.]+$/u, '').trim();
return `${baseName || '资源'}-编辑版`;
}
export function resourceEditPromptMaxLength(
editKind: LocalProjectResourceEditKind,
) {
switch (editKind) {
case 'background-music':
return 140;
case 'sound-effect':
return 1_900;
case 'video':
return 4_000;
default:
return 32_000;
}
}