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
10 changes: 2 additions & 8 deletions ui/src/features/agent/agentActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2476,12 +2476,6 @@ const TAB_LABELS: Record<AgentTab, string> = {
settings: 'Settings',
}

/** Canonical Studio SFX-pack bridge; the implementation stays lazy. */
export async function queueSfxPackForAgent(action: AgentQueueSfxPackAction): Promise<string> {
const { queueSfxPack } = await import('./audioActions')
return queueSfxPack(action)
}

export async function executeAgentActions(
actions: AgentAction[],
onStep?: (message: string) => void,
Expand Down Expand Up @@ -2732,8 +2726,8 @@ export async function executeAgentActions(
preparedStudio = true
results.push({ action, ok: true, message: outcome.message, report: outcome.report })
} else if (action.type === 'queue_sfx_pack') {
const { queueSfxPack } = await import('./audioActions')
results.push({ action, ok: true, message: await queueSfxPack(action) })
const outcome = await defaultApplicationAdapters.studio.queueSfxPack(action)
results.push({ action, ok: true, message: outcome.message, report: outcome.report })
} else if (action.type === 'start_generation') {
if (!preparedStudio) throw new Error('Studio no se preparó en este turno; no lo he lanzado.')
const outcome = await defaultApplicationAdapters.studio.startGeneration(action)
Expand Down
7 changes: 6 additions & 1 deletion 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, 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 type { AgentApply3dRhythmAction, AgentApplySeriesPlanAction, AgentApplyStoryProposalAction, AgentApproveStorySectionAction, AgentApproveStoryVisualsAction, AgentAssembleSeriesEpisodeAction, AgentAttachStudioReferencesAction, AgentCommitSeriesCanonAction, AgentConfigureStudioLorasAction, AgentConfigureStorySongAction, AgentCreateComicAction, AgentCreateSeriesEpisodeAction, AgentCreateStoryAction, AgentCreateWorkspaceAction, AgentGenerateComicAction, AgentGenerateSeriesPlanAction, AgentGenerateStorySectionAction, AgentGenerateStorySongAction, AgentGenerateStoryVisualsAction, AgentPrepare3dAction, AgentPrepareAudioAction, AgentPrepareImageAction, AgentPrepareVideoAction, AgentQueueSfxPackAction, AgentRenderSeriesShotsAction, AgentReviewSeriesAttemptsAction, AgentSelectWorkspaceAction, AgentStartGenerationAction, AgentStageStoryComicAction, AgentStartDirectorProductionAction, AgentStageStoryMusicVideoAction, AgentStageStoryVideoAction, AgentUpdateSeriesEpisodeAction, AgentUpdateStoryAction } from './agentActions'
import {
executionKey,
executionReport,
Expand Down Expand Up @@ -65,6 +65,7 @@ export interface StudioAdapter {
startGeneration(action: AgentStartGenerationAction): Promise<AdapterOutcome>
attachReferences(action: AgentAttachStudioReferencesAction): Promise<AdapterOutcome>
configureLoras(action: AgentConfigureStudioLorasAction): Promise<AdapterOutcome>
queueSfxPack(action: AgentQueueSfxPackAction): Promise<AdapterOutcome>
}

export interface StoryLabAdapter {
Expand Down Expand Up @@ -331,6 +332,10 @@ export function createDefaultApplicationAdapters(): WizardApplicationAdapters {
const { configureLoras } = await import('../studio/adapters')
return presentStudioSliceResult(await configureLoras(action), 'Image / Video')
},
async queueSfxPack(action) {
const { queueSfx } = await import('../studio/adapters')
return presentStudioSliceResult(await queueSfx(action), 'Audio → SFX')
},
}
adapters.storyLab = {
open: () => navigate('story_lab'),
Expand Down
28 changes: 2 additions & 26 deletions ui/src/features/agent/audioActions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useStore } from '../../stores/useStore'
import type { AgentPrepareAudioAction, AgentQueueSfxPackAction } from './agentActions'
import { applySfxClip, openStudioAudio, prepareAudio as prepareStudioAudio, selectAudioModel } from '../studio/actions'
import type { AgentPrepareAudioAction } from './agentActions'
import { prepareAudio as prepareStudioAudio } from '../studio/actions'

export async function prepareAudio(action: AgentPrepareAudioAction): Promise<string> {
const result = await prepareStudioAudio(action)
Expand All @@ -18,27 +18,3 @@ export async function queueMusic(action: AgentPrepareAudioAction): Promise<{ mes
if (created.status === 'failed') throw new Error(created.error || created.message || 'La canción no pudo entrar en cola.')
return { message: `He enviado la canción a la cola (${created.id}).`, taskId: created.id }
}

export async function queueSfxPack(action: AgentQueueSfxPackAction): Promise<string> {
if (!action.confirm) throw new Error('Encolar el pack de SFX requiere confirm=true tras una petición explícita.')
if (!action.clips.length) throw new Error('El pack de SFX no incluye clips.')
openStudioAudio('sfx')
await selectAudioModel(action.modelType, 'sfx')
const ids: string[] = []
const negative = action.negativePrompt || 'music, speech, talking, vocals, long melody'
for (const clip of action.clips) {
applySfxClip(clip, negative)
const before = new Set(useStore.getState().jobs)
await useStore.getState().startGeneration()
const created = useStore.getState().jobs.find(job => !before.has(job))
if (!created) throw new Error(`HocusPocus no encoló el efecto ${clip.name}.`)
if (created.status === 'failed') throw new Error(created.error || created.message || `Falló ${clip.name}.`)
ids.push(`${clip.name}${created.id ? ` (${created.id})` : ''}`)
}
return [
`He encolado **${ids.length} efectos SFX** en Studio → Audio → SFX.`,
'Irán detrás de lo que ya use la GPU y aparecerán en la galería Audios al terminar.',
'',
...ids.map(id => `- ${id}`),
].join('\n')
}
17 changes: 3 additions & 14 deletions ui/src/features/agent/studioCapabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* agentActions import and, in turn, avoids an initialization cycle when the
* parent capability registry imports this file as a side effect.
*/
import type { defineCapability, CapabilityDefinition, CapabilityExecutionOutcome } from './capabilityRegistry'
import type { defineCapability, CapabilityDefinition } from './capabilityRegistry'
import type {
AgentAction,
AgentAttachStudioReferencesAction,
Expand Down Expand Up @@ -49,10 +49,6 @@ function stringArray(value: unknown, maxItems: number, maxLength: number): strin
: []
}

function studioTarget(mode: 'video' | 'image' | 'audio' | '3d', title: string) {
return { kind: 'studio_form', id: mode, title: `Studio → ${title}` }
}

function commonPresentation(anchors: string[]) {
return { destination: 'studio' as const, anchors, replay: 'atomic' as const }
}
Expand Down Expand Up @@ -186,14 +182,6 @@ function validType<T extends AgentAction>(type: T['type'], action: AgentAction):
return action.type === type ? [] : [`${type} is invalid`]
}

async function bridgeSfx<TAction extends AgentAction>(
action: TAction,
): Promise<CapabilityExecutionOutcome> {
if (action.type !== 'queue_sfx_pack') throw new Error(`No hay puente Studio para ${action.type}.`)
const { queueSfxPackForAgent } = await import('./agentActions')
return { message: await queueSfxPackForAgent(action), target: studioTarget('audio', 'Audio → SFX') }
}

/**
* Register the Studio family after the core registry has finished evaluating.
*
Expand Down Expand Up @@ -287,7 +275,8 @@ export function registerStudioCapabilities(register: typeof defineCapability): v
risk: 'compute', confirmation: 'required', progress: 'Encolando el pack de SFX…',
resolve: sfxAction,
validate(action) { return action.confirm === true && action.clips.length > 0 ? validType('queue_sfx_pack', action) : ['confirmed SFX clips are required'] },
async prepare(action) { return action }, execute: bridgeSfx,
async prepare(action) { return action },
async execute(action, context) { return context.adapters.studio.queueSfxPack(action) },
correlate(_action, outcome) { return outcome.target }, async track(_action, outcome) { return outcome },
report: { targetKind: 'studio_sfx_pack', successState: 'completed' }, summarize(_action, outcome) { return outcome.message },
presentation: commonPresentation(['audio-mode', 'sfx-pack', 'queue']),
Expand Down
29 changes: 29 additions & 0 deletions ui/src/features/studio/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
PrepareAudioCommand,
PrepareImageCommand,
PrepareVideoCommand,
QueueSfxPackCommand,
} from './commands'

export type StudioSfxClip = {
Expand Down Expand Up @@ -113,6 +114,34 @@ export async function selectAudioModel(
return selectedModel?.name || selected
}

export async function queueSfxPack(action: QueueSfxPackCommand): Promise<CommandResult> {
if (!action.confirm) throw new Error('Encolar el pack de SFX requiere confirm=true tras una petición explícita.')
if (!action.clips.length) throw new Error('El pack de SFX no incluye clips.')
openStudioAudio('sfx')
await selectAudioModel(action.modelType, 'sfx')
const ids: string[] = []
const negative = action.negativePrompt || 'music, speech, talking, vocals, long melody'
for (const clip of action.clips) {
applySfxClip(clip, negative)
const before = new Set(useStore.getState().jobs)
await useStore.getState().startGeneration()
const created = useStore.getState().jobs.find(job => !before.has(job))
if (!created) throw new Error(`HocusPocus no encoló el efecto ${clip.name}.`)
if (created.status === 'failed') throw new Error(created.error || created.message || `Falló ${clip.name}.`)
ids.push(`${clip.name}${created.id ? ` (${created.id})` : ''}`)
}
return studioResult(
'audio',
'Audio → SFX',
[
`He encolado **${ids.length} efectos SFX** en Studio → Audio → SFX.`,
'Irán detrás de lo que ya use la GPU y aparecerán en la galería Audios al terminar.',
'',
...ids.map(id => `- ${id}`),
].join('\n'),
)
}

export function applySfxClip(clip: StudioSfxClip, negativePrompt: string): void {
const state = useStore.getState()
state.setDurationSeconds(Math.max(1, Math.min(20, clip.durationSeconds)))
Expand Down
6 changes: 6 additions & 0 deletions ui/src/features/studio/adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
prepareAudio,
prepareImage,
prepareVideo,
queueSfxPack,
startPreparedGeneration,
} from './actions'
import {
Expand All @@ -21,6 +22,7 @@ import type {
PrepareAudioCommand,
PrepareImageCommand,
PrepareVideoCommand,
QueueSfxPackCommand,
QueueTaskCommand,
} from './commands'

Expand Down Expand Up @@ -67,3 +69,7 @@ export async function attachReferences(command: AttachStudioReferencesCommand) {
export async function configureLoras(command: ConfigureStudioLorasCommand) {
return configureStudioLoras(command)
}

export async function queueSfx(command: QueueSfxPackCommand) {
return queueSfxPack(command)
}
14 changes: 14 additions & 0 deletions ui/src/features/studio/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,17 @@ export type ConfigureStudioLorasCommand = {
loras: StudioLoraSelection[]
replaceExisting: boolean
}

export type QueueSfxClip = {
name: string
prompt: string
durationSeconds: number
}

export type QueueSfxPackCommand = {
style: string
clips: QueueSfxClip[]
modelType?: string
negativePrompt?: string
confirm: true
}
4 changes: 1 addition & 3 deletions ui/tests/agentCapabilityPorts.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ const SLICE_AGENT_IMPORT_ALLOWLIST = [
const LEGACY_EXECUTE_ALLOWLIST = [
'attach_videoclip_alternative_song',
'mount_videoclip_alternative_song',
'queue_sfx_pack',
]

const AGENT_ACTIONS_IMPORTS = [
Expand All @@ -48,7 +47,6 @@ const AGENT_ACTIONS_IMPORTS = [
'./agentUiBus',
'./alternativeSongActions',
'./applicationAdapters',
'./audioActions',
'./capabilityRegistry',
'./capabilityRunner',
'./characterKitActions',
Expand Down Expand Up @@ -204,7 +202,7 @@ test('capabilities execute through adapters except the frozen legacy executors',
+ `added=${JSON.stringify(added)} removed=${JSON.stringify(removed)}`,
)
assert.equal(registered.length, 73)
assert.equal(legacy.length, 3)
assert.equal(legacy.length, 2)
})

test('agentActions.ts and labActions.ts keep their current module graph until a slice PR shrinks it', () => {
Expand Down