1|-- ============================================================
2|-- KTV服务 uapi/uapiio 补全迁移脚本
3|-- 为14个GPU服务端点补全 stream/data/response/ioid 字段
4|-- 执行环境: Sage生产数据库
5|-- ============================================================
6|
7|-- ============================================================
8|-- 1. uapiio 输入字段定义
9|-- ============================================================
10|
11|-- ASR语音识别
12|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
13|  'ktv_asr_transcribe_io', 'KTV语音识别', '上传音频文件，返回识别文本和时间戳',
14|  '{"audio_url":{"type":"string","required":true,"description":"音频文件URL"},"language":{"type":"string","required":false,"default":"zh","description":"语言代码"}}'
15|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
16|
17|-- Demucs音频分离
18|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
19|  'ktv_demucs_separate_io', 'KTV音频分离', '分离人声和伴奏',
20|  '{"audio_url":{"type":"string","required":true,"description":"音频文件URL"}}'
21|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
22|
23|-- 人脸检测
24|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
25|  'ktv_face_detect_io', 'KTV人脸检测', '检测图像中的人脸位置',
26|  '{"image_url":{"type":"string","required":true,"description":"图像URL"}}'
27|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
28|
29|-- 人脸识别
30|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
31|  'ktv_face_recognize_io', 'KTV人脸识别', '识别人脸身份',
32|  '{"image_url":{"type":"string","required":true,"description":"图像URL"},"face_id":{"type":"string","required":false,"description":"人脸ID"}}'
33|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
34|
35|-- 人脸比对
36|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
37|  'ktv_face_compare_io', 'KTV人脸比对', '比较两张人脸的相似度',
38|  '{"image_url1":{"type":"string","required":true,"description":"图像1 URL"},"image_url2":{"type":"string","required":true,"description":"图像2 URL"}}'
39|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
40|
41|-- 字幕渲染
42|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
43|  'ktv_subtitle_render_io', 'KTV字幕渲染', '将歌词时间轴渲染为ASS字幕文件',
44|  '{"lyrics_json":{"type":"string","required":true,"description":"歌词时间轴JSON [{\"text\":\"...\",\"start\":0,\"end\":3},...]"},"width":{"type":"integer","required":false,"default":1920,"description":"视频宽度"},"height":{"type":"integer","required":false,"default":1080,"description":"视频高度"}}'
45|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
46|
47|-- 视频合并
48|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
49|  'ktv_merge_video_io', 'KTV视频合并', '合并视频、音频和字幕',
50|  '{"video_url":{"type":"string","required":false,"description":"视频URL"},"audio_url":{"type":"string","required":false,"description":"音频URL"},"subtitle_url":{"type":"string","required":false,"description":"字幕ASS文件URL"},"output_format":{"type":"string","required":false,"default":"mp4","description":"输出格式"}}'
51|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
52|
53|-- 歌曲评分
54|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
55|  'ktv_songrate_evaluate_io', 'KTV歌曲评分', 'AI评估歌曲质量',
56|  '{"audio_url":{"type":"string","required":true,"description":"音频文件URL"},"scene":{"type":"string","required":false,"default":"pop","description":"场景类型(pop/folk/rock等)"}}'
57|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
58|
59|-- 音乐合成（异步提交）
60|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
61|  'ktv_synth_generate_io', 'KTV音乐合成', 'AI歌声合成（异步任务）',
62|  '{"lyrics":{"type":"string","required":true,"description":"歌词文本"},"style":{"type":"string","required":false,"default":"pop","description":"演唱风格"},"reference_audio":{"type":"string","required":false,"description":"参考音频URL（声音克隆）"}}'
63|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
64|
65|-- 合成状态查询
66|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
67|  'ktv_synth_status_io', 'KTV合成状态', '查询音乐合成任务状态',
68|  '{"task_id":{"type":"string","required":true,"description":"任务ID"}}'
69|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
70|
71|-- 超分辨率（异步提交）
72|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
73|  'ktv_realesrgan_upscale_io', 'KTV超分辨率', '图像/视频超分辨率（异步任务）',
74|  '{"image_url":{"type":"string","required":true,"description":"图像URL"},"scale":{"type":"integer","required":false,"default":2,"description":"放大倍数"}}'
75|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
76|
77|-- 超分状态查询
78|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
79|  'ktv_realesrgan_status_io', 'KTV超分状态', '查询超分任务状态',
80|  '{"task_id":{"type":"string","required":true,"description":"任务ID"}}'
81|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
82|
83|-- RVC声音转换（异步）
84|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
85|  'ktv_rvc_convert_io', 'KTV声音转换', '声音克隆/变声（异步任务）',
86|  '{"audio_url":{"type":"string","required":true,"description":"输入音频URL"},"voice_model":{"type":"string","required":true,"description":"目标声音模型名"}}'
87|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
88|
89|-- 视频评估
90|INSERT INTO uapiio (id, name, description, input_fields) VALUES (
91|  'ktv_video_eval_evaluate_io', 'KTV视频评估', '评估视频质量',
92|  '{"video_url":{"type":"string","required":true,"description":"视频URL"}}'
93|) ON DUPLICATE KEY UPDATE description=VALUES(description), input_fields=VALUES(input_fields);
94|

