#!/bin/bash # test-language-e2e.sh — 端到端测试 zh-CMN + en-US 链路 set -e API=http://127.0.0.1:3100 echo "=== 创建中文书 (zh-CMN) ===" ZH_RAW=$(curl -sS -X POST $API/api/book-generator/langgraph/books \ -H 'Content-Type: application/json' \ -d '{"title":"语言测试中文","description":"测试中文内容生成和普通话音频","bookScale":"500","language":"zh-CMN","autoGenerateContent":true,"autoGenerateAudio":false,"generateForeword":false,"generateAfterword":false}') echo "$ZH_RAW" | python3 -c 'import json,sys; d=json.load(sys.stdin); b=d["data"]["book"]; print("id="+str(b["id"])+" targetLanguage="+str(b.get("targetLanguage","未设"))+" genStage="+str(b["genStage"]))' ZH_ID=$(echo "$ZH_RAW" | python3 -c 'import json,sys; print(json.load(sys.stdin)["data"]["book"]["id"])') echo "中文书ID: $ZH_ID" echo echo "=== 创建英文书 (en-US) ===" EN_RAW=$(curl -sS -X POST $API/api/book-generator/langgraph/books \ -H 'Content-Type: application/json' \ -d '{"title":"Language Test English","description":"Test English content and audio","bookScale":"500","language":"en-US","autoGenerateContent":true,"autoGenerateAudio":false,"generateForeword":false,"generateAfterword":false}') echo "$EN_RAW" | python3 -c 'import json,sys; d=json.load(sys.stdin); b=d["data"]["book"]; print("id="+str(b["id"])+" targetLanguage="+str(b.get("targetLanguage","未设"))+" genStage="+str(b["genStage"]))' EN_ID=$(echo "$EN_RAW" | python3 -c 'import json,sys; print(json.load(sys.stdin)["data"]["book"]["id"])') echo "英文书ID: $EN_ID" echo echo "=== 等待 60s 让 AI 生成内容 ===" sleep 60 echo echo "=== 查章节和内容语言(每本书看前 2 章标题+摘要) ===" cd /data/ai/audio/server node -e " const p = new (require('@prisma/client').PrismaClient()); (async () => { for (const id of [$ZH_ID, $EN_ID]) { const b = await p.book.findUnique({ where: { id }, select: { id: true, title: true, targetLanguage: true, genStage: true } }); const chs = await p.bookChapter.findMany({ where: { bookId: id }, orderBy: { number: 'asc' }, take: 3, select: { id: true, title: true, content: true, genStage: true } }); console.log('--- book '+b.id+' (lang='+b.targetLanguage+', stage='+b.genStage+', '+chs.length+' ch) ---'); for (const c of chs) { const head = (c.content || '').substring(0, 200); console.log(' ch#'+c.id+' ['+c.genStage+'] '+c.title); console.log(' '+head.replace(/\\n/g,' / ')); } } await p.\$disconnect(); })(); " echo echo "=== 触发音频生成(让 2 本书都进 TtsTask 队列) ===" for ID in $ZH_ID $EN_ID; do echo "--- book $ID ---" # 触发 book 的所有叶节点 audio curl -sS -X POST "$API/api/book-generator/langgraph/books/$ID/audio" \ -H 'Content-Type: application/json' -d '{}' | head -c 200 echo done echo echo "=== 等待 60s 让 TTS 跑完 ===" sleep 60 echo echo "=== 查 TtsTask 状态(应该有 targetLanguage 字段) ===" cd /data/ai/audio/server node -e " const p = new (require('@prisma/client').PrismaClient()); (async () => { const tasks = await p.ttsTask.findMany({ where: { chapterId: { in: (await p.bookChapter.findMany({ where: { bookId: { in: [$ZH_ID, $EN_ID] } }, select: { id: true } })).map(c => c.id) } }, orderBy: { id: 'desc' }, take: 5 }); for (const t of tasks) { console.log(' ttsTask#'+t.id+' status='+t.status+' voiceId='+t.voiceId+' targetLang='+t.targetLanguage+' prefV='+t.preferredVendor+' prefM='+t.preferredModel); } await p.\$disconnect(); })(); " echo echo "=== 查生成的音频文件 (DB: BookChapter.audioUrl) ===" node -e " const p = new (require('@prisma/client').PrismaClient()); (async () => { for (const id of [$ZH_ID, $EN_ID]) { const chs = await p.bookChapter.findMany({ where: { bookId: id, audioUrl: { not: null } }, take: 3, select: { id: true, title: true, audioUrl: true, audioDuration: true } }); console.log('--- book '+id+' 音频 ---'); for (const c of chs) console.log(' ch#'+c.id+' '+c.title+' dur='+c.audioDuration+'s url='+(c.audioUrl||'').slice(-50)); } await p.\$disconnect(); })(); "