diff --git a/spx-gui/.github/instructions/vue-coding.instructions.md b/spx-gui/.github/instructions/vue-coding.instructions.md index 8afd891f4..8c5b95d77 100644 --- a/spx-gui/.github/instructions/vue-coding.instructions.md +++ b/spx-gui/.github/instructions/vue-coding.instructions.md @@ -7,3 +7,4 @@ Apply the [general coding guidelines](./general-coding.instructions.md) to all c Here are instructions for Vue Component development in spx-gui: * Generate accessibility info for interactive elements using `v-radar` directive. +* Check `src/components/ui/tokens/` for all design tokens, they can be used as CSS variables. For example: `--ui-gap-middle`, `--ui-color-primary`. diff --git a/spx-gui/src/apis/asset.ts b/spx-gui/src/apis/asset.ts index bc61984db..19437c16f 100644 --- a/spx-gui/src/apis/asset.ts +++ b/spx-gui/src/apis/asset.ts @@ -1,5 +1,6 @@ import type { FileCollection, ByPage, PaginationParams } from './common' import { client, Visibility } from './common' +import type { ProjectSettings } from './project' export { Visibility } @@ -9,6 +10,8 @@ export enum AssetType { Sound = 'sound' } +export type AssetSettings = ProjectSettings + export type AssetData = { /** Unique identifier */ id: string diff --git a/spx-gui/src/apis/assets-gen/index.ts b/spx-gui/src/apis/assets-gen/index.ts new file mode 100644 index 000000000..e55097e2e --- /dev/null +++ b/spx-gui/src/apis/assets-gen/index.ts @@ -0,0 +1,487 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { AssetType } from '@/apis/asset' +import type { AssetSettings } from '@/models/common/asset' +import { timeout } from '@/utils/utils' + +// Mock data imports +import defaultCostumeUrl from './mockdata/balrog/default_costume.png' +import idleFrameUrl from './mockdata/balrog/idle_frame.png' +import walkFrameUrl from './mockdata/balrog/walk_frame.png' +import attackStartUrl from './mockdata/balrog/attack_start.png' +import attackEndUrl from './mockdata/balrog/attack_end.png' +import idleVideoUrl from './mockdata/balrog/idle.mp4' +import walkVideoUrl from './mockdata/balrog/walk.mp4' +import attackVideoUrl from './mockdata/balrog/attack.mp4' +import soundUrl from './mockdata/sound.mp3' +import backdropUrl from './mockdata/backdrop.png' + +/** + * Base settings type that can be enriched + */ +export type BaseSettings = Partial + +/** + * Settings specific to sprite generation + */ +export type SpriteSettings = AssetSettings & { + name?: string +} + +/** + * Settings specific to costume generation + */ +export type CostumeSettings = AssetSettings & { + spriteName?: string + name?: string +} + +/** + * Settings specific to animation generation + */ +export type AnimationSettings = AssetSettings & { + spriteName?: string + name?: string +} + +/** + * Settings specific to backdrop generation + */ +export type BackdropSettings = AssetSettings & { + name?: string +} + +/** + * Settings specific to sound generation + */ +export type SoundSettings = AssetSettings & { + name?: string + duration?: number +} + +/** + * Union type for all settings + */ +export type Settings = SpriteSettings | CostumeSettings | AnimationSettings | BackdropSettings | SoundSettings + +/** + * Enriches initial settings with AI-generated details based on asset type + * @param initialSettings Partial settings to enrich + * @param assetType Type of asset being generated + * @returns Promise resolving to enriched settings + */ +export async function enrichSettings( + initialSettings: BaseSettings, + assetType: AssetType | 'costume' | 'animation' +): Promise { + // Simulate AI processing delay + await timeout(800) + + const baseEnriched: AssetSettings = { + ...initialSettings, + description: null, + projectDescription: null, + artStyle: initialSettings.artStyle ?? 'pixel-art', + perspective: initialSettings.perspective ?? 'side-view', + category: null + } + + switch (assetType) { + case AssetType.Sprite: + return { + ...baseEnriched, + name: '炎魔', + description: + initialSettings.description + + '一个由深灰色像素块组成的高大魁梧身影。肌肉发达的躯干和四肢上布满了橙黄色余烬般的发光裂纹。', + category: 'character' + } satisfies SpriteSettings + + case 'costume': + return { + ...baseEnriched, + name: '战士装扮', + description: + initialSettings.description + '一套像素风格的角色服装,具有精细的细节和鲜艳的色彩' + } satisfies CostumeSettings + + case 'animation': + return { + ...baseEnriched, + name: '行走循环', + description: + initialSettings.description + '一段流畅的动画序列,展示角色的自然移动' + } satisfies AnimationSettings + + case AssetType.Backdrop: + return { + ...baseEnriched, + name: '森林空地', + description: initialSettings.description + '一幅精细的像素风格背景场景,具有丰富的氛围元素', + category: 'scene' + } satisfies BackdropSettings + + case AssetType.Sound: + return { + ...baseEnriched, + name: '风声呼啸', + description: initialSettings.description + '一段清晰的氛围音效', + duration: 3, + category: 'effect' + } satisfies SoundSettings + + default: + throw new Error(`Unknown asset type: ${assetType}`) + } +} + +export type AnimationDescription = { + name: string + description: string +} + +/** + * Description for a costume to be generated + */ +export type CostumeDescription = { + name: string + description: string +} + +/** + * Sprite content descriptions including costumes and animations + */ +export type SpriteContentDescriptions = { + costumes: CostumeDescription[] + animations: AnimationDescription[] +} + +/** + * Generates costume and animation descriptions for a sprite + * @param settings Sprite settings + * @returns Promise resolving to costume and animation descriptions + */ +export async function generateSpriteContentDescriptions(_settings: SpriteSettings): Promise { + // Simulate AI processing delay + await timeout(1500) + + // Return mock descriptions based on Balrog example + return { + costumes: [ + { + name: '默认', + description: '默认站立姿势,身体上可见发光的余烬裂纹' + } + ], + animations: [ + { + name: '待机', + description: + '角色静止站立,有轻微的呼吸动画,身体上的余烬轻轻闪烁' + }, + { + name: '行走', + description: '角色向前迈着有力的步伐,身体随着每一步轻轻摇摆' + }, + { + name: '攻击', + description: '角色猛然向前冲刺,释放出强力的攻击动作' + } + ] + } +} + +/** + * Generates animation type descriptions based on sprite settings + * @param settings Sprite settings + * @returns Promise resolving to array of animation descriptions + */ +export async function generateAnimationDescriptions(_settings: SpriteSettings): Promise { + // Simulate AI processing delay + await timeout(1000) + + // Return mock animation descriptions based on Balrog example + return [ + { + name: '待机', + description: + '角色静止站立,有轻微的呼吸动画,身体上的余烬轻轻闪烁' + }, + { name: '行走', description: '角色向前迈着有力的步伐,身体随着每一步轻轻摇摆' }, + { + name: '攻击', + description: '角色猛然向前冲刺,释放出强力的攻击动作' + } + ] +} + +/** + * Generates a single costume image based on settings + * @param settings Costume settings + * @returns Promise resolving to image URL + */ +export async function generateCostumeImage(_settings: CostumeSettings): Promise { + // Simulate image generation delay + await timeout(2000) + + // Return mock costume image from balrog mockdata + return defaultCostumeUrl +} + +/** + * Modifies an existing costume image based on user instructions + * @param imageUrl URL of the existing image + * @param instruction User's modification instructions + * @param settings Current costume settings + * @returns Promise resolving to modified image URL + */ +export async function modifyCostumeImage( + _imageUrl: string, + _instruction: string, + _settings: CostumeSettings +): Promise { + // Simulate image modification delay + await timeout(2000) + + // Return mock modified image (in reality, this would call an AI service) + return defaultCostumeUrl +} + +/** + * Generates a reference frame image for an animation based on a costume reference + * @param settings Animation settings + * @param referenceImageUrl URL of the reference costume image + * @returns Promise resolving to frame image URL + */ +export async function generateAnimationFrame( + _settings: AnimationSettings, + _referenceImageUrl: string +): Promise { + // Simulate frame generation delay + await timeout(2000) + + // Return mock frame image + return idleFrameUrl +} + +/** + * Modifies an existing animation frame image based on user instructions + * @param imageUrl URL of the existing image + * @param instruction User's modification instructions + * @param settings Current animation settings + * @returns Promise resolving to modified image URL + */ +export async function modifyAnimationFrame( + _imageUrl: string, + _instruction: string, + _settings: AnimationSettings +): Promise { + // Simulate image modification delay + await timeout(2000) + + // Return mock modified image + return idleFrameUrl +} + +/** + * Generates start and end frame images for an animation + * @param settings Animation settings + * @returns Promise resolving to start and end frame URLs + */ +export async function generateAnimationFrames(settings: AnimationSettings): Promise<{ + startFrameUrl: string + endFrameUrl: string +}> { + // Simulate frame generation delay + await timeout(2500) + + const animType = settings.name?.toLowerCase() || 'idle' + + // Return appropriate mock frames based on animation type + const frameMap: Record = { + idle: { + start: idleFrameUrl, + end: idleFrameUrl + }, + walk: { + start: walkFrameUrl, + end: walkFrameUrl + }, + attack: { + start: attackStartUrl, + end: attackEndUrl + } + } + + const frames = frameMap[animType] || frameMap.idle + + return { + startFrameUrl: frames.start, + endFrameUrl: frames.end + } +} + +/** + * Task status for video generation + */ +export type VideoTaskStatus = { + status: 'pending' | 'processing' | 'completed' | 'failed' + /** Result video URL, only available when status is 'completed' */ + resultUrl?: string + /** Error message, only available when status is 'failed' */ + error?: string +} + +// Mock task storage for simulating async video generation +const mockVideoTasks = new Map< + string, + { + status: VideoTaskStatus['status'] + resultUrl?: string + createdAt: number + settings: AnimationSettings + } +>() + +// Video generation takes 30 seconds in mock +const VIDEO_GENERATION_DURATION = 30_000 + +/** + * Submits an animation video generation task + * @param settings Animation settings + * @param referenceFrameUrl URL of the reference frame + * @returns Promise resolving to task ID + */ +export async function submitAnimationVideoTask( + settings: AnimationSettings, + _referenceFrameUrl: string +): Promise { + // Simulate network delay for task submission + await timeout(500) + + // Generate a mock task ID + const taskId = `video-task-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` + + // Store the task + mockVideoTasks.set(taskId, { + status: 'processing', + createdAt: Date.now(), + settings + }) + + return taskId +} + +/** + * Gets the status of an animation video generation task + * @param taskId The task ID returned from submitAnimationVideoTask + * @returns Promise resolving to task status + */ +export async function getAnimationVideoTaskStatus(taskId: string): Promise { + // Simulate network delay + await timeout(200) + + const task = mockVideoTasks.get(taskId) + if (task == null) { + return { + status: 'failed', + error: 'Task not found' + } + } + + // Check if the task has been processing long enough + const elapsed = Date.now() - task.createdAt + if (elapsed >= VIDEO_GENERATION_DURATION && task.status === 'processing') { + // Task is complete, determine the result URL + const animType = task.settings.name?.toLowerCase() || 'idle' + const videoMap: Record = { + idle: idleVideoUrl, + walk: walkVideoUrl, + attack: attackVideoUrl + } + const resultUrl = videoMap[animType] || videoMap.idle + + // Update task status + task.status = 'completed' + task.resultUrl = resultUrl + } + + return { + status: task.status, + resultUrl: task.resultUrl + } +} + +/** + * Extracts frames from a video at specified intervals + * @param videoUrl URL of the video + * @param interval Interval between frames in milliseconds + * @returns Promise resolving to array of frame URLs + */ +export async function getFramesFromVideo(videoUrl: string, _interval: number = 100): Promise { + // Simulate frame extraction delay + await timeout(1500) + + // Determine which animation type based on video URL + const animType = videoUrl.includes('walk') + ? 'walk' + : videoUrl.includes('attack') + ? 'attack' + : videoUrl.includes('idle') + ? 'idle' + : 'idle' + + // Return mock frames from appropriate directory + // Note: In production, these would be dynamically loaded from the video + const frameCount = 7 + + const framePromises = Array.from({ length: frameCount }, async (_, i) => { + const frameNum = String(i).padStart(4, '0') + const frameModule = await import(`./mockdata/balrog/${animType}_frames/${animType}_${frameNum}.png`) + return frameModule.default + }) + + return Promise.all(framePromises) +} + +/** + * Generates a backdrop image based on settings + * @param settings Backdrop settings + * @returns Promise resolving to image URL + */ +export async function generateBackdropImage(_settings: BackdropSettings): Promise { + // Simulate image generation delay + await timeout(2000) + + return backdropUrl +} + +/** + * Modifies an existing backdrop image based on user instructions + * @param imageUrl URL of the existing image + * @param instruction User's modification instructions + * @param settings Current backdrop settings + * @returns Promise resolving to modified image URL + */ +export async function modifyBackdropImage( + _imageUrl: string, + _instruction: string, + _settings: BackdropSettings +): Promise { + // Simulate image modification delay + await timeout(2000) + + return backdropUrl +} + +/** + * Generates sound audio based on settings + * @param settings Sound settings + * @returns Promise resolving to audio URL + */ +export async function generateSoundAudio(_settings: SoundSettings): Promise { + // Simulate audio generation delay + await timeout(2000) + + // Return mock sound file + return soundUrl +} diff --git a/spx-gui/src/apis/assets-gen/mockdata/backdrop.png b/spx-gui/src/apis/assets-gen/mockdata/backdrop.png new file mode 100644 index 000000000..fae6c723a Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/backdrop.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack.mp4 b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack.mp4 new file mode 100644 index 000000000..67d0175cb Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack.mp4 differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_end.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_end.png new file mode 100644 index 000000000..69b6bf53c Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_end.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_end_prompt.txt b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_end_prompt.txt new file mode 100644 index 000000000..ffd6df676 --- /dev/null +++ b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_end_prompt.txt @@ -0,0 +1 @@ +Ending frame of an attack animation. Balrog in a powerful, recoiling pose after its whip strike, feet planted wide. Perfect right side view. The figure is composed of dark, charcoal-gray pixel clusters with glowing orange cracks. It has massive horns, tattered fiery wings, and holds a smoldering whip. Face is a shadow with two piercing white-hot eyes. Empty white background, no ground or shadows. \ No newline at end of file diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0000.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0000.png new file mode 100644 index 000000000..50a81f1e2 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0000.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0001.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0001.png new file mode 100644 index 000000000..35f5763f9 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0001.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0002.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0002.png new file mode 100644 index 000000000..fd70fdf70 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0002.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0003.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0003.png new file mode 100644 index 000000000..5dee49058 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0003.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0004.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0004.png new file mode 100644 index 000000000..be906cf84 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0004.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0005.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0005.png new file mode 100644 index 000000000..6fa6ba7b8 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0005.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0006.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0006.png new file mode 100644 index 000000000..0d043b325 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_frames/attack_0006.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_start.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_start.png new file mode 100644 index 000000000..d4c407adb Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_start.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_start_prompt.txt b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_start_prompt.txt new file mode 100644 index 000000000..f59eadc21 --- /dev/null +++ b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_start_prompt.txt @@ -0,0 +1 @@ +A towering Balrog in a perfect right side view. Its muscular pixel-art form is coiled in a starting stance, one arm drawn back holding a smoldering fiery whip. Glowing embers pulse across its dark torso, and its tattered wings are slightly unfurled. Empty white background. \ No newline at end of file diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_video_prompt.txt b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_video_prompt.txt new file mode 100644 index 000000000..08d9482f3 --- /dev/null +++ b/spx-gui/src/apis/assets-gen/mockdata/balrog/attack_video_prompt.txt @@ -0,0 +1 @@ +A towering, pixel-art Balrog of Moria stands tall in a white void. It roars, a wave of fiery energy pulsing from its core, causing the embers on its body to flare brightly. It leans forward aggressively, then charges across the empty space with powerful, ground-shaking strides, its tattered wings stretching wide. It violently cracks its long, fiery whip, sending a ripple of orange and yellow pixels snapping through the air. The creature continues its relentless charge towards the viewer, its piercing white eyes fixed ahead, before it unleashes a final, immense roar, engulfing the screen in fire and smoke. \ No newline at end of file diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/default_costume.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/default_costume.png new file mode 100644 index 000000000..2f9fe9ddf Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/default_costume.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/idle.mp4 b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle.mp4 new file mode 100644 index 000000000..1b115c308 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle.mp4 differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frame.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frame.png new file mode 100644 index 000000000..f2cfe2f83 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frame.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frame_prompt.txt b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frame_prompt.txt new file mode 100644 index 000000000..bddcace89 --- /dev/null +++ b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frame_prompt.txt @@ -0,0 +1 @@ +Perfect right side view of a towering pixel-art Balrog in a classic idle stance, standing broad-shouldered with its head slightly forward. Its massive, bat-like wings are half-furled at its back, and it holds a smoldering whip loosely coiled at its side. Glowing embers crackle across its dark, muscular form. Isolated on a plain white background. \ No newline at end of file diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0000.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0000.png new file mode 100644 index 000000000..0c3854e00 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0000.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0001.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0001.png new file mode 100644 index 000000000..2b88ccfca Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0001.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0002.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0002.png new file mode 100644 index 000000000..302fccc1e Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0002.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0003.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0003.png new file mode 100644 index 000000000..ac9117f5b Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0003.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0004.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0004.png new file mode 100644 index 000000000..427292613 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0004.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0005.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0005.png new file mode 100644 index 000000000..8c0539fd8 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0005.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0006.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0006.png new file mode 100644 index 000000000..3ebeec820 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_frames/idle_0006.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_video_prompt.txt b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_video_prompt.txt new file mode 100644 index 000000000..7743f1daa --- /dev/null +++ b/spx-gui/src/apis/assets-gen/mockdata/balrog/idle_video_prompt.txt @@ -0,0 +1 @@ +A towering pixel-art Balrog stands centered in an empty white void. Its massive, charcoal-gray chest slowly rises and falls with deep, rhythmic breathing. Glowing embers within its form pulse with a soft, fiery light. The creature's horned head tilts slightly, its piercing white eyes slowly scanning the emptiness. Its tattered, bat-like wings gently rustle and shift, as if stirred by an unseen heat haze. In one hand, the Balrog loosely holds its whip; the fiery tip of the whip slowly undulates and curls, coiling and uncoiling in a lazy, hypnotic rhythm. The entire sequence is a seamless, looping idle animation. \ No newline at end of file diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/settings_description.txt b/spx-gui/src/apis/assets-gen/mockdata/balrog/settings_description.txt new file mode 100644 index 000000000..0dfb3c904 --- /dev/null +++ b/spx-gui/src/apis/assets-gen/mockdata/balrog/settings_description.txt @@ -0,0 +1 @@ +A towering, broad-shouldered figure composed of dark, charcoal-gray pixel clusters. Glowing cracks of orange and yellow embers cover its muscular torso and limbs. Two massive, horn-like obsidian protrusions curve from its head. Its wings are tattered, bat-like shapes of deep crimson and black, edged with a fiery glow. It clutches a fiery whip, a serpentine line of bright red and yellow pixels that smolders at its tip. The face is a shadowy recess with two piercing white-hot points for eyes. \ No newline at end of file diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/walk.mp4 b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk.mp4 new file mode 100644 index 000000000..3e717bf85 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk.mp4 differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frame.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frame.png new file mode 100644 index 000000000..cc8bdc3d0 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frame.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frame_prompt.txt b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frame_prompt.txt new file mode 100644 index 000000000..75eb55694 --- /dev/null +++ b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frame_prompt.txt @@ -0,0 +1 @@ +Perfect right side view of the Balrog of Moria in a powerful, striding pose. Its muscular torso and limbs, made of charcoal-gray pixel clusters with glowing ember cracks, are captured mid-walk. One massive leg is forward, the other back, with its tattered, fiery-edged wings partially unfurled. Its head, with obsidian horns, is turned slightly forward, shadowy face illuminated by two piercing white-hot eyes. It clutches a long, serpentine fiery whip that arcs behind it, smoldering at the tip. Empty white background. \ No newline at end of file diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0000.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0000.png new file mode 100644 index 000000000..1d7ae625c Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0000.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0001.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0001.png new file mode 100644 index 000000000..bfa70a094 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0001.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0002.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0002.png new file mode 100644 index 000000000..91b8b2a30 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0002.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0003.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0003.png new file mode 100644 index 000000000..9a7027e31 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0003.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0004.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0004.png new file mode 100644 index 000000000..773f81c47 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0004.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0005.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0005.png new file mode 100644 index 000000000..9fab0df74 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0005.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0006.png b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0006.png new file mode 100644 index 000000000..214fe4c3f Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_frames/walk_0006.png differ diff --git a/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_video_prompt.txt b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_video_prompt.txt new file mode 100644 index 000000000..a2b2dd6ad --- /dev/null +++ b/spx-gui/src/apis/assets-gen/mockdata/balrog/walk_video_prompt.txt @@ -0,0 +1 @@ +A towering, charcoal-gray Balrog with glowing ember cracks across its muscular body and massive horns. It walks with a powerful, heavy, bipedal stride, its broad shoulders rolling with each step. Its tattered, bat-like wings shift and settle slightly with the movement. In one hand, it casually swings a long, fiery whip in a slow, sinuous, figure-eight motion. The entire walk cycle is seamless and loopable on a plain white background. \ No newline at end of file diff --git a/spx-gui/src/apis/assets-gen/mockdata/sound.mp3 b/spx-gui/src/apis/assets-gen/mockdata/sound.mp3 new file mode 100644 index 000000000..16d039076 Binary files /dev/null and b/spx-gui/src/apis/assets-gen/mockdata/sound.mp3 differ diff --git a/spx-gui/src/apis/project.ts b/spx-gui/src/apis/project.ts index 3317353d3..baa26a5cb 100644 --- a/spx-gui/src/apis/project.ts +++ b/spx-gui/src/apis/project.ts @@ -12,6 +12,12 @@ export enum ProjectDataType { Sound = 2 } +export type ProjectSettings = { + artStyle: string | null + perspective: string | null + description: string | null +} + export type ProjectData = { /** Unique identifier */ id: string @@ -47,6 +53,7 @@ export type ProjectData = { releaseCount: number /** Number of remixes associated with the project */ remixCount: number + settings: ProjectSettings } export type AddProjectByRemixParams = Pick & { @@ -56,6 +63,18 @@ export type AddProjectByRemixParams = Pick & export type AddProjectParams = Pick +async function polyfillProjectSettings(p: ProjectData | Promise): Promise { + const project = p instanceof Promise ? await p : p + if (project.settings == null) { + project.settings = { + artStyle: 'pixel', + perspective: 'side', + description: 'A fighting game among ninjas.' + } + } + return project as ProjectData +} + export async function addProject(params: AddProjectParams | AddProjectByRemixParams, signal?: AbortSignal) { return client.post('/project', params, { signal }) as Promise } @@ -64,9 +83,9 @@ export type UpdateProjectParams = Pick & Partial> export async function updateProject(owner: string, name: string, params: UpdateProjectParams, signal?: AbortSignal) { - return client.put(`/project/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`, params, { + return polyfillProjectSettings(client.put(`/project/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`, params, { signal - }) as Promise + }) as Promise) } export function deleteProject(owner: string, name: string) { @@ -102,13 +121,15 @@ export type ListProjectParams = PaginationParams & { } export async function listProject(params?: ListProjectParams) { - return client.get('/projects/list', params) as Promise> + const result = (await client.get('/projects/list', params)) as ByPage + result.data = await Promise.all(result.data.map(polyfillProjectSettings)) + return result } export async function getProject(owner: string, name: string, signal?: AbortSignal) { - return client.get(`/project/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`, undefined, { + return polyfillProjectSettings(client.get(`/project/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`, undefined, { signal - }) as Promise + }) as Promise) } export enum ExploreOrder { diff --git a/spx-gui/src/components/asset/index.ts b/spx-gui/src/components/asset/index.ts index 2830019ab..6c5ba368e 100644 --- a/spx-gui/src/components/asset/index.ts +++ b/spx-gui/src/components/asset/index.ts @@ -23,16 +23,19 @@ import { useEditorCtx } from '../editor/EditorContextProvider.vue' import { useCodeEditorCtx, useRenameWarning } from '../editor/code-editor/context' import { getResourceIdentifier } from '../editor/code-editor/common' import AssetLibraryModal from './library/AssetLibraryModal.vue' +import { type SpriteGeneration, isSpriteGeneration } from './library/generators/SpriteGeneratorModal.vue' import AssetSaveModal from './library/AssetSaveModal.vue' import LoadFromScratchModal from './scratch/LoadFromScratchModal.vue' import PreprocessModal from './preprocessing/PreprocessModal.vue' import GroupCostumesModal from './animation/GroupCostumesModal.vue' import AssetLibraryManagementModal from './library/management/AssetLibraryManagementModal.vue' +export { isSpriteGeneration, type SpriteGeneration } + export function useAddAssetFromLibrary() { const invokeAssetLibraryModal = useModal(AssetLibraryModal) return async function addAssetFromLibrary(project: Project, type: T) { - return (await invokeAssetLibraryModal({ project, type })) as Array> + return (await invokeAssetLibraryModal({ project, type })) as Array | SpriteGeneration : AssetModel> } } diff --git a/spx-gui/src/components/asset/library/AssetLibraryModal.vue b/spx-gui/src/components/asset/library/AssetLibraryModal.vue index 9e3cedecd..850bcda03 100644 --- a/spx-gui/src/components/asset/library/AssetLibraryModal.vue +++ b/spx-gui/src/components/asset/library/AssetLibraryModal.vue @@ -1,64 +1,124 @@