Skip to content
Merged

more #25

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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ src/lib/paraglide

# AI Debug Logs
logs/
.vscode/chrome/
.vscode/chrome/
demo.json
40 changes: 26 additions & 14 deletions src/lib/ai/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,32 @@ export const AI_MODELS = {
input: 0.3,
output: 1.2
}
}
},
// // Kimi K2.5 - Excellent for creative tasks, long context
// 'moonshotai/kimi-k2.5': {
// id: 'moonshotai/kimi-k2.5',
// name: 'Kimi K2.5',
// provider: 'Moonshot AI',
// description: 'Excellent for creative and complex tasks with 128K context',
// recommended: true,
// costTier: 'low',
// pricing: {
// input: 0.5,
// output: 2.8
// }
// }
'moonshotai/kimi-k2.5': {
id: 'moonshotai/kimi-k2.5',
name: 'Kimi K2.5',
provider: 'Moonshot AI',
description: 'Excellent for creative and complex tasks with 128K context',
recommended: true,
costTier: 'low',
pricing: {
input: 0.5,
output: 2.8
}
},
'x-ai/grok-4.1-fast': {
id: 'x-ai/grok-4.1-fast',
name: 'Grok 4.1 Fast',
provider: 'X AI',
description: 'Excellent for creative and complex tasks with 128K context',
recommended: true,
costTier: 'low',
pricing: {
input: 0.2,
output: 0.5
}
}

// // Claude 4.5 Sonnet - Great reasoning and creativity
// 'anthropic/claude-sonnet-4.5': {
Expand Down Expand Up @@ -98,7 +110,7 @@ export type ModelId = keyof typeof AI_MODELS;
/**
* Default model to use
*/
export const DEFAULT_MODEL_ID: ModelId = 'minimax/minimax-m2.5';
export const DEFAULT_MODEL_ID: ModelId = 'x-ai/grok-4.1-fast';

/**
* Get model by ID with fallback to default
Expand Down
10 changes: 3 additions & 7 deletions src/lib/ai/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type {
RemoveKeyframeOutput
} from './schemas';
import type { Layer, ProjectData } from '$lib/schemas/animation';
import { defaultLayerStyle, defaultTransform } from '$lib/schemas/base';

/**
* Context for resolving layer IDs (e.g. "layer_0" -> "actual-uuid")
Expand Down Expand Up @@ -436,13 +437,8 @@ export function mutateGroupLayers(
id: groupId,
name: input.name ?? 'Group',
type: 'group' as const,
transform: {
position: { x: 0, y: 0, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
scale: { x: 1, y: 1 },
anchor: 'center' as const
},
style: { opacity: 1 },
transform: defaultTransform(),
style: defaultLayerStyle(),
visible: true,
locked: false,
keyframes: [],
Expand Down
8 changes: 2 additions & 6 deletions src/lib/components/editor/panels/add-layer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,14 @@
import type { LayerCategory } from '$lib/layers/base';
import { SvelteMap } from 'svelte/reactivity';
import type { Component } from 'svelte';
import { defaultTransform } from '$lib/schemas/base';

const editorState = $derived(getEditorState());
const projectStore = $derived(editorState.project);

function addLayer(type: LiteralUnion<LayerTypeString, string>) {
const layer = createLayer(type, {
transform: {
position: { x: 0, y: 0, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
scale: { x: 1, y: 1 },
anchor: 'center'
},
transform: defaultTransform(),
projectDimensions: {
width: projectStore.state.width,
height: projectStore.state.height
Expand Down
17 changes: 10 additions & 7 deletions src/lib/components/editor/panels/input-property.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
metadata,
value,
layer,
onUpdateProp
onUpdateProp,
targetPath = 'props'
}: {
metadata: PropertyMetadata;
value: unknown;
layer: TypedLayer;
onUpdateProp: (prop: string, value: unknown) => void;
/** The path prefix for the property (e.g., 'props' or 'style') */
targetPath?: 'transform' | 'props' | 'style';
} = $props();
</script>

