diff --git a/api/app/id_generator.py b/api/app/id_generator.py new file mode 100644 index 0000000..b3a1fbf --- /dev/null +++ b/api/app/id_generator.py @@ -0,0 +1,17 @@ +import uuid +from typing import Any, Type + +from sqlalchemy.orm import Session + + +def short_id(prefix: str, size: int = 8) -> str: + return f"{prefix}_{uuid.uuid4().hex[:size]}" + + +def unique_short_id(prefix: str, db: Session, model_cls: Type[Any], size: int = 8) -> str: + for _ in range(10): + candidate = short_id(prefix, size=size) + exists = db.query(model_cls.id).filter(model_cls.id == candidate).first() + if not exists: + return candidate + raise RuntimeError(f"failed to generate unique id for {model_cls.__name__}") diff --git a/api/app/routers/asr.py b/api/app/routers/asr.py index 470368d..b167802 100644 --- a/api/app/routers/asr.py +++ b/api/app/routers/asr.py @@ -1,6 +1,5 @@ import os import time -import uuid from typing import List, Optional import httpx @@ -8,6 +7,7 @@ from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile from sqlalchemy.orm import Session from ..db import get_db +from ..id_generator import unique_short_id from ..models import ASRModel from ..schemas import ( ASRModelCreate, ASRModelUpdate, ASRModelOut, @@ -72,7 +72,7 @@ def get_asr_model(id: str, db: Session = Depends(get_db)): def create_asr_model(data: ASRModelCreate, db: Session = Depends(get_db)): """创建ASR模型""" asr_model = ASRModel( - id=data.id or str(uuid.uuid4())[:8], + id=unique_short_id("asr", db, ASRModel), user_id=1, # 默认用户 name=data.name, vendor=data.vendor, diff --git a/api/app/routers/llm.py b/api/app/routers/llm.py index 8454f54..be02a2d 100644 --- a/api/app/routers/llm.py +++ b/api/app/routers/llm.py @@ -1,12 +1,12 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from typing import List, Optional -import uuid import httpx import time from datetime import datetime from ..db import get_db +from ..id_generator import unique_short_id from ..models import LLMModel from ..schemas import ( LLMModelCreate, LLMModelUpdate, LLMModelOut, @@ -53,7 +53,7 @@ def get_llm_model(id: str, db: Session = Depends(get_db)): def create_llm_model(data: LLMModelCreate, db: Session = Depends(get_db)): """创建LLM模型""" llm_model = LLMModel( - id=data.id or str(uuid.uuid4())[:8], + id=unique_short_id("llm", db, LLMModel), user_id=1, # 默认用户 name=data.name, vendor=data.vendor, diff --git a/api/app/routers/voices.py b/api/app/routers/voices.py index 6cb4258..1e4a722 100644 --- a/api/app/routers/voices.py +++ b/api/app/routers/voices.py @@ -1,6 +1,5 @@ import base64 import os -import uuid from typing import Optional import httpx @@ -8,6 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from ..db import get_db +from ..id_generator import unique_short_id from ..models import Voice from ..schemas import VoiceCreate, VoiceOut, VoicePreviewRequest, VoicePreviewResponse, VoiceUpdate @@ -78,7 +78,7 @@ def create_voice(data: VoiceCreate, db: Session = Depends(get_db)): voice_key = raw_id if ":" in raw_id else f"{model}:{raw_id}" voice = Voice( - id=data.id or str(uuid.uuid4())[:8], + id=unique_short_id("tts", db, Voice), user_id=1, name=data.name, vendor=vendor, diff --git a/api/init_db.py b/api/init_db.py index 07e91c6..5c1ace4 100644 --- a/api/init_db.py +++ b/api/init_db.py @@ -9,8 +9,29 @@ from contextlib import contextmanager sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from app.db import Base, engine, DATABASE_URL +from app.id_generator import short_id from app.models import Voice, Assistant, KnowledgeBase, Workflow, LLMModel, ASRModel, KnowledgeDocument +VOICE_MODEL = "FunAudioLLM/CosyVoice2-0.5B" + +SEED_VOICE_IDS = { + "alex": short_id("tts"), + "david": short_id("tts"), + "bella": short_id("tts"), + "claire": short_id("tts"), +} + +SEED_LLM_IDS = { + "deepseek_chat": short_id("llm"), + "glm_4": short_id("llm"), + "embedding_3_small": short_id("llm"), +} + +SEED_ASR_IDS = { + "sensevoice_small": short_id("asr"), + "telespeech_asr": short_id("asr"), +} + def ensure_db_dir(): """确保 SQLite 数据目录存在。""" @@ -105,15 +126,51 @@ def init_default_data(): # 参考: https://docs.siliconflow.cn/cn/api-reference/audio/create-speech voices = [ # 男声 (Male Voices) - Voice(id="alex", name="Alex", vendor="OpenAI Compatible", gender="Male", language="en", - description="Steady male voice.", is_system=True), - Voice(id="david", name="David", vendor="OpenAI Compatible", gender="Male", language="en", - description="Cheerful male voice.", is_system=True), + Voice( + id=SEED_VOICE_IDS["alex"], + name="Alex", + vendor="OpenAI Compatible", + gender="Male", + language="en", + description="Steady male voice.", + model=VOICE_MODEL, + voice_key=f"{VOICE_MODEL}:alex", + is_system=True, + ), + Voice( + id=SEED_VOICE_IDS["david"], + name="David", + vendor="OpenAI Compatible", + gender="Male", + language="en", + description="Cheerful male voice.", + model=VOICE_MODEL, + voice_key=f"{VOICE_MODEL}:david", + is_system=True, + ), # 女声 (Female Voices) - Voice(id="bella", name="Bella", vendor="OpenAI Compatible", gender="Female", language="en", - description="Passionate female voice.", is_system=True), - Voice(id="claire", name="Claire", vendor="OpenAI Compatible", gender="Female", language="en", - description="Gentle female voice.", is_system=True), + Voice( + id=SEED_VOICE_IDS["bella"], + name="Bella", + vendor="OpenAI Compatible", + gender="Female", + language="en", + description="Passionate female voice.", + model=VOICE_MODEL, + voice_key=f"{VOICE_MODEL}:bella", + is_system=True, + ), + Voice( + id=SEED_VOICE_IDS["claire"], + name="Claire", + vendor="OpenAI Compatible", + gender="Female", + language="en", + description="Gentle female voice.", + model=VOICE_MODEL, + voice_key=f"{VOICE_MODEL}:claire", + is_system=True, + ), ] seed_if_empty(db, Voice, voices, "✅ 默认声音数据已初始化 (OpenAI Compatible CosyVoice 2.0)") @@ -144,14 +201,14 @@ def init_default_assistants(): prompt="你是一个友好的AI助手,请用简洁清晰的语言回答用户的问题。", language="zh", voice_output_enabled=True, - voice="anna", + voice=SEED_VOICE_IDS["bella"], speed=1.0, hotwords=[], tools=["current_time"], interruption_sensitivity=500, config_mode="platform", - llm_model_id="deepseek-chat", - asr_model_id="paraformer-v2", + llm_model_id=SEED_LLM_IDS["deepseek_chat"], + asr_model_id=SEED_ASR_IDS["sensevoice_small"], ), Assistant( id="customer_service", @@ -162,7 +219,7 @@ def init_default_assistants(): prompt="你是一个专业的客服人员,耐心解答客户问题,提供优质的服务体验。", language="zh", voice_output_enabled=True, - voice="bella", + voice=SEED_VOICE_IDS["claire"], speed=1.0, hotwords=["客服", "投诉", "咨询"], tools=["current_time"], @@ -178,7 +235,7 @@ def init_default_assistants(): prompt="You are a friendly English tutor. Help users practice English conversation and explain grammar points clearly.", language="en", voice_output_enabled=True, - voice="alex", + voice=SEED_VOICE_IDS["alex"], speed=1.0, hotwords=["grammar", "vocabulary", "practice"], tools=["current_time"], @@ -257,7 +314,7 @@ def init_default_llm_models(): with db_session() as db: llm_models = [ LLMModel( - id="deepseek-chat", + id=SEED_LLM_IDS["deepseek_chat"], user_id=1, name="DeepSeek Chat", vendor="OpenAI Compatible", @@ -270,7 +327,7 @@ def init_default_llm_models(): enabled=True, ), LLMModel( - id="glm-4", + id=SEED_LLM_IDS["glm_4"], user_id=1, name="GLM-4", vendor="ZhipuAI", @@ -283,7 +340,7 @@ def init_default_llm_models(): enabled=True, ), LLMModel( - id="text-embedding-3-small", + id=SEED_LLM_IDS["embedding_3_small"], user_id=1, name="Embedding 3 Small", vendor="OpenAI Compatible", @@ -302,7 +359,7 @@ def init_default_asr_models(): with db_session() as db: asr_models = [ ASRModel( - id="FunAudioLLM/SenseVoiceSmall", + id=SEED_ASR_IDS["sensevoice_small"], user_id=1, name="FunAudioLLM/SenseVoiceSmall", vendor="OpenAI Compatible", @@ -316,7 +373,7 @@ def init_default_asr_models(): enabled=True, ), ASRModel( - id="TeleAI/TeleSpeechASR", + id=SEED_ASR_IDS["telespeech_asr"], user_id=1, name="TeleAI/TeleSpeechASR", vendor="OpenAI Compatible", diff --git a/web/services/backendApi.ts b/web/services/backendApi.ts index d892b48..90b52f8 100644 --- a/web/services/backendApi.ts +++ b/web/services/backendApi.ts @@ -297,7 +297,6 @@ export const fetchVoices = async (): Promise => { export const createVoice = async (data: Partial): Promise => { const payload = { - id: data.id || undefined, name: data.name || 'New Voice', vendor: data.vendor || 'OpenAI Compatible', gender: data.gender || 'Female', @@ -359,7 +358,6 @@ export const fetchASRModels = async (): Promise => { export const createASRModel = async (data: Partial): Promise => { const payload = { - id: data.id || undefined, name: data.name || 'New ASR Model', vendor: data.vendor || 'OpenAI Compatible', language: data.language || 'zh', @@ -450,7 +448,6 @@ export const fetchLLMModels = async (): Promise => { export const createLLMModel = async (data: Partial): Promise => { const payload = { - id: data.id || undefined, name: data.name || 'New LLM Model', vendor: data.vendor || 'OpenAI Compatible', type: data.type || 'text',