fix: apply_effect creates standalone effect elements on effect tracks
Instead of adding inline effects to existing clips (which didn't show in the timeline), apply_effect now creates EffectElement items on effect tracks via InsertElementCommand — matching the drag-and-drop behavior from the effects panel.
This commit is contained in:
parent
2e32740698
commit
277321628c
|
|
@ -6,6 +6,7 @@ import { AddTrackCommand, InsertElementCommand } from "@/lib/commands/timeline";
|
|||
import { DEFAULT_NEW_ELEMENT_DURATION } from "@/lib/timeline/creation";
|
||||
import {
|
||||
buildElementFromMedia,
|
||||
buildEffectElement,
|
||||
buildTextElement,
|
||||
} from "@/lib/timeline/element-utils";
|
||||
import { DEFAULTS } from "@/lib/timeline/defaults";
|
||||
|
|
@ -16,18 +17,13 @@ import type {
|
|||
TimelineElement,
|
||||
TimelineTrack,
|
||||
TrackType,
|
||||
VisualElement,
|
||||
} from "@/lib/timeline";
|
||||
import type { TextStyleOverrides } from "@/agent/tools/add-text.tool";
|
||||
import type { UpdateTextArgs } from "@/agent/tools/update-text.tool";
|
||||
import { canPlaceTimeSpansOnTrack } from "@/lib/timeline/placement/overlap";
|
||||
import { validateElementTrackCompatibility } from "@/lib/timeline/placement";
|
||||
import { findTrackInSceneTracks } from "@/lib/timeline/track-element-update";
|
||||
import { isVisualElement } from "@/lib/timeline";
|
||||
import { AddClipEffectCommand } from "@/lib/commands/timeline/element/effects/add-effect";
|
||||
import { UpdateClipEffectParamsCommand } from "@/lib/commands/timeline/element/effects/update-effect-params";
|
||||
import { effectsRegistry } from "@/lib/effects";
|
||||
import type { ParamValues } from "@/lib/params";
|
||||
|
||||
/**
|
||||
* Thin adapter: the ONLY file in agent/ that imports from core/.
|
||||
|
|
@ -552,18 +548,22 @@ export const EditorContextAdapter = {
|
|||
};
|
||||
},
|
||||
|
||||
addEffect({
|
||||
trackId,
|
||||
elementId,
|
||||
addEffectElement({
|
||||
effectType,
|
||||
start,
|
||||
end,
|
||||
params,
|
||||
}: {
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
effectType: string;
|
||||
start: number;
|
||||
end: number;
|
||||
params?: Record<string, number | string | boolean>;
|
||||
}):
|
||||
| { effectId: string; elementId: string; appliedParams: ParamValues }
|
||||
| {
|
||||
elementId: string;
|
||||
trackId: string;
|
||||
appliedParams: Record<string, number | string | boolean>;
|
||||
}
|
||||
| { error: string } {
|
||||
const core = EditorCore.getInstance();
|
||||
const activeScene = core.scenes.getActiveSceneOrNull();
|
||||
|
|
@ -575,20 +575,6 @@ export const EditorContextAdapter = {
|
|||
return { error: `Effect not found: ${effectType}` };
|
||||
}
|
||||
|
||||
const [resolved] = findTimelineElementsWithTracksByIds({
|
||||
tracks: activeScene.tracks,
|
||||
elementIds: [elementId],
|
||||
});
|
||||
if (!resolved) {
|
||||
return { error: `Timeline element not found: ${elementId}` };
|
||||
}
|
||||
if (resolved.track.id !== trackId) {
|
||||
return { error: `Track not found: ${trackId}` };
|
||||
}
|
||||
if (!isVisualElement(resolved.element)) {
|
||||
return { error: "Element does not support effects" };
|
||||
}
|
||||
|
||||
const definition = effectsRegistry.get(effectType);
|
||||
|
||||
if (params) {
|
||||
|
|
@ -598,37 +584,38 @@ export const EditorContextAdapter = {
|
|||
}
|
||||
}
|
||||
|
||||
const addCommand = new AddClipEffectCommand({
|
||||
trackId,
|
||||
elementId,
|
||||
effectType,
|
||||
});
|
||||
core.command.execute({ command: addCommand });
|
||||
|
||||
const effectId = addCommand.getEffectId();
|
||||
if (!effectId) {
|
||||
return { error: "Failed to apply effect" };
|
||||
const startTimeTicks = secondsToTicks(start);
|
||||
const durationTicks = secondsToTicks(end) - startTimeTicks;
|
||||
if (durationTicks <= 0) {
|
||||
return { error: "Invalid time range" };
|
||||
}
|
||||
|
||||
let appliedParams: ParamValues = {};
|
||||
const effect = (resolved.element as VisualElement).effects?.find(
|
||||
(e) => e.id === effectId,
|
||||
);
|
||||
appliedParams = effect?.params ?? {};
|
||||
const element = buildEffectElement({
|
||||
effectType,
|
||||
startTime: startTimeTicks,
|
||||
duration: durationTicks,
|
||||
});
|
||||
|
||||
if (params && Object.keys(params).length > 0) {
|
||||
const updateCommand = new UpdateClipEffectParamsCommand({
|
||||
trackId,
|
||||
elementId,
|
||||
effectId,
|
||||
params,
|
||||
});
|
||||
core.command.execute({ command: updateCommand });
|
||||
|
||||
appliedParams = { ...appliedParams, ...params };
|
||||
element.params = { ...element.params, ...params };
|
||||
}
|
||||
|
||||
return { effectId, elementId, appliedParams };
|
||||
const insertCommand = new InsertElementCommand({
|
||||
element,
|
||||
placement: { mode: "auto", trackType: "effect" },
|
||||
});
|
||||
core.command.execute({ command: insertCommand });
|
||||
|
||||
const trackId = insertCommand.getTrackId();
|
||||
if (!trackId) {
|
||||
return { error: "Failed to place effect element" };
|
||||
}
|
||||
|
||||
return {
|
||||
elementId: insertCommand.getElementId(),
|
||||
trackId,
|
||||
appliedParams: element.params,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,39 +10,39 @@ const applyEffectTool: ToolDefinition = {
|
|||
_context: AgentContext,
|
||||
): Promise<
|
||||
| {
|
||||
effectId: string;
|
||||
elementId: string;
|
||||
trackId: string;
|
||||
appliedParams: Record<string, number | string | boolean>;
|
||||
}
|
||||
| { error: string }
|
||||
> => {
|
||||
const trackId = args.trackId;
|
||||
const elementId = args.elementId;
|
||||
const effectType = args.effectType;
|
||||
const start = args.start;
|
||||
const end = args.end;
|
||||
const params = args.params as
|
||||
| Record<string, number | string | boolean>
|
||||
| undefined;
|
||||
|
||||
if (typeof trackId !== "string" || !trackId.trim()) {
|
||||
return { error: "Invalid track id" };
|
||||
}
|
||||
|
||||
if (typeof elementId !== "string" || !elementId.trim()) {
|
||||
return { error: "Invalid element id" };
|
||||
}
|
||||
|
||||
if (typeof effectType !== "string" || !effectType.trim()) {
|
||||
return { error: "Invalid effect type" };
|
||||
}
|
||||
|
||||
if (typeof start !== "number" || !Number.isFinite(start) || start < 0) {
|
||||
return { error: "Invalid start time" };
|
||||
}
|
||||
|
||||
if (typeof end !== "number" || !Number.isFinite(end) || end <= start) {
|
||||
return { error: "Invalid end time" };
|
||||
}
|
||||
|
||||
if (params !== undefined && typeof params !== "object") {
|
||||
return { error: "Invalid effect parameters" };
|
||||
}
|
||||
|
||||
return EditorContextAdapter.addEffect({
|
||||
trackId,
|
||||
elementId,
|
||||
return EditorContextAdapter.addEffectElement({
|
||||
effectType,
|
||||
start,
|
||||
end,
|
||||
params,
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -152,11 +152,11 @@ export const getEffectSchema: ToolSchema = {
|
|||
export const applyEffectSchema: ToolSchema = {
|
||||
name: "apply_effect",
|
||||
description:
|
||||
"Applies an effect to a visual timeline element (video, image, text, sticker, or graphic). Use list_effects to discover available effects, get_effect to learn their parameters, then apply_effect with the desired params. Default parameter values are used when params are omitted.",
|
||||
"Adds an effect element to the timeline on an effect track, like dragging an effect from the effects panel. The effect covers the time range from start to end (in seconds). Use list_effects to discover available effects, get_effect to learn their parameters, then apply_effect with the desired params. Default parameter values are used when params are omitted.",
|
||||
parameters: [
|
||||
{ key: "trackId", type: "string", required: true },
|
||||
{ key: "elementId", type: "string", required: true },
|
||||
{ key: "effectType", type: "string", required: true },
|
||||
{ key: "start", type: "number", required: true },
|
||||
{ key: "end", type: "number", required: true },
|
||||
{ key: "params", type: "object", required: false },
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -609,14 +609,14 @@ Obtener metadata detallada de un efecto específico, incluyendo todos los parám
|
|||
## 16. `apply_effect`
|
||||
|
||||
### Propósito
|
||||
Aplicar un efecto existente a un elemento visual del timeline (video, imagen, texto, sticker o gráfico). Soporta los 7 efectos registrados: blur, brightness-contrast, grayscale, saturation, sepia, invert, vignette.
|
||||
Agregar un efecto como elemento standalone al timeline en una pista de efectos, equivalente a drag & dropear un efecto desde el panel de efectos. Soporta los 7 efectos registrados: blur, brightness-contrast, grayscale, saturation, sepia, invert, vignette.
|
||||
|
||||
### Input
|
||||
```ts
|
||||
{
|
||||
trackId: string;
|
||||
elementId: string;
|
||||
effectType: string;
|
||||
start: number;
|
||||
end: number;
|
||||
params?: Record<string, number | string | boolean>;
|
||||
}
|
||||
```
|
||||
|
|
@ -624,32 +624,31 @@ Aplicar un efecto existente a un elemento visual del timeline (video, imagen, te
|
|||
### Output
|
||||
```ts
|
||||
{
|
||||
effectId: string;
|
||||
elementId: string;
|
||||
trackId: string;
|
||||
appliedParams: Record<string, number | string | boolean>;
|
||||
}
|
||||
```
|
||||
|
||||
### Requirements
|
||||
- MUST validate `effectType` exists in the effects registry.
|
||||
- MUST validate `trackId` + `elementId` exist in the active timeline.
|
||||
- MUST validate the target element is a visual element (video, image, text, sticker, graphic).
|
||||
- MUST validate `start` and `end` as valid timeline seconds with `start < end`.
|
||||
- MUST validate `params` against the effect's parameter definitions (types, ranges).
|
||||
- MUST apply default params when `params` are omitted.
|
||||
- MUST use `AddClipEffectCommand` for the initial effect creation.
|
||||
- MUST use `UpdateClipEffectParamsCommand` when custom params are provided.
|
||||
- MUST preserve undo/redo behavior (both commands go on the undo stack).
|
||||
- MUST create an `EffectElement` via `buildEffectElement()` with the requested time range.
|
||||
- MUST merge custom `params` into the default effect instance before insertion.
|
||||
- MUST use `InsertElementCommand` with `{ mode: "auto", trackType: "effect" }` to place on an effect track (creating one if needed).
|
||||
- MUST preserve undo/redo behavior.
|
||||
- MUST return the final applied parameter values.
|
||||
|
||||
### Errors
|
||||
- Invalid track id: `{ error: "Invalid track id" }`.
|
||||
- Invalid element id: `{ error: "Invalid element id" }`.
|
||||
- Invalid effect type: `{ error: "Invalid effect type" }`.
|
||||
- Invalid start time: `{ error: "Invalid start time" }`.
|
||||
- Invalid end time: `{ error: "Invalid end time" }`.
|
||||
- Invalid params: `{ error: "Invalid effect parameters" }`.
|
||||
- Effect not found: `{ error: "Effect not found: <type>" }`.
|
||||
- Missing element: `{ error: "Timeline element not found: <id>" }`.
|
||||
- Missing track: `{ error: "Track not found: <id>" }`.
|
||||
- Unsupported element: `{ error: "Element does not support effects" }`.
|
||||
- Invalid time range: `{ error: "Invalid time range" }`.
|
||||
- No active timeline: `{ error: "No active timeline" }`.
|
||||
- Failed placement: `{ error: "Failed to place effect element" }`.
|
||||
- Unknown parameter: `{ error: "Unknown parameter: <key>" }`.
|
||||
- Out of range: `{ error: "Parameter '<key>' must be >= <min>" }`.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue