fix(voices): #1219 review — close the /use dedup race + generic materialize error
- /archetypes/{id}/use: re-check the personality row inside the write connection
right before INSERT (the pre-render SELECT could race a concurrent /use of the
same archetype). Reuse the winner's row and drop the just-rendered sample. A
UNIQUE index isn't viable — marketplace/persona imports reuse the personality
column — so this closes the realistic single-user window (greptile P2).
- VoiceSelector: log the raw materialize error to the console and show a generic,
actionable localized message instead of interpolating backend detail; drop the
{{message}} placeholder from voiceSelector.addVoiceFailed across all 21 locales
(CodeRabbit).
This commit is contained in:
@@ -341,6 +341,21 @@ async def use_archetype(archetype_id: str, name: Optional[str] = Query(None)):
|
||||
profile_name = (name or a["name"]).strip() or a["name"]
|
||||
try:
|
||||
with db_conn() as conn:
|
||||
# Re-check under the write connection right before inserting: a
|
||||
# concurrent /use for the same archetype may have inserted while we
|
||||
# were rendering (the pre-render SELECT above raced). Reuse that row
|
||||
# and drop our just-rendered sample instead of creating a duplicate.
|
||||
# (personality is NOT globally unique — marketplace/persona imports
|
||||
# reuse the column — so a UNIQUE index isn't an option; this closes
|
||||
# the realistic window for the single-user desktop app.)
|
||||
dup = conn.execute(
|
||||
"SELECT id, name FROM voice_profiles WHERE personality = ? LIMIT 1",
|
||||
(a["id"],),
|
||||
).fetchone()
|
||||
if dup is not None:
|
||||
with __import__("contextlib").suppress(OSError):
|
||||
os.remove(audio_path)
|
||||
return {"profile_id": dup["id"], "name": dup["name"]}
|
||||
conn.execute(
|
||||
"INSERT INTO voice_profiles "
|
||||
"(id, name, ref_audio_path, ref_text, instruct, language, seed, personality, created_at) "
|
||||
|
||||
@@ -252,8 +252,10 @@ export default function VoiceSelector({
|
||||
setJustMaterialized({ id: r.profile_id, name: r.name });
|
||||
onChange?.(r.profile_id);
|
||||
} catch (e) {
|
||||
// Keep the previous value; just tell the user it didn't take.
|
||||
toast.error(t('voiceSelector.addVoiceFailed', { message: e?.message || String(e) }));
|
||||
// Keep the previous value; tell the user it didn't take with an
|
||||
// actionable, non-technical message (the raw error goes to the console).
|
||||
console.error('[VoiceSelector] failed to add gallery voice', e);
|
||||
toast.error(t('voiceSelector.addVoiceFailed'));
|
||||
} finally {
|
||||
setMaterializing(false);
|
||||
}
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "إنشاء صوت جديد",
|
||||
"gallery": "المعرض",
|
||||
"addingVoice": "جارٍ إضافة الصوت…",
|
||||
"addVoiceFailed": "تعذّرت إضافة هذا الصوت: {{message}}"
|
||||
"addVoiceFailed": "تعذّرت إضافة هذا الصوت"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "أدخل رقم التعريف الشخصي للوصول",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Erstellen Sie eine neue Stimme",
|
||||
"gallery": "Galerie",
|
||||
"addingVoice": "Stimme wird hinzugefügt…",
|
||||
"addVoiceFailed": "Diese Stimme konnte nicht hinzugefügt werden: {{message}}"
|
||||
"addVoiceFailed": "Diese Stimme konnte nicht hinzugefügt werden"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Zugangs-PIN eingeben",
|
||||
|
||||
@@ -2513,7 +2513,7 @@
|
||||
"createVoice": "Create a new voice",
|
||||
"gallery": "Gallery",
|
||||
"addingVoice": "Adding voice…",
|
||||
"addVoiceFailed": "Couldn't add that voice: {{message}}"
|
||||
"addVoiceFailed": "Couldn't add that voice — please try again."
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Sharing & Remote Access",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Crea una nueva voz",
|
||||
"gallery": "Galería",
|
||||
"addingVoice": "Añadiendo voz…",
|
||||
"addVoiceFailed": "No se pudo añadir esa voz: {{message}}"
|
||||
"addVoiceFailed": "No se pudo añadir esa voz"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Introduce el PIN de acceso",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Créer une nouvelle voix",
|
||||
"gallery": "Galerie",
|
||||
"addingVoice": "Ajout de la voix…",
|
||||
"addVoiceFailed": "Impossible d'ajouter cette voix : {{message}}"
|
||||
"addVoiceFailed": "Impossible d'ajouter cette voix"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Saisir le code PIN d'accès",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "एक नई आवाज बनाएं",
|
||||
"gallery": "गैलरी",
|
||||
"addingVoice": "आवाज़ जोड़ी जा रही है…",
|
||||
"addVoiceFailed": "वह आवाज़ नहीं जोड़ी जा सकी: {{message}}"
|
||||
"addVoiceFailed": "वह आवाज़ नहीं जोड़ी जा सकी"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "एक्सेस पिन दर्ज करें",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Ciptakan suara baru",
|
||||
"gallery": "Galeri",
|
||||
"addingVoice": "Menambahkan suara…",
|
||||
"addVoiceFailed": "Tidak dapat menambahkan suara itu: {{message}}"
|
||||
"addVoiceFailed": "Tidak dapat menambahkan suara itu"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Masukkan PIN akses",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Crea una nuova voce",
|
||||
"gallery": "Galleria",
|
||||
"addingVoice": "Aggiunta voce…",
|
||||
"addVoiceFailed": "Impossibile aggiungere quella voce: {{message}}"
|
||||
"addVoiceFailed": "Impossibile aggiungere quella voce"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Inserisci il PIN di accesso",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "新しい音声を作成する",
|
||||
"gallery": "ギャラリー",
|
||||
"addingVoice": "音声を追加中…",
|
||||
"addVoiceFailed": "その音声を追加できませんでした: {{message}}"
|
||||
"addVoiceFailed": "その音声を追加できませんでした"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "アクセスPINを入力してください",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "새 목소리 만들기",
|
||||
"gallery": "갤러리",
|
||||
"addingVoice": "음성 추가 중…",
|
||||
"addVoiceFailed": "해당 음성을 추가할 수 없습니다: {{message}}"
|
||||
"addVoiceFailed": "해당 음성을 추가할 수 없습니다"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "액세스 PIN을 입력하세요.",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Creëer een nieuwe stem",
|
||||
"gallery": "Galerij",
|
||||
"addingVoice": "Stem toevoegen…",
|
||||
"addVoiceFailed": "Kon die stem niet toevoegen: {{message}}"
|
||||
"addVoiceFailed": "Kon die stem niet toevoegen"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Voer de toegangspincode in",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Utwórz nowy głos",
|
||||
"gallery": "Galeria",
|
||||
"addingVoice": "Dodawanie głosu…",
|
||||
"addVoiceFailed": "Nie udało się dodać tego głosu: {{message}}"
|
||||
"addVoiceFailed": "Nie udało się dodać tego głosu"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Wprowadź PIN dostępu",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Crie uma nova voz",
|
||||
"gallery": "Galeria",
|
||||
"addingVoice": "Adicionando voz…",
|
||||
"addVoiceFailed": "Não foi possível adicionar essa voz: {{message}}"
|
||||
"addVoiceFailed": "Não foi possível adicionar essa voz"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Digite o PIN de acesso",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Создать новый голос",
|
||||
"gallery": "Галерея",
|
||||
"addingVoice": "Добавление голоса…",
|
||||
"addVoiceFailed": "Не удалось добавить этот голос: {{message}}"
|
||||
"addVoiceFailed": "Не удалось добавить этот голос"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Введите PIN-код доступа",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Skapa en ny röst",
|
||||
"gallery": "Galleri",
|
||||
"addingVoice": "Lägger till röst…",
|
||||
"addVoiceFailed": "Kunde inte lägga till den rösten: {{message}}"
|
||||
"addVoiceFailed": "Kunde inte lägga till den rösten"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Ange åtkomst-PIN",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "สร้างเสียงใหม่",
|
||||
"gallery": "แกลเลอรี",
|
||||
"addingVoice": "กำลังเพิ่มเสียง…",
|
||||
"addVoiceFailed": "ไม่สามารถเพิ่มเสียงนั้นได้: {{message}}"
|
||||
"addVoiceFailed": "ไม่สามารถเพิ่มเสียงนั้นได้"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "ป้อน PIN การเข้าถึง",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Yeni bir ses oluştur",
|
||||
"gallery": "Galeri",
|
||||
"addingVoice": "Ses ekleniyor…",
|
||||
"addVoiceFailed": "Bu ses eklenemedi: {{message}}"
|
||||
"addVoiceFailed": "Bu ses eklenemedi"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Erişim PIN'ini girin",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Створіть новий голос",
|
||||
"gallery": "Галерея",
|
||||
"addingVoice": "Додавання голосу…",
|
||||
"addVoiceFailed": "Не вдалося додати цей голос: {{message}}"
|
||||
"addVoiceFailed": "Не вдалося додати цей голос"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Введіть PIN-код доступу",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "Tạo một giọng nói mới",
|
||||
"gallery": "Thư viện",
|
||||
"addingVoice": "Đang thêm giọng nói…",
|
||||
"addVoiceFailed": "Không thể thêm giọng nói đó: {{message}}"
|
||||
"addVoiceFailed": "Không thể thêm giọng nói đó"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "Nhập mã PIN truy cập",
|
||||
|
||||
@@ -2236,7 +2236,7 @@
|
||||
"createVoice": "创建新声音",
|
||||
"gallery": "画廊",
|
||||
"addingVoice": "正在添加声音…",
|
||||
"addVoiceFailed": "无法添加该声音:{{message}}"
|
||||
"addVoiceFailed": "无法添加该声音"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "输入访问 PIN 码",
|
||||
|
||||
@@ -2229,7 +2229,7 @@
|
||||
"createVoice": "創造新聲音",
|
||||
"gallery": "圖庫",
|
||||
"addingVoice": "正在新增聲音…",
|
||||
"addVoiceFailed": "無法新增該聲音:{{message}}"
|
||||
"addVoiceFailed": "無法新增該聲音"
|
||||
},
|
||||
"remote_gate": {
|
||||
"title": "輸入訪問 PIN 碼",
|
||||
|
||||
Reference in New Issue
Block a user