6bc2f43da7
- 新增 BusinessReportDialog 组件,展示整体经营/成本费用/风险态势/利润机会四部分文字报告 - BossPage 标题栏新增「本月经营态势」按钮 - 后端新增 /api/tts 路由,调用千问 CosyVoice TTS API 合成语音 - 前端按句分段合成、逐句顺序播放,边播边合成 - 报告含待确认门店数量
62 lines
1.9 KiB
TypeScript
62 lines
1.9 KiB
TypeScript
import { Router } from 'express'
|
|
import { sendSuccess, sendError } from '../middleware/error.js'
|
|
import type { AuthRequest } from '../middleware/auth.js'
|
|
|
|
const router = Router()
|
|
|
|
const TTS_API_KEY = process.env.TTS_API_KEY || 'sk-ws-H.EHYXPRE.Gv4a.MEQCIDOofoXP-VBQILl0c_kspJZhSUj8fyYIZupigveHnpRrAiAsjWXuJ3dpyTBDCUF1ZLdcvLz8GJoan0ObIkCwfE06kQ'
|
|
const TTS_API_HOST = process.env.TTS_API_HOST || 'llm-znfsxsdp6uwik7ad.cn-beijing.maas.aliyuncs.com'
|
|
const TTS_URL = `https://${TTS_API_HOST}/api/v1/services/audio/tts/SpeechSynthesizer`
|
|
|
|
router.post('/', async (req: AuthRequest, res) => {
|
|
try {
|
|
const { text } = req.body
|
|
if (!text || typeof text !== 'string') {
|
|
return sendError(res, 'text is required')
|
|
}
|
|
|
|
if (text.length > 3000) {
|
|
return sendError(res, 'text too long (max 3000 characters)')
|
|
}
|
|
|
|
const response = await fetch(TTS_URL, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${TTS_API_KEY}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
model: 'cosyvoice-v3-flash',
|
|
input: {
|
|
text,
|
|
voice: 'longanyang',
|
|
format: 'mp3',
|
|
sample_rate: 22050,
|
|
volume: 50,
|
|
rate: 1.0,
|
|
},
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const errText = await response.text()
|
|
console.error('TTS API error:', response.status, errText)
|
|
return sendError(res, `TTS API error: ${response.status}`, 502)
|
|
}
|
|
|
|
const result: any = await response.json()
|
|
|
|
if (result.output?.audio?.url) {
|
|
sendSuccess(res, { url: result.output.audio.url })
|
|
} else {
|
|
console.error('TTS API unexpected response:', JSON.stringify(result))
|
|
sendError(res, 'TTS API returned no audio URL', 502)
|
|
}
|
|
} catch (err: any) {
|
|
console.error('TTS route error:', err.message)
|
|
sendError(res, err.message, 500)
|
|
}
|
|
})
|
|
|
|
export default router
|