talk / backend /sql /031_user_voice_prompts.sql
user
Deploy StyleVoiceChat backend
aa9892b
Raw History Blame Contribute Delete
3.8 kB
-- 031_user_voice_prompts.sql (2026-05-18, Phase 2 of CosyVoice 3.0 ๋‹ค์ค‘ ์—”์ง„).
--
-- engine ๋ณ„ voice_clone_prompt ์˜์† ์บ์‹ฑ ๋งคํ•‘.
--
-- ๋ฐฐ๊ฒฝ: sql/024 ์˜ user_voices.embedding_storage_path ๋Š” ๋‹จ์ผ ์—”์ง„ (Qwen3) ๊ฐ€์ •.
-- CosyVoice 3.0 (Phase 1 admin spawn ์™„๋ฃŒ) ๊ฐ™์€ ๋‹ค๋ฅธ ์—”์ง„์ด ์ถ”๊ฐ€๋˜๋ฉด์„œ, ๊ฐ™์€
-- ref_audio ์—์„œ ์—”์ง„๋ณ„๋กœ ๋‹ค๋ฅธ prompt ๊ฐ€ ๋งŒ๋“ค์–ด์ง„๋‹ค (Qwen3 = ICL prompt items
-- dict, CosyVoice = campplus 192-dim speaker embedding). ํ•œ ์‚ฌ์šฉ์ž voice ๊ฐ€ ๋‘
-- ์—”์ง„ ๋ชจ๋‘ prompt ์บ์‹œ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
--
-- ์„ค๊ณ„:
-- - ๋ณ„๋„ ๋งคํ•‘ ํ…Œ์ด๋ธ” (PK = user_id + voice_name + engine_kind)
-- - ๊ธฐ์กด user_voices.embedding_storage_path ๋Š” ํ˜ธํ™˜์„ฑ ์œ„ํ•ด ์œ ์ง€ (Qwen3 fast-path)
-- - ๋ฐฑํ•„: ๊ธฐ์กด embedding_storage_path ๊ฐ€ ์ฑ„์›Œ์ง„ row โ†’ engine_kind='qwen3' ๋กœ ๋งคํ•‘
--
-- ํšŒ๊ท€ 0 ๊ฐ€๋“œ:
-- - ๊ธฐ์กด ์ฝ”๋“œ ๊ฒฝ๋กœ (`update_voice_embedding_path` + `user_voices.embedding_storage_path`)
-- ๋Š” ๊ทธ๋Œ€๋กœ ๋™์ž‘ (Qwen3 ํ˜ธํ™˜). ์ƒˆ ๋งคํ•‘ ํ…Œ์ด๋ธ”์€ ์ถ”๊ฐ€ ์ •๋ณด์ผ ๋ฟ.
-- - ์ƒˆ ์—”์ง„ prompt ๊ฐ€ ์—†์œผ๋ฉด polling ์œผ๋กœ ์ƒˆ row ๊ฐ€ ์ƒ๊ธด ๋‹ค์Œ set_voice_clone ์‹œ
-- ์‚ฌ์šฉ. ๊ทธ ์‚ฌ์ด์—” ref_audio b64 ํด๋ฐฑ (์•ˆ์ „๋ง).
--
-- ์ฐธ์กฐ:
-- - doc/runbook/tts_engine_multi.md Phase 2 (voice_clone_prompt ์บ์‹ฑ ๋ถ„๊ธฐ)
-- - doc/runbook/tts_prompt_cache.md (Qwen3 prompt ์บ์‹ฑ ๊ธฐ์กด ํ๋ฆ„)
-- - doc/runbook/voice_dual_register.md (4๋‹จ๊ณ„ UI ํ˜ธํ™˜)
CREATE TABLE IF NOT EXISTS public.user_voice_prompts (
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
voice_name TEXT NOT NULL,
engine_kind TEXT NOT NULL,
storage_path TEXT NOT NULL,
ref_transcript TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, voice_name, engine_kind),
CONSTRAINT user_voice_prompts_engine_kind_check
CHECK (engine_kind IN ('qwen3', 'cosyvoice35'))
);
CREATE INDEX IF NOT EXISTS idx_user_voice_prompts_engine
ON public.user_voice_prompts (engine_kind, user_id);
COMMENT ON TABLE public.user_voice_prompts IS
'์—”์ง„๋ณ„ voice_clone_prompt ์˜์† ์บ์‹ฑ ๋งคํ•‘. ๊ฐ™์€ (user_id, voice_name) ๊ฐ€ ์—ฌ๋Ÿฌ engine_kind ์˜ prompt ๋ฅผ ๋™์‹œ ๋ณด์œ  ๊ฐ€๋Šฅ. ์ž์„ธํ•œ ๋ฐฐ๊ฒฝ์€ doc/runbook/tts_engine_multi.md Phase 2.';
COMMENT ON COLUMN public.user_voice_prompts.engine_kind IS
'TTS ์—”์ง„ ์ข…๋ฅ˜. qwen3 (์šด์˜ default) | cosyvoice35 (Phase 1 beta). tts_scale_instances.engine_kind ์™€ ๋™์ผ enum.';
COMMENT ON COLUMN public.user_voice_prompts.storage_path IS
'voice-clones bucket ์˜ prompt ํŒŒ์ผ ๊ฒฝ๋กœ. ์˜ˆ: <user_id>/<stem>.qwen3.prompt.pt ๋˜๋Š” <user_id>/<stem>.cosyvoice35.prompt.pt';
COMMENT ON COLUMN public.user_voice_prompts.ref_transcript IS
'์ถ”์ถœ ์‹œ์ ์˜ ref_text ์Šค๋ƒ…์ƒท. ๋ณ€๊ฒฝ๋˜๋ฉด prompt ์žฌ์ถ”์ถœ ํŠธ๋ฆฌ๊ฑฐ (apply_dual_register).';
-- ๋ฐฑํ•„: ๊ธฐ์กด user_voices.embedding_storage_path ๊ฐ€ NOT NULL ์ธ row ๋ฅผ qwen3 ๋กœ ๋งคํ•‘.
-- (์ฒด์ด๋‹๋˜์–ด๋„ ON CONFLICT DO NOTHING ์œผ๋กœ idempotent.)
INSERT INTO public.user_voice_prompts (user_id, voice_name, engine_kind, storage_path, ref_transcript)
SELECT user_id, name, 'qwen3', embedding_storage_path, transcript
FROM public.user_voices
WHERE embedding_storage_path IS NOT NULL AND embedding_storage_path <> ''
ON CONFLICT (user_id, voice_name, engine_kind) DO NOTHING;
-- RLS โ€” user_voices ์™€ ๋™์ผ ์ •์ฑ… (๋ณธ์ธ ๋ฐ์ดํ„ฐ๋งŒ).
ALTER TABLE public.user_voice_prompts ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS user_voice_prompts_owner ON public.user_voice_prompts;
CREATE POLICY user_voice_prompts_owner ON public.user_voice_prompts
FOR ALL
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);