CREATE TABLE IF NOT EXISTS gateway_cloned_voices ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), gateway_user_id uuid REFERENCES gateway_users(id) ON DELETE CASCADE, user_id text NOT NULL, gateway_tenant_id uuid REFERENCES gateway_tenants(id) ON DELETE SET NULL, tenant_id text, tenant_key text, provider text NOT NULL, platform_id uuid REFERENCES integration_platforms(id) ON DELETE SET NULL, platform_model_id uuid REFERENCES platform_models(id) ON DELETE SET NULL, source_task_id uuid REFERENCES gateway_tasks(id) ON DELETE SET NULL, source_attempt_id uuid REFERENCES gateway_task_attempts(id) ON DELETE SET NULL, model text NOT NULL DEFAULT '', preview_model text NOT NULL DEFAULT '', voice_id text NOT NULL, display_name text NOT NULL DEFAULT '', demo_audio_url text NOT NULL DEFAULT '', status text NOT NULL DEFAULT 'active', expires_at timestamptz, last_used_at timestamptz, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); ALTER TABLE IF EXISTS gateway_cloned_voices ADD COLUMN IF NOT EXISTS gateway_user_id uuid REFERENCES gateway_users(id) ON DELETE CASCADE, ADD COLUMN IF NOT EXISTS user_id text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS gateway_tenant_id uuid REFERENCES gateway_tenants(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS tenant_id text, ADD COLUMN IF NOT EXISTS tenant_key text, ADD COLUMN IF NOT EXISTS provider text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS platform_id uuid REFERENCES integration_platforms(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS platform_model_id uuid REFERENCES platform_models(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS source_task_id uuid REFERENCES gateway_tasks(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS source_attempt_id uuid REFERENCES gateway_task_attempts(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS model text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS preview_model text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS voice_id text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS display_name text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS demo_audio_url text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS status text NOT NULL DEFAULT 'active', ADD COLUMN IF NOT EXISTS expires_at timestamptz, ADD COLUMN IF NOT EXISTS last_used_at timestamptz, ADD COLUMN IF NOT EXISTS metadata jsonb NOT NULL DEFAULT '{}'::jsonb, ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(), ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); CREATE UNIQUE INDEX IF NOT EXISTS idx_gateway_cloned_voices_platform_voice ON gateway_cloned_voices(platform_id, voice_id) WHERE platform_id IS NOT NULL AND voice_id <> ''; CREATE INDEX IF NOT EXISTS idx_gateway_cloned_voices_user_created ON gateway_cloned_voices(gateway_user_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_gateway_cloned_voices_provider_voice ON gateway_cloned_voices(provider, voice_id); CREATE INDEX IF NOT EXISTS idx_gateway_cloned_voices_user_id_created ON gateway_cloned_voices(user_id, created_at DESC); INSERT INTO base_model_catalog ( provider_id, provider_key, canonical_model_key, provider_model_name, model_type, display_name, capabilities, base_billing_config, default_rate_limit_policy, metadata, catalog_type, default_snapshot, status ) VALUES ( (SELECT id FROM model_catalog_providers WHERE provider_key = 'minimax' OR provider_code = 'minimax' LIMIT 1), 'minimax', 'minimax:voice-clone', 'voice_clone', '["voice_clone"]'::jsonb, 'MiniMax-Voice-Clone', '{"originalTypes":["voice_clone"],"inputModalities":["audio","text"],"outputModalities":["voice"],"previewModels":["speech-2.8-hd","speech-2.8-turbo","speech-2.6-hd","speech-2.6-turbo","speech-02-hd","speech-02-turbo"],"sourceAudio":{"formats":["mp3","m4a","wav"],"minSeconds":10,"maxSeconds":300,"maxBytes":20971520},"promptAudio":{"formats":["mp3","m4a","wav"],"maxSeconds":8,"maxBytes":20971520}}'::jsonb, '{"audio":{"basePrice":1,"baseWeight":1},"currency":"resource"}'::jsonb, '{"rules":[{"metric":"rpm","limit":60,"windowSeconds":60},{"metric":"concurrent","limit":5,"leaseTtlSeconds":120}]}'::jsonb, '{"source":"minimax.voice_clone","sourceProviderCode":"minimax","sourceProviderName":"MiniMax","sourceSpecType":"minimax","alias":"MiniMax-Voice-Clone","description":"Clone a MiniMax TTS voice and bind the cloned voice to the source platform.","iconPath":"https://static.51easyai.com/minimax-color.png","billingType":"external-api","selectable":true}'::jsonb, 'system', '{"providerKey":"minimax","canonicalModelKey":"minimax:voice-clone","providerModelName":"voice_clone","modelType":["voice_clone"],"modelAlias":"MiniMax-Voice-Clone","displayName":"MiniMax-Voice-Clone","capabilities":{"originalTypes":["voice_clone"],"inputModalities":["audio","text"],"outputModalities":["voice"],"previewModels":["speech-2.8-hd","speech-2.8-turbo","speech-2.6-hd","speech-2.6-turbo","speech-02-hd","speech-02-turbo"],"sourceAudio":{"formats":["mp3","m4a","wav"],"minSeconds":10,"maxSeconds":300,"maxBytes":20971520},"promptAudio":{"formats":["mp3","m4a","wav"],"maxSeconds":8,"maxBytes":20971520}},"baseBillingConfig":{"audio":{"basePrice":1,"baseWeight":1},"currency":"resource"},"defaultRateLimitPolicy":{"rules":[{"metric":"rpm","limit":60,"windowSeconds":60},{"metric":"concurrent","limit":5,"leaseTtlSeconds":120}]},"metadata":{"source":"minimax.voice_clone","sourceProviderCode":"minimax","sourceProviderName":"MiniMax","sourceSpecType":"minimax","alias":"MiniMax-Voice-Clone","description":"Clone a MiniMax TTS voice and bind the cloned voice to the source platform.","iconPath":"https://static.51easyai.com/minimax-color.png","billingType":"external-api","selectable":true},"status":"active"}'::jsonb, 'active' ) ON CONFLICT (canonical_model_key) DO UPDATE SET provider_id = EXCLUDED.provider_id, provider_key = EXCLUDED.provider_key, provider_model_name = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.provider_model_name ELSE base_model_catalog.provider_model_name END, model_type = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.model_type ELSE base_model_catalog.model_type END, display_name = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.display_name ELSE base_model_catalog.display_name END, capabilities = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.capabilities ELSE base_model_catalog.capabilities END, base_billing_config = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.base_billing_config ELSE base_model_catalog.base_billing_config END, default_rate_limit_policy = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.default_rate_limit_policy ELSE base_model_catalog.default_rate_limit_policy END, metadata = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.metadata ELSE base_model_catalog.metadata END, status = CASE WHEN base_model_catalog.customized_at IS NULL THEN 'active' ELSE base_model_catalog.status END, updated_at = now(); INSERT INTO platform_models ( platform_id, base_model_id, model_name, provider_model_name, model_alias, model_type, display_name, capabilities, pricing_mode, billing_config, retry_policy, rate_limit_policy, enabled ) SELECT p.id, b.id, b.provider_model_name, b.provider_model_name, b.display_name, b.model_type, b.display_name, b.capabilities, 'inherit_discount', b.base_billing_config, '{"enabled":true,"maxAttempts":1}'::jsonb, b.default_rate_limit_policy, true FROM integration_platforms p JOIN base_model_catalog b ON b.canonical_model_key = 'minimax:voice-clone' WHERE p.provider = 'minimax' AND p.deleted_at IS NULL ON CONFLICT (platform_id, model_name) DO UPDATE SET base_model_id = EXCLUDED.base_model_id, provider_model_name = EXCLUDED.provider_model_name, model_alias = EXCLUDED.model_alias, display_name = EXCLUDED.display_name, model_type = EXCLUDED.model_type, capabilities = EXCLUDED.capabilities, pricing_mode = EXCLUDED.pricing_mode, billing_config = EXCLUDED.billing_config, retry_policy = EXCLUDED.retry_policy, rate_limit_policy = EXCLUDED.rate_limit_policy, enabled = EXCLUDED.enabled, updated_at = now();