Expand Down Expand Up @@ -66,7 +69,7 @@
/>
{:else if metadata.meta?.widget === 'textarea'}
<Textarea
id={`props.${metadata.name}`}
id={`${targetPath}.${metadata.name}`}
value={typeof value === 'string' ? value : ''}
oninput={(e) => onUpdateProp(metadata.name, e.currentTarget.value)}
spellcheck="false"
Expand All @@ -78,7 +81,7 @@
/>
{:else if metadata.meta?.widget === 'color'}
<Input
id={`props.${metadata.name}`}
id={`${targetPath}.${metadata.name}`}
type="color"
value={typeof value === 'string' ? value : '#000000'}
oninput={(e) => onUpdateProp(metadata.name, e.currentTarget.value)}
Expand All @@ -89,7 +92,7 @@
<Component {value} onChange={(newValue) => onUpdateProp(metadata.name, newValue)} {layer} />
{:else if metadata.type === 'number'}
<ScrubInput
id={`props.${metadata.name}`}
id={`${targetPath}.${metadata.name}`}
value={typeof value === 'number' ? value : 0}
min={metadata.min}
max={metadata.max}
Expand All @@ -99,7 +102,7 @@
{:else if metadata.type === 'boolean'}
<label class="flex items-center gap-2">
<input
id={`props.${metadata.name}`}
id={`${targetPath}.${metadata.name}`}
type="checkbox"
checked={typeof value === 'boolean' ? value : false}
onchange={(e) => onUpdateProp(metadata.name, e.currentTarget.checked)}
Expand All @@ -109,7 +112,7 @@
</label>
{:else if metadata.type === 'select' && metadata.options}
<select
id={`props.${metadata.name}`}
id={`${targetPath}.${metadata.name}`}
value={typeof value === 'string' || typeof value === 'number' ? value : ''}
onchange={(e) => onUpdateProp(metadata.name, e.currentTarget.value)}
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none"
Expand All @@ -121,7 +124,7 @@
{:else}
<!-- Default to text input for strings and unknown types -->
<Input
id={`props.${metadata.name}`}
id={`${targetPath}.${metadata.name}`}
type="text"
value={typeof value === 'string' ? value : ''}
oninput={(e) => onUpdateProp(metadata.name, e.currentTarget.value)}
Expand Down
34 changes: 28 additions & 6 deletions src/lib/components/editor/panels/properties-group.svelte
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
<script lang="ts">
import Label from '$lib/components/ui/label/label.svelte';
import { ChevronRight } from '@lucide/svelte';
import type { Snippet } from 'svelte';

const { label, children }: { label: string | Snippet; children: Snippet } = $props();
let {
label,
children,
defaultOpen = false
}: { label: string | Snippet; children: Snippet; defaultOpen?: boolean } = $props();

let isOpen = $derived(defaultOpen);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

$derived will override user toggle when defaultOpen prop changes — use $state instead.

$derived(defaultOpen) re-derives whenever the prop changes, which resets the user's toggle state. Even though $derived is assignable in Svelte 5.25+, the derivation still wins when its dependency updates. The intent here is "initialize from prop, then manage locally," which is $state:

-  let isOpen = $derived(defaultOpen);
+  let isOpen = $state(defaultOpen);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let isOpen = $derived(defaultOpen);
let isOpen = $state(defaultOpen);
🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@src/lib/components/editor/panels/properties-group.svelte` at line 12, The
isOpen store is currently created with $derived(defaultOpen) which re-syncs
whenever the defaultOpen prop changes and thus overrides user toggles; change
this to use $state(defaultOpen) so isOpen is initialized from defaultOpen once
and thereafter managed locally by the component (replace the $derived(...)
instantiation with $state(...) and keep all existing reads/writes to isOpen
intact).

</script>

<div class="space-y-3">
{#if typeof label === 'string'}
<Label class="font-semibold">{label}</Label>
{:else}
{@render label()}
<button
type="button"
class="flex w-full items-center gap-1.5 text-left transition-colors hover:text-foreground"
onclick={() => (isOpen = !isOpen)}
>
<ChevronRight
class="h-3.5 w-3.5 shrink-0 transition-transform duration-200"
style="transform: rotate({isOpen ? 90 : 0}deg)"
/>
{#if typeof label === 'string'}
<Label class="pointer-events-none cursor-pointer font-semibold">{label}</Label>
{:else}
{@render label()}
{/if}
</button>

{#if isOpen}
<div class="space-y-3">
{@render children()}
</div>
{/if}
{@render children()}
</div>
Loading