From 7de13ffc6d2bbce55bec432f23ac9526e3fe6e19 Mon Sep 17 00:00:00 2001
From: hubian <908234780@qq.com>
Date: Tue, 28 Apr 2026 17:28:07 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20AI=E5=9B=9E=E5=A4=8D=E8=AF=AD=E9=9F=B3?=
=?UTF-8?q?=E6=92=AD=E6=94=BE=E5=8A=9F=E8=83=BD=EF=BC=88Edge=20TTS?=
=?UTF-8?q?=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/app.py | 58 ++++++++++++++++++-
www/admin.js | 35 +++++++++++
www/app.js | 153 +++++++++++++++++++++++++++++++++++++++++++++++--
www/style.css | 13 ++++-
4 files changed, 252 insertions(+), 7 deletions(-)
diff --git a/backend/app.py b/backend/app.py
index 98d7044..3ce8c3e 100644
--- a/backend/app.py
+++ b/backend/app.py
@@ -4,7 +4,7 @@ AI Chat App - 后台管理服务
端口: 19021 (与前端同一端口)
"""
-from flask import Flask, jsonify, request, send_from_directory
+from flask import Flask, jsonify, request, send_from_directory, Response
from flask_cors import CORS
import os
import json
@@ -12,6 +12,8 @@ import sqlite3
from datetime import datetime
import hashlib
import base64
+import asyncio
+import edge_tts
app = Flask(__name__, static_folder='../www')
CORS(app)
@@ -273,6 +275,8 @@ def init_db():
('app_description', '提供智能对话、多种智能体服务', '应用简介'),
('privacy_policy_url', '', '隐私政策链接'),
('user_agreement_url', '', '用户协议链接'),
+ ('tts_provider', 'edge', 'TTS方案'),
+ ('tts_voice', 'zh-CN-XiaoxiaoNeural', 'TTS语音'),
]
for key, value, desc in default_configs:
cursor.execute('INSERT INTO system_configs (key, value, description) VALUES (?, ?, ?)', (key, value, desc))
@@ -285,6 +289,8 @@ def init_db():
('app_description', '提供智能对话、多种智能体服务', '应用简介'),
('privacy_policy_url', '', '隐私政策链接'),
('user_agreement_url', '', '用户协议链接'),
+ ('tts_provider', 'edge', 'TTS方案'),
+ ('tts_voice', 'zh-CN-XiaoxiaoNeural', 'TTS语音'),
]
for key, value, desc in default_configs:
cursor.execute('SELECT COUNT(*) FROM system_configs WHERE key=?', (key,))
@@ -1386,12 +1392,62 @@ def get_frontend_config():
'description': system.get('app_description', '提供智能对话、多种智能体服务'),
'privacyPolicyUrl': system.get('privacy_policy_url', ''),
'userAgreementUrl': system.get('user_agreement_url', ''),
+ 'ttsProvider': system.get('tts_provider', 'edge'),
+ 'ttsVoice': system.get('tts_voice', 'zh-CN-XiaoxiaoNeural'),
}
}
return jsonify(config)
+# ==================== TTS 语音合成 ====================
+
+@app.route('/api/tts', methods=['POST'])
+def generate_tts():
+ """使用 Edge TTS 生成语音"""
+ data = request.json
+ text = data.get('text', '')
+ voice = data.get('voice', 'zh-CN-XiaoxiaoNeural') # 默认中文女声
+
+ if not text:
+ return jsonify({'error': '缺少文本内容'}), 400
+
+ try:
+ # 使用 asyncio 运行 edge_tts
+ async def generate_audio():
+ communicate = edge_tts.Communicate(text, voice)
+ audio_data = b''
+ for chunk in communicate.stream_sync():
+ if chunk['type'] == 'audio':
+ audio_data += chunk['data']
+ return audio_data
+
+ audio_data = asyncio.run(generate_audio())
+
+ # 返回音频数据(MP3格式)
+ return Response(audio_data, mimetype='audio/mpeg')
+
+ except Exception as e:
+ return jsonify({'error': f'TTS生成失败: {str(e)}'}), 500
+
+
+@app.route('/api/tts/voices', methods=['GET'])
+def get_tts_voices():
+ """获取可用的 TTS 语音列表"""
+ try:
+ voices = asyncio.run(edge_tts.list_voices())
+ # 过滤中文语音
+ chinese_voices = [v for v in voices if v['Locale'].startswith('zh-')]
+ voice_list = [{
+ 'name': v['ShortName'],
+ 'gender': v['Gender'],
+ 'locale': v['Locale']
+ } for v in chinese_voices]
+ return jsonify({'voices': voice_list})
+ except Exception as e:
+ return jsonify({'error': f'获取语音列表失败: {str(e)}'}), 500
+
+
# ==================== 启动 ====================
if __name__ == '__main__':
diff --git a/www/admin.js b/www/admin.js
index f063e84..63c4ee0 100644
--- a/www/admin.js
+++ b/www/admin.js
@@ -1399,6 +1399,39 @@ async function loadSystemPage(content) {
+
TTS语音配置
+
+
+
+
+ 目前仅支持 Edge TTS,后续将添加更多方案
+
+
+
+
+
+ 选择AI回复的朗读语音
+
+
链接配置
@@ -1436,6 +1469,8 @@ async function saveSystemConfig() {
admin_password: document.getElementById('adminPassword').value,
privacy_policy_url: document.getElementById('privacyPolicyUrl').value,
user_agreement_url: document.getElementById('userAgreementUrl').value,
+ tts_provider: document.getElementById('ttsProvider').value,
+ tts_voice: document.getElementById('ttsVoice').value,
};
await fetchAPI('/api/admin/system', 'POST', data);
diff --git a/www/app.js b/www/app.js
index c8874fe..a3959a5 100644
--- a/www/app.js
+++ b/www/app.js
@@ -52,6 +52,11 @@ let backendConfig = null; // 从API获取的配置
// 用户状态
let currentUser = null; // 当前登录用户 { username, password, registeredAt }
+// TTS 语音播放状态
+let enableTTS = false; // 是否启用语音播放
+let currentPlayingAudio = null; // 当前播放的音频对象
+let ttsVoice = 'zh-CN-XiaoxiaoNeural'; // TTS 语音
+
// 每日使用统计(未登录用户)
let dailyUsage = {
date: null, // 日期 YYYY-MM-DD
@@ -127,6 +132,8 @@ async function loadBackendConfig() {
// 将后台系统配置赋值到 CONFIG
if (backendConfig.system) {
CONFIG.system = backendConfig.system;
+ // 加载 TTS 配置
+ ttsVoice = backendConfig.system.ttsVoice || 'zh-CN-XiaoxiaoNeural';
}
// 将后台 LLM 配置赋值到 CONFIG
@@ -3165,6 +3172,9 @@ function showAgentChatPage() {
+
@@ -3257,6 +3267,21 @@ function showAgentChatPage() {
});
}
+ // 绑定 TTS 开关按钮(智能体对话)
+ const ttsBtn = document.getElementById('ttsBtn');
+ if (ttsBtn) {
+ ttsBtn.addEventListener('click', () => {
+ enableTTS = !enableTTS;
+ ttsBtn.classList.toggle('active', enableTTS);
+ showToast(enableTTS ? '语音播放已开启' : '语音播放已关闭');
+ // 如果关闭,停止当前播放
+ if (!enableTTS && currentPlayingAudio) {
+ currentPlayingAudio.pause();
+ currentPlayingAudio = null;
+ }
+ });
+ }
+
// 绑定功能开关按钮事件
if (thinkingBtn) {
thinkingBtn.addEventListener('click', () => {
@@ -3649,8 +3674,8 @@ function openConversation(id) {
🤖
${escapeHtml(currentConversation.title)}
-