ch862747537 commited on
Commit
e7d5fd1
·
0 Parent(s):

Initial commit for Hugging Face Space

Browse files
.gitignore ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 忽略Python的缓存文件
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+
6
+ # 忽略日志文件和目录
7
+ logs/
8
+ *.log
9
+
10
+ # 忽略输出目录
11
+ output/
12
+
13
+ # 忽略数据目录(如果数据文件很大或不需要在线上运行)
14
+ data/
15
+
16
+ # 忽略本地开发环境文件
17
+ .venv/
18
+ venv/
19
+ env/
20
+ *.env
21
+ .vscode/
Dockerfile ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方Python 3.9 slim版本作为基础镜像
2
+ FROM python:3.9-slim
3
+
4
+ # 创建一个非root用户,并切换到该用户,增强安全性
5
+ RUN useradd -m -u 1000 user
6
+ USER user
7
+ ENV PATH="/home/user/.local/bin:$PATH"
8
+
9
+ # 设置容器内的工作目录
10
+ WORKDIR /app
11
+
12
+ # 将依赖文件复制到工作目录
13
+ # 使用 --chown=user 确保文件所有者为我们创建的非root用户
14
+ COPY --chown=user ./requirements.txt requirements.txt
15
+
16
+ # 安装项目依赖
17
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
18
+
19
+ # 将src目录下的所有代码复制到工作目录的src子目录中
20
+ COPY --chown=user ./src /app/src
21
+
22
+ # 暴露端口,让Hugging Face平台可以访问我们的应用
23
+ EXPOSE 7860
24
+
25
+ # 容器启动时运行的命令
26
+ # 运行src目录下的app.py文件中的app实例
27
+ # --host 0.0.0.0 使其可以从外部访问
28
+ # --port 7860 使用Hugging Face指定的端口
29
+ CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AI大模型辩论系统
2
+
3
+ ## 项目简介
4
+
5
+ 本项目是一个可以让两个AI大模型(ZhipuAI/GLM-4.5和deepseek-ai/DeepSeek-V3.1)根据给定话题进行实时辩论的系统。用户将能够实时观看两个模型之间的对话内容,并可以控制辩论的进行。
6
+
7
+ ## 功能特点
8
+
9
+ - 支持两个AI大模型之间的实时辩论
10
+ - 可自定义辩论话题和轮数
11
+ - 实时显示辩论内容
12
+ - 支持暂停、恢复和停止辩论
13
+ - 保存完整的辩论记录
14
+ - 提供多种显示模式(简单控制台、控制台实时显示、文件保存、复合显示)
15
+ - 可查看历史辩论记录
16
+ - 支持流式输出,实时显示模型生成内容
17
+ - 提供图形用户界面(GUI),便于使用和分享
18
+
19
+ ## 系统要求
20
+
21
+ - Python 3.7+
22
+ - requests库
23
+ - tkinter库(用于GUI界面)
24
+
25
+ ## 安装说明
26
+
27
+ 1. 克隆或下载本项目
28
+ 2. 安装依赖包:
29
+ ```
30
+ pip install -r requirements.txt
31
+ ```
32
+
33
+ ## 使用方法
34
+
35
+ ### 命令行版本
36
+
37
+ 在项目根目录下运行主程序:
38
+
39
+ ```
40
+ python src/1_辩论系统.py
41
+ ```
42
+
43
+ ### 命令行参数
44
+
45
+ ```
46
+ python src/1_辩论系统.py [--api-key API_KEY] [--display-type DISPLAY_TYPE]
47
+ ```
48
+
49
+ 参数说明:
50
+ - `--api-key`: API密钥(默认:ms-b4690538-3224-493a-8f5b-4073d527f788)
51
+ - `--display-type`: 显示类型,可选值:simple, console, file, composite(默认:composite)
52
+
53
+ ### GUI版本
54
+
55
+ 在项目根目录下运行GUI测试程序:
56
+
57
+ ```
58
+ python src/5_GUI测试.py
59
+ ```
60
+
61
+ GUI界面功能:
62
+ - 选择模型(GLM-4.5或DeepSeek-V3.1)
63
+ - 输入提示文本
64
+ - 实时流式显示模型输出
65
+ - 开始/停止生成按钮
66
+ - 清空输出和保存输出功能
67
+
68
+ ### 操作流程
69
+
70
+ 1. 启动系统后,会显示主菜单:
71
+ - 1. 开始新辩论
72
+ - 2. 查看历史辩论记录
73
+ - 3. 系统设置
74
+ - 4. 退出系统
75
+
76
+ 2. 选择"开始新辩论"后,按提示输入:
77
+ - 辩论话题
78
+ - 辩论轮数(默认5轮)
79
+ - 首发模型(默认GLM-4.5)
80
+
81
+ 3. 确认参数后,辩论将自动开始,您可以实时观看两个模型的辩论内容。
82
+
83
+ 4. 在辩论过程中,您可以输入以下命令控制辩论:
84
+ - `stop`: 停止辩论
85
+ - `pause`: 暂停辩论
86
+ - `resume`: 恢复辩论
87
+
88
+ 5. 辩论结束后,系统会自动保存辩论记录。
89
+
90
+ ## 项目结构
91
+
92
+ ```
93
+ 20250907_大模型辩论/
94
+ ├── src/ # 源代码目录
95
+ │ ├── 1_辩论系统.py # 主程序文件
96
+ │ ├── 2_模型接口.py # 模型API接口封装
97
+ │ ├── 3_辩论控制器.py # 辩论流程控制
98
+ │ ├── 4_显示界面.py # 实时显示界面
99
+ │ └── 5_GUI测试.py # GUI测试程序
100
+ ├── data/ # 数据目录
101
+ │ └── 辩论话题.txt # 存储辩论话题
102
+ ├── output/ # 输出目录
103
+ │ ├── 辩论记录/ # 每次辩论的完整记录
104
+ │ └── 辩论统计/ # 辩论统计数据
105
+ ├── logs/ # 日志目录
106
+ │ └── 系统运行日志.log # 系统运行日志
107
+ ├── docs/ # 文档目录
108
+ │ ├── 项目规划.md # 项目规划文档
109
+ │ ├── 修改记录.md # 项目修改记录
110
+ │ └── 更新计划.md # 更新计划文档
111
+ ├── models/ # 模型目录(预留)
112
+ ├── sql/ # SQL脚本目录(预留)
113
+ ├── requirements.txt # 依赖包列表
114
+ └── README.md # 本文档
115
+ ```
116
+
117
+ ## 模块说明
118
+
119
+ ### 1_辩论系统.py
120
+
121
+ 主程序模块,整合所有模块,提供用户交互界面。包含:
122
+ - 系统主菜单
123
+ - 辩论参数设置
124
+ - 历史记录查看
125
+ - 系统设置
126
+
127
+ ### 2_模型接口.py
128
+
129
+ 模型接口模块,封装两个大模型的API调用。包含:
130
+ - ModelInterface基类
131
+ - GLM45Interface类(GLM-4.5模型接口)
132
+ - DeepSeekV31Interface类(DeepSeek-V3.1模型接口)
133
+ - ModelManager类(模型管理器)
134
+
135
+ ### 3_辩论控制器.py
136
+
137
+ 辩论控制器模块,管理辩论流程和控制发言顺序。包含:
138
+ - DebateMessage类(辩论消息)
139
+ - DebateSession类(辩论会话)
140
+ - DebateController类(辩论控制器)
141
+
142
+ ### 4_显示界面.py
143
+
144
+ 显示界面模块,用于实时显示辩论内容。包含:
145
+ - ConsoleDisplay类(控制台显示界面)
146
+ - SimpleDisplay类(简单显示界面)
147
+ - FileDisplay类(文件显示界面)
148
+ - CompositeDisplay类(复合显示界面)
149
+ - DisplayManager类(显示管理器)
150
+
151
+ ### 5_GUI测试.py
152
+
153
+ GUI测试模块,提供图形用户界面。包含:
154
+ - StreamOutputGUI类(流式输出GUI界面)
155
+ - 支持模型选择和流式输出显示
156
+ - 提供完整的用户交互功能
157
+
158
+ ## API信息
159
+
160
+ - API基础地址:https://api-inference.modelscope.cn/v1/
161
+ - 支持的模型:
162
+ - ZhipuAI/GLM-4.5
163
+ - deepseek-ai/DeepSeek-V3.1
164
+
165
+ ## 注意事项
166
+
167
+ 1. 确保网络连接正常,能够访问API服务器。
168
+ 2. 辩论过程中请耐心等待,模型响��可能需要一定时间。
169
+ 3. 辩论记录会自动保存在`output/辩论记录/`目录下。
170
+ 4. 系统日志会记录在`logs/`目录下,便于排查问题。
171
+
172
+ ## 常见问题
173
+
174
+ ### Q: 如何更换API密钥?
175
+ A: 可以通过命令行参数`--api-key`指定,或者在代码中修改默认值。
176
+
177
+ ### Q: 如何更改显示模式?
178
+ A: 可以通过命令行参数`--display-type`指定,或者在系统设置中更改。
179
+
180
+ ### Q: 辩论过程中出现错误怎么办?
181
+ A: 系统会自动记录错误信息到日志文件,可以查看`logs/`目录下的日志文件排查问题。
182
+
183
+ ### Q: 如何查看历史辩论记录?
184
+ A: 在主菜单选择"查看历史辩论记录",然后选择要查看的记录即可。
185
+
186
+ ## 开发计划
187
+
188
+ 1. 增加更多模型支持
189
+ 2. 优化辩论逻辑和提示词
190
+ 3. 添加辩论评分功能
191
+ 4. 开发图形用户界面
192
+ 5. 支持更多输出格式(如HTML、PDF等)
193
+
194
+ ## 许可证
195
+
196
+ 本项目仅供学习和研究使用。
197
+
198
+ ## 联系方式
199
+
200
+ 如有问题或建议,请通过以下方式联系:
201
+ - 邮箱:[您的邮箱]
202
+ - GitHub:[您的GitHub地址]
docs/修改记录.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 项目修改记录
2
+
3
+ ## 第1次修改
4
+ - 创建项目目录结构
5
+ - 实现模型接口模块 (2_模型接口.py)
6
+ - 封装ZhipuAI/GLM-4.5和moonshotai/Kimi-K2-Instruct-0905模型API
7
+ - 实现ModelManager类统一管理模型接口
8
+ - 添加错误处理和日志记录
9
+ - 实现辩论控制器模块 (3_辩论控制器.py)
10
+ - 设计DebateMessage类管理辩论消息
11
+ - 实现DebateSession类管理辩论会话
12
+ - 创建DebateController类控制辩论流程
13
+ - 支持多线程异步处理辩论
14
+ - 实现显示界面模块 (4_显示界面.py)
15
+ - 实现SimpleDisplay类简单显示
16
+ - 实现ConsoleDisplay类控制台实时显示
17
+ - 实现FileDisplay类文件保存显示
18
+ - 实现CompositeDisplay类复合显示
19
+ - 创建DisplayManager类统一管理显示界面
20
+ - 实现主程序模块 (1_辩论系统.py)
21
+ - 整合所有模块
22
+ - 提供用户交互界面
23
+ - 实现辩论参数设置
24
+ - 实现历史记录查看
25
+ - 实现系统设置
26
+ - 创建项目规划文档 (docs/项目规划.md)
27
+ - 创建requirements.txt文件
28
+ - 创建README.md文档
29
+ - 创建修改记录文档 (docs/修改记录.md)
30
+
31
+ ## 第2次修改
32
+ - 创建更新计划文档 (docs/更新计划.md)
33
+ - 制定流式输出和GUI界面的实现方案
34
+ - 规划实施步骤和时间安排
35
+ - 分析技术风险并制定应对措施
36
+
37
+ ## 第3次修改
38
+ - 修改模型接口模块 (2_模型接口.py)
39
+ - 添加流式输出支持,实现send_stream_request方法
40
+ - 为GLM45Interface和DeepSeekV31Interface类添加chat_stream方法
41
+ - 添加回调函数机制支持逐字输出
42
+ - 更新测试代码以验证流式输出功能
43
+ - 修复模型名称引用错误(kimi_k2改为deepseek_v31)
44
+
45
+ ## 第4次修改
46
+ - 修改requirements.txt文件
47
+ - 添加tkinter依赖(用于GUI界面开发)
48
+ - 创建GUI测试程序 (src/5_GUI测试.py)
49
+ - 实现基于tkinter的图形用户界面
50
+ - 支持模型选择(GLM-4.5和DeepSeek-V3.1)
51
+ - 支持流式输出显示
52
+ - 提供开始/停止/清空/保存功能
53
+ - 添加状态栏和错误处理
docs/更新计划.md ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AI大模型辩论系统更新计划
2
+
3
+ ## 目标
4
+
5
+ 1. 实现大模型回复的流式输出,提升用户体验
6
+ 2. 开发图形用户界面(GUI),使系统更易于使用和分享
7
+
8
+ ## 当前系统分析
9
+
10
+ ### 现有架构
11
+ - **模型接口模块**:使用requests库发送HTTP请求,一次性获取完整响应
12
+ - **辩论控制器模块**:管理辩论流程和模型交互
13
+ - **显示界面模块**:提供多种显示方式(简单控制台、控制台实时显示、文件保存、复合显示)
14
+ - **主程序模块**:整合所有模块,提供命令行界面
15
+
16
+ ### 存在的问题
17
+ 1. 当前模型接口使用非流式请求,需要等待模型完全生成回复后才能显示
18
+ 2. 只有命令行界面,不利于分享和用户体验
19
+
20
+ ## 实现方案
21
+
22
+ ### 1. 流式输出实现
23
+
24
+ #### 1.1 修改模型接口模块 (2_模型接口.py)
25
+ - 添加支持流式输出的chat_stream方法
26
+ - 使用requests的stream=True参数
27
+ - 实现SSE(Server-Sent Events)解析
28
+ - 提供逐字输出的回调函数
29
+
30
+ #### 1.2 修改辩论控制器模块 (3_辩论控制器.py)
31
+ - 添加支持流式输出的辩论方法
32
+ - 修改_debate_loop方法以支持流式输出
33
+ - 更新DebateController类以处理流式回调
34
+
35
+ #### 1.3 修改显示界面模块 (4_显示界面.py)
36
+ - 更新显示界面以支持流式内容显示
37
+ - 添加流式显示的视觉效果(如打字机效果)
38
+
39
+ ### 2. GUI界面实现
40
+
41
+ #### 2.1 技术选型
42
+ - 使用Python的tkinter库创建桌面GUI应用
43
+ - 或使用Flask/FastAPI创建Web应用
44
+
45
+ #### 2.2 功能设计
46
+ - 主界面:包含辩论话题输入、模型选择、轮数设置等
47
+ - 实时显示区域:显示辩论内容,支持流式输出效果
48
+ - 控制按钮:开始、暂停、停止、保存等
49
+ - 历史记录查看界面
50
+ - 设置界面:API密钥、显示设置等
51
+
52
+ #### 2.3 实现步骤
53
+ 1. 创建GUI主窗口和布局
54
+ 2. 实现辩论参数设置界面
55
+ 3. 实现辩论内容显示区域
56
+ 4. 实现控制按钮功能
57
+ 5. 实现历史记录查看功能
58
+ 6. 实现设置功能
59
+
60
+ ## 实施计划
61
+
62
+ ### 第一阶段:流式输出实现(预计2天)
63
+ 1. 修改模型接口模块,添加流式输出支持
64
+ 2. 修改辩论控制器模块,支持流式输出
65
+ 3. 更新显示界面模块,支持流式显示
66
+ 4. 测试流式输出功能
67
+
68
+ ### 第二阶段:GUI界面开发(预计3天)
69
+ 1. 设计GUI界面布局和功能
70
+ 2. 实现主界面和参数设置
71
+ 3. 实现辩论内容显示区域
72
+ 4. 实现控制功能
73
+ 5. 实现历史记录和设置功能
74
+ 6. 测试GUI界面
75
+
76
+ ### 第三阶段:集成测试和优化(预计1天)
77
+ 1. 集成流式输出和GUI界面
78
+ 2. 进行完整功能测试
79
+ 3. 优化用户体验
80
+ 4. 编写使用文档
81
+
82
+ ## 预期效果
83
+
84
+ 1. **流式输出**:用户可以实时看到模型生成的内容,提升交互体验
85
+ 2. **GUI界面**:提供直观易用的图形界面,便于分享和使用
86
+ 3. **兼容性**:保持原有命令行界面功能,同时提供GUI选项
87
+ 4. **稳定性**:确保系统稳定运行,错误处理完善
88
+
89
+ ## 风险评估和应对
90
+
91
+ ### 技术风险
92
+ - **流式输出兼容性**:某些模型可能不支持流式输出
93
+ - 应对:提供降级方案,不支持流式时使用现有方式
94
+
95
+ - **GUI跨平台兼容性**:不同操作系统可能显示效果不同
96
+ - 应对:使用标准组件,进行多平台测试
97
+
98
+ ### 时间风险
99
+ - **开发时间超期**:功能实现可能比预期复杂
100
+ - 应对:分阶段实现,优先核心功能
101
+
102
+ ## 结论
103
+
104
+ 通过实现流式输出和GUI界面,可以显著提升AI大模型辩论系统的用户体验和可用性。流式输出让用户能够实时看到模型生成的内容,增强交互感;GUI界面使系统更易于使用和分享。该更新计划分阶段实施,风险可控,预期效果良好。
docs/项目规划.md ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AI大模型辩论系统项目规划
2
+
3
+ ## 项目概述
4
+
5
+ 本项目旨在创建一个可以让两个AI大模型(ZhipuAI/GLM-4.5和moonshotai/Kimi-K2-Instruct-0905)根据给定话题进行实时辩论的系统。用户将能够实时观看两个模型之间的对话内容,并可以控制辩论的进行。
6
+
7
+ ## 系统架构
8
+
9
+ ### 1. 核心组件
10
+
11
+ - **辩论控制器**:管理辩论流程,控制发言顺序,处理用户输入
12
+ - **模型接口**:与两个大模型API进行交互的封装
13
+ - **实时显示界面**:展示辩论内容的用户界面
14
+ - **日志记录系统**:记录辩论过程和结果
15
+
16
+ ### 2. 数据流
17
+
18
+ 1. 用户输入辩论话题和初始参数
19
+ 2. 辩论控制器初始化辩论
20
+ 3. 模型A生成初始论点
21
+ 4. 模型B针对模型A的论点进行反驳
22
+ 5. 模型A针对模型B的反驳进行回应
23
+ 6. 循环进行,直到达到设定的轮数或用户手动停止
24
+ 7. 实时显示每轮辩论内容
25
+ 8. 记录完整辩论过程
26
+
27
+ ## 技术实现方案
28
+
29
+ ### 1. 目录结构
30
+
31
+ ```
32
+ 20250907_大模型辩论/
33
+ ├── src/ # 源代码目录
34
+ │ ├── 1_辩论系统.py # 主程序文件
35
+ │ ├── 2_模型接口.py # 模型API接口封装
36
+ │ ├── 3_辩论控制器.py # 辩论流程控制
37
+ │ └── 4_显示界面.py # 实时显示界面
38
+ ├── data/ # 数据目录
39
+ │ └── 辩论话题.txt # 存储辩论话题
40
+ ├── output/ # 输出目录
41
+ │ ├── 辩论记录/ # 每次辩论的完整记录
42
+ │ └── 辩论统计/ # 辩论统计数据
43
+ ├── logs/ # 日志目录
44
+ │ └── 系统运行日志.log # 系统运行日志
45
+ ├── docs/ # 文档目录
46
+ │ └── 项目规划.md # 本文档
47
+ ├── models/ # 模型目录(预留)
48
+ ├── sql/ # SQL脚本目录(预留)
49
+ ├── requirements.txt # 依赖包列表
50
+ └── README.md # 项目说明文档
51
+ ```
52
+
53
+ ### 2. 核心功能模块
54
+
55
+ #### 2.1 模型接口模块 (2_模型接口.py)
56
+
57
+ - 封装两个大模型的API调用
58
+ - 处理API请求和响应
59
+ - 错误处理和重试机制
60
+ - 支持自定义模型参数
61
+
62
+ #### 2.2 辩论控制器模块 (3_辩论控制器.py)
63
+
64
+ - 管理辩论流程和状态
65
+ - 控制发言顺序和轮次
66
+ - 处理辩论规则和逻辑
67
+ - 提供辩论开始、暂停、继续、结束等控制功能
68
+
69
+ #### 2.3 显示界面模块 (4_显示界面.py)
70
+
71
+ - 实时显示辩论内容
72
+ - 区分不同模型的发言
73
+ - 提供用户控制界面
74
+ - 支持滚动查看历史内容
75
+
76
+ #### 2.4 主程序模块 (1_辩论系统.py)
77
+
78
+ - 整合所有模块
79
+ - 处理用户输入
80
+ - 协调各模块工作
81
+ - 提供命令行界面
82
+
83
+ ### 3. 辩论流程设计
84
+
85
+ 1. **初始化阶段**
86
+ - 用户输入辩论话题
87
+ - 设置辩论轮数(默认为5轮)
88
+ - 设置每个模型的发言角色(正方/反方)
89
+ - 选择首发模型
90
+
91
+ 2. **辩论阶段**
92
+ - 首发模型根据话题生成初始论点
93
+ - 对方模型针对论点进行反驳
94
+ - 首发模型再针对反驳进行回应
95
+ - 交替进行,直到达到设定轮数
96
+
97
+ 3. **结束阶段**
98
+ - 显示辩论总结
99
+ - 保存辩论记录
100
+ - 生成辩论统计信息
101
+
102
+ ### 4. 技术细节
103
+
104
+ #### 4.1 API调用
105
+
106
+ - 使用requests库进行HTTP请求
107
+ - API基础地址:https://api-inference.modelscope.cn/v1/
108
+ - API Key:ms-b4690538-3224-493a-8f5b-4073d527f788
109
+ - 模型标识:
110
+ - ZhipuAI/GLM-4.5
111
+ - moonshotai/Kimi-K2-Instruct-0905
112
+
113
+ #### 4.2 实时显示
114
+
115
+ - 使用多线程实现异步处理
116
+ - 主线程处理用户交互
117
+ - 子线程处理API调用和辩论逻辑
118
+ - 使用队列传递辩论内容
119
+
120
+ #### 4.3 日志记录
121
+
122
+ - 记录系统运行状态
123
+ - 记录API调用详情
124
+ - 记录每轮辩论内容
125
+ - 记录错误和异常信息
126
+
127
+ ## 实施计划
128
+
129
+ 1. **第一阶段**:创建项目结构,实现模型接口模块
130
+ 2. **第二阶段**:实现辩论控制器模块
131
+ 3. **第三阶段**:实现显示界面模块
132
+ 4. **第四阶段**:整合所有模块,完善主程序
133
+ 5. **第五阶段**:测试和优化
134
+
135
+ ## 预期效果
136
+
137
+ - 用户可以输入任意话题,观看两个AI模型的辩论
138
+ - 实时显示辩论内容,无需等待全部完成
139
+ - 可以控制辩论的进行(暂停、继续、结束)
140
+ - 完整记录辩论过程,便于后续分析
141
+ - 系统稳定运行,错误处理完善
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ requests
4
+ jinja2
src/app.py ADDED
@@ -0,0 +1,473 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ AI大模型辩论系统Web版本
5
+ 基于FastAPI的Web应用,提供图形用户界面的辩论系统
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import json
11
+ import logging
12
+ from datetime import datetime
13
+ from typing import Optional
14
+ import importlib.util
15
+ import asyncio
16
+
17
+ # 在代码开头强制设置终端编码为UTF-8
18
+ os.system('chcp 6001 > nul')
19
+
20
+ # 获取当前脚本文件所在目录的绝对路径
21
+ current_script_dir = os.path.dirname(os.path.abspath(__file__))
22
+
23
+ # 项目根目录 (即 '20250907_大模型辩论' 目录, 是 'src' 的上一级)
24
+ project_root = os.path.dirname(current_script_dir)
25
+
26
+ # 定义数据输入、输出和日志目录
27
+ DATA_DIR = os.path.join(project_root, 'data')
28
+ OUTPUT_DIR = os.path.join(project_root, 'output')
29
+ LOGS_DIR = os.path.join(project_root, 'logs')
30
+
31
+ # 确保目录存在
32
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
33
+ os.makedirs(LOGS_DIR, exist_ok=True)
34
+
35
+ # 配置日志
36
+ logging.basicConfig(
37
+ level=logging.INFO,
38
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
39
+ handlers=[
40
+ logging.FileHandler(os.path.join(LOGS_DIR, '辩论系统Web日志.log'), encoding='utf-8'),
41
+ logging.StreamHandler(sys.stdout)
42
+ ]
43
+ )
44
+ logger = logging.getLogger(__name__)
45
+
46
+ # 捕获警告并记录到日志
47
+ logging.captureWarnings(True)
48
+
49
+ # 导入FastAPI相关模块
50
+ try:
51
+ from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
52
+ from fastapi.responses import HTMLResponse
53
+ from fastapi.staticfiles import StaticFiles
54
+ from fastapi.templating import Jinja2Templates
55
+ import uvicorn
56
+ logger.info("FastAPI模块导入成功")
57
+ except ImportError as e:
58
+ logger.error(f"导入FastAPI模块失败: {str(e)}")
59
+ print("请安装FastAPI: pip install fastapi uvicorn")
60
+ sys.exit(1)
61
+
62
+ # 导入自定义模块
63
+ try:
64
+ # 动态导入模型接口模块
65
+ model_interface_path = os.path.join(current_script_dir, "model_interface.py")
66
+ model_interface_spec = importlib.util.spec_from_file_location("model_interface", model_interface_path)
67
+ model_interface = importlib.util.module_from_spec(model_interface_spec)
68
+ model_interface_spec.loader.exec_module(model_interface)
69
+
70
+ # 动态导入辩论控制器模块
71
+ debate_controller_path = os.path.join(current_script_dir, "debate_controller.py")
72
+ debate_controller_spec = importlib.util.spec_from_file_location("debate_controller", debate_controller_path)
73
+ debate_controller = importlib.util.module_from_spec(debate_controller_spec)
74
+ debate_controller_spec.loader.exec_module(debate_controller)
75
+
76
+ # 从模块中获取需要的类
77
+ ModelManager = model_interface.ModelManager
78
+ DebateMessage = debate_controller.DebateMessage
79
+ DebateSession = debate_controller.DebateSession
80
+
81
+ except Exception as e:
82
+ logger.error(f"导入模块失败: {str(e)}")
83
+ sys.exit(1)
84
+
85
+ # 创建FastAPI应用
86
+ app = FastAPI(title="AI大模型辩论系统", description="基于FastAPI的AI大模型辩论系统Web版本")
87
+
88
+ # 设置静态文件目录
89
+ static_dir = os.path.join(project_root, "static")
90
+ os.makedirs(static_dir, exist_ok=True)
91
+ app.mount("/static", StaticFiles(directory=static_dir), name="static")
92
+
93
+ # 设置模板目录
94
+ templates_dir = os.path.join(project_root, "templates")
95
+ os.makedirs(templates_dir, exist_ok=True)
96
+ templates = Jinja2Templates(directory=templates_dir)
97
+
98
+ # 全局变量
99
+ api_key = "ms-b4690538-3224-493a-8f5b-4073d527f788"
100
+ model_manager = ModelManager(api_key)
101
+ active_debate_session = None
102
+ active_websocket = None
103
+
104
+ @app.get("/", response_class=HTMLResponse)
105
+ async def read_root(request: Request):
106
+ """主页路由,返回Web界面"""
107
+ return templates.TemplateResponse("index.html", {
108
+ "request": request,
109
+ "title": "AI大模型辩论系统"
110
+ })
111
+
112
+ @app.get("/api/status")
113
+ async def get_status():
114
+ """获取系统状态"""
115
+ return {
116
+ "status": "running",
117
+ "timestamp": datetime.now().isoformat(),
118
+ "models": ["glm45", "deepseek_v31"]
119
+ }
120
+
121
+ @app.websocket("/ws")
122
+ async def websocket_endpoint(websocket: WebSocket):
123
+ """WebSocket端点,用于实时通信"""
124
+ global active_websocket
125
+ await websocket.accept()
126
+ active_websocket = websocket
127
+
128
+ try:
129
+ while True:
130
+ data = await websocket.receive_text()
131
+ # 处理接收到的消息
132
+ await handle_websocket_message(websocket, data)
133
+ except WebSocketDisconnect:
134
+ logger.info("WebSocket连接已断开")
135
+ active_websocket = None
136
+ except Exception as e:
137
+ logger.error(f"WebSocket处理错误: {str(e)}")
138
+ active_websocket = None
139
+
140
+ async def handle_websocket_message(websocket: WebSocket, message: str):
141
+ """处理WebSocket消息"""
142
+ try:
143
+ data = json.loads(message)
144
+ action = data.get("action")
145
+
146
+ if action == "start_debate":
147
+ await start_debate(websocket, data)
148
+ elif action == "stop_debate":
149
+ await stop_debate(websocket)
150
+ elif action == "pause_debate":
151
+ await pause_debate(websocket)
152
+ elif action == "resume_debate":
153
+ await resume_debate(websocket)
154
+ else:
155
+ await websocket.send_text(json.dumps({
156
+ "type": "error",
157
+ "message": f"未知操作: {action}"
158
+ }))
159
+ except json.JSONDecodeError:
160
+ await websocket.send_text(json.dumps({
161
+ "type": "error",
162
+ "message": "无效的JSON格式"
163
+ }))
164
+ except Exception as e:
165
+ logger.error(f"处理WebSocket消息时出错: {str(e)}")
166
+ await websocket.send_text(json.dumps({
167
+ "type": "error",
168
+ "message": f"处理消息时出错: {str(e)}"
169
+ }))
170
+
171
+ async def start_debate(websocket: WebSocket, data: dict):
172
+ """开始辩论"""
173
+ global active_debate_session
174
+ loop = asyncio.get_event_loop()
175
+
176
+ try:
177
+ topic = data.get("topic", "人工智能是否会取代人类的工作")
178
+ rounds = int(data.get("rounds", 5))
179
+ first_model = data.get("first_model", "glm45")
180
+ initial_prompt = data.get("initial_prompt", "").strip() # 新增:获取自定义初始提示
181
+
182
+ await websocket.send_text(json.dumps({ "type": "debate_started", "message": "辩论已开始", "topic": topic, "rounds": rounds, "first_model": first_model }))
183
+
184
+ active_debate_session = DebateSession(topic, rounds, first_model)
185
+
186
+ # 新增:如果存在自定义初始提示,则添加到会话历史中
187
+ if initial_prompt:
188
+ active_debate_session.add_message(DebateMessage("user", initial_prompt, "system"))
189
+
190
+ model_a_name = first_model
191
+ model_b_name = 'deepseek_v31' if model_a_name == 'glm45' else 'glm45'
192
+ model_a = model_manager.get_model(model_a_name)
193
+ model_b = model_manager.get_model(model_b_name)
194
+
195
+ active_debate_session.start_time = datetime.now()
196
+
197
+ speakers = [(model_a_name, model_a), (model_b_name, model_b)]
198
+
199
+ # Main debate loop
200
+ for i in range(rounds * 2):
201
+ if not active_debate_session.is_active:
202
+ break
203
+
204
+ round_num = (i // 2) + 1
205
+ if i % 2 == 0:
206
+ active_debate_session.current_round = round_num
207
+ await websocket.send_text(json.dumps({ "type": "round_info", "message": f"--- 第{round_num}轮 ---" }))
208
+
209
+ speaker_name, speaker_model = speakers[i % 2]
210
+ role = "正方" if speaker_name == first_model else "反方"
211
+
212
+ await websocket.send_text(json.dumps({ "type": "model_speaking", "model": speaker_name, "role": role }))
213
+
214
+ prompt = active_debate_session.generate_prompt(speaker_name, first_model)
215
+
216
+ response_content = ""
217
+ def stream_callback(content):
218
+ nonlocal response_content
219
+ response_content += content
220
+ asyncio.run_coroutine_threadsafe(
221
+ websocket.send_text(json.dumps({"type": "stream_content", "content": content})), loop
222
+ )
223
+
224
+ await loop.run_in_executor(None, speaker_model.chat_stream, active_debate_session.get_messages_for_model(speaker_name) + [{"role": "user", "content": prompt}], stream_callback)
225
+ await websocket.send_text(json.dumps({"type": "stream_end", "model": speaker_name}))
226
+
227
+ active_debate_session.add_message(DebateMessage("user", prompt, "system"))
228
+ active_debate_session.add_message(DebateMessage("assistant", response_content, speaker_name))
229
+ save_debate_record() # Incremental save
230
+
231
+ if active_debate_session.is_active:
232
+ active_debate_session.end_time = datetime.now()
233
+ active_debate_session.is_active = False
234
+ await websocket.send_text(json.dumps({ "type": "debate_ended", "message": "=== 辩论结束 ===" }))
235
+ save_debate_record()
236
+ logger.info("辩论结束")
237
+
238
+ except Exception as e:
239
+ logger.error(f"辩论过程中出错: {str(e)}")
240
+ await websocket.send_text(json.dumps({ "type": "error", "message": f"辩论过程中出错: {str(e)}" }))
241
+
242
+ async def stop_debate(websocket: WebSocket):
243
+ """停止辩论"""
244
+ global active_debate_session
245
+ if active_debate_session:
246
+ active_debate_session.is_active = False
247
+ active_debate_session.end_time = datetime.now()
248
+ await websocket.send_text(json.dumps({
249
+ "type": "debate_stopped",
250
+ "message": "辩论已停止"
251
+ }))
252
+ save_debate_record()
253
+ active_debate_session = None
254
+
255
+ async def pause_debate(websocket: WebSocket):
256
+ """暂停辩论"""
257
+ await websocket.send_text(json.dumps({
258
+ "type": "debate_paused",
259
+ "message": "辩论已暂停"
260
+ }))
261
+
262
+ async def resume_debate(websocket: WebSocket):
263
+ """继续辩论"""
264
+ await websocket.send_text(json.dumps({
265
+ "type": "debate_resumed",
266
+ "message": "辩论已继续"
267
+ }))
268
+
269
+ def save_debate_record():
270
+ """保存辩论记录"""
271
+ global active_debate_session
272
+ if active_debate_session:
273
+ try:
274
+ output_dir = os.path.join(OUTPUT_DIR, "辩论记录")
275
+ os.makedirs(output_dir, exist_ok=True)
276
+ file_path = os.path.join(output_dir, f"{active_debate_session.debate_id}.json")
277
+ active_debate_session.save_to_file(file_path)
278
+ logger.info(f"辩论记录已保存: {file_path}")
279
+ except Exception as e:
280
+ logger.error(f"保存辩论记录时出错: {str(e)}")
281
+
282
+ def create_templates():
283
+ """创建HTML模板文件"""
284
+ index_html = """
285
+ <!DOCTYPE html>
286
+ <html lang="zh-CN">
287
+ <head>
288
+ <meta charset="UTF-8">
289
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
290
+ <title>AI大模型辩论系统</title>
291
+ <style>
292
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; background-color: #f0f2f5; color: #333; height: 100vh; display: flex; flex-direction: column; }
293
+ .container { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); width: 95%; max-width: 1400px; margin: 20px auto; flex-grow: 1; display: flex; flex-direction: column; }
294
+ .main-layout { display: flex; gap: 20px; flex-grow: 1; min-height: 0; }
295
+ .sidebar { flex: 0 0 320px; display: flex; flex-direction: column; gap: 15px; }
296
+ .chat-area { flex: 1; display: flex; flex-direction: column; min-width: 0; }
297
+ .header { text-align: center; margin-bottom: 20px; flex-shrink: 0; }
298
+ .header h1 { color: #1a73e8; }
299
+ .controls, .control-group { margin-bottom: 20px; }
300
+ .controls { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
301
+ .control-group { flex: 1; min-width: 200px; }
302
+ label { display: block; margin-bottom: 5px; font-weight: 600; color: #555; }
303
+ input, select, button, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; box-sizing: border-box; font-size: 16px; }
304
+ textarea { resize: vertical; }
305
+ button { background-color: #1a73e8; color: white; border: none; cursor: pointer; transition: background-color 0.3s; }
306
+ button:hover { background-color: #1558b8; }
307
+ button:disabled { background-color: #ccc; cursor: not-allowed; }
308
+ .output-container { background-color: #f8f9fa; border: 1px solid #ddd; border-radius: 8px; padding: 20px; height: 500px; overflow-y: auto; display: flex; flex-direction: column; gap: 15px; }
309
+ .status { padding: 10px; border-radius: 6px; margin-bottom: 15px; border: 1px solid; }
310
+ .status.connected { background-color: #e6f4ea; color: #155724; border-color: #c3e6cb; }
311
+ .status.disconnected { background-color: #f8d7da; color: #721c24; border-color: #f5c6cb; }
312
+
313
+ /* 聊天气泡样式 */
314
+ .message { display: flex; align-items: flex-start; gap: 10px; max-width: 80%; }
315
+ .message .avatar { width: 40px; height: 40px; border-radius: 50%; color: white; display: flex; align-items: center; justify-content: center; font-weight: bold; flex-shrink: 0; font-size: 18px; }
316
+ .message .content { background-color: #ffffff; padding: 10px 15px; border-radius: 18px; box-shadow: 0 1px 2px rgba(0,0,0,0.1); }
317
+ .message .sender { font-weight: bold; margin-bottom: 5px; color: #333; }
318
+ .message.glm45 { align-self: flex-start; }
319
+ .message.glm45 .avatar { background-color: #34a853; } /* Google Green */
320
+ .message.glm45 .content { border-top-left-radius: 4px; }
321
+ .message.deepseek_v31 { align-self: flex-end; flex-direction: row-reverse; }
322
+ .message.deepseek_v31 .avatar { background-color: #4285f4; } /* Google Blue */
323
+ .message.deepseek_v31 .content { background-color: #e7f3ff; border-top-right-radius: 4px; }
324
+ .message .text { white-space: pre-wrap; word-wrap: break-word; }
325
+ .message .text p { margin: 0 0 10px; }
326
+ .message .text h1, .message .text h2, .message .text h3 { margin: 15px 0 10px; border-bottom: 1px solid #eee; padding-bottom: 5px; }
327
+ .message .text ul, .message .text ol { padding-left: 20px; }
328
+ .message .text code { background-color: #eee; padding: 2px 4px; border-radius: 4px; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; }
329
+ .message .text pre { background-color: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 6px; overflow-x: auto; }
330
+ .message .text pre code { background-color: transparent; padding: 0; }
331
+ .round-separator { text-align: center; color: #888; font-size: 0.9em; margin: 20px 0; font-weight: 600; }
332
+
333
+ /* 响应式设计 - 针对手机等小屏幕设备 */
334
+ @media (max-width: 768px) {
335
+ body { padding: 0; }
336
+ .container { width: 100%; margin: 0; border-radius: 0; padding: 10px; height: 100%; }
337
+ .main-layout { flex-direction: column; }
338
+ .sidebar { flex: 0 0 auto; }
339
+ .header h1 { font-size: 1.5em; }
340
+ .controls { flex-direction: column; }
341
+ .control-group { min-width: unset; }
342
+ }
343
+ </style>
344
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
345
+ <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
346
+ </head>
347
+ <body>
348
+ <div class="container">
349
+ <div class="header"><h1>AI大模型辩论系统</h1><p>观看两个AI大模型实时辩论</p></div>
350
+
351
+ <div class="main-layout">
352
+ <div class="sidebar">
353
+ <div class="control-group"><label for="topic">辩论话题:</label><input type="text" id="topic" value="人工智能是否会取代人类的工作"></div>
354
+ <div class="control-group">
355
+ <label for="initialPrompt">自定义初始提示 (可选):</label>
356
+ <textarea id="initialPrompt" rows="6" placeholder="例如:请作为正方,用强势的语气开启辩论..."></textarea>
357
+ </div>
358
+ <div class="control-group"><label for="rounds">轮数:</label><input type="number" id="rounds" min="1" max="10" value="5"></div>
359
+ <div class="control-group"><label for="firstModel">首发模型:</label><select id="firstModel"><option value="glm45">ZhipuAI/GLM-4.5</option><option value="deepseek_v31">deepseek-ai/DeepSeek-V3.1</option></select></div>
360
+ <div class="controls">
361
+ <button id="startBtn" disabled>开始辩论</button>
362
+ <button id="stopBtn" disabled>停止辩论</button>
363
+ </div>
364
+ </div>
365
+ <div class="chat-area">
366
+ <label for="output">辩论实况:</label>
367
+ <div id="output" class="output-container"></div>
368
+ </div>
369
+ </div>
370
+ </div>
371
+ <script>
372
+ let websocket = null, isConnected = false, currentMessageElement = null, currentMessageContent = '';
373
+ const startBtn = document.getElementById('startBtn'), stopBtn = document.getElementById('stopBtn');
374
+ const outputDiv = document.getElementById('output');
375
+
376
+ function handleWebSocketMessage(data) {
377
+ let shouldScroll = Math.abs(outputDiv.scrollHeight - outputDiv.clientHeight - outputDiv.scrollTop) < 10;
378
+
379
+ switch (data.type) {
380
+ case 'debate_started':
381
+ outputDiv.innerHTML = '';
382
+ const topicDiv = document.createElement('div');
383
+ topicDiv.className = 'round-separator';
384
+ topicDiv.innerHTML = `<strong>话题:</strong> ${data.topic}`;
385
+ outputDiv.appendChild(topicDiv);
386
+ break;
387
+ case 'round_info':
388
+ const separator = document.createElement('div');
389
+ separator.className = 'round-separator';
390
+ separator.textContent = data.message.trim();
391
+ outputDiv.appendChild(separator);
392
+ break;
393
+ case 'model_speaking':
394
+ currentMessageContent = ''; // 重置当前消息内容
395
+ const messageDiv = document.createElement('div');
396
+ messageDiv.className = `message ${data.model}`;
397
+ messageDiv.innerHTML = `
398
+ <div class="avatar">${data.model.substring(0, 1).toUpperCase()}</div>
399
+ <div class="content">
400
+ <div class="sender">${data.model} (${data.role})</div>
401
+ <div class="text"><i>正在思考...</i></div>
402
+ </div>`;
403
+ outputDiv.appendChild(messageDiv);
404
+ currentMessageElement = messageDiv.querySelector('.text');
405
+ break;
406
+ case 'stream_content':
407
+ if (currentMessageElement) {
408
+ currentMessageContent += data.content;
409
+ currentMessageElement.innerHTML = DOMPurify.sanitize(marked.parse(currentMessageContent));
410
+ }
411
+ break;
412
+ case 'stream_end':
413
+ currentMessageElement = null;
414
+ break;
415
+ case 'debate_ended':
416
+ case 'debate_stopped':
417
+ const endMsg = document.createElement('div');
418
+ endMsg.className = 'round-separator';
419
+ endMsg.textContent = data.message;
420
+ outputDiv.appendChild(endMsg);
421
+ startBtn.disabled = false; stopBtn.disabled = true;
422
+ break;
423
+ case 'error':
424
+ outputDiv.innerHTML += `<div class="round-separator" style="color: red;">错误: ${data.message}</div>`;
425
+ break;
426
+ }
427
+ if(shouldScroll) {
428
+ outputDiv.scrollTop = outputDiv.scrollHeight;
429
+ }
430
+ }
431
+
432
+ function connect() {
433
+ const wsUrl = `ws://${window.location.host}/ws`;
434
+ websocket = new WebSocket(wsUrl);
435
+ websocket.onopen = () => { isConnected = true; startBtn.disabled = false; };
436
+ websocket.onmessage = (event) => handleWebSocketMessage(JSON.parse(event.data));
437
+ websocket.onclose = () => { isConnected = false; startBtn.disabled = true; stopBtn.disabled = true; };
438
+ websocket.onerror = (error) => { console.error('WebSocket Error:', error); };
439
+ }
440
+
441
+ window.addEventListener('load', connect);
442
+
443
+ startBtn.addEventListener('click', () => {
444
+ if (!websocket) return;
445
+ const message = {
446
+ action: "start_debate",
447
+ topic: document.getElementById('topic').value,
448
+ rounds: parseInt(document.getElementById('rounds').value),
449
+ first_model: document.getElementById('firstModel').value,
450
+ initial_prompt: document.getElementById('initialPrompt').value
451
+ };
452
+ websocket.send(JSON.stringify(message));
453
+ startBtn.disabled = true; stopBtn.disabled = false;
454
+ });
455
+ stopBtn.addEventListener('click', () => {
456
+ if (!websocket) return;
457
+ websocket.send(JSON.stringify({ action: "stop_debate" }));
458
+ });
459
+ </script>
460
+ </body>
461
+ </html>"""
462
+ with open(os.path.join(templates_dir, "index.html"), "w", encoding="utf-8") as f:
463
+ f.write(index_html)
464
+ logger.info("Web应用模板文件已创建")
465
+
466
+ if __name__ == "__main__":
467
+ os.makedirs(static_dir, exist_ok=True)
468
+ os.makedirs(templates_dir, exist_ok=True)
469
+ create_templates()
470
+ logger.info("启动Web服务器...")
471
+ # 智能端口切换:优先使用环境变量PORT,否则默认为8000
472
+ port = int(os.environ.get("PORT", 8000))
473
+ uvicorn.run(app, host="0.0.0.0", port=port)
src/debate_controller.py ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 辩论控制器模块
4
+ 管理辩论流程和控制发言顺序
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import json
10
+ import time
11
+ import logging
12
+ import threading
13
+ from typing import Dict, List, Any, Optional, Callable
14
+ from datetime import datetime
15
+ from queue import Queue
16
+
17
+ # 在代码开头强制设置终端编码为UTF-8
18
+ os.system('chcp 65001 > nul')
19
+
20
+ # 获取当前脚本文件所在目录的绝对路径
21
+ current_script_dir = os.path.dirname(os.path.abspath(__file__))
22
+
23
+ # 项目根目录 (即 '20250907_大模型辩论' 目录, 是 'src' 的上一级)
24
+ project_root = os.path.dirname(current_script_dir)
25
+
26
+ # 定义数据输入、输出和日志目录
27
+ DATA_DIR = os.path.join(project_root, 'data')
28
+ OUTPUT_DIR = os.path.join(project_root, 'output')
29
+ LOGS_DIR = os.path.join(project_root, 'logs')
30
+
31
+ # 确保目录存在
32
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
33
+ os.makedirs(LOGS_DIR, exist_ok=True)
34
+
35
+ # 配置日志
36
+ logging.basicConfig(
37
+ level=logging.INFO,
38
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
39
+ handlers=[
40
+ logging.FileHandler(os.path.join(LOGS_DIR, '辩论控制器日志.log'), encoding='utf-8'),
41
+ logging.StreamHandler(sys.stdout)
42
+ ]
43
+ )
44
+ logger = logging.getLogger(__name__)
45
+
46
+ # 捕获警告并记录到日志
47
+ logging.captureWarnings(True)
48
+
49
+ class DebateMessage:
50
+ """辩论消息类"""
51
+
52
+ def __init__(self, role: str, content: str, model_name: str, timestamp: datetime = None):
53
+ """
54
+ 初始化辩论消息
55
+
56
+ Args:
57
+ role: 消息角色 ('user' 或 'assistant')
58
+ content: 消息内容
59
+ model_name: 模型名称
60
+ timestamp: 时间戳
61
+ """
62
+ self.role = role
63
+ self.content = content
64
+ self.model_name = model_name
65
+ self.timestamp = timestamp or datetime.now()
66
+
67
+ def to_dict(self) -> Dict[str, Any]:
68
+ """转换为字典"""
69
+ return {
70
+ 'role': self.role,
71
+ 'content': self.content,
72
+ 'model_name': self.model_name,
73
+ 'timestamp': self.timestamp.isoformat()
74
+ }
75
+
76
+ @classmethod
77
+ def from_dict(cls, data: Dict[str, Any]) -> 'DebateMessage':
78
+ """从字典创建实例"""
79
+ return cls(
80
+ role=data['role'],
81
+ content=data['content'],
82
+ model_name=data['model_name'],
83
+ timestamp=datetime.fromisoformat(data['timestamp'])
84
+ )
85
+
86
+ class DebateSession:
87
+ """辩论会话类"""
88
+
89
+ def __init__(self, topic: str, max_rounds: int = 5, first_model: str = 'glm45'):
90
+ """
91
+ 初始化辩论会话
92
+
93
+ Args:
94
+ topic: 辩论话题
95
+ max_rounds: 最大轮数
96
+ first_model: 首发模型 ('glm45' 或 'kimi_k2')
97
+ """
98
+ self.topic = topic
99
+ self.max_rounds = max_rounds
100
+ self.first_model = first_model
101
+ self.messages: List[DebateMessage] = []
102
+ self.current_round = 0
103
+ self.is_active = True
104
+ self.is_paused = False
105
+ self.start_time = None
106
+ self.end_time = None
107
+ self.debate_id = f"debate_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
108
+
109
+ logger.info(f"辩论会话初始化完成,话题: {topic}, 最大轮数: {max_rounds}, 首发模型: {first_model}")
110
+
111
+ def add_message(self, message: DebateMessage):
112
+ """添加消息到辩论记录"""
113
+ self.messages.append(message)
114
+ logger.info(f"添加消息到辩论记录,模型: {message.model_name}, 内容长度: {len(message.content)}")
115
+
116
+ def get_messages_for_model(self, model_name: str) -> List[Dict[str, str]]:
117
+ """
118
+ 获取指定模型的消息历史
119
+
120
+ Args:
121
+ model_name: 模型名称
122
+
123
+ Returns:
124
+ 消息历史列表
125
+ """
126
+ # 转换为API需要的格式
127
+ result = []
128
+ for msg in self.messages:
129
+ result.append({
130
+ 'role': msg.role,
131
+ 'content': msg.content
132
+ })
133
+ return result
134
+
135
+ def get_debate_summary(self) -> Dict[str, Any]:
136
+ """获取辩论摘要"""
137
+ return {
138
+ 'debate_id': self.debate_id,
139
+ 'topic': self.topic,
140
+ 'max_rounds': self.max_rounds,
141
+ 'current_round': self.current_round,
142
+ 'total_messages': len(self.messages),
143
+ 'is_active': self.is_active,
144
+ 'is_paused': self.is_paused,
145
+ 'start_time': self.start_time.isoformat() if self.start_time else None,
146
+ 'end_time': self.end_time.isoformat() if self.end_time else None,
147
+ 'duration': (self.end_time - self.start_time).total_seconds() if self.start_time and self.end_time else None
148
+ }
149
+
150
+ def save_to_file(self, file_path: str):
151
+ """保存辩论记录到文件"""
152
+ data = {
153
+ 'debate_info': self.get_debate_summary(),
154
+ 'messages': [msg.to_dict() for msg in self.messages]
155
+ }
156
+
157
+ with open(file_path, 'w', encoding='utf-8') as f:
158
+ json.dump(data, f, ensure_ascii=False, indent=2)
159
+
160
+ logger.info(f"辩论记录已保存到: {file_path}")
161
+
162
+ @classmethod
163
+ def load_from_file(cls, file_path: str) -> 'DebateSession':
164
+ """从文件加载辩论记录"""
165
+ with open(file_path, 'r', encoding='utf-8') as f:
166
+ data = json.load(f)
167
+
168
+ debate_info = data['debate_info']
169
+ session = cls(
170
+ topic=debate_info['topic'],
171
+ max_rounds=debate_info['max_rounds'],
172
+ first_model='glm45' # 默认值,实际值应该从消息中推断
173
+ )
174
+
175
+ session.debate_id = debate_info['debate_id']
176
+ session.current_round = debate_info['current_round']
177
+ session.is_active = debate_info['is_active']
178
+ session.is_paused = debate_info['is_paused']
179
+ session.start_time = datetime.fromisoformat(debate_info['start_time']) if debate_info['start_time'] else None
180
+ session.end_time = datetime.fromisoformat(debate_info['end_time']) if debate_info['end_time'] else None
181
+
182
+ session.messages = [DebateMessage.from_dict(msg_data) for msg_data in data['messages']]
183
+
184
+ logger.info(f"从文件加载辩论记录: {file_path}")
185
+ return session
186
+
187
+ def generate_prompt(self, speaker_name: str, first_model_name: str) -> str:
188
+ """
189
+ 根据辩论历史和当前发言者生成提示
190
+
191
+ Args:
192
+ speaker_name: 当前发言者名称
193
+ first_model_name: 首发模型名称
194
+
195
+ Returns:
196
+ 生成的提示字符串
197
+ """
198
+ is_positive_side = (speaker_name == first_model_name)
199
+ role = "正方" if is_positive_side else "反方"
200
+
201
+ if not self.messages:
202
+ # 辩论开始,第一个发言者
203
+ return f"你将作为{role},就以下话题进行辩论:{self.topic}。请提出你的主要论点,陈述你的核心立场和关键论据。"
204
+ else:
205
+ last_message = self.messages[-1]
206
+ opponent_statement = last_message.content
207
+
208
+ if len(self.messages) == 1:
209
+ # 第二个发言者,对开场陈述进行反驳
210
+ return f"你将作为{role},就以下话题进行辩论:{self.topic}。你的对手({role})的开场陈述是:\n\n“{opponent_statement}”\n\n请直接反驳对方的观点,并提出你自己的论点。"
211
+ else:
212
+ # 后续发言,进行反驳和回应
213
+ return f"现在轮到你({role})发言。你的对手刚刚的发言是:\n\n“{opponent_statement}”\n\n请针对他的观点进行反驳,并进一步阐述和强化你自己的立场。"
214
+
215
+ class DebateController:
216
+ """辩论控制器类"""
217
+
218
+ def __init__(self, model_manager, output_callback: Optional[Callable] = None):
219
+ """
220
+ 初始化辩论控制器
221
+
222
+ Args:
223
+ model_manager: 模型管理器实例
224
+ output_callback: 输出回调函数,用于实时显示辩论内容
225
+ """
226
+ self.model_manager = model_manager
227
+ self.output_callback = output_callback
228
+ self.current_session: Optional[DebateSession] = None
229
+ self.debate_thread: Optional[threading.Thread] = None
230
+ self.stop_event = threading.Event()
231
+ self.pause_event = threading.Event()
232
+
233
+ logger.info("辩论控制器初始化完成")
234
+
235
+ def create_debate(self, topic: str, max_rounds: int = 5, first_model: str = 'glm45') -> DebateSession:
236
+ """
237
+ 创建新的辩论会话
238
+
239
+ Args:
240
+ topic: 辩论话题
241
+ max_rounds: 最大轮数
242
+ first_model: 首发模型
243
+
244
+ Returns:
245
+ 辩论会话实例
246
+ """
247
+ self.current_session = DebateSession(topic, max_rounds, first_model)
248
+ logger.info(f"创建新辩论会话,话题: {topic}")
249
+ return self.current_session
250
+
251
+ def start_debate(self):
252
+ """开始辩论"""
253
+ if not self.current_session:
254
+ logger.error("没有活动的辩论会话")
255
+ return
256
+
257
+ if self.current_session.is_active:
258
+ logger.warning("辩论已经在进行中")
259
+ return
260
+
261
+ self.current_session.is_active = True
262
+ self.current_session.is_paused = False
263
+ self.current_session.start_time = datetime.now()
264
+ self.stop_event.clear()
265
+ self.pause_event.clear()
266
+
267
+ # 启动辩论线程
268
+ self.debate_thread = threading.Thread(target=self._debate_loop)
269
+ self.debate_thread.daemon = True
270
+ self.debate_thread.start()
271
+
272
+ logger.info("辩论开始")
273
+
274
+ def pause_debate(self):
275
+ """暂停辩论"""
276
+ if not self.current_session or not self.current_session.is_active:
277
+ logger.warning("没有活动的辩论会话")
278
+ return
279
+
280
+ self.current_session.is_paused = True
281
+ self.pause_event.set()
282
+ logger.info("辩论已暂停")
283
+
284
+ def resume_debate(self):
285
+ """恢复辩论"""
286
+ if not self.current_session or not self.current_session.is_active:
287
+ logger.warning("没有活动的辩论会话")
288
+ return
289
+
290
+ self.current_session.is_paused = False
291
+ self.pause_event.clear()
292
+ logger.info("辩论已恢复")
293
+
294
+ def stop_debate(self):
295
+ """停止辩论"""
296
+ if not self.current_session or not self.current_session.is_active:
297
+ logger.warning("没有活动的辩论会话")
298
+ return
299
+
300
+ self.stop_event.set()
301
+ self.current_session.is_active = False
302
+ self.current_session.is_paused = False
303
+ self.current_session.end_time = datetime.now()
304
+
305
+ if self.debate_thread and self.debate_thread.is_alive():
306
+ self.debate_thread.join(timeout=5)
307
+
308
+ # 保存辩论记录
309
+ self._save_debate_record()
310
+
311
+ logger.info("辩论已停止")
312
+
313
+ def _debate_loop(self):
314
+ """辩论主循环"""
315
+ session = self.current_session
316
+ if not session:
317
+ return
318
+
319
+ # 确定模型顺序
320
+ model_a = session.first_model
321
+ model_b = 'deepseek_v31' if model_a == 'glm45' else 'glm45'
322
+
323
+ # 获取模型接口
324
+ model_a_interface = self.model_manager.get_model(model_a)
325
+ model_b_interface = self.model_manager.get_model(model_b)
326
+
327
+ # 构建初始提示
328
+ initial_prompt_a = f"你将作为正方,就以下话题进行辩论:{session.topic}。请提出你的主要论点。"
329
+ initial_prompt_b = f"你将作为反方,就以下话题进行辩论:{session.topic}。请针对对方的论点进行反驳。"
330
+
331
+ # 第一轮:模型A提出论点
332
+ if not self._should_continue_debate(session):
333
+ return
334
+
335
+ self._output_message(f"=== 辩论开始 ===\n话题: {session.topic}\n")
336
+ self._output_message(f"--- 第1轮 ---\n")
337
+
338
+ # 模型A发言
339
+ self._output_message(f"{model_a} (正方): ")
340
+ response_a = model_a_interface.chat([{"role": "user", "content": initial_prompt_a}])
341
+ self._output_message(f"{response_a}\n\n")
342
+
343
+ # 记录消息
344
+ session.add_message(DebateMessage("user", initial_prompt_a, "system"))
345
+ session.add_message(DebateMessage("assistant", response_a, model_a))
346
+
347
+ session.current_round = 1
348
+
349
+ # 后续轮次
350
+ while self._should_continue_debate(session):
351
+ # 检查暂停状态
352
+ if self.pause_event.is_set():
353
+ self._output_message("辩论已暂停,等待恢复...\n")
354
+ self.pause_event.wait()
355
+ self._output_message("辩论已恢复\n")
356
+
357
+ session.current_round += 1
358
+ self._output_message(f"--- 第{session.current_round}轮 ---\n")
359
+
360
+ # 模型B反驳
361
+ self._output_message(f"{model_b} (反方): ")
362
+ prompt_b = f"{initial_prompt_b}\n\n对方的论点: {response_a}\n\n请进行反驳。"
363
+ response_b = model_b_interface.chat(session.get_messages_for_model(model_b) + [{"role": "user", "content": prompt_b}])
364
+ self._output_message(f"{response_b}\n\n")
365
+
366
+ # 记录消息
367
+ session.add_message(DebateMessage("user", prompt_b, "system"))
368
+ session.add_message(DebateMessage("assistant", response_b, model_b))
369
+
370
+ # 检查是否应该继续
371
+ if not self._should_continue_debate(session):
372
+ break
373
+
374
+ # 模型A回应
375
+ self._output_message(f"{model_a} (正方): ")
376
+ prompt_a = f"请针对对方的反驳进行回应:{response_b}"
377
+ response_a = model_a_interface.chat(session.get_messages_for_model(model_a) + [{"role": "user", "content": prompt_a}])
378
+ self._output_message(f"{response_a}\n\n")
379
+
380
+ # 记录消息
381
+ session.add_message(DebateMessage("user", prompt_a, "system"))
382
+ session.add_message(DebateMessage("assistant", response_a, model_a))
383
+
384
+ # 辩论结束
385
+ session.is_active = False
386
+ session.end_time = datetime.now()
387
+ self._output_message("=== 辩论结束 ===\n")
388
+
389
+ # 保存辩论记录
390
+ self._save_debate_record()
391
+
392
+ def _should_continue_debate(self, session: DebateSession) -> bool:
393
+ """检查是否应该继续辩论"""
394
+ if self.stop_event.is_set():
395
+ return False
396
+
397
+ if session.current_round >= session.max_rounds:
398
+ return False
399
+
400
+ return True
401
+
402
+ def _output_message(self, message: str):
403
+ """输出消息"""
404
+ if self.output_callback:
405
+ self.output_callback(message)
406
+ else:
407
+ print(message, end='', flush=True)
408
+
409
+ def _save_debate_record(self):
410
+ """保存辩论记录"""
411
+ if not self.current_session:
412
+ return
413
+
414
+ # 创建输出文件路径
415
+ output_dir = os.path.join(OUTPUT_DIR, "辩论记录")
416
+ os.makedirs(output_dir, exist_ok=True)
417
+
418
+ file_path = os.path.join(output_dir, f"{self.current_session.debate_id}.json")
419
+ self.current_session.save_to_file(file_path)
420
+
421
+ logger.info(f"辩论记录已保存: {file_path}")
422
+
423
+ # 测试代码
424
+ if __name__ == "__main__":
425
+ # 简单测试
426
+ import sys
427
+ import os
428
+ import importlib.util
429
+
430
+ # 动态导入模块
431
+ module_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "2_模型接口.py")
432
+ spec = importlib.util.spec_from_file_location("model_interface", module_path)
433
+ model_interface = importlib.util.module_from_spec(spec)
434
+ spec.loader.exec_module(model_interface)
435
+
436
+ # 创建模型管理器
437
+ api_key = "ms-b4690538-3224-493a-8f5b-4073d527f788"
438
+ model_manager = model_interface.ModelManager(api_key)
439
+
440
+ # 创建辩论控制器
441
+ def print_callback(message):
442
+ print(message, end='', flush=True)
443
+
444
+ controller = DebateController(model_manager, print_callback)
445
+
446
+ # 创建辩论会话
447
+ session = controller.create_debate("人工智能是否会取代人类的工作", max_rounds=3, first_model='glm45')
448
+
449
+ # 开始辩论
450
+ controller.start_debate()
451
+
452
+ # 等待辩论结束
453
+ if controller.debate_thread:
454
+ controller.debate_thread.join()
src/model_interface.py ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 模型接口模块
4
+ 用于与两个大模型API进行交互的封装
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import requests
10
+ import json
11
+ import time
12
+ import logging
13
+ from typing import Dict, Any, Optional, Callable
14
+ from urllib3.exceptions import InsecureRequestWarning
15
+
16
+ # 在代码开头强制设置终端编码为UTF-8
17
+ os.system('chcp 65001 > nul')
18
+
19
+ # 获取当前脚本文件所在目录的绝对路径
20
+ current_script_dir = os.path.dirname(os.path.abspath(__file__))
21
+
22
+ # 项目根目录 (即 '20250907_大模型辩论' 目录, 是 'src' 的上一级)
23
+ project_root = os.path.dirname(current_script_dir)
24
+
25
+ # 定义数据输入、输出和日志目录
26
+ DATA_DIR = os.path.join(project_root, 'data')
27
+ OUTPUT_DIR = os.path.join(project_root, 'output')
28
+ LOGS_DIR = os.path.join(project_root, 'logs')
29
+
30
+ # 确保目录存在
31
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
32
+ os.makedirs(LOGS_DIR, exist_ok=True)
33
+
34
+ # 配置日志
35
+ logging.basicConfig(
36
+ level=logging.INFO,
37
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
38
+ handlers=[
39
+ logging.FileHandler(os.path.join(LOGS_DIR, '模型接口日志.log'), encoding='utf-8'),
40
+ logging.StreamHandler(sys.stdout)
41
+ ]
42
+ )
43
+ logger = logging.getLogger(__name__)
44
+
45
+ # 捕获警告并记录到日志
46
+ logging.captureWarnings(True)
47
+
48
+ class ModelInterface:
49
+ """模型接口基类"""
50
+
51
+ def __init__(self, api_key: str, base_url: str):
52
+ """
53
+ 初始化模型接口
54
+
55
+ Args:
56
+ api_key: API密钥
57
+ base_url: API基础URL
58
+ """
59
+ self.api_key = api_key
60
+ self.base_url = base_url
61
+ self.headers = {
62
+ 'Authorization': f'Bearer {api_key}',
63
+ 'Content-Type': 'application/json'
64
+ }
65
+ logger.info(f"模型接口初始化完成,基础URL: {base_url}")
66
+
67
+ def send_request(self, model: str, messages: list, **kwargs) -> Dict[str, Any]:
68
+ """
69
+ 发送请求到模型API
70
+
71
+ Args:
72
+ model: 模型名称
73
+ messages: 消息列表
74
+ **kwargs: 其他参数
75
+
76
+ Returns:
77
+ API响应结果
78
+ """
79
+ payload = {
80
+ 'model': model,
81
+ 'messages': messages,
82
+ **kwargs
83
+ }
84
+
85
+ try:
86
+ logger.info(f"向模型 {model} 发送请求")
87
+ response = requests.post(
88
+ f"{self.base_url}/chat/completions",
89
+ headers=self.headers,
90
+ json=payload,
91
+ timeout=60
92
+ )
93
+ response.raise_for_status()
94
+ result = response.json()
95
+ logger.info(f"模型 {model} 响应成功")
96
+ return result
97
+ except requests.exceptions.RequestException as e:
98
+ logger.error(f"请求模型 {model} 失败: {str(e)}")
99
+ raise
100
+ except json.JSONDecodeError as e:
101
+ logger.error(f"解析模型 {model} 响应失败: {str(e)}")
102
+ raise
103
+
104
+ def send_stream_request(self, model: str, messages: list, callback: Callable[[str], None], **kwargs) -> str:
105
+ """
106
+ 发送流式请求到模型API
107
+
108
+ Args:
109
+ model: 模型名称
110
+ messages: 消息列表
111
+ callback: 回调函数,用于处理流式输出的每个字符
112
+ **kwargs: 其他参数
113
+
114
+ Returns:
115
+ 完整的响应文本
116
+ """
117
+ payload = {
118
+ 'model': model,
119
+ 'messages': messages,
120
+ 'stream': True,
121
+ **kwargs
122
+ }
123
+
124
+ full_response = ""
125
+ try:
126
+ logger.info(f"向模型 {model} 发送流式请求")
127
+ response = requests.post(
128
+ f"{self.base_url}/chat/completions",
129
+ headers=self.headers,
130
+ json=payload,
131
+ timeout=60,
132
+ stream=True
133
+ )
134
+ response.raise_for_status()
135
+
136
+ # 处理流式响应
137
+ for line in response.iter_lines():
138
+ if line:
139
+ decoded_line = line.decode('utf-8')
140
+ if decoded_line.startswith("data: "):
141
+ data = decoded_line[6:] # 去掉 "data: " 前缀
142
+ if data != "[DONE]":
143
+ try:
144
+ json_data = json.loads(data)
145
+ content = json_data["choices"][0]["delta"].get("content", "")
146
+ if content:
147
+ full_response += content
148
+ # 调用回调函数处理每个字符
149
+ callback(content)
150
+ except json.JSONDecodeError:
151
+ pass # 忽略无法解析的行
152
+
153
+ logger.info(f"模型 {model} 流式响应完成")
154
+ return full_response
155
+ except requests.exceptions.RequestException as e:
156
+ logger.error(f"流式请求模型 {model} 失败: {str(e)}")
157
+ raise
158
+ except Exception as e:
159
+ logger.error(f"处理流式响应时出错: {str(e)}")
160
+ raise
161
+
162
+ def get_response_text(self, response: Dict[str, Any]) -> str:
163
+ """
164
+ 从API响应中提取回复文本
165
+
166
+ Args:
167
+ response: API响应
168
+
169
+ Returns:
170
+ 回复文本
171
+ """
172
+ try:
173
+ return response['choices'][0]['message']['content']
174
+ except (KeyError, IndexError) as e:
175
+ logger.error(f"提取回复文本失败: {str(e)}")
176
+ raise
177
+
178
+ class GLM45Interface(ModelInterface):
179
+ """GLM-4.5模型接口"""
180
+
181
+ def __init__(self, api_key: str):
182
+ """
183
+ 初始化GLM-4.5模型接口
184
+
185
+ Args:
186
+ api_key: API密钥
187
+ """
188
+ super().__init__(api_key, "https://api-inference.modelscope.cn/v1")
189
+ self.model_name = "ZhipuAI/GLM-4.5"
190
+ logger.info(f"GLM-4.5模型接口初始化完成")
191
+
192
+ def chat(self, messages: list, temperature: float = 0.7, max_tokens: int = 1000) -> str:
193
+ """
194
+ 与GLM-4.5模型对话
195
+
196
+ Args:
197
+ messages: 消息列表
198
+ temperature: 温度参数,控制随机性
199
+ max_tokens: 最大令牌数
200
+
201
+ Returns:
202
+ 模型回复文本
203
+ """
204
+ try:
205
+ response = self.send_request(
206
+ model=self.model_name,
207
+ messages=messages,
208
+ temperature=temperature,
209
+ max_tokens=max_tokens
210
+ )
211
+ return self.get_response_text(response)
212
+ except Exception as e:
213
+ logger.error(f"GLM-4.5对话失败: {str(e)}")
214
+ return f"GLM-4.5对话失败: {str(e)}"
215
+
216
+ def chat_stream(self, messages: list, callback: Callable[[str], None], temperature: float = 0.7, max_tokens: int = 1000) -> str:
217
+ """
218
+ 与GLM-4.5模型流式对话
219
+
220
+ Args:
221
+ messages: 消息列表
222
+ callback: 回调函数,用于处理流式输出的每个字符
223
+ temperature: 温度参数,控制随机性
224
+ max_tokens: 最大令牌数
225
+
226
+ Returns:
227
+ 完整的模型回复文本
228
+ """
229
+ try:
230
+ return self.send_stream_request(
231
+ model=self.model_name,
232
+ messages=messages,
233
+ callback=callback,
234
+ temperature=temperature
235
+ # 移除max_tokens参数,避免截断
236
+ # max_tokens=max_tokens
237
+ )
238
+ except Exception as e:
239
+ logger.error(f"GLM-4.5流式对话失败: {str(e)}")
240
+ error_msg = f"GLM-4.5流式对话失败: {str(e)}"
241
+ # 通过回调函数发送错误信息
242
+ callback(f"\n{error_msg}\n")
243
+ return error_msg
244
+
245
+ class DeepSeekV31Interface(ModelInterface):
246
+ """DeepSeek-V3.1模型接口"""
247
+
248
+ def __init__(self, api_key: str):
249
+ """
250
+ 初始化DeepSeek-V3.1模型接口
251
+
252
+ Args:
253
+ api_key: API密钥
254
+ """
255
+ super().__init__(api_key, "https://api-inference.modelscope.cn/v1")
256
+ self.model_name = "deepseek-ai/DeepSeek-V3.1"
257
+ logger.info(f"DeepSeek-V3.1模型接口初始化完成")
258
+
259
+ def chat(self, messages: list, temperature: float = 0.7, max_tokens: int = 1000) -> str:
260
+ """
261
+ 与DeepSeek-V3.1模型对话
262
+
263
+ Args:
264
+ messages: 消息列表
265
+ temperature: 温度参数,控制随机性
266
+ max_tokens: 最大令牌数
267
+
268
+ Returns:
269
+ 模型回复文本
270
+ """
271
+ try:
272
+ response = self.send_request(
273
+ model=self.model_name,
274
+ messages=messages,
275
+ temperature=temperature,
276
+ max_tokens=max_tokens
277
+ )
278
+ return self.get_response_text(response)
279
+ except Exception as e:
280
+ logger.error(f"DeepSeek-V3.1对话失败: {str(e)}")
281
+ return f"DeepSeek-V3.1对话失败: {str(e)}"
282
+
283
+ def chat_stream(self, messages: list, callback: Callable[[str], None], temperature: float = 0.7, max_tokens: int = 1000) -> str:
284
+ """
285
+ 与DeepSeek-V3.1模型流式对话
286
+
287
+ Args:
288
+ messages: 消息列表
289
+ callback: 回调函数,用于处理流式输出的每个字符
290
+ temperature: 温度参数,控制随机性
291
+ max_tokens: 最大令牌数
292
+
293
+ Returns:
294
+ 完整的模型回复文本
295
+ """
296
+ try:
297
+ return self.send_stream_request(
298
+ model=self.model_name,
299
+ messages=messages,
300
+ callback=callback,
301
+ temperature=temperature
302
+ # 暂时移除max_tokens参数,排查兼容性问题
303
+ # max_tokens=max_tokens
304
+ )
305
+ except Exception as e:
306
+ logger.error(f"DeepSeek-V3.1流式对话失败: {str(e)}")
307
+ error_msg = f"DeepSeek-V3.1流式对话失败: {str(e)}"
308
+ # 通过回调函数发送错误信息
309
+ callback(f"\n{error_msg}\n")
310
+ return error_msg
311
+
312
+ class ModelManager:
313
+ """模型管理器,统一管理两个模型接口"""
314
+
315
+ def __init__(self, api_key: str):
316
+ """
317
+ 初始化模型管理器
318
+
319
+ Args:
320
+ api_key: API密钥
321
+ """
322
+ self.api_key = api_key
323
+ self.glm45 = GLM45Interface(api_key)
324
+ self.deepseek_v31 = DeepSeekV31Interface(api_key)
325
+ logger.info("模型管理器初始化完成")
326
+
327
+ def get_model(self, model_name: str) -> ModelInterface:
328
+ """
329
+ 获取指定模型接口
330
+
331
+ Args:
332
+ model_name: 模型名称 ('glm45' 或 'deepseek_v31')
333
+
334
+ Returns:
335
+ 模型接口实例
336
+ """
337
+ if model_name.lower() == 'glm45':
338
+ return self.glm45
339
+ elif model_name.lower() == 'deepseek_v31':
340
+ return self.deepseek_v31
341
+ else:
342
+ logger.error(f"不支持的模型名称: {model_name}")
343
+ raise ValueError(f"不支持的模型名称: {model_name}")
344
+
345
+ # 测试代码
346
+ if __name__ == "__main__":
347
+ # 测试模型接口
348
+ api_key = "ms-b4690538-3224-493a-8f5b-4073d527f788"
349
+ manager = ModelManager(api_key)
350
+
351
+ # 测试GLM-4.5
352
+ glm45 = manager.get_model('glm45')
353
+ messages = [{"role": "user", "content": "你好,请简单介绍一下自己"}]
354
+
355
+ print("=== 测试GLM-4.5普通对话 ===")
356
+ response = glm45.chat(messages)
357
+ print(f"GLM-4.5回复: {response}")
358
+
359
+ print("\n=== 测试GLM-4.5流式对话 ===")
360
+ def stream_callback(content):
361
+ print(content, end='', flush=True)
362
+
363
+ response = glm45.chat_stream(messages, stream_callback)
364
+ print(f"\n完整回复: {response}")
365
+
366
+ # 测试DeepSeek-V3.1
367
+ deepseek_v31 = manager.get_model('deepseek_v31')
368
+ messages = [{"role": "user", "content": "你好,请简单介绍一下自己"}]
369
+
370
+ print("\n=== 测试DeepSeek-V3.1普通对话 ===")
371
+ response = deepseek_v31.chat(messages)
372
+ print(f"DeepSeek-V3.1回复: {response}")
373
+
374
+ print("\n=== 测试DeepSeek-V3.1流式对话 ===")
375
+ response = deepseek_v31.chat_stream(messages, stream_callback)
376
+ print(f"\n完整回复: {response}")
src/test.py ADDED
File without changes
src/旧文件/1_辩论系统.py ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ AI大模型辩论系统主程序
4
+ 整合所有模块,提供用户交互界面
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ import logging
11
+ import argparse
12
+ from datetime import datetime
13
+
14
+ # 在代码开头强制设置终端编码为UTF-8
15
+ os.system('chcp 65001 > nul')
16
+
17
+ # 获取当前脚本文件所在目录的绝对路径
18
+ current_script_dir = os.path.dirname(os.path.abspath(__file__))
19
+
20
+ # 项目根目录 (即 '20250907_大模型辩论' 目录, 是 'src' 的上一级)
21
+ project_root = os.path.dirname(current_script_dir)
22
+
23
+ # 定义数据输入、输出和日志目录
24
+ DATA_DIR = os.path.join(project_root, 'data')
25
+ OUTPUT_DIR = os.path.join(project_root, 'output')
26
+ LOGS_DIR = os.path.join(project_root, 'logs')
27
+
28
+ # 确保目录存在
29
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
30
+ os.makedirs(LOGS_DIR, exist_ok=True)
31
+
32
+ # 配置日志
33
+ logging.basicConfig(
34
+ level=logging.INFO,
35
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
36
+ handlers=[
37
+ logging.FileHandler(os.path.join(LOGS_DIR, '系统运行日志.log'), encoding='utf-8'),
38
+ logging.StreamHandler(sys.stdout)
39
+ ]
40
+ )
41
+ logger = logging.getLogger(__name__)
42
+
43
+ # 捕获警告并记录到日志
44
+ logging.captureWarnings(True)
45
+
46
+ # 导入自定义模块
47
+ try:
48
+ import importlib.util
49
+ import os
50
+
51
+ # 动态导入模型接口模块
52
+ model_interface_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "2_模型接口.py")
53
+ model_interface_spec = importlib.util.spec_from_file_location("model_interface", model_interface_path)
54
+ model_interface = importlib.util.module_from_spec(model_interface_spec)
55
+ model_interface_spec.loader.exec_module(model_interface)
56
+
57
+ # 动态导入辩论控制器模块
58
+ debate_controller_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "3_辩论控制器.py")
59
+ debate_controller_spec = importlib.util.spec_from_file_location("debate_controller", debate_controller_path)
60
+ debate_controller = importlib.util.module_from_spec(debate_controller_spec)
61
+ debate_controller_spec.loader.exec_module(debate_controller)
62
+
63
+ # 动态导入显示界面模块
64
+ display_interface_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "4_显示界面.py")
65
+ display_interface_spec = importlib.util.spec_from_file_location("display_interface", display_interface_path)
66
+ display_interface = importlib.util.module_from_spec(display_interface_spec)
67
+ display_interface_spec.loader.exec_module(display_interface)
68
+
69
+ # 从模块中获取需要的类
70
+ ModelManager = model_interface.ModelManager
71
+ DebateController = debate_controller.DebateController
72
+ DisplayManager = display_interface.DisplayManager
73
+
74
+ except ImportError as e:
75
+ logger.error(f"导入模块失败: {str(e)}")
76
+ print("请确保所有模块文件都在同一目录下")
77
+ sys.exit(1)
78
+
79
+ class DebateSystem:
80
+ """AI大模型辩论系统"""
81
+
82
+ def __init__(self, api_key: str, display_type: str = "composite"):
83
+ """
84
+ 初始化辩论系统
85
+
86
+ Args:
87
+ api_key: API密钥
88
+ display_type: 显示类型 ('simple', 'console', 'file', 'composite')
89
+ """
90
+ self.api_key = api_key
91
+ self.display_type = display_type
92
+
93
+ # 初始化模型管理器
94
+ self.model_manager = ModelManager(api_key)
95
+
96
+ # 初始化显示管理器
97
+ self.display_manager = DisplayManager(display_type)
98
+
99
+ # 初始化辩论控制器
100
+ self.debate_controller = DebateController(
101
+ self.model_manager,
102
+ output_callback=self.display_manager.add_message
103
+ )
104
+
105
+ logger.info("AI大模型辩论系统初始化完成")
106
+
107
+ def start(self):
108
+ """启动辩论系统"""
109
+ logger.info("启动AI大模型辩论系统")
110
+
111
+ # 启动显示界面
112
+ self.display_manager.start()
113
+
114
+ # 显示欢迎信息
115
+ self.display_manager.add_message("=" * 50 + "\n")
116
+ self.display_manager.add_message("欢迎使用AI大模型辩论系统\n")
117
+ self.display_manager.add_message("=" * 50 + "\n\n")
118
+
119
+ # 主循环
120
+ while True:
121
+ try:
122
+ # 显示菜单
123
+ self.display_manager.add_message("请选择操作:\n")
124
+ self.display_manager.add_message("1. 开始新辩论\n")
125
+ self.display_manager.add_message("2. 查看历史辩论记录\n")
126
+ self.display_manager.add_message("3. 系统设置\n")
127
+ self.display_manager.add_message("4. 退出系统\n")
128
+ self.display_manager.add_message("请输入选项 (1-4): ")
129
+
130
+ # 获取用户输入
131
+ choice = input().strip()
132
+
133
+ if choice == "1":
134
+ self.start_new_debate()
135
+ elif choice == "2":
136
+ self.view_history()
137
+ elif choice == "3":
138
+ self.system_settings()
139
+ elif choice == "4":
140
+ self.display_manager.add_message("感谢使用AI大模型辩论系统,再见!\n")
141
+ break
142
+ else:
143
+ self.display_manager.add_message("无效选项,请重新输入\n\n")
144
+
145
+ except KeyboardInterrupt:
146
+ self.display_manager.add_message("\n\n检测到中断信号,正在退出系统...\n")
147
+ break
148
+ except Exception as e:
149
+ logger.error(f"系统运行出错: {str(e)}")
150
+ self.display_manager.add_message(f"系统运行出错: {str(e)}\n\n")
151
+
152
+ # 停止显示界面
153
+ self.display_manager.stop()
154
+ logger.info("AI大模型辩论系统已关闭")
155
+
156
+ def start_new_debate(self):
157
+ """开始新辩论"""
158
+ self.display_manager.add_message("\n--- 开始新辩论 ---\n")
159
+
160
+ # 获取辩论话题
161
+ self.display_manager.add_message("请输入辩论话题: ")
162
+ topic = input().strip()
163
+
164
+ if not topic:
165
+ self.display_manager.add_message("话题不能为空\n\n")
166
+ return
167
+
168
+ # 获取辩论轮数
169
+ self.display_manager.add_message("请输入辩论轮数 (默认5轮): ")
170
+ max_rounds_input = input().strip()
171
+
172
+ try:
173
+ max_rounds = int(max_rounds_input) if max_rounds_input else 5
174
+ if max_rounds < 1 or max_rounds > 10:
175
+ self.display_manager.add_message("轮数必须在1-10之间,使用默认值5\n")
176
+ max_rounds = 5
177
+ except ValueError:
178
+ self.display_manager.add_message("无效输入,使用默认值5轮\n")
179
+ max_rounds = 5
180
+
181
+ # 获取首发模型
182
+ self.display_manager.add_message("请选择首发模型 (1. GLM-4.5, 2. DeepSeek-V3.1, 默认1): ")
183
+ first_model_input = input().strip()
184
+
185
+ if first_model_input == "2":
186
+ first_model = "deepseek_v31"
187
+ else:
188
+ first_model = "glm45"
189
+
190
+ # 显示参数设置
191
+ self.display_manager.add_message(f"\n辩论参数设置:\n")
192
+ self.display_manager.add_message(f"话题: {topic}\n")
193
+ self.display_manager.add_message(f"轮数: {max_rounds}\n")
194
+ self.display_manager.add_message(f"首发模型: {first_model}\n")
195
+
196
+ # 确认开始
197
+ self.display_manager.add_message("\n确认开始辩论? (y/n): ")
198
+ confirm = input().strip().lower()
199
+
200
+ if confirm != "y":
201
+ self.display_manager.add_message("已取消辩论\n\n")
202
+ return
203
+
204
+ # 创建辩论会话
205
+ debate_session = self.debate_controller.create_debate(topic, max_rounds, first_model)
206
+
207
+ # 开始辩论
208
+ self.display_manager.add_message("\n辩论即将开始...\n\n")
209
+ time.sleep(1)
210
+
211
+ self.debate_controller.start_debate()
212
+
213
+ # 等待辩论结束
214
+ try:
215
+ while self.debate_controller.current_session and self.debate_controller.current_session.is_active:
216
+ time.sleep(1)
217
+
218
+ # 检查用户输入
219
+ if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
220
+ user_input = sys.stdin.readline().strip()
221
+ if user_input.lower() == "stop":
222
+ self.debate_controller.stop_debate()
223
+ self.display_manager.add_message("\n用户手动停止辩论\n")
224
+ elif user_input.lower() == "pause":
225
+ self.debate_controller.pause_debate()
226
+ self.display_manager.add_message("\n辩论已暂停\n")
227
+ elif user_input.lower() == "resume":
228
+ self.debate_controller.resume_debate()
229
+ self.display_manager.add_message("\n辩论已恢复\n")
230
+
231
+ except KeyboardInterrupt:
232
+ self.display_manager.add_message("\n\n检测到中断信号,正在停止辩论...\n")
233
+ self.debate_controller.stop_debate()
234
+
235
+ self.display_manager.add_message("\n辩论已结束\n\n")
236
+
237
+ def view_history(self):
238
+ """查看历史辩论记录"""
239
+ self.display_manager.add_message("\n--- 历史辩论记录 ---\n")
240
+
241
+ # 获取辩论记录目录
242
+ debate_records_dir = os.path.join(OUTPUT_DIR, "辩论记录")
243
+
244
+ if not os.path.exists(debate_records_dir):
245
+ self.display_manager.add_message("暂无辩论记录\n\n")
246
+ return
247
+
248
+ # 获取所有辩论记录文件
249
+ record_files = [f for f in os.listdir(debate_records_dir) if f.endswith('.json')]
250
+
251
+ if not record_files:
252
+ self.display_manager.add_message("暂无辩论记录\n\n")
253
+ return
254
+
255
+ # 显示辩论记录列表
256
+ self.display_manager.add_message("可用的辩论记录:\n")
257
+ for i, filename in enumerate(record_files, 1):
258
+ self.display_manager.add_message(f"{i}. {filename}\n")
259
+
260
+ # 选择查看记录
261
+ self.display_manager.add_message("\n请选择要查看的记录 (输入序号,0返回): ")
262
+ choice = input().strip()
263
+
264
+ try:
265
+ choice_num = int(choice)
266
+ if choice_num == 0:
267
+ self.display_manager.add_message("\n")
268
+ return
269
+
270
+ if 1 <= choice_num <= len(record_files):
271
+ selected_file = record_files[choice_num - 1]
272
+ self.display_debate_record(os.path.join(debate_records_dir, selected_file))
273
+ else:
274
+ self.display_manager.add_message("无效选择\n\n")
275
+ except ValueError:
276
+ self.display_manager.add_message("无效输入\n\n")
277
+
278
+ def display_debate_record(self, file_path: str):
279
+ """显示辩论记录"""
280
+ try:
281
+ import json
282
+
283
+ with open(file_path, 'r', encoding='utf-8') as f:
284
+ data = json.load(f)
285
+
286
+ debate_info = data['debate_info']
287
+ messages = data['messages']
288
+
289
+ self.display_manager.add_message(f"\n--- 辩论记录详情 ---\n")
290
+ self.display_manager.add_message(f"辩论ID: {debate_info['debate_id']}\n")
291
+ self.display_manager.add_message(f"话题: {debate_info['topic']}\n")
292
+ self.display_manager.add_message(f"最大轮数: {debate_info['max_rounds']}\n")
293
+ self.display_manager.add_message(f"实际轮数: {debate_info['current_round']}\n")
294
+ self.display_manager.add_message(f"总消息数: {debate_info['total_messages']}\n")
295
+
296
+ if debate_info['start_time']:
297
+ start_time = datetime.fromisoformat(debate_info['start_time'])
298
+ self.display_manager.add_message(f"开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}\n")
299
+
300
+ if debate_info['end_time']:
301
+ end_time = datetime.fromisoformat(debate_info['end_time'])
302
+ self.display_manager.add_message(f"结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}\n")
303
+
304
+ if debate_info['duration']:
305
+ duration = int(debate_info['duration'])
306
+ minutes, seconds = divmod(duration, 60)
307
+ self.display_manager.add_message(f"持续时间: {minutes}分{seconds}秒\n")
308
+
309
+ self.display_manager.add_message("\n--- 辩论内容 ---\n")
310
+
311
+ for msg in messages:
312
+ timestamp = datetime.fromisoformat(msg['timestamp'])
313
+ self.display_manager.add_message(f"[{timestamp.strftime('%H:%M:%S')}] {msg['model_name']}: {msg['content']}\n\n")
314
+
315
+ except Exception as e:
316
+ logger.error(f"显示辩论记录时出错: {str(e)}")
317
+ self.display_manager.add_message(f"显示辩论记录时出错: {str(e)}\n\n")
318
+
319
+ def system_settings(self):
320
+ """系统设置"""
321
+ self.display_manager.add_message("\n--- 系统设置 ---\n")
322
+
323
+ while True:
324
+ self.display_manager.add_message("请选择设置项:\n")
325
+ self.display_manager.add_message("1. 更改显示类型\n")
326
+ self.display_manager.add_message("2. 查看系统信息\n")
327
+ self.display_manager.add_message("3. 返回主菜单\n")
328
+ self.display_manager.add_message("请输入选项 (1-3): ")
329
+
330
+ choice = input().strip()
331
+
332
+ if choice == "1":
333
+ self.change_display_type()
334
+ elif choice == "2":
335
+ self.show_system_info()
336
+ elif choice == "3":
337
+ self.display_manager.add_message("\n")
338
+ break
339
+ else:
340
+ self.display_manager.add_message("无效选项,请重新输入\n\n")
341
+
342
+ def change_display_type(self):
343
+ """更改显示类型"""
344
+ self.display_manager.add_message("\n当前显示类型: " + self.display_type + "\n")
345
+ self.display_manager.add_message("可选显示类型:\n")
346
+ self.display_manager.add_message("1. simple - 简单控制台输出\n")
347
+ self.display_manager.add_message("2. console - 控制台实时显示\n")
348
+ self.display_manager.add_message("3. file - 仅保存到文件\n")
349
+ self.display_manager.add_message("4. composite - 复合显示 (默认)\n")
350
+ self.display_manager.add_message("请输入选项 (1-4): ")
351
+
352
+ choice = input().strip()
353
+
354
+ display_types = {
355
+ "1": "simple",
356
+ "2": "console",
357
+ "3": "file",
358
+ "4": "composite"
359
+ }
360
+
361
+ if choice in display_types:
362
+ # 停止当前显示
363
+ self.display_manager.stop()
364
+
365
+ # 更改显示类型
366
+ self.display_type = display_types[choice]
367
+
368
+ # 重新初始化显示管理器
369
+ self.display_manager = DisplayManager(self.display_type)
370
+
371
+ # 更新辩论控制器的回调
372
+ self.debate_controller.output_callback = self.display_manager.add_message
373
+
374
+ # 启动新显示
375
+ self.display_manager.start()
376
+
377
+ self.display_manager.add_message(f"\n显示类型已更改为: {self.display_type}\n\n")
378
+ else:
379
+ self.display_manager.add_message("无效选项\n\n")
380
+
381
+ def show_system_info(self):
382
+ """显示系统信息"""
383
+ self.display_manager.add_message("\n--- 系统信息 ---\n")
384
+ self.display_manager.add_message(f"API基础URL: https://api-inference.modelscope.cn/v1/\n")
385
+ self.display_manager.add_message(f"支持的模型:\n")
386
+ self.display_manager.add_message(f" - ZhipuAI/GLM-4.5\n")
387
+ self.display_manager.add_message(f" - deepseek-ai/DeepSeek-V3.1\n")
388
+ self.display_manager.add_message(f"当前显示类型: {self.display_type}\n")
389
+ self.display_manager.add_message(f"项目根目录: {project_root}\n")
390
+ self.display_manager.add_message(f"数据目录: {DATA_DIR}\n")
391
+ self.display_manager.add_message(f"输出目录: {OUTPUT_DIR}\n")
392
+ self.display_manager.add_message(f"日志目录: {LOGS_DIR}\n\n")
393
+
394
+ def main():
395
+ """主函数"""
396
+ # 解析命令行参数
397
+ parser = argparse.ArgumentParser(description='AI大模型辩论系统')
398
+ parser.add_argument('--api-key', type=str, default='ms-b4690538-3224-493a-8f5b-4073d527f788',
399
+ help='API密钥')
400
+ parser.add_argument('--display-type', type=str, default='composite',
401
+ choices=['simple', 'console', 'file', 'composite'],
402
+ help='显示类型')
403
+
404
+ args = parser.parse_args()
405
+
406
+ # 创建并启动辩论系统
407
+ debate_system = DebateSystem(args.api_key, args.display_type)
408
+ debate_system.start()
409
+
410
+ if __name__ == "__main__":
411
+ # 导入select模块(用于检测用户输入)
412
+ import select
413
+
414
+ main()
src/旧文件/4_显示界面.py ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 显示界面模块
4
+ 用于实时显示辩论内容
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ import threading
11
+ import logging
12
+ from typing import Optional, Dict, Any
13
+ from datetime import datetime
14
+ import queue
15
+
16
+ # 在代码开头强制设置终端编码为UTF-8
17
+ os.system('chcp 65001 > nul')
18
+
19
+ # 获取当前脚本文件所在目录的绝对路径
20
+ current_script_dir = os.path.dirname(os.path.abspath(__file__))
21
+
22
+ # 项目根目录 (即 '20250907_大模型辩论' 目录, 是 'src' 的上一级)
23
+ project_root = os.path.dirname(current_script_dir)
24
+
25
+ # 定义数据输入、输出和日志目录
26
+ DATA_DIR = os.path.join(project_root, 'data')
27
+ OUTPUT_DIR = os.path.join(project_root, 'output')
28
+ LOGS_DIR = os.path.join(project_root, 'logs')
29
+
30
+ # 确保目录存在
31
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
32
+ os.makedirs(LOGS_DIR, exist_ok=True)
33
+
34
+ # 配置日志
35
+ logging.basicConfig(
36
+ level=logging.INFO,
37
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
38
+ handlers=[
39
+ logging.FileHandler(os.path.join(LOGS_DIR, '显示界面日志.log'), encoding='utf-8'),
40
+ logging.StreamHandler(sys.stdout)
41
+ ]
42
+ )
43
+ logger = logging.getLogger(__name__)
44
+
45
+ # 捕获警告并记录到日志
46
+ logging.captureWarnings(True)
47
+
48
+ class ConsoleDisplay:
49
+ """控制台显示界面"""
50
+
51
+ def __init__(self):
52
+ """初始化控制台显示界面"""
53
+ self.message_queue = queue.Queue()
54
+ self.display_thread = None
55
+ self.stop_event = threading.Event()
56
+ self.is_running = False
57
+
58
+ logger.info("控制台显示界面初始化完成")
59
+
60
+ def start(self):
61
+ """启动显示界面"""
62
+ if self.is_running:
63
+ logger.warning("显示界面已经在运行中")
64
+ return
65
+
66
+ self.is_running = True
67
+ self.stop_event.clear()
68
+
69
+ # 启动显示线程
70
+ self.display_thread = threading.Thread(target=self._display_loop)
71
+ self.display_thread.daemon = True
72
+ self.display_thread.start()
73
+
74
+ logger.info("显示界面已启动")
75
+
76
+ def stop(self):
77
+ """停止显示界面"""
78
+ if not self.is_running:
79
+ logger.warning("显示界面未在运行")
80
+ return
81
+
82
+ self.is_running = False
83
+ self.stop_event.set()
84
+
85
+ if self.display_thread and self.display_thread.is_alive():
86
+ self.display_thread.join(timeout=1)
87
+
88
+ logger.info("显示界面已停止")
89
+
90
+ def add_message(self, message: str):
91
+ """添加消息到显示队列"""
92
+ if not self.is_running:
93
+ logger.warning("显示界面未在运行,无法添加消息")
94
+ return
95
+
96
+ self.message_queue.put(message)
97
+
98
+ def clear_screen(self):
99
+ """清空屏幕"""
100
+ os.system('cls' if os.name == 'nt' else 'clear')
101
+
102
+ def _display_loop(self):
103
+ """显示主循环"""
104
+ buffer = ""
105
+
106
+ while not self.stop_event.is_set():
107
+ try:
108
+ # 从队列获取消息,设置超时以便检查停止事件
109
+ message = self.message_queue.get(timeout=0.1)
110
+
111
+ # 将消息添加到缓冲区
112
+ buffer += message
113
+
114
+ # 清空屏幕并显示缓冲区内容
115
+ self.clear_screen()
116
+ print(buffer, end='', flush=True)
117
+
118
+ # 标记消息已处理
119
+ self.message_queue.task_done()
120
+
121
+ except queue.Empty:
122
+ # 队列为空,继续循环
123
+ continue
124
+ except Exception as e:
125
+ logger.error(f"显示消息时出错: {str(e)}")
126
+ continue
127
+
128
+ class SimpleDisplay:
129
+ """简单显示界面,直接输出到控制台"""
130
+
131
+ def __init__(self):
132
+ """初始化简单显示界面"""
133
+ logger.info("简单显示界面初始化完成")
134
+
135
+ def start(self):
136
+ """启动显示界面"""
137
+ logger.info("简单显示界面已启动")
138
+
139
+ def stop(self):
140
+ """停止显示界面"""
141
+ logger.info("简单显示界面已停止")
142
+
143
+ def add_message(self, message: str):
144
+ """添加消息到显示"""
145
+ print(message, end='', flush=True)
146
+
147
+ class FileDisplay:
148
+ """文件显示界面,将辩论内容保存到文件"""
149
+
150
+ def __init__(self, output_dir: str = None):
151
+ """
152
+ 初始化文件显示界面
153
+
154
+ Args:
155
+ output_dir: 输出目录
156
+ """
157
+ self.output_dir = output_dir or os.path.join(OUTPUT_DIR, "辩论记录")
158
+ os.makedirs(self.output_dir, exist_ok=True)
159
+
160
+ self.current_file = None
161
+ self.file_handle = None
162
+
163
+ logger.info("文件显示界面初始化完成")
164
+
165
+ def start(self, debate_id: str = None):
166
+ """
167
+ 启动文件显示���面
168
+
169
+ Args:
170
+ debate_id: 辩论ID,用于生成文件名
171
+ """
172
+ if self.file_handle:
173
+ logger.warning("文件显示界面已经在运行中")
174
+ return
175
+
176
+ # 生成文件名
177
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
178
+ if debate_id:
179
+ filename = f"{debate_id}_实时记录.txt"
180
+ else:
181
+ filename = f"辩论记录_{timestamp}.txt"
182
+
183
+ self.current_file = os.path.join(self.output_dir, filename)
184
+
185
+ # 打开文件
186
+ self.file_handle = open(self.current_file, 'w', encoding='utf-8')
187
+
188
+ logger.info(f"文件显示界面已启动,输出文件: {self.current_file}")
189
+
190
+ def stop(self):
191
+ """停止文件显示界面"""
192
+ if not self.file_handle:
193
+ logger.warning("文件显示界面未在运行")
194
+ return
195
+
196
+ # 关闭文件
197
+ self.file_handle.close()
198
+ self.file_handle = None
199
+
200
+ logger.info(f"文件显示界面已停止,输出文件: {self.current_file}")
201
+ self.current_file = None
202
+
203
+ def add_message(self, message: str):
204
+ """添加消息到文件"""
205
+ if not self.file_handle:
206
+ logger.warning("文件显示界面未在运行,无法添加消息")
207
+ return
208
+
209
+ try:
210
+ self.file_handle.write(message)
211
+ self.file_handle.flush()
212
+ except Exception as e:
213
+ logger.error(f"写入文件时出错: {str(e)}")
214
+
215
+ class CompositeDisplay:
216
+ """复合显示界面,可以同时使用多种显示方式"""
217
+
218
+ def __init__(self, displays: list = None):
219
+ """
220
+ 初始化复合显示界面
221
+
222
+ Args:
223
+ displays: 显示界面列表
224
+ """
225
+ self.displays = displays or []
226
+
227
+ logger.info("复合显示界面初始化完成")
228
+
229
+ def add_display(self, display):
230
+ """添加显示界面"""
231
+ self.displays.append(display)
232
+ logger.info("已添加显示界面")
233
+
234
+ def start(self, **kwargs):
235
+ """启动所有显示界面"""
236
+ for display in self.displays:
237
+ try:
238
+ if hasattr(display, 'start'):
239
+ if isinstance(display, FileDisplay):
240
+ display.start(**kwargs)
241
+ else:
242
+ display.start()
243
+ except Exception as e:
244
+ logger.error(f"启动显示界面时出错: {str(e)}")
245
+
246
+ logger.info("所有显示界面已启动")
247
+
248
+ def stop(self):
249
+ """停止所有显示界面"""
250
+ for display in self.displays:
251
+ try:
252
+ if hasattr(display, 'stop'):
253
+ display.stop()
254
+ except Exception as e:
255
+ logger.error(f"停止显示界面时出错: {str(e)}")
256
+
257
+ logger.info("所有显示界面已停止")
258
+
259
+ def add_message(self, message: str):
260
+ """添加消息到所有显示界面"""
261
+ for display in self.displays:
262
+ try:
263
+ if hasattr(display, 'add_message'):
264
+ display.add_message(message)
265
+ except Exception as e:
266
+ logger.error(f"添加消息到显示界面时出错: {str(e)}")
267
+
268
+ class DisplayManager:
269
+ """显示管理器,统一管理所有显示界面"""
270
+
271
+ def __init__(self, display_type: str = "simple"):
272
+ """
273
+ 初始化显示管理器
274
+
275
+ Args:
276
+ display_type: 显示类型 ('simple', 'console', 'file', 'composite')
277
+ """
278
+ self.display_type = display_type
279
+ self.display = None
280
+
281
+ # 根据类型创建显示界面
282
+ if display_type == "simple":
283
+ self.display = SimpleDisplay()
284
+ elif display_type == "console":
285
+ self.display = ConsoleDisplay()
286
+ elif display_type == "file":
287
+ self.display = FileDisplay()
288
+ elif display_type == "composite":
289
+ # 复合显示,包含简单显示和文件显示
290
+ simple_display = SimpleDisplay()
291
+ file_display = FileDisplay()
292
+ self.display = CompositeDisplay([simple_display, file_display])
293
+ else:
294
+ logger.warning(f"不支持的显示类型: {display_type},使用简单显示")
295
+ self.display = SimpleDisplay()
296
+
297
+ logger.info(f"显示管理器初始化完成,显示类型: {display_type}")
298
+
299
+ def start(self, **kwargs):
300
+ """启动显示界面"""
301
+ if self.display:
302
+ self.display.start(**kwargs)
303
+
304
+ def stop(self):
305
+ """停止显示界面"""
306
+ if self.display:
307
+ self.display.stop()
308
+
309
+ def add_message(self, message: str):
310
+ """添加消息到显示界面"""
311
+ if self.display:
312
+ self.display.add_message(message)
313
+
314
+ # 测试代码
315
+ if __name__ == "__main__":
316
+ # 测试简单��示
317
+ print("=== 测试简单显示 ===")
318
+ simple_display = DisplayManager("simple")
319
+ simple_display.start()
320
+
321
+ for i in range(5):
322
+ simple_display.add_message(f"简单显示消息 {i}\n")
323
+ time.sleep(0.5)
324
+
325
+ simple_display.stop()
326
+
327
+ # 测试文件显示
328
+ print("\n=== 测试文件显示 ===")
329
+ file_display = DisplayManager("file")
330
+ file_display.start(debate_id="test_debate")
331
+
332
+ for i in range(5):
333
+ file_display.add_message(f"文件显示消息 {i}\n")
334
+ time.sleep(0.5)
335
+
336
+ file_display.stop()
337
+
338
+ # 测试复合显示
339
+ print("\n=== 测试复合显示 ===")
340
+ composite_display = DisplayManager("composite")
341
+ composite_display.start(debate_id="test_composite")
342
+
343
+ for i in range(5):
344
+ composite_display.add_message(f"复合显示消息 {i}\n")
345
+ time.sleep(0.5)
346
+
347
+ composite_display.stop()
src/旧文件/5_GUI测试.py ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ GUI测试程序
4
+ 用于验证流式输出功能
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import threading
10
+ import logging
11
+ from tkinter import *
12
+ from tkinter import ttk, scrolledtext, messagebox
13
+ from tkinter.filedialog import asksaveasfilename
14
+ import importlib.util
15
+
16
+ # 在代码开头强制设置终端编码为UTF-8
17
+ os.system('chcp 65001 > nul')
18
+
19
+ # 获取当前脚本文件所在目录的绝对路径
20
+ current_script_dir = os.path.dirname(os.path.abspath(__file__))
21
+
22
+ # 项目根目录 (即 '20250907_大模型辩论' 目录, 是 'src' 的上一级)
23
+ project_root = os.path.dirname(current_script_dir)
24
+
25
+ # 定义数据输入、输出和日志目录
26
+ DATA_DIR = os.path.join(project_root, 'data')
27
+ OUTPUT_DIR = os.path.join(project_root, 'output')
28
+ LOGS_DIR = os.path.join(project_root, 'logs')
29
+
30
+ # 确保目录存在
31
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
32
+ os.makedirs(LOGS_DIR, exist_ok=True)
33
+
34
+ # 配置日志
35
+ logging.basicConfig(
36
+ level=logging.INFO,
37
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
38
+ handlers=[
39
+ logging.FileHandler(os.path.join(LOGS_DIR, 'GUI测试日志.log'), encoding='utf-8'),
40
+ logging.StreamHandler(sys.stdout)
41
+ ]
42
+ )
43
+ logger = logging.getLogger(__name__)
44
+
45
+ # 捕获警告并记录到日志
46
+ logging.captureWarnings(True)
47
+
48
+ # 导入自定义模块
49
+ try:
50
+ # 动态导入模型接口模块
51
+ model_interface_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "2_模型接口.py")
52
+ model_interface_spec = importlib.util.spec_from_file_location("model_interface", model_interface_path)
53
+ model_interface = importlib.util.module_from_spec(model_interface_spec)
54
+ model_interface_spec.loader.exec_module(model_interface)
55
+
56
+ # 从模块中获取需要的类
57
+ ModelManager = model_interface.ModelManager
58
+
59
+ except ImportError as e:
60
+ logger.error(f"导入模块失败: {str(e)}")
61
+ print("请确保所有模块文件都在同一目录下")
62
+ sys.exit(1)
63
+
64
+ class StreamOutputGUI:
65
+ """流式输出GUI界面"""
66
+
67
+ def __init__(self, root):
68
+ self.root = root
69
+ self.root.title("AI大模型流式输出测试")
70
+ self.root.geometry("800x600")
71
+
72
+ # API密钥
73
+ self.api_key = "ms-b4690538-3224-493a-8f5b-4073d527f788"
74
+
75
+ # 初始化模型管理器
76
+ self.model_manager = ModelManager(self.api_key)
77
+
78
+ # 当前使用的模型
79
+ self.current_model = "glm45"
80
+
81
+ # 是否正在生成
82
+ self.is_generating = False
83
+
84
+ # 创建界面
85
+ self.create_widgets()
86
+
87
+ logger.info("GUI界面初始化完成")
88
+
89
+ def create_widgets(self):
90
+ """创建界面组件"""
91
+ # 主框架
92
+ main_frame = ttk.Frame(self.root, padding="10")
93
+ main_frame.grid(row=0, column=0, sticky=(W, E, N, S))
94
+
95
+ # 配置网格权重
96
+ self.root.columnconfigure(0, weight=1)
97
+ self.root.rowconfigure(0, weight=1)
98
+ main_frame.columnconfigure(1, weight=1)
99
+ main_frame.rowconfigure(2, weight=1)
100
+
101
+ # 模型选择
102
+ ttk.Label(main_frame, text="选择模型:").grid(row=0, column=0, sticky=W, padx=(0, 10))
103
+ self.model_var = StringVar(value="glm45")
104
+ model_combo = ttk.Combobox(main_frame, textvariable=self.model_var,
105
+ values=["glm45", "deepseek_v31"], state="readonly", width=15)
106
+ model_combo.grid(row=0, column=1, sticky=W, padx=(0, 10))
107
+ model_combo.bind("<<ComboboxSelected>>", self.on_model_change)
108
+
109
+ # 输入框
110
+ ttk.Label(main_frame, text="输入提示:").grid(row=1, column=0, sticky=W, padx=(0, 10), pady=(10, 0))
111
+ self.input_text = scrolledtext.ScrolledText(main_frame, height=3, width=60)
112
+ self.input_text.grid(row=1, column=1, columnspan=2, sticky=(W, E), pady=(10, 0))
113
+ self.input_text.insert("1.0", "请用100字介绍一下人工智能的发展历程")
114
+
115
+ # 控制按钮框架
116
+ button_frame = ttk.Frame(main_frame)
117
+ button_frame.grid(row=2, column=0, columnspan=3, sticky=(W, E), pady=(10, 0))
118
+
119
+ # 开始按钮
120
+ self.start_button = ttk.Button(button_frame, text="开始生成", command=self.start_generation)
121
+ self.start_button.pack(side=LEFT, padx=(0, 10))
122
+
123
+ # 停止按钮
124
+ self.stop_button = ttk.Button(button_frame, text="停止生成", command=self.stop_generation, state=DISABLED)
125
+ self.stop_button.pack(side=LEFT, padx=(0, 10))
126
+
127
+ # 清空按钮
128
+ ttk.Button(button_frame, text="清空输出", command=self.clear_output).pack(side=LEFT, padx=(0, 10))
129
+
130
+ # 保存按钮
131
+ ttk.Button(button_frame, text="保存输出", command=self.save_output).pack(side=LEFT, padx=(0, 10))
132
+
133
+ # 输出框
134
+ ttk.Label(main_frame, text="输出结果:").grid(row=3, column=0, sticky=W, padx=(0, 10), pady=(10, 0))
135
+ self.output_text = scrolledtext.ScrolledText(main_frame, height=20, width=80)
136
+ self.output_text.grid(row=4, column=0, columnspan=3, sticky=(W, E, N, S), pady=(5, 0))
137
+
138
+ # 状态栏
139
+ self.status_var = StringVar(value="就绪")
140
+ status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=SUNKEN, anchor=W)
141
+ status_bar.grid(row=5, column=0, columnspan=3, sticky=(W, E), pady=(10, 0))
142
+
143
+ def on_model_change(self, event=None):
144
+ """模型选择改变事件"""
145
+ self.current_model = self.model_var.get()
146
+ logger.info(f"切换模型为: {self.current_model}")
147
+ self.status_var.set(f"已选择模型: {self.current_model}")
148
+
149
+ def start_generation(self):
150
+ """开始生成"""
151
+ if self.is_generating:
152
+ return
153
+
154
+ # 获取输入文本
155
+ prompt = self.input_text.get("1.0", END).strip()
156
+ if not prompt:
157
+ messagebox.showwarning("警告", "请输入提示文本")
158
+ return
159
+
160
+ # 更新界面状态
161
+ self.is_generating = True
162
+ self.start_button.config(state=DISABLED)
163
+ self.stop_button.config(state=NORMAL)
164
+ self.status_var.set("正在生成...")
165
+
166
+ # 清空输出框
167
+ self.output_text.delete("1.0", END)
168
+
169
+ # 在新线程中执行生成任务
170
+ thread = threading.Thread(target=self.generate_content, args=(prompt,))
171
+ thread.daemon = True
172
+ thread.start()
173
+
174
+ logger.info(f"开始生成内容,模型: {self.current_model},提示: {prompt}")
175
+
176
+ def stop_generation(self):
177
+ """停止生成"""
178
+ self.is_generating = False
179
+ self.start_button.config(state=NORMAL)
180
+ self.stop_button.config(state=DISABLED)
181
+ self.status_var.set("已停止生成")
182
+ logger.info("停止生成")
183
+
184
+ def generate_content(self, prompt):
185
+ """生成内容"""
186
+ try:
187
+ # 获取模型接口
188
+ model = self.model_manager.get_model(self.current_model)
189
+
190
+ # 构造消息
191
+ messages = [{"role": "user", "content": prompt}]
192
+
193
+ # 定义流式输出回调函数
194
+ def stream_callback(content):
195
+ if self.is_generating:
196
+ # 在主线程中更新界面
197
+ self.root.after(0, self.append_output, content)
198
+ else:
199
+ # 如果已停止生成,则中断
200
+ return False
201
+
202
+ # 调用流式输出方法
203
+ full_response = model.chat_stream(messages, stream_callback)
204
+
205
+ # 生成完成后更新界面
206
+ self.root.after(0, self.generation_completed)
207
+
208
+ logger.info(f"生成完成,响应长度: {len(full_response)} 字符")
209
+
210
+ except Exception as e:
211
+ logger.error(f"生成内容时出错: {str(e)}")
212
+ self.root.after(0, self.generation_error, str(e))
213
+
214
+ def append_output(self, content):
215
+ """追加输出内容"""
216
+ if self.is_generating:
217
+ self.output_text.insert(END, content)
218
+ self.output_text.see(END) # 滚动到末尾
219
+
220
+ def generation_completed(self):
221
+ """生成完成"""
222
+ self.is_generating = False
223
+ self.start_button.config(state=NORMAL)
224
+ self.stop_button.config(state=DISABLED)
225
+ self.status_var.set("生成完成")
226
+ logger.info("生成完成")
227
+
228
+ def generation_error(self, error_msg):
229
+ """生成出错"""
230
+ self.is_generating = False
231
+ self.start_button.config(state=NORMAL)
232
+ self.stop_button.config(state=DISABLED)
233
+ self.status_var.set("生成出错")
234
+ self.output_text.insert(END, f"\n[错误] {error_msg}\n")
235
+ logger.error(f"生成出错: {error_msg}")
236
+ messagebox.showerror("错误", f"生成内容时出错:\n{error_msg}")
237
+
238
+ def clear_output(self):
239
+ """清空输出"""
240
+ self.output_text.delete("1.0", END)
241
+ self.status_var.set("输出已清空")
242
+ logger.info("清空输出")
243
+
244
+ def save_output(self):
245
+ """保存输出"""
246
+ content = self.output_text.get("1.0", END)
247
+ if not content.strip():
248
+ messagebox.showinfo("提示", "没有内容可保存")
249
+ return
250
+
251
+ try:
252
+ filename = asksaveasfilename(
253
+ defaultextension=".txt",
254
+ filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")],
255
+ title="保存输出内容"
256
+ )
257
+ if filename:
258
+ with open(filename, "w", encoding="utf-8") as f:
259
+ f.write(content)
260
+ self.status_var.set(f"内容已保存到: {filename}")
261
+ logger.info(f"内容已保存到: {filename}")
262
+ except Exception as e:
263
+ logger.error(f"保存文件时出错: {str(e)}")
264
+ messagebox.showerror("错误", f"保存文件时出错:\n{str(e)}")
265
+
266
+ def main():
267
+ """主函数"""
268
+ # 创建根窗口
269
+ root = Tk()
270
+
271
+ # 创建GUI应用
272
+ app = StreamOutputGUI(root)
273
+
274
+ # 设置窗口关闭事件
275
+ def on_closing():
276
+ if app.is_generating:
277
+ if messagebox.askokcancel("确认", "正在生成内容,确定要退出吗?"):
278
+ app.stop_generation()
279
+ root.destroy()
280
+ else:
281
+ root.destroy()
282
+
283
+ root.protocol("WM_DELETE_WINDOW", on_closing)
284
+
285
+ # 运行主循环
286
+ root.mainloop()
287
+
288
+ if __name__ == "__main__":
289
+ main()
src/旧文件/6_辩论系统GUI.py ADDED
@@ -0,0 +1,732 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ AI大模型辩论系统GUI版本
4
+ 提供图形用户界面的辩论系统
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import threading
10
+ import time
11
+ import json
12
+ import logging
13
+ from datetime import datetime
14
+ from tkinter import *
15
+ from tkinter import ttk, scrolledtext, messagebox, filedialog
16
+ import importlib.util
17
+
18
+ # 在代码开头强制设置终端编码为UTF-8
19
+ os.system('chcp 65001 > nul')
20
+
21
+ # 获取当前脚本文件所在目录的绝对路径
22
+ current_script_dir = os.path.dirname(os.path.abspath(__file__))
23
+
24
+ # 项目根目录 (即 '20250907_大模型辩论' 目录, 是 'src' 的上一级)
25
+ project_root = os.path.dirname(current_script_dir)
26
+
27
+ # 定义数据输入、输出和日志目录
28
+ DATA_DIR = os.path.join(project_root, 'data')
29
+ OUTPUT_DIR = os.path.join(project_root, 'output')
30
+ LOGS_DIR = os.path.join(project_root, 'logs')
31
+
32
+ # 确保目录存在
33
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
34
+ os.makedirs(LOGS_DIR, exist_ok=True)
35
+
36
+ # 配置日志
37
+ logging.basicConfig(
38
+ level=logging.INFO,
39
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
40
+ handlers=[
41
+ logging.FileHandler(os.path.join(LOGS_DIR, '辩论系统GUI日志.log'), encoding='utf-8'),
42
+ logging.StreamHandler(sys.stdout)
43
+ ]
44
+ )
45
+ logger = logging.getLogger(__name__)
46
+
47
+ # 捕获警告并记录到日志
48
+ logging.captureWarnings(True)
49
+
50
+ # 导入自定义模块
51
+ try:
52
+ # 动态导入模型接口模块
53
+ model_interface_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "2_模型接口.py")
54
+ model_interface_spec = importlib.util.spec_from_file_location("model_interface", model_interface_path)
55
+ model_interface = importlib.util.module_from_spec(model_interface_spec)
56
+ model_interface_spec.loader.exec_module(model_interface)
57
+
58
+ # 动态导入辩论控制器模块
59
+ debate_controller_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "3_辩论控制器.py")
60
+ debate_controller_spec = importlib.util.spec_from_file_location("debate_controller", debate_controller_path)
61
+ debate_controller = importlib.util.module_from_spec(debate_controller_spec)
62
+ debate_controller_spec.loader.exec_module(debate_controller)
63
+
64
+ # 从模块中获取需要的类
65
+ ModelManager = model_interface.ModelManager
66
+ DebateMessage = debate_controller.DebateMessage
67
+ DebateSession = debate_controller.DebateSession
68
+
69
+ except ImportError as e:
70
+ logger.error(f"导入模块失败: {str(e)}")
71
+ messagebox.showerror("错误", f"导入模块失败: {str(e)}\n请确保所有模块文件都在同一目录下")
72
+ sys.exit(1)
73
+
74
+ class DebateSystemGUI:
75
+ """AI大模型辩论系统GUI版本"""
76
+
77
+ def __init__(self, root):
78
+ self.root = root
79
+ self.root.title("AI大模型辩论系统")
80
+ self.root.geometry("1000x700")
81
+ self.root.minsize(800, 600)
82
+
83
+ # API密钥
84
+ self.api_key = "ms-b4690538-3224-493a-8f5b-4073d527f788"
85
+
86
+ # 初始化模型管理器
87
+ self.model_manager = ModelManager(self.api_key)
88
+
89
+ # 辩论会话
90
+ self.debate_session = None
91
+
92
+ # 是否正在辩论
93
+ self.is_debating = False
94
+
95
+ # 暂停标志
96
+ self.is_paused = False
97
+
98
+ # 创建界面
99
+ self.create_widgets()
100
+
101
+ logger.info("AI大模型辩论系统GUI初始化完成")
102
+
103
+ def create_widgets(self):
104
+ """创建界面组件"""
105
+ # 创建菜单栏
106
+ self.create_menu()
107
+
108
+ # 主框架
109
+ main_frame = ttk.Frame(self.root, padding="10")
110
+ main_frame.pack(fill=BOTH, expand=True)
111
+
112
+ # 配置网格权重
113
+ main_frame.columnconfigure(1, weight=1)
114
+ main_frame.rowconfigure(0, weight=0)
115
+ main_frame.rowconfigure(1, weight=1)
116
+
117
+ # 参数设置框架
118
+ settings_frame = ttk.LabelFrame(main_frame, text="辩论参数设置", padding="10")
119
+ settings_frame.grid(row=0, column=0, columnspan=2, sticky=(W, E), pady=(0, 10))
120
+ settings_frame.columnconfigure(1, weight=1)
121
+ settings_frame.columnconfigure(3, weight=1)
122
+
123
+ # 辩论话题
124
+ ttk.Label(settings_frame, text="辩论话题:").grid(row=0, column=0, sticky=W, padx=(0, 5))
125
+ self.topic_var = StringVar(value="人工智能是否会取代人类的工作")
126
+ self.topic_entry = ttk.Entry(settings_frame, textvariable=self.topic_var, width=50)
127
+ self.topic_entry.grid(row=0, column=1, columnspan=3, sticky=(W, E), padx=(0, 10))
128
+
129
+ # 辩论轮数
130
+ ttk.Label(settings_frame, text="辩论轮数:").grid(row=1, column=0, sticky=W, padx=(0, 5), pady=(5, 0))
131
+ self.rounds_var = StringVar(value="5")
132
+ self.rounds_entry = ttk.Entry(settings_frame, textvariable=self.rounds_var, width=10)
133
+ self.rounds_entry.grid(row=1, column=1, sticky=W, padx=(0, 10), pady=(5, 0))
134
+
135
+ # 首发模型
136
+ ttk.Label(settings_frame, text="首发模型:").grid(row=1, column=2, sticky=W, padx=(0, 5), pady=(5, 0))
137
+ self.first_model_var = StringVar(value="glm45")
138
+ first_model_combo = ttk.Combobox(settings_frame, textvariable=self.first_model_var,
139
+ values=["glm45", "deepseek_v31"], state="readonly", width=15)
140
+ first_model_combo.grid(row=1, column=3, sticky=W, pady=(5, 0))
141
+
142
+ # 控制按钮框架
143
+ control_frame = ttk.Frame(settings_frame)
144
+ control_frame.grid(row=2, column=0, columnspan=4, sticky=(W, E), pady=(10, 0))
145
+
146
+ # 开始辩论按钮
147
+ self.start_button = ttk.Button(control_frame, text="开始辩论", command=self.start_debate)
148
+ self.start_button.pack(side=LEFT, padx=(0, 10))
149
+
150
+ # 暂停/继续按钮
151
+ self.pause_button = ttk.Button(control_frame, text="暂停辩论", command=self.toggle_pause, state=DISABLED)
152
+ self.pause_button.pack(side=LEFT, padx=(0, 10))
153
+
154
+ # 停止辩论按钮
155
+ self.stop_button = ttk.Button(control_frame, text="停止辩论", command=self.stop_debate, state=DISABLED)
156
+ self.stop_button.pack(side=LEFT, padx=(0, 10))
157
+
158
+ # 清空输出按钮
159
+ ttk.Button(control_frame, text="清空输出", command=self.clear_output).pack(side=LEFT, padx=(0, 10))
160
+
161
+ # 辩论内容显示框架
162
+ debate_frame = ttk.LabelFrame(main_frame, text="辩论内容", padding="10")
163
+ debate_frame.grid(row=1, column=0, columnspan=2, sticky=(W, E, N, S), pady=(0, 10))
164
+ debate_frame.columnconfigure(0, weight=1)
165
+ debate_frame.rowconfigure(0, weight=1)
166
+
167
+ # 辩论内容显示区域
168
+ self.debate_text = scrolledtext.ScrolledText(debate_frame, wrap=WORD, state=DISABLED)
169
+ self.debate_text.grid(row=0, column=0, sticky=(W, E, N, S))
170
+
171
+ # 状态栏
172
+ self.status_var = StringVar(value="就绪")
173
+ status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=SUNKEN, anchor=W)
174
+ status_bar.grid(row=2, column=0, columnspan=2, sticky=(W, E))
175
+
176
+ def create_menu(self):
177
+ """创建菜单栏"""
178
+ menubar = Menu(self.root)
179
+ self.root.config(menu=menubar)
180
+
181
+ # 文件菜单
182
+ file_menu = Menu(menubar, tearoff=0)
183
+ menubar.add_cascade(label="文件", menu=file_menu)
184
+ file_menu.add_command(label="保存辩论记录", command=self.save_debate_record)
185
+ file_menu.add_command(label="查看历史记录", command=self.view_history)
186
+ file_menu.add_separator()
187
+ file_menu.add_command(label="退出", command=self.on_closing)
188
+
189
+ # 设置菜单
190
+ settings_menu = Menu(menubar, tearoff=0)
191
+ menubar.add_cascade(label="设置", menu=settings_menu)
192
+ settings_menu.add_command(label="API密钥设置", command=self.set_api_key)
193
+
194
+ # 帮助菜单
195
+ help_menu = Menu(menubar, tearoff=0)
196
+ menubar.add_cascade(label="帮助", menu=help_menu)
197
+ help_menu.add_command(label="关于", command=self.show_about)
198
+
199
+ def start_debate(self):
200
+ """开始辩论"""
201
+ if self.is_debating:
202
+ return
203
+
204
+ # 获取参数
205
+ topic = self.topic_var.get().strip()
206
+ if not topic:
207
+ messagebox.showwarning("警告", "请输入辩论话题")
208
+ return
209
+
210
+ try:
211
+ rounds = int(self.rounds_var.get())
212
+ if rounds < 1 or rounds > 10:
213
+ messagebox.showwarning("警告", "辩论轮数必须在1-10之间")
214
+ return
215
+ except ValueError:
216
+ messagebox.showwarning("警告", "请输入有效的辩论轮数")
217
+ return
218
+
219
+ first_model = self.first_model_var.get()
220
+
221
+ # 更新界面状态
222
+ self.is_debating = True
223
+ self.is_paused = False
224
+ self.start_button.config(state=DISABLED)
225
+ self.pause_button.config(state=NORMAL, text="暂停辩论")
226
+ self.stop_button.config(state=NORMAL)
227
+ self.topic_entry.config(state=DISABLED)
228
+ self.rounds_entry.config(state=DISABLED)
229
+ self.first_model_var.set(first_model)
230
+
231
+ # 清空输出
232
+ self.clear_output()
233
+
234
+ # 显示辩论开始信息
235
+ self.append_output("=" * 50 + "\n")
236
+ self.append_output("AI大模型辩论系统\n")
237
+ self.append_output("=" * 50 + "\n\n")
238
+ self.append_output(f"辩论话题: {topic}\n")
239
+ self.append_output(f"辩论轮数: {rounds}\n")
240
+ self.append_output(f"首发模型: {first_model}\n")
241
+ self.append_output("\n辩论即将开始...\n\n")
242
+
243
+ # 更新状态栏
244
+ self.status_var.set("正在辩论中...")
245
+
246
+ # 在新线程中执行辩论任务
247
+ thread = threading.Thread(target=self.run_debate, args=(topic, rounds, first_model))
248
+ thread.daemon = True
249
+ thread.start()
250
+
251
+ logger.info(f"开始辩论,���题: {topic},轮数: {rounds},首发模型: {first_model}")
252
+
253
+ def run_debate(self, topic, rounds, first_model):
254
+ """运行辩论"""
255
+ try:
256
+ # 创建辩论会话
257
+ self.debate_session = DebateSession(topic, rounds, first_model)
258
+
259
+ # 确定模型顺序
260
+ model_a_name = first_model
261
+ model_b_name = 'deepseek_v31' if model_a_name == 'glm45' else 'glm45'
262
+
263
+ # 获取模型接口
264
+ model_a = self.model_manager.get_model(model_a_name)
265
+ model_b = self.model_manager.get_model(model_b_name)
266
+
267
+ # 构建初始提示
268
+ initial_prompt_a = f"你将作为正方,就以下话题进行辩论:{topic}。请提出你的主要论点。"
269
+ initial_prompt_b = f"你将作为反方,就以下话题进行辩论:{topic}。请针对对方的论点进行反驳。"
270
+
271
+ # 更新状态
272
+ self.debate_session.current_round = 1
273
+ self.debate_session.start_time = datetime.now()
274
+
275
+ # 显示开始信息
276
+ self.root.after(0, self.append_output, f"=== 辩论开始 ===\n话题: {topic}\n")
277
+ self.root.after(0, self.append_output, f"--- 第1轮 ---\n")
278
+
279
+ # 第一轮:模型A提出论点
280
+ if not self.is_debating:
281
+ return
282
+
283
+ # 等待暂停
284
+ while self.is_paused and self.is_debating:
285
+ time.sleep(0.1)
286
+
287
+ if not self.is_debating:
288
+ return
289
+
290
+ # 模型A发言
291
+ self.root.after(0, self.append_output, f"{model_a_name} (正方): ")
292
+ response_a = self.get_model_response(model_a, [{"role": "user", "content": initial_prompt_a}])
293
+ self.root.after(0, self.append_output, f"{response_a}\n\n")
294
+
295
+ # 记录消息
296
+ self.debate_session.add_message(DebateMessage("user", initial_prompt_a, "system"))
297
+ self.debate_session.add_message(DebateMessage("assistant", response_a, model_a_name))
298
+
299
+ # 后续轮次
300
+ for round_num in range(2, rounds + 1):
301
+ # 检查是否继续
302
+ if not self.is_debating:
303
+ break
304
+
305
+ # 等待暂停
306
+ while self.is_paused and self.is_debating:
307
+ time.sleep(0.1)
308
+
309
+ if not self.is_debating:
310
+ break
311
+
312
+ self.debate_session.current_round = round_num
313
+ self.root.after(0, self.append_output, f"--- 第{round_num}轮 ---\n")
314
+
315
+ # 模型B反驳
316
+ self.root.after(0, self.append_output, f"{model_b_name} (反方): ")
317
+ prompt_b = f"{initial_prompt_b}\n\n对方的论点: {response_a}\n\n请进行反驳。"
318
+ response_b = self.get_model_response(model_b,
319
+ self.debate_session.get_messages_for_model(model_b_name) +
320
+ [{"role": "user", "content": prompt_b}])
321
+ self.root.after(0, self.append_output, f"{response_b}\n\n")
322
+
323
+ # 记录消息
324
+ self.debate_session.add_message(DebateMessage("user", prompt_b, "system"))
325
+ self.debate_session.add_message(DebateMessage("assistant", response_b, model_b_name))
326
+
327
+ # 检查是否继续
328
+ if not self.is_debating:
329
+ break
330
+
331
+ # 等待暂停
332
+ while self.is_paused and self.is_debating:
333
+ time.sleep(0.1)
334
+
335
+ if not self.is_debating:
336
+ break
337
+
338
+ # 模型A回应
339
+ self.root.after(0, self.append_output, f"{model_a_name} (正方): ")
340
+ prompt_a = f"请针对对方的反驳进行回应:{response_b}"
341
+ response_a = self.get_model_response(model_a,
342
+ self.debate_session.get_messages_for_model(model_a_name) +
343
+ [{"role": "user", "content": prompt_a}])
344
+ self.root.after(0, self.append_output, f"{response_a}\n\n")
345
+
346
+ # 记录消息
347
+ self.debate_session.add_message(DebateMessage("user", prompt_a, "system"))
348
+ self.debate_session.add_message(DebateMessage("assistant", response_a, model_a_name))
349
+
350
+ # 辩论结束
351
+ if self.is_debating:
352
+ self.debate_session.end_time = datetime.now()
353
+ self.debate_session.is_active = False
354
+ self.root.after(0, self.append_output, "=== 辩论结束 ===\n")
355
+ self.root.after(0, self.debate_completed)
356
+
357
+ # 保存辩论记录
358
+ self.save_debate_record_to_file()
359
+
360
+ logger.info("辩论结束")
361
+
362
+ except Exception as e:
363
+ logger.error(f"辩论过程中出错: {str(e)}")
364
+ self.root.after(0, self.debate_error, str(e))
365
+
366
+ def get_model_response(self, model, messages):
367
+ """获取模型响应"""
368
+ try:
369
+ # 使用列表来存储流式输出的内容(避免作用域问题)
370
+ full_response = []
371
+
372
+ def stream_callback(content):
373
+ if self.is_debating and not self.is_paused:
374
+ self.root.after(0, self.append_output, content)
375
+ full_response.append(content)
376
+ # 注意:这里不需要return False,因为模型接口会处理流式输出的中断
377
+
378
+ # 调用流式输出方法
379
+ model.chat_stream(messages, stream_callback)
380
+ return "".join(full_response)
381
+ except Exception as e:
382
+ logger.error(f"获取模型响应时出错: {str(e)}")
383
+ return f"[错误] {str(e)}"
384
+
385
+ def toggle_pause(self):
386
+ """切换暂停状态"""
387
+ if not self.is_debating:
388
+ return
389
+
390
+ self.is_paused = not self.is_paused
391
+ if self.is_paused:
392
+ self.pause_button.config(text="继续辩论")
393
+ self.status_var.set("辩论已暂停")
394
+ logger.info("辩论已暂停")
395
+ else:
396
+ self.pause_button.config(text="暂停辩论")
397
+ self.status_var.set("正在辩论中...")
398
+ logger.info("辩论已继续")
399
+
400
+ def stop_debate(self):
401
+ """停止辩论"""
402
+ self.is_debating = False
403
+ self.is_paused = False
404
+ self.start_button.config(state=NORMAL)
405
+ self.pause_button.config(state=DISABLED, text="暂停辩论")
406
+ self.stop_button.config(state=DISABLED)
407
+ self.topic_entry.config(state=NORMAL)
408
+ self.rounds_entry.config(state=NORMAL)
409
+ self.status_var.set("辩论已停止")
410
+ self.append_output("\n用户手动停止辩论\n")
411
+ logger.info("辩论已停止")
412
+
413
+ def debate_completed(self):
414
+ """辩论完成"""
415
+ self.is_debating = False
416
+ self.is_paused = False
417
+ self.start_button.config(state=NORMAL)
418
+ self.pause_button.config(state=DISABLED, text="暂停辩论")
419
+ self.stop_button.config(state=DISABLED)
420
+ self.topic_entry.config(state=NORMAL)
421
+ self.rounds_entry.config(state=NORMAL)
422
+ self.status_var.set("辩论已完成")
423
+ logger.info("辩论已完成")
424
+
425
+ def debate_error(self, error_msg):
426
+ """辩论出错"""
427
+ self.is_debating = False
428
+ self.is_paused = False
429
+ self.start_button.config(state=NORMAL)
430
+ self.pause_button.config(state=DISABLED, text="暂停辩论")
431
+ self.stop_button.config(state=DISABLED)
432
+ self.topic_entry.config(state=NORMAL)
433
+ self.rounds_entry.config(state=NORMAL)
434
+ self.status_var.set("辩论出错")
435
+ self.append_output(f"\n[错误] {error_msg}\n")
436
+ logger.error(f"辩论出错: {error_msg}")
437
+ messagebox.showerror("错误", f"辩论过程中出错:\n{error_msg}")
438
+
439
+ def append_output(self, content):
440
+ """追加输出内容"""
441
+ self.debate_text.config(state=NORMAL)
442
+ self.debate_text.insert(END, content)
443
+ self.debate_text.see(END) # 滚动到末尾
444
+ self.debate_text.config(state=DISABLED)
445
+
446
+ def clear_output(self):
447
+ """清空输出"""
448
+ self.debate_text.config(state=NORMAL)
449
+ self.debate_text.delete("1.0", END)
450
+ self.debate_text.config(state=DISABLED)
451
+ logger.info("清空输出")
452
+
453
+ def save_debate_record_to_file(self):
454
+ """保存辩论记录到文件"""
455
+ if not self.debate_session:
456
+ return
457
+
458
+ try:
459
+ # 创建输出文件路径
460
+ output_dir = os.path.join(OUTPUT_DIR, "辩论记录")
461
+ os.makedirs(output_dir, exist_ok=True)
462
+
463
+ file_path = os.path.join(output_dir, f"{self.debate_session.debate_id}.json")
464
+
465
+ # 保存辩论记录
466
+ self.debate_session.save_to_file(file_path)
467
+
468
+ logger.info(f"辩论记录已保存: {file_path}")
469
+ except Exception as e:
470
+ logger.error(f"保存辩论记录时出错: {str(e)}")
471
+
472
+ def save_debate_record(self):
473
+ """保存当前辩论记录"""
474
+ if not self.debate_session:
475
+ messagebox.showinfo("提示", "当前没有辩论记录可保存")
476
+ return
477
+
478
+ try:
479
+ # 选择保存位置
480
+ filename = filedialog.asksaveasfilename(
481
+ defaultextension=".json",
482
+ filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")],
483
+ title="��存辩论记录"
484
+ )
485
+
486
+ if filename:
487
+ # 保存辩论记录
488
+ self.debate_session.save_to_file(filename)
489
+ self.status_var.set(f"辩论记录已保存到: {filename}")
490
+ logger.info(f"辩论记录已保存到: {filename}")
491
+ messagebox.showinfo("成功", f"辩论记录已保存到:\n{filename}")
492
+ except Exception as e:
493
+ logger.error(f"保存辩论记录时出错: {str(e)}")
494
+ messagebox.showerror("错误", f"保存辩论记录时出错:\n{str(e)}")
495
+
496
+ def view_history(self):
497
+ """查看历史记录"""
498
+ try:
499
+ # 获取辩论记录目录
500
+ debate_records_dir = os.path.join(OUTPUT_DIR, "辩论记录")
501
+
502
+ if not os.path.exists(debate_records_dir):
503
+ messagebox.showinfo("提示", "暂无辩论记录")
504
+ return
505
+
506
+ # 获取所有辩论记录文件
507
+ record_files = [f for f in os.listdir(debate_records_dir) if f.endswith('.json')]
508
+
509
+ if not record_files:
510
+ messagebox.showinfo("提示", "暂无辩论记录")
511
+ return
512
+
513
+ # 创建历史记录窗口
514
+ history_window = Toplevel(self.root)
515
+ history_window.title("历史辩论记录")
516
+ history_window.geometry("600x400")
517
+ history_window.minsize(400, 300)
518
+
519
+ # 创建列表框
520
+ list_frame = ttk.Frame(history_window, padding="10")
521
+ list_frame.pack(fill=BOTH, expand=True)
522
+
523
+ list_frame.columnconfigure(0, weight=1)
524
+ list_frame.rowconfigure(0, weight=1)
525
+
526
+ # 创建列表框和滚动条
527
+ listbox = Listbox(list_frame)
528
+ scrollbar = ttk.Scrollbar(list_frame, orient=VERTICAL, command=listbox.yview)
529
+ listbox.configure(yscrollcommand=scrollbar.set)
530
+
531
+ listbox.grid(row=0, column=0, sticky=(W, E, N, S))
532
+ scrollbar.grid(row=0, column=1, sticky=(N, S))
533
+
534
+ # 添加记录到列表框
535
+ for filename in record_files:
536
+ listbox.insert(END, filename)
537
+
538
+ # 创建按钮框架
539
+ button_frame = ttk.Frame(history_window, padding="10")
540
+ button_frame.pack(fill=X)
541
+
542
+ # 查看按钮
543
+ def view_selected():
544
+ selection = listbox.curselection()
545
+ if selection:
546
+ filename = listbox.get(selection[0])
547
+ file_path = os.path.join(debate_records_dir, filename)
548
+ self.display_debate_record(file_path)
549
+ history_window.destroy()
550
+ else:
551
+ messagebox.showwarning("警告", "请选择一个记录文件")
552
+
553
+ ttk.Button(button_frame, text="查看", command=view_selected).pack(side=LEFT, padx=(0, 10))
554
+
555
+ # 删除按钮
556
+ def delete_selected():
557
+ selection = listbox.curselection()
558
+ if selection:
559
+ filename = listbox.get(selection[0])
560
+ if messagebox.askyesno("确认", f"确定要删除记录文件 {filename} 吗?"):
561
+ file_path = os.path.join(debate_records_dir, filename)
562
+ try:
563
+ os.remove(file_path)
564
+ listbox.delete(selection[0])
565
+ self.status_var.set(f"已删除记录文件: {filename}")
566
+ logger.info(f"已删除记录文件: {filename}")
567
+ except Exception as e:
568
+ logger.error(f"删除记录文件时出错: {str(e)}")
569
+ messagebox.showerror("错误", f"删除记录文件时出错:\n{str(e)}")
570
+ else:
571
+ messagebox.showwarning("警告", "请选择一个记录文件")
572
+
573
+ ttk.Button(button_frame, text="删除", command=delete_selected).pack(side=LEFT, padx=(0, 10))
574
+
575
+ # 关闭按钮
576
+ ttk.Button(button_frame, text="关闭", command=history_window.destroy).pack(side=LEFT)
577
+
578
+ except Exception as e:
579
+ logger.error(f"查看历史记录时出错: {str(e)}")
580
+ messagebox.showerror("错误", f"查看历史记录时出错:\n{str(e)}")
581
+
582
+ def display_debate_record(self, file_path):
583
+ """显示辩论记录"""
584
+ try:
585
+ with open(file_path, 'r', encoding='utf-8') as f:
586
+ data = json.load(f)
587
+
588
+ debate_info = data['debate_info']
589
+ messages = data['messages']
590
+
591
+ # 创建显示窗口
592
+ record_window = Toplevel(self.root)
593
+ record_window.title(f"辩论记录 - {debate_info['debate_id']}")
594
+ record_window.geometry("800x600")
595
+ record_window.minsize(600, 400)
596
+
597
+ # 创建文本框和滚动条
598
+ text_frame = ttk.Frame(record_window, padding="10")
599
+ text_frame.pack(fill=BOTH, expand=True)
600
+
601
+ text_frame.columnconfigure(0, weight=1)
602
+ text_frame.rowconfigure(0, weight=1)
603
+
604
+ text_area = scrolledtext.ScrolledText(text_frame, wrap=WORD, state=DISABLED)
605
+ text_area.grid(row=0, column=0, sticky=(W, E, N, S))
606
+
607
+ # 构建显示内容
608
+ content = f"=== 辩论记录详情 ===\n"
609
+ content += f"辩论ID: {debate_info['debate_id']}\n"
610
+ content += f"话题: {debate_info['topic']}\n"
611
+ content += f"最大轮数: {debate_info['max_rounds']}\n"
612
+ content += f"实际轮数: {debate_info['current_round']}\n"
613
+ content += f"总消息数: {debate_info['total_messages']}\n"
614
+
615
+ if debate_info['start_time']:
616
+ start_time = datetime.fromisoformat(debate_info['start_time'])
617
+ content += f"开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}\n"
618
+
619
+ if debate_info['end_time']:
620
+ end_time = datetime.fromisoformat(debate_info['end_time'])
621
+ content += f"结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}\n"
622
+
623
+ if debate_info['duration']:
624
+ duration = int(debate_info['duration'])
625
+ minutes, seconds = divmod(duration, 60)
626
+ content += f"持续时间: {minutes}分{seconds}秒\n"
627
+
628
+ content += "\n=== 辩论内容 ===\n\n"
629
+
630
+ for msg in messages:
631
+ timestamp = datetime.fromisoformat(msg['timestamp'])
632
+ content += f"[{timestamp.strftime('%H:%M:%S')}] {msg['model_name']}: {msg['content']}\n\n"
633
+
634
+ # 显示内容
635
+ text_area.config(state=NORMAL)
636
+ text_area.insert("1.0", content)
637
+ text_area.config(state=DISABLED)
638
+
639
+ except Exception as e:
640
+ logger.error(f"显示辩论记录时出错: {str(e)}")
641
+ messagebox.showerror("错误", f"显示辩论记录时出错:\n{str(e)}")
642
+
643
+ def set_api_key(self):
644
+ """设置API密钥"""
645
+ # 创建设置窗口
646
+ settings_window = Toplevel(self.root)
647
+ settings_window.title("API密钥设置")
648
+ settings_window.geometry("400x150")
649
+ settings_window.resizable(False, False)
650
+
651
+ # 创建输入框
652
+ frame = ttk.Frame(settings_window, padding="20")
653
+ frame.pack(fill=BOTH, expand=True)
654
+
655
+ ttk.Label(frame, text="API密钥:").grid(row=0, column=0, sticky=W, pady=(0, 10))
656
+
657
+ api_key_var = StringVar(value=self.api_key)
658
+ api_key_entry = ttk.Entry(frame, textvariable=api_key_var, width=40, show="*")
659
+ api_key_entry.grid(row=1, column=0, columnspan=2, sticky=(W, E), pady=(0, 20))
660
+
661
+ # 按钮框架
662
+ button_frame = ttk.Frame(frame)
663
+ button_frame.grid(row=2, column=0, columnspan=2, sticky=E)
664
+
665
+ def save_api_key():
666
+ new_api_key = api_key_var.get().strip()
667
+ if new_api_key:
668
+ self.api_key = new_api_key
669
+ # 重新初始化模型管理器
670
+ self.model_manager = ModelManager(self.api_key)
671
+ self.status_var.set("API密钥已更新")
672
+ logger.info("API密钥已更新")
673
+ settings_window.destroy()
674
+ else:
675
+ messagebox.showwarning("警告", "请输入有效的API密钥")
676
+
677
+ ttk.Button(button_frame, text="保存", command=save_api_key).pack(side=LEFT, padx=(0, 10))
678
+ ttk.Button(button_frame, text="取消", command=settings_window.destroy).pack(side=LEFT)
679
+
680
+ def show_about(self):
681
+ """显示关于信息"""
682
+ about_text = """AI大模型辩论系统 GUI版本
683
+
684
+ 版本: 1.0
685
+ 作者: AI开发者
686
+ 日期: 2025年9月
687
+
688
+ 本系统支持以下功能:
689
+ - 两个AI大模型实时辩论
690
+ - 流式输出显示
691
+ - 图形用户界面
692
+ - 辩论记录保存和查看
693
+ - 暂停/继续/停止控制
694
+
695
+ 支持的模型:
696
+ - ZhipuAI/GLM-4.5
697
+ - deepseek-ai/DeepSeek-V3.1
698
+ """
699
+ messagebox.showinfo("关于", about_text)
700
+
701
+ def on_closing(self):
702
+ """窗口关闭事件"""
703
+ if self.is_debating:
704
+ if messagebox.askokcancel("确认", "正在辩论中,确定要退出吗?"):
705
+ self.is_debating = False
706
+ self.root.destroy()
707
+ else:
708
+ self.root.destroy()
709
+
710
+ def main():
711
+ """主函数"""
712
+ # 创建根窗口
713
+ root = Tk()
714
+
715
+ # 设置窗口图标(如果有的话)
716
+ try:
717
+ # root.iconbitmap('icon.ico') # 如果有图标文件可以取消注释
718
+ pass
719
+ except:
720
+ pass
721
+
722
+ # 创建GUI应用
723
+ app = DebateSystemGUI(root)
724
+
725
+ # 设置窗口关闭事件
726
+ root.protocol("WM_DELETE_WINDOW", app.on_closing)
727
+
728
+ # 运行主循环
729
+ root.mainloop()
730
+
731
+ if __name__ == "__main__":
732
+ main()
templates/index.html ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="zh-CN">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>AI大模型辩论系统</title>
8
+ <style>
9
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; background-color: #f0f2f5; color: #333; height: 100vh; display: flex; flex-direction: column; }
10
+ .container { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); width: 95%; max-width: 1400px; margin: 20px auto; flex-grow: 1; display: flex; flex-direction: column; }
11
+ .main-layout { display: flex; gap: 20px; flex-grow: 1; min-height: 0; }
12
+ .sidebar { flex: 0 0 320px; display: flex; flex-direction: column; gap: 15px; }
13
+ .chat-area { flex: 1; display: flex; flex-direction: column; min-width: 0; }
14
+ .header { text-align: center; margin-bottom: 20px; flex-shrink: 0; }
15
+ .header h1 { color: #1a73e8; }
16
+ .controls, .control-group { margin-bottom: 20px; }
17
+ .controls { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
18
+ .control-group { flex: 1; min-width: 200px; }
19
+ label { display: block; margin-bottom: 5px; font-weight: 600; color: #555; }
20
+ input, select, button, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; box-sizing: border-box; font-size: 16px; }
21
+ textarea { resize: vertical; }
22
+ button { background-color: #1a73e8; color: white; border: none; cursor: pointer; transition: background-color 0.3s; }
23
+ button:hover { background-color: #1558b8; }
24
+ button:disabled { background-color: #ccc; cursor: not-allowed; }
25
+ .output-container { background-color: #f8f9fa; border: 1px solid #ddd; border-radius: 8px; padding: 20px; height: 500px; overflow-y: auto; display: flex; flex-direction: column; gap: 15px; }
26
+ .status { padding: 10px; border-radius: 6px; margin-bottom: 15px; border: 1px solid; }
27
+ .status.connected { background-color: #e6f4ea; color: #155724; border-color: #c3e6cb; }
28
+ .status.disconnected { background-color: #f8d7da; color: #721c24; border-color: #f5c6cb; }
29
+
30
+ /* 聊天气泡样式 */
31
+ .message { display: flex; align-items: flex-start; gap: 10px; max-width: 80%; }
32
+ .message .avatar { width: 40px; height: 40px; border-radius: 50%; color: white; display: flex; align-items: center; justify-content: center; font-weight: bold; flex-shrink: 0; font-size: 18px; }
33
+ .message .content { background-color: #ffffff; padding: 10px 15px; border-radius: 18px; box-shadow: 0 1px 2px rgba(0,0,0,0.1); }
34
+ .message .sender { font-weight: bold; margin-bottom: 5px; color: #333; }
35
+ .message.glm45 { align-self: flex-start; }
36
+ .message.glm45 .avatar { background-color: #34a853; } /* Google Green */
37
+ .message.glm45 .content { border-top-left-radius: 4px; }
38
+ .message.deepseek_v31 { align-self: flex-end; flex-direction: row-reverse; }
39
+ .message.deepseek_v31 .avatar { background-color: #4285f4; } /* Google Blue */
40
+ .message.deepseek_v31 .content { background-color: #e7f3ff; border-top-right-radius: 4px; }
41
+ .message .text { white-space: pre-wrap; word-wrap: break-word; }
42
+ .message .text p { margin: 0 0 10px; }
43
+ .message .text h1, .message .text h2, .message .text h3 { margin: 15px 0 10px; border-bottom: 1px solid #eee; padding-bottom: 5px; }
44
+ .message .text ul, .message .text ol { padding-left: 20px; }
45
+ .message .text code { background-color: #eee; padding: 2px 4px; border-radius: 4px; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; }
46
+ .message .text pre { background-color: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 6px; overflow-x: auto; }
47
+ .message .text pre code { background-color: transparent; padding: 0; }
48
+ .round-separator { text-align: center; color: #888; font-size: 0.9em; margin: 20px 0; font-weight: 600; }
49
+
50
+ /* 响应式设计 - 针对手机等小屏幕设备 */
51
+ @media (max-width: 768px) {
52
+ body { padding: 0; }
53
+ .container { width: 100%; margin: 0; border-radius: 0; padding: 10px; height: 100%; }
54
+ .main-layout { flex-direction: column; }
55
+ .sidebar { flex: 0 0 auto; }
56
+ .header h1 { font-size: 1.5em; }
57
+ .controls { flex-direction: column; }
58
+ .control-group { min-width: unset; }
59
+ }
60
+ </style>
61
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
62
+ <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
63
+ </head>
64
+ <body>
65
+ <div class="container">
66
+ <div class="header"><h1>AI大模型辩论系统</h1><p>观看两个AI大模型实时辩论</p></div>
67
+
68
+ <div class="main-layout">
69
+ <div class="sidebar">
70
+ <div class="control-group"><label for="topic">辩论话题:</label><input type="text" id="topic" value="人工智能是否会取代人类的工作"></div>
71
+ <div class="control-group">
72
+ <label for="initialPrompt">自定义初始提示 (可选):</label>
73
+ <textarea id="initialPrompt" rows="6" placeholder="例如:请作为正方,用强势的语气开启辩论..."></textarea>
74
+ </div>
75
+ <div class="control-group"><label for="rounds">轮数:</label><input type="number" id="rounds" min="1" max="10" value="5"></div>
76
+ <div class="control-group"><label for="firstModel">首发模型:</label><select id="firstModel"><option value="glm45">ZhipuAI/GLM-4.5</option><option value="deepseek_v31">deepseek-ai/DeepSeek-V3.1</option></select></div>
77
+ <div class="controls">
78
+ <button id="startBtn" disabled>开始辩论</button>
79
+ <button id="stopBtn" disabled>停止辩论</button>
80
+ </div>
81
+ </div>
82
+ <div class="chat-area">
83
+ <label for="output">辩论实况:</label>
84
+ <div id="output" class="output-container"></div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ <script>
89
+ let websocket = null, isConnected = false, currentMessageElement = null, currentMessageContent = '';
90
+ const startBtn = document.getElementById('startBtn'), stopBtn = document.getElementById('stopBtn');
91
+ const outputDiv = document.getElementById('output');
92
+
93
+ function handleWebSocketMessage(data) {
94
+ let shouldScroll = Math.abs(outputDiv.scrollHeight - outputDiv.clientHeight - outputDiv.scrollTop) < 10;
95
+
96
+ switch (data.type) {
97
+ case 'debate_started':
98
+ outputDiv.innerHTML = '';
99
+ const topicDiv = document.createElement('div');
100
+ topicDiv.className = 'round-separator';
101
+ topicDiv.innerHTML = `<strong>话题:</strong> ${data.topic}`;
102
+ outputDiv.appendChild(topicDiv);
103
+ break;
104
+ case 'round_info':
105
+ const separator = document.createElement('div');
106
+ separator.className = 'round-separator';
107
+ separator.textContent = data.message.trim();
108
+ outputDiv.appendChild(separator);
109
+ break;
110
+ case 'model_speaking':
111
+ currentMessageContent = ''; // 重置当前消息内容
112
+ const messageDiv = document.createElement('div');
113
+ messageDiv.className = `message ${data.model}`;
114
+ messageDiv.innerHTML = `
115
+ <div class="avatar">${data.model.substring(0, 1).toUpperCase()}</div>
116
+ <div class="content">
117
+ <div class="sender">${data.model} (${data.role})</div>
118
+ <div class="text"><i>正在思考...</i></div>
119
+ </div>`;
120
+ outputDiv.appendChild(messageDiv);
121
+ currentMessageElement = messageDiv.querySelector('.text');
122
+ break;
123
+ case 'stream_content':
124
+ if (currentMessageElement) {
125
+ currentMessageContent += data.content;
126
+ currentMessageElement.innerHTML = DOMPurify.sanitize(marked.parse(currentMessageContent));
127
+ }
128
+ break;
129
+ case 'stream_end':
130
+ currentMessageElement = null;
131
+ break;
132
+ case 'debate_ended':
133
+ case 'debate_stopped':
134
+ const endMsg = document.createElement('div');
135
+ endMsg.className = 'round-separator';
136
+ endMsg.textContent = data.message;
137
+ outputDiv.appendChild(endMsg);
138
+ startBtn.disabled = false; stopBtn.disabled = true;
139
+ break;
140
+ case 'error':
141
+ outputDiv.innerHTML += `<div class="round-separator" style="color: red;">错误: ${data.message}</div>`;
142
+ break;
143
+ }
144
+ if(shouldScroll) {
145
+ outputDiv.scrollTop = outputDiv.scrollHeight;
146
+ }
147
+ }
148
+
149
+ function connect() {
150
+ const wsUrl = `ws://${window.location.host}/ws`;
151
+ websocket = new WebSocket(wsUrl);
152
+ websocket.onopen = () => { isConnected = true; startBtn.disabled = false; };
153
+ websocket.onmessage = (event) => handleWebSocketMessage(JSON.parse(event.data));
154
+ websocket.onclose = () => { isConnected = false; startBtn.disabled = true; stopBtn.disabled = true; };
155
+ websocket.onerror = (error) => { console.error('WebSocket Error:', error); };
156
+ }
157
+
158
+ window.addEventListener('load', connect);
159
+
160
+ startBtn.addEventListener('click', () => {
161
+ if (!websocket) return;
162
+ const message = {
163
+ action: "start_debate",
164
+ topic: document.getElementById('topic').value,
165
+ rounds: parseInt(document.getElementById('rounds').value),
166
+ first_model: document.getElementById('firstModel').value,
167
+ initial_prompt: document.getElementById('initialPrompt').value
168
+ };
169
+ websocket.send(JSON.stringify(message));
170
+ startBtn.disabled = true; stopBtn.disabled = false;
171
+ });
172
+ stopBtn.addEventListener('click', () => {
173
+ if (!websocket) return;
174
+ websocket.send(JSON.stringify({ action: "stop_debate" }));
175
+ });
176
+ </script>
177
+ </body>
178
+ </html>