Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 15 additions & 292 deletions ui/src/features/agent/agentActions.ts

Large diffs are not rendered by default.

74 changes: 72 additions & 2 deletions ui/src/features/agent/applicationAdapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { rememberedCharacterKitLibrary } from '../characters/session'
import type { SeriesAssemblyJob } from '../series/assemblyContract'
import type { SeriesJobStatus } from '../series/types'
import type { MediaFilter } from '../../types'
import type { AgentApply3dRhythmAction, AgentApplySeriesPlanAction, AgentApplyStoryProposalAction, AgentApproveStorySectionAction, AgentApproveStoryVisualsAction, AgentAssembleSeriesEpisodeAction, AgentCommitSeriesCanonAction, AgentConfigureStorySongAction, AgentCreateComicAction, AgentCreateSeriesEpisodeAction, AgentCreateStoryAction, AgentCreateWorkspaceAction, AgentGenerateComicAction, AgentGenerateSeriesPlanAction, AgentGenerateStorySectionAction, AgentGenerateStorySongAction, AgentGenerateStoryVisualsAction, AgentRenderSeriesShotsAction, AgentReviewSeriesAttemptsAction, AgentSelectWorkspaceAction, AgentStageStoryComicAction, AgentStartDirectorProductionAction, AgentStageStoryMusicVideoAction, AgentStageStoryVideoAction, AgentUpdateSeriesEpisodeAction, AgentUpdateStoryAction } from './agentActions'
import type { AgentApply3dRhythmAction, AgentApplySeriesPlanAction, AgentApplyStoryProposalAction, AgentApproveStorySectionAction, AgentApproveStoryVisualsAction, AgentAssembleSeriesEpisodeAction, AgentAttachStudioReferencesAction, AgentCommitSeriesCanonAction, AgentConfigureStudioLorasAction, AgentConfigureStorySongAction, AgentCreateComicAction, AgentCreateSeriesEpisodeAction, AgentCreateStoryAction, AgentCreateWorkspaceAction, AgentGenerateComicAction, AgentGenerateSeriesPlanAction, AgentGenerateStorySectionAction, AgentGenerateStorySongAction, AgentGenerateStoryVisualsAction, AgentPrepare3dAction, AgentPrepareAudioAction, AgentPrepareImageAction, AgentPrepareVideoAction, AgentRenderSeriesShotsAction, AgentReviewSeriesAttemptsAction, AgentSelectWorkspaceAction, AgentStartGenerationAction, AgentStageStoryComicAction, AgentStartDirectorProductionAction, AgentStageStoryMusicVideoAction, AgentStageStoryVideoAction, AgentUpdateSeriesEpisodeAction, AgentUpdateStoryAction } from './agentActions'
import {
executionKey,
executionReport,
Expand All @@ -16,7 +16,6 @@ import {
import { openAgentActivityDetails, requestAgentSceneControl, requestAgentSceneRhythm, requestAgentSceneWorkflow, requestAgentStoryVisualGeneration, type AgentSceneControlRequest, type AgentSceneWorkflowRequest } from './agentUiBus'
import type { AgentRhythmGrid } from './agentUiBus'
import { queueMusic } from './audioActions'
import type { AgentPrepareAudioAction } from './agentActions'
import type { AgentTab } from './capabilityRegistry'
import type {
AgentAddVideoEditorAudioAction,
Expand Down Expand Up @@ -59,6 +58,13 @@ export interface AdapterOutcome {
export interface StudioAdapter {
open(tab?: 'studio' | 'images' | 'videos' | 'audio' | '3d'): Promise<AdapterOutcome>
queueMusic(action: AgentPrepareAudioAction): Promise<AdapterOutcome>
prepareVideo(action: AgentPrepareVideoAction): Promise<AdapterOutcome>
prepareImage(action: AgentPrepareImageAction): Promise<AdapterOutcome>
prepareAudio(action: AgentPrepareAudioAction): Promise<AdapterOutcome>
prepare3d(action: AgentPrepare3dAction): Promise<AdapterOutcome>
startGeneration(action: AgentStartGenerationAction): Promise<AdapterOutcome>
attachReferences(action: AgentAttachStudioReferencesAction): Promise<AdapterOutcome>
configureLoras(action: AgentConfigureStudioLorasAction): Promise<AdapterOutcome>
}

export interface StoryLabAdapter {
Expand Down Expand Up @@ -279,6 +285,52 @@ export function createDefaultApplicationAdapters(): WizardApplicationAdapters {
const result = await queueMusic(action)
return { ...result, target: { kind: 'queue_task', id: result.taskId, title: 'Song generation' } }
},
async prepareVideo(action) {
const { prepareVideoForm } = await import('../studio/adapters')
return presentStudioSliceResult(await prepareVideoForm(action), 'Video')
},
async prepareImage(action) {
const { prepareImageForm } = await import('../studio/adapters')
return presentStudioSliceResult(await prepareImageForm(action), 'Image')
},
async prepareAudio(action) {
const { prepareAudioForm } = await import('../studio/adapters')
return presentStudioSliceResult(await prepareAudioForm(action), 'Audio')
},
async prepare3d(action) {
const { prepare3dForm } = await import('../studio/adapters')
return presentStudioSliceResult(await prepare3dForm(action), '3D')
},
async startGeneration(action) {
const { startGeneration } = await import('../studio/adapters')
const result = await startGeneration()
const presented = await presentStudioSliceResult(result, 'Studio generation')
const taskId = result.taskIds[0]
return {
...presented,
taskId,
report: executionReport({
state: 'queued',
message: presented.message,
target: presented.target,
taskId,
recoverable: true,
executionKey: executionKey({
workspace: useStore.getState().activeWorkspace || 'default',
type: action.type,
params: action,
}),
}),
}
},
async attachReferences(action) {
const { attachReferences } = await import('../studio/adapters')
return presentStudioSliceResult(await attachReferences(action), 'Image / Video')
},
async configureLoras(action) {
const { configureLoras } = await import('../studio/adapters')
return presentStudioSliceResult(await configureLoras(action), 'Image / Video')
},
}
adapters.storyLab = {
open: () => navigate('story_lab'),
Expand Down Expand Up @@ -823,6 +875,24 @@ async function presentQueueSliceResult(result: CommandResult): Promise<AdapterOu
}
}

async function presentStudioSliceResult(result: CommandResult, fallbackTitle: string): Promise<AdapterOutcome> {
await navigate('studio')
const summary = typeof result.artifacts[0]?.metadata?.summary === 'string'
? result.artifacts[0].metadata.summary
: 'Studio listo.'
const title = String(result.artifacts[0]?.metadata?.title || fallbackTitle)
const mode = String(result.artifacts[0]?.metadata?.mode || 'studio')
return {
message: summary,
target: {
kind: mode === 'generation' ? 'generation_task' : 'studio_form',
id: result.entities[0]?.id || mode,
title,
},
taskId: result.taskIds[0],
}
}

async function presentWorkspaceSliceResult(result: CommandResult): Promise<AdapterOutcome> {
const summary = typeof result.artifacts[0]?.metadata?.summary === 'string'
? result.artifacts[0].metadata.summary
Expand Down
71 changes: 5 additions & 66 deletions ui/src/features/agent/audioActions.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,16 @@
import { useStore } from '../../stores/useStore'
import type { AgentPrepareAudioAction, AgentQueueSfxPackAction } from './agentActions'
import type { AgentSfxClip } from './sfxPack'

const AUDIO_SUB_MODE_DEFAULTS: Record<AgentPrepareAudioAction['subMode'], string> = {
speech: 'kugelaudio_0_open',
music: 'ace_step_v1_5_xl_sft_lm_4b',
sfx: 'mmaudio_v2',
}

function openStudioAudio(subMode: AgentPrepareAudioAction['subMode']): void {
const state = useStore.getState()
state.setSettingsOpen(false)
state.setDashboardOpen(false)
state.setSidebarMode('studio')
state.setSidebarOpen(true)
state.setGenerationMode('audio')
state.setAudioSubMode(subMode)
}

async function selectAudioModel(preferred?: string, subMode: AgentPrepareAudioAction['subMode'] = 'sfx'): Promise<string> {
let state = useStore.getState()
if (!state.modelsLoaded) await state.loadModels()
state = useStore.getState()
const fallback = AUDIO_SUB_MODE_DEFAULTS[subMode]
const requested = preferred && state.models.some(model => model.model_type === preferred)
? preferred
: state.params.model_type && state.models.some(model => model.model_type === state.params.model_type)
? state.params.model_type
: fallback
if (requested && state.params.model_type !== requested) {
state.selectModel(requested)
}
const selected = useStore.getState().params.model_type || requested || fallback
const selectedModel = useStore.getState().models.find(model => model.model_type === selected)
return selectedModel?.name || selected
}

function applySfxClip(clip: AgentSfxClip, negativePrompt: string): void {
const state = useStore.getState()
state.setDurationSeconds(Math.max(1, Math.min(20, clip.durationSeconds)))
state.setParams({
prompt: clip.prompt,
MMAudio_prompt: clip.prompt,
MMAudio_neg_prompt: negativePrompt || 'music, speech, talking, vocals, long melody',
video_guide: undefined,
})
}
import { applySfxClip, openStudioAudio, prepareAudio as prepareStudioAudio, selectAudioModel } from '../studio/actions'

export async function prepareAudio(action: AgentPrepareAudioAction): Promise<string> {
openStudioAudio(action.subMode)
const modelName = await selectAudioModel(action.modelType, action.subMode)
const state = useStore.getState()
if (action.subMode === 'sfx') {
applySfxClip({
name: 'sfx',
prompt: action.prompt,
durationSeconds: action.durationSeconds ?? 2,
}, action.negativePrompt || '')
} else {
state.setDurationSeconds(action.durationSeconds ?? state.durationSeconds)
state.setParams({
prompt: action.prompt,
negative_prompt: action.negativePrompt || '',
})
}
const duration = useStore.getState().durationSeconds
const room = action.subMode === 'sfx' ? 'SFX' : action.subMode === 'music' ? 'Music' : 'Speech'
return `He preparado Studio → Audio → ${room} con ${modelName}, ${duration.toFixed(0)} s y el prompt indicado. La pestaña Audios solo muestra resultados; la creación queda en Studio.`
const result = await prepareStudioAudio(action)
const summary = result.artifacts[0]?.metadata?.summary
return typeof summary === 'string' ? summary : 'Studio → Audio listo.'
}

export async function queueMusic(action: AgentPrepareAudioAction): Promise<{ message: string; taskId: string }> {
if (action.subMode !== 'music') throw new Error('queueMusic solo acepta peticiones de música.')
await prepareAudio(action)
await prepareStudioAudio(action)
const known = new Set(useStore.getState().jobs)
await useStore.getState().startGeneration()
const created = useStore.getState().jobs.find(job => !known.has(job))
Expand Down
Loading