-- ============================================================
-- Part 2: uapi 字段补全 + async 模型 query_apiname 更新
-- ============================================================
1|-- ============================================================
2|-- KTV服务 uapi 补全迁移脚本 Part 2
3|-- 更新14条uapi记录：stream/data/response/ioid/headers
4|-- ============================================================
5|
6|-- ============================================================
7|-- 1. ASR语音识别 (sync)
8|--    GPU: /asr/api/asr-transcribe → localhost:9925
9|--    uapiio: ktv_asr_transcribe_io
10|-- ============================================================
11|UPDATE uapi SET
12|  stream = 'sync',
13|  headers = '{"Content-Type": "application/json"}',
14|  data = '{
15|    "audio_url": "{{audio_url}}"
16|{% if language %}
17|    ,"language": "{{language}}"
18|{% endif %}
19|}',
20|  response = '{
21|    "status": "{{status}}",
22|    "text": "{{text}}",
23|    "segments": {{json.dumps(segments, ensure_ascii=False)}},
24|    "usage": {"audio_seconds": {{audio_seconds|float}}}
25|}',
26|  ioid = 'ktv_asr_transcribe_io'
27|WHERE id = 'uapi_ktv_asr_transcribe';
28|
29|-- ============================================================
30|-- 2. Demucs音频分离 (sync)
31|--    GPU: /demucs/api/demucs-separate → localhost:9083
32|-- ============================================================
33|UPDATE uapi SET
34|  stream = 'sync',
35|  headers = '{"Content-Type": "application/json"}',
36|  data = '{
37|    "audio_url": "{{audio_url}}"
38|}',
39|  response = '{
40|    "status": "{{status}}",
41|    "vocals_url": "{{vocals_url}}",
42|    "accompaniment_url": "{{accompaniment_url}}",
43|    "usage": {"audio_seconds": {{audio_seconds|float}}}
44|}',
45|  ioid = 'ktv_demucs_separate_io'
46|WHERE id = 'uapi_ktv_demucs_separate';
47|
48|-- ============================================================
49|-- 3. 人脸检测 (sync)
50|--    GPU: /face/api/face-detect → localhost:9091
51|-- ============================================================
52|UPDATE uapi SET
53|  stream = 'sync',
54|  headers = '{"Content-Type": "application/json"}',
55|  data = '{
56|    "image_url": "{{image_url}}"
57|}',
58|  response = '{
59|    "status": "{{status}}",
60|    "faces": {{json.dumps(faces, ensure_ascii=False)}},
61|    "usage": {"detect_count": {{faces|length}}}
62|}',
63|  ioid = 'ktv_face_detect_io'
64|WHERE id = 'uapi_ktv_face_detect';
65|
66|-- ============================================================
67|-- 4. 人脸识别 (sync)
68|-- ============================================================
69|UPDATE uapi SET
70|  stream = 'sync',
71|  headers = '{"Content-Type": "application/json"}',
72|  data = '{
73|    "image_url": "{{image_url}}"
74|{% if face_id %}
75|    ,"face_id": "{{face_id}}"
76|{% endif %}
77|}',
78|  response = '{
79|    "status": "{{status}}",
80|    "matches": {{json.dumps(matches, ensure_ascii=False)}},
81|    "usage": {"recognize_count": {{matches|length}}}
82|}',
83|  ioid = 'ktv_face_recognize_io'
84|WHERE id = 'uapi_ktv_face_recognize';
85|
86|-- ============================================================
87|-- 5. 人脸比对 (sync)
88|-- ============================================================
89|UPDATE uapi SET
90|  stream = 'sync',
91|  headers = '{"Content-Type": "application/json"}',
92|  data = '{
93|    "image_url1": "{{image_url1}}",
94|    "image_url2": "{{image_url2}}"
95|}',
96|  response = '{
97|    "status": "{{status}}",
98|    "similarity": {{similarity|float}},
99|    "usage": {"compare_count": 1}
100|}',
101|  ioid = 'ktv_face_compare_io'
102|WHERE id = 'uapi_ktv_face_compare';
103|
104|-- ============================================================
105|-- 6. 字幕渲染 (sync)
106|--    GPU: /subtitle/api/subtitle-render → localhost:9080
107|-- ============================================================
108|UPDATE uapi SET
109|  stream = 'sync',
110|  headers = '{"Content-Type": "application/json"}',
111|  data = '{
112|    "lyrics_json": {{lyrics_json}}
113|{% if width %}
114|    ,"width": {{width}}
115|{% endif %}
116|{% if height %}
117|    ,"height": {{height}}
118|{% endif %}
119|}',
120|  response = '{
121|    "status": "{{status}}",
122|    "ass_url": "{{ass_url}}",
123|    "usage": {"subtitle_count": {{segments|length}}}
124|}',
125|  ioid = 'ktv_subtitle_render_io'
126|WHERE id = 'uapi_ktv_subtitle_render';
127|
128|-- ============================================================
129|-- 7. 视频合并 (sync)
130|--    GPU: /merge/api/merge-video → localhost:9080
131|-- ============================================================
132|UPDATE uapi SET
133|  stream = 'sync',
134|  headers = '{"Content-Type": "application/json"}',
135|  data = '{
136|{% if video_url %}
137|    "video_url": "{{video_url}}"
138|{% endif %}
139|{% if audio_url %}
140|    ,"audio_url": "{{audio_url}}"
141|{% endif %}
142|{% if subtitle_url %}
143|    ,"subtitle_url": "{{subtitle_url}}"
144|{% endif %}
145|{% if output_format %}
146|    ,"output_format": "{{output_format}}"
147|{% endif %}
148|}',
149|  response = '{
150|    "status": "{{status}}",
151|    "output_url": "{{output_url}}",
152|    "duration": {{duration|float}},
153|    "usage": {"output_seconds": {{duration|float}}}
154|}',
155|  ioid = 'ktv_merge_video_io'
156|WHERE id = 'uapi_ktv_merge_video';
157|
158|-- ============================================================
159|-- 8. 歌曲评分 (sync)
160|--    GPU: /songrate/api/songrate-evaluate → localhost:8901
161|-- ============================================================
162|UPDATE uapi SET
163|  stream = 'sync',
164|  headers = '{"Content-Type": "application/json"}',
165|  data = '{
166|    "audio_url": "{{audio_url}}"
167|{% if scene %}
168|    ,"scene": "{{scene}}"
169|{% endif %}
170|}',
171|  response = '{
172|    "status": "{{status}}",
173|    "score": {{score|float}},
174|    "details": {{json.dumps(details, ensure_ascii=False)}},
175|    "usage": {"audio_seconds": {{audio_seconds|float}}}
176|}',
177|  ioid = 'ktv_songrate_evaluate_io'
178|WHERE id = 'uapi_ktv_songrate_evaluate';
179|
180|-- ============================================================
181|-- 9. 音乐合成 (async - 提交任务)
182|--    GPU: /synth/api/synth-generate → localhost:9084
183|-- ============================================================
184|UPDATE uapi SET
185|  stream = 'async',
186|  headers = '{"Content-Type": "application/json"}',
187|  data = '{
188|    "lyrics": "{{lyrics}}"
189|{% if style %}
190|    ,"style": "{{style}}"
191|{% endif %}
192|{% if reference_audio %}
193|    ,"reference_audio": "{{reference_audio}}"
194|{% endif %}
195|}',
196|  response = '{
197|    "taskid": "{{task_id}}",
198|    "taskstatus": "PENDING"
199|}',
200|  ioid = 'ktv_synth_generate_io'
201|WHERE id = 'uapi_ktv_synth_generate';
202|
203|-- ============================================================
204|-- 10. 合成状态查询 (sync)
205|--    GPU: /synth/api/synth-status → localhost:9084
206|-- ============================================================
207|UPDATE uapi SET
208|  stream = 'sync',
209|  headers = '{"Content-Type": "application/json"}',
210|  data = '{"task_id": "{{task_id}}"}',
211|  response = '{
212|{% if status == "SUCCEEDED" %}
213|    "taskstatus": "SUCCEEDED",
214|    "output_url": "{{output_url}}",
215|    "usage": {"audio_seconds": {{duration|float}}}
216|{% elif status == "FAILED" %}
217|    "taskstatus": "FAILED",
218|    "error": "{{error}}"
219|{% else %}
220|    "taskstatus": "PENDING"
221|{% endif %}
222|}',
223|  ioid = 'ktv_synth_status_io'
224|WHERE id = 'uapi_ktv_synth_status';
225|
226|-- ============================================================
227|-- 11. 超分辨率 (async - 提交任务)
228|--    GPU: /realesrgan/api/realesrgan-upscale → localhost:9082
229|-- ============================================================
230|UPDATE uapi SET
231|  stream = 'async',
232|  headers = '{"Content-Type": "application/json"}',
233|  data = '{
234|    "image_url": "{{image_url}}"
235|{% if scale %}
236|    ,"scale": {{scale}}
237|{% endif %}
238|}',
239|  response = '{
240|    "taskid": "{{task_id}}",
241|    "taskstatus": "PENDING"
242|}',
243|  ioid = 'ktv_realesrgan_upscale_io'
244|WHERE id = 'uapi_ktv_realesrgan_upscale';
245|
246|-- ============================================================
247|-- 12. 超分状态查询 (sync)
248|--    GPU: /realesrgan/api/realesrgan-status → localhost:9082
249|-- ============================================================
250|UPDATE uapi SET
251|  stream = 'sync',
252|  headers = '{"Content-Type": "application/json"}',
253|  data = '{"task_id": "{{task_id}}"}',
254|  response = '{
255|{% if status == "SUCCEEDED" %}
256|    "taskstatus": "SUCCEEDED",
257|    "output_url": "{{output_url}}",
258|    "usage": {"image_count": 1}
259|{% elif status == "FAILED" %}
260|    "taskstatus": "FAILED",
261|    "error": "{{error}}"
262|{% else %}
263|    "taskstatus": "PENDING"
264|{% endif %}
265|}',
266|  ioid = 'ktv_realesrgan_status_io'
267|WHERE id = 'uapi_ktv_realesrgan_status';
268|
269|-- ============================================================
270|-- 13. RVC声音转换 (sync)
271|--    GPU: /rvc/api/rvc-convert → localhost:9085
272|-- ============================================================
273|UPDATE uapi SET
274|  stream = 'sync',
275|  headers = '{"Content-Type": "application/json"}',
276|  data = '{
277|    "audio_url": "{{audio_url}}",
278|    "voice_model": "{{voice_model}}"
279|}',
280|  response = '{
281|    "status": "{{status}}",
282|    "output_url": "{{output_url}}",
283|    "usage": {"audio_seconds": {{audio_seconds|float}}}
284|}',
285|  ioid = 'ktv_rvc_convert_io'
286|WHERE id = 'uapi_ktv_rvc_convert';
287|
288|-- ============================================================
289|-- 14. 视频评估 (sync)
290|--    GPU: /video-eval/api/video-eval-evaluate → localhost:8902
291|-- ============================================================
292|UPDATE uapi SET
293|  stream = 'sync',
294|  headers = '{"Content-Type": "application/json"}',
295|  data = '{
296|    "video_url": "{{video_url}}"
297|}',
298|  response = '{
299|    "status": "{{status}}",
300|    "score": {{score|float}},
301|    "details": {{json.dumps(details, ensure_ascii=False)}},
302|    "usage": {"video_seconds": {{video_seconds|float}}}
303|}',
304|  ioid = 'ktv_video_eval_evaluate_io'
305|WHERE id = 'uapi_ktv_video_eval_evaluate';
306|
307|-- ============================================================
308|-- 关键：更新 llm_api_map 中的 async 模型的 query_apiname
309|-- synth-generate 查询 synth-status
310|-- realesrgan-upscale 查询 realesrgan-status
311|-- ============================================================
312|UPDATE llm_api_map SET query_apiname = 'synth-status', query_period = 5
313|WHERE llmid = 'llm_ktv_synth_generate' AND llmcatelogid = 'ktv_pipeline';
314|
315|UPDATE llm_api_map SET query_apiname = 'realesrgan-status', query_period = 3
316|WHERE llmid = 'llm_ktv_realesrgan_upscale' AND llmcatelogid = 'ktv_pipeline';
317|

-- ============================================================
-- Part 3: RBAC 权限
-- ============================================================
1|-- ============================================================
2|-- KTV Pipeline v1 API RBAC 权限配置
3|-- 新增 /v1/pipeline/submit 端点权限
4|-- ============================================================
5|
6|-- 权限路径（供 load_path.py 或不直接支持的模块参考）
7|-- 目录级
8|-- /llmage/v1/pipeline logined
9|-- /llmage/v1/pipeline/submit logined
10|-- 文件级
11|-- /llmage/v1/pipeline/submit/index.dspy logined
12|
13|-- ============================================================
14|-- Customer 角色权限（API调用者需要）
15|-- ============================================================
16|INSERT INTO permission (id, path, name)
17|SELECT REPLACE(UUID(), '-', ''), '/llmage/v1/pipeline/submit/index.dspy', 'KTV流水线提交'
18|WHERE NOT EXISTS (
19|    SELECT 1 FROM permission WHERE path = '/llmage/v1/pipeline/submit/index.dspy'
20|);
21|
22|-- 为 customer.admin 和 customer.user 角色授予权限
23|-- 注意：以下语句依赖 set_role_perm.py 或等效的角色-权限关联表
24|-- 如果用的是直接 role_permission 表：
25|INSERT IGNORE INTO role_permission (role_id, permission_id)
26|SELECT r.id, p.id
27|FROM role r, permission p
28|WHERE r.name IN ('customer.admin', 'customer.user')
29|AND p.path = '/llmage/v1/pipeline/submit/index.dspy';
30|
31|-- ============================================================
32|-- 管理角色权限
33|-- ============================================================
34|INSERT IGNORE INTO role_permission (role_id, permission_id)
35|SELECT r.id, p.id
36|FROM role r, permission p
37|WHERE r.name IN ('owner.superuser', 'owner.admin', 'reseller.admin', 'reseller.operator')
38|AND p.path = '/llmage/v1/pipeline/submit/index.dspy';
39|
