-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat(asset-gen): AI audio generation + shared model panel (hardened) #1264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Scriptwonder
merged 11 commits into
CoplayDev:beta
from
Scriptwonder:feat/assetgen-audio-model-panel
Jul 13, 2026
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
66b96d2
feat(asset-gen): audio backend — fal adapter, import pipeline, job ma…
Scriptwonder 8c0a5df
feat(asset-gen): model catalog + generate_audio tool + default-model …
Scriptwonder 236e718
feat(asset-gen): GUI model dropdowns + fal audio row + refresh
Scriptwonder a2890bf
fix(asset-gen): model dropdown ballooned to a huge box
Scriptwonder 94122fb
feat(asset-gen): rename the 'Asset Gen' tab to 'Generative'
Scriptwonder 5e3107e
feat(asset-gen): group 3D/2D/sound into darker panels; inline key status
Scriptwonder 4faf7a5
fix(asset-gen): security hardening + correctness fixes (+19 tests)
Scriptwonder ea3ce5a
chore(asset-gen): "Asset Gen" tool-group label + regenerate reference…
Scriptwonder 26f8a10
fix(asset-gen): address Copilot review — floor duration, fail-closed …
Scriptwonder 36cbe62
refactor(asset-gen): dedupe generate_* tool shell (CodeRabbit review)
Scriptwonder 55f357b
Merge remote-tracking branch 'upstream/beta' into feat/assetgen-audio…
Scriptwonder File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
MCPForUnity/Editor/Services/AssetGen/AssetGenModelCatalog.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using MCPForUnity.Editor.Helpers; | ||
| using MCPForUnity.Editor.Services.AssetGen.Providers; | ||
|
|
||
| namespace MCPForUnity.Editor.Services.AssetGen | ||
| { | ||
| /// <summary> | ||
| /// One selectable model in the Asset Generation panel, plus the metadata the GUI shows | ||
| /// (use-case / price / max duration) and the license caveat to surface. <see cref="Id"/> is the | ||
| /// exact string passed as the tool's <c>model</c> param — it reaches the adapter request as the | ||
| /// fal endpoint, the Tripo <c>model_version</c>, the Meshy <c>ai_model</c>, or the OpenRouter | ||
| /// model slug. | ||
| /// </summary> | ||
| public sealed class ModelEntry | ||
| { | ||
| public string Id; | ||
| public string Label; | ||
| public string Provider; | ||
| public string Kind; // image | model | audio | ||
| public string UseCase; | ||
| public string PriceLabel; | ||
| public float MaxDurationSeconds; // 0 => not time-bounded (image / 3D) | ||
| // Audio duration knob. DurationField is the request key ("seconds_total" / "duration"); | ||
| // null => the model has no duration control (prompt-only). DefaultDurationSeconds is used | ||
| // when the caller passes 0 to a model whose endpoint requires a duration. MinDurationSeconds | ||
| // is the clamp floor. | ||
| public string DurationField; | ||
| public float DefaultDurationSeconds; | ||
| public float MinDurationSeconds; | ||
| public bool Loopable; | ||
| public string CommercialNote; // non-null => show a license caveat under the dropdown | ||
| public bool FromRefresh; // true => merged from a fal-catalog refresh (Phase 5) | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Curated, always-present registry of selectable models per provider+kind, with metadata for | ||
| /// the Asset Generation panel. The first curated entry per (provider, kind) is the default, and | ||
| /// each default's <see cref="ModelEntry.Id"/> references the owning adapter's constant directly | ||
| /// — so the panel's shown default always equals what an omitted <c>model</c> param resolves to | ||
| /// (a drift-guard test pins the two). A fal-catalog refresh overlay is layered on in Phase 5. | ||
| /// </summary> | ||
| public static class AssetGenModelCatalog | ||
| { | ||
| private static readonly ModelEntry[] Curated = | ||
| { | ||
| // Image — fal (FalAdapter.DefaultModel is first => the default) | ||
| new ModelEntry { Id = FalAdapter.DefaultModel, Label = "FLUX.2", Provider = "fal", Kind = "image", UseCase = "General image" }, | ||
| new ModelEntry { Id = "fal-ai/flux-2/flash", Label = "FLUX.2 Flash", Provider = "fal", Kind = "image", UseCase = "Fast / cheap image" }, | ||
| new ModelEntry { Id = "fal-ai/flux-2-pro", Label = "FLUX.2 Pro", Provider = "fal", Kind = "image", UseCase = "Top-quality image" }, | ||
|
|
||
| // Image — openrouter | ||
| new ModelEntry { Id = OpenRouterAdapter.DefaultModel, Label = "Gemini 2.5 Flash Image", Provider = "openrouter", Kind = "image", UseCase = "General image" }, | ||
|
|
||
| // 3D — tripo / meshy (defaults reference the adapter constants) | ||
| new ModelEntry { Id = TripoAdapter.ModelVersion, Label = "Tripo v3.1", Provider = "tripo", Kind = "model", UseCase = "Text / image -> 3D" }, | ||
| new ModelEntry { Id = "P1-20260311", Label = "Tripo P1 (premium)", Provider = "tripo", Kind = "model", UseCase = "Premium 3D" }, | ||
| new ModelEntry { Id = MeshyAdapter.DefaultModel, Label = "Meshy 6", Provider = "meshy", Kind = "model", UseCase = "Text / image -> 3D" }, | ||
|
|
||
| // Audio — fal (order: stable-audio, cassette SFX, cassette music, lyria). DurationField | ||
| // is the request key each endpoint expects; null (Lyria) => prompt-only, no duration knob. | ||
| new ModelEntry { Id = FalAudioAdapter.DefaultModel, Label = "Stable Audio 2.5", Provider = "fal", Kind = "audio", UseCase = "Music + SFX", PriceLabel = "$0.20/gen", MaxDurationSeconds = 190f, | ||
| DurationField = "seconds_total", DefaultDurationSeconds = 30f, | ||
| CommercialNote = "Free under $1M annual revenue (Stability Community License); an Enterprise license is required at or above $1M." }, | ||
| new ModelEntry { Id = "cassetteai/sound-effects-generator", Label = "CassetteAI SFX", Provider = "fal", Kind = "audio", UseCase = "Sound effects", PriceLabel = "$0.01/gen", MaxDurationSeconds = 30f, | ||
| DurationField = "duration", DefaultDurationSeconds = 10f, MinDurationSeconds = 1f }, | ||
| new ModelEntry { Id = "cassetteai/music-generator", Label = "CassetteAI Music", Provider = "fal", Kind = "audio", UseCase = "Background music", PriceLabel = "$0.02/min", MaxDurationSeconds = 180f, | ||
| DurationField = "duration", DefaultDurationSeconds = 10f, MinDurationSeconds = 1f }, | ||
| new ModelEntry { Id = "fal-ai/lyria2", Label = "Google Lyria 2", Provider = "fal", Kind = "audio", UseCase = "Background music", PriceLabel = "$0.10/30s", MaxDurationSeconds = 30f }, | ||
| }; | ||
|
|
||
| /// <summary>Curated entries for a provider+kind, in curated order (default first). Never null.</summary> | ||
| public static IReadOnlyList<ModelEntry> ForProvider(string provider, string kind) | ||
| { | ||
| var result = new List<ModelEntry>(); | ||
| foreach (ModelEntry e in Curated) | ||
| if (Eq(e.Provider, provider) && Eq(e.Kind, kind)) result.Add(e); | ||
| return result; | ||
| } | ||
|
|
||
| /// <summary>The curated entry with this exact id, or null.</summary> | ||
| public static ModelEntry Find(string id) | ||
| { | ||
| if (string.IsNullOrEmpty(id)) return null; | ||
| foreach (ModelEntry e in Curated) | ||
| if (Eq(e.Id, id)) return e; | ||
| return null; | ||
| } | ||
|
|
||
| /// <summary>The default model id for a provider+kind (the first curated entry), or null.</summary> | ||
| public static string DefaultModelId(string provider, string kind) | ||
| { | ||
| foreach (ModelEntry e in Curated) | ||
| if (Eq(e.Provider, provider) && Eq(e.Kind, kind)) return e.Id; | ||
| return null; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// The model id a generate_* tool should use: an explicit <paramref name="requested"/> wins, | ||
| /// else the GUI-selected model for this (kind, provider), else the curated default. Null when | ||
| /// nothing resolves (the adapter then falls back to its own constant). Single home for the | ||
| /// empty -> GUI-selected -> catalog-default precedence shared by all three generate tools. | ||
| /// </summary> | ||
| public static string ResolveModel(string kind, string provider, string requested) | ||
| { | ||
| string model = requested; | ||
| if (string.IsNullOrWhiteSpace(model)) model = AssetGenPrefs.GetSelectedModel(kind, provider); | ||
| if (string.IsNullOrWhiteSpace(model)) model = DefaultModelId(provider, kind); | ||
| return string.IsNullOrWhiteSpace(model) ? null : model; | ||
| } | ||
|
|
||
| /// <summary>Clears any test/refresh state. The refresh overlay is added in Phase 5; no-op today.</summary> | ||
| internal static void ResetForTests() { } | ||
|
|
||
| private static bool Eq(string a, string b) | ||
| => string.Equals(a, b, StringComparison.OrdinalIgnoreCase); | ||
| } | ||
| } |
11 changes: 11 additions & 0 deletions
11
MCPForUnity/Editor/Services/AssetGen/AssetGenModelCatalog.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Normalize whitespace-only
outputFoldertonull.When
outputFolderis whitespace-only (e.g." "),normalizedis set to the raw whitespace string and the method returnstrue. Downstream consumers receive a whitespace-only path that may bypassIsNullOrEmptychecks. Normalizing tonull(consistent withAssetGenModelCatalog.ResolveModelwhich returnsnullfor whitespace) makes the contract clearer: whitespace means "no folder specified."🛡️ Proposed fix
public static bool NormalizeOutputFolder(string outputFolder, out string normalized, out string error) { - normalized = outputFolder; + normalized = null; error = null; - if (string.IsNullOrWhiteSpace(outputFolder)) return true; + if (string.IsNullOrWhiteSpace(outputFolder)) return true; if (TryGetAssetsFolder(outputFolder, out normalized)) return true;📝 Committable suggestion
🤖 Prompt for AI Agents