const express = require('express'); const router = express.Router(); const { v4: uuidv4 } = require('uuid'); const ToolExecutor = require('../services/toolExecutor'); const DEFAULT_TOOLS = require('../config/tools'); const db = require('../db'); const directSessions = new Map(); router.get('/config', (req, res) => { res.json({ success: true, data: { models: [ { value: '1.2.1.0', label: 'O2.0(推荐,精品音质)' }, { value: 'O', label: 'O(基础版)' }, { value: '2.2.0.0', label: 'SC2.0(推荐,声音复刻)' }, { value: 'SC', label: 'SC(基础版)' }, ], speakers: [ { value: 'zh_female_vv_jupiter_bigtts', label: 'VV(活泼女声)', series: 'O' }, { value: 'zh_female_xiaohe_jupiter_bigtts', label: '小禾(甜美女声·台湾口音)', series: 'O' }, { value: 'zh_male_yunzhou_jupiter_bigtts', label: '云舟(沉稳男声)', series: 'O' }, { value: 'zh_male_xiaotian_jupiter_bigtts', label: '小天(磁性男声)', series: 'O' }, { value: 'saturn_common_female_1', label: 'Saturn 女声1', series: 'SC2.0' }, { value: 'saturn_common_male_1', label: 'Saturn 男声1', series: 'SC2.0' }, { value: 'ICL_common_female_1', label: 'ICL 女声1', series: 'SC' }, { value: 'ICL_common_male_1', label: 'ICL 男声1', series: 'SC' }, ], tools: DEFAULT_TOOLS.map((t) => ({ name: t.function.name, description: t.function.description, })), }, }); }); router.post('/direct/session', async (req, res) => { try { const { userId, sessionId } = req.body || {}; const sid = sessionId || uuidv4(); const directSession = { sessionId: sid, userId: userId || null, startTime: Date.now(), direct: true, }; directSessions.set(sid, directSession); await db.createSession(sid, userId || null, 'voice'); res.json({ success: true, data: { sessionId: sid, userId: userId || null, }, }); } catch (error) { console.error('[DirectVoice] Create session failed:', error.message); res.status(500).json({ success: false, error: error.message }); } }); router.post('/direct/message', async (req, res) => { try { const { sessionId, role, text, source, toolName } = req.body || {}; if (!sessionId || !text || !source) { return res.status(400).json({ success: false, error: 'sessionId, text and source are required' }); } await db.addMessage(sessionId, role === 'user' ? 'user' : 'assistant', text, source, toolName || null); res.json({ success: true }); } catch (error) { console.error('[DirectVoice] Add message failed:', error.message); res.status(500).json({ success: false, error: error.message }); } }); router.post('/diag', (req, res) => { try { const { sessionId, roomId, type, payload } = req.body || {}; console.log(`[Diag] type=${type || 'unknown'} session=${sessionId || '-'} room=${roomId || '-'} payload=${JSON.stringify(payload || {})}`); res.json({ success: true }); } catch (error) { console.error('[Diag] Error:', error.message); res.status(500).json({ success: false, error: error.message }); } }); router.post('/direct/query', async (req, res) => { try { const { sessionId, query, appendUserMessage } = req.body || {}; if (!sessionId) { return res.status(400).json({ success: false, error: 'sessionId is required' }); } const context = await db.getHistoryForLLM(sessionId, 20).catch(() => []); const cleanQuery = (query || '').trim(); if (appendUserMessage && cleanQuery) { await db.addMessage(sessionId, 'user', cleanQuery, 'voice_asr').catch(() => null); } const result = await ToolExecutor.execute('search_knowledge', { query: cleanQuery }, context); let contentText = JSON.stringify(result); if (result && result.results && Array.isArray(result.results)) { contentText = result.results.map((item) => item.content || JSON.stringify(item)).join('\n'); } else if (result && result.error) { contentText = result.error; } else if (typeof result === 'string') { contentText = result; } const ragItems = result && result.results && Array.isArray(result.results) && result.results.length > 0 ? result.results.map((item) => ({ title: item.title || '知识库结果', content: item.content || JSON.stringify(item), })) : [{ title: '知识库结果', content: contentText, }]; await db.addMessage(sessionId, 'assistant', contentText, 'voice_tool', 'search_knowledge', { route: 'search_knowledge', original_text: cleanQuery, tool_name: 'search_knowledge', tool_args: { query: cleanQuery }, source: result?.source || null, original_query: result?.original_query || cleanQuery, rewritten_query: result?.rewritten_query || null, selected_dataset_ids: result?.selected_dataset_ids || null, selected_kb_routes: result?.selected_kb_routes || null, hit: typeof result?.hit === 'boolean' ? result.hit : null, reason: result?.reason || null, error_type: result?.errorType || null, latency_ms: result?.latency_ms || null, }).catch(() => null); res.json({ success: true, data: { sessionId, query: cleanQuery, contentText, ragItems, ragJson: JSON.stringify(ragItems), }, }); } catch (error) { console.error('[DirectVoice] Query failed:', error.message); res.status(500).json({ success: false, error: error.message }); } }); router.post('/direct/stop', async (req, res) => { try { const { sessionId } = req.body || {}; if (!sessionId) { return res.status(400).json({ success: false, error: 'sessionId is required' }); } directSessions.delete(sessionId); const messages = await db.getMessages(sessionId).catch(() => []); res.json({ success: true, data: { sessionId, messageCount: messages.length, }, }); } catch (error) { console.error('[DirectVoice] Stop session failed:', error.message); res.status(500).json({ success: false, error: error.message }); } }); module.exports = router;