ch862747537 commited on
Commit
3efffd7
·
1 Parent(s): 9305ec9

feat: Deploy to Hugging Face Space

Browse files
Dockerfile CHANGED
@@ -19,6 +19,7 @@ RUN pip install --no-cache-dir --upgrade -r requirements.txt
19
  # 将src目录下的所有代码复制到工作目录的src子目录中
20
  COPY --chown=user ./src /app/src
21
  COPY --chown=user ./templates /app/templates
 
22
 
23
  # 暴露端口,让Hugging Face平台可以访问我们的应用
24
  EXPOSE 7860
 
19
  # 将src目录下的所有代码复制到工作目录的src子目录中
20
  COPY --chown=user ./src /app/src
21
  COPY --chown=user ./templates /app/templates
22
+ COPY --chown=user ./static /app/static
23
 
24
  # 暴露端口,让Hugging Face平台可以访问我们的应用
25
  EXPOSE 7860
docs/修改记录.md CHANGED
@@ -26,7 +26,7 @@
26
  - 创建项目规划文档 (docs/项目规划.md)
27
  - 创建requirements.txt文件
28
  - 创建README.md文档
29
- - 创建修改记录文档 (docs/修改记录.md)
30
 
31
  ## 第2次修改
32
  - 创建更新计划文档 (docs/更新计划.md)
 
26
  - 创建项目规划文档 (docs/项目规划.md)
27
  - 创建requirements.txt文件
28
  - 创建README.md文档
29
+ - 创建修改记录文档 (docs/修改记录.md)
30
 
31
  ## 第2次修改
32
  - 创建更新计划文档 (docs/更新计划.md)
src/app.py CHANGED
@@ -13,6 +13,7 @@ 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')
@@ -100,6 +101,8 @@ 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):
@@ -139,18 +142,20 @@ async def websocket_endpoint(websocket: WebSocket):
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",
@@ -170,23 +175,19 @@ async def handle_websocket_message(websocket: WebSocket, message: str):
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)
@@ -198,9 +199,6 @@ async def start_debate(websocket: WebSocket, data: dict):
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
@@ -228,43 +226,43 @@ async def start_debate(websocket: WebSocket, data: dict):
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
  """保存辩论记录"""
@@ -280,67 +278,17 @@ def save_debate_record():
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>
@@ -368,106 +316,40 @@ def create_templates():
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)
 
13
  from typing import Optional
14
  import importlib.util
15
  import asyncio
16
+ import socket
17
 
18
  # 在代码开头强制设置终端编码为UTF-8
19
  os.system('chcp 6001 > nul')
 
101
  model_manager = ModelManager(api_key)
102
  active_debate_session = None
103
  active_websocket = None
104
+ active_debate_task = None
105
+
106
 
107
  @app.get("/", response_class=HTMLResponse)
108
  async def read_root(request: Request):
 
142
 
143
  async def handle_websocket_message(websocket: WebSocket, message: str):
144
  """处理WebSocket消息"""
145
+ global active_debate_task
146
  try:
147
  data = json.loads(message)
148
  action = data.get("action")
149
 
150
  if action == "start_debate":
151
+ if active_debate_task and not active_debate_task.done():
152
+ await websocket.send_text(json.dumps({
153
+ "type": "error", "message": "另一场辩论正在进行中,请等待其结束或停止它。"
154
+ }))
155
+ return
156
+ active_debate_task = asyncio.create_task(start_debate(websocket, data))
157
  elif action == "stop_debate":
158
  await stop_debate(websocket)
 
 
 
 
159
  else:
160
  await websocket.send_text(json.dumps({
161
  "type": "error",
 
175
 
176
  async def start_debate(websocket: WebSocket, data: dict):
177
  """开始辩论"""
178
+ global active_debate_session, active_debate_task
179
  loop = asyncio.get_event_loop()
180
 
181
  try:
182
  topic = data.get("topic", "人工智能是否会取代人类的工作")
183
  rounds = int(data.get("rounds", 5))
184
  first_model = data.get("first_model", "glm45")
185
+ initial_prompt = data.get("initial_prompt", "").strip()
186
 
187
  await websocket.send_text(json.dumps({ "type": "debate_started", "message": "辩论已开始", "topic": topic, "rounds": rounds, "first_model": first_model }))
188
 
189
+ active_debate_session = DebateSession(topic, rounds, first_model, initial_prompt)
190
 
 
 
 
 
191
  model_a_name = first_model
192
  model_b_name = 'deepseek_v31' if model_a_name == 'glm45' else 'glm45'
193
  model_a = model_manager.get_model(model_a_name)
 
199
 
200
  # Main debate loop
201
  for i in range(rounds * 2):
 
 
 
202
  round_num = (i // 2) + 1
203
  if i % 2 == 0:
204
  active_debate_session.current_round = round_num
 
226
  active_debate_session.add_message(DebateMessage("assistant", response_content, speaker_name))
227
  save_debate_record() # Incremental save
228
 
229
+ if active_debate_session and active_debate_session.is_active:
230
  active_debate_session.end_time = datetime.now()
231
  active_debate_session.is_active = False
232
  await websocket.send_text(json.dumps({ "type": "debate_ended", "message": "=== 辩论结束 ===" }))
233
  save_debate_record()
234
  logger.info("辩论结束")
235
 
236
+ except asyncio.CancelledError:
237
+ logger.info("辩论任务被取消。")
238
+ if active_debate_session:
239
+ active_debate_session.is_active = False
240
+ active_debate_session.end_time = datetime.now()
241
+ save_debate_record() # 保存部分辩论记录
242
+ await websocket.send_text(json.dumps({
243
+ "type": "debate_stopped",
244
+ "message": "辩论已由用户停止"
245
+ }))
246
+ raise # 重新引发CancelledError以确保任务状态正确
247
  except Exception as e:
248
+ logger.error(f"辩论过程中出错: {str(e)}", exc_info=True)
249
  await websocket.send_text(json.dumps({ "type": "error", "message": f"辩论过程中出错: {str(e)}" }))
250
+ finally:
251
+ active_debate_session = None
252
+ active_debate_task = None
253
+ logger.info("辩论会话清理完毕。")
254
+
255
 
256
  async def stop_debate(websocket: WebSocket):
257
  """停止辩论"""
258
+ global active_debate_task
259
+ if active_debate_task and not active_debate_task.done():
260
+ active_debate_task.cancel()
261
+ logger.info("发送取消请求到辩论任务。")
262
+ else:
263
+ logger.warning("请求停止辩论,但没有活动的任务。")
264
+ await websocket.send_text(json.dumps({"type": "info", "message": "没有正在进行的辩论可供停止。"}))
 
 
 
 
 
 
 
 
 
 
265
 
 
 
 
 
 
 
266
 
267
  def save_debate_record():
268
  """保存辩论记录"""
 
278
  logger.error(f"保存辩论记录时出错: {str(e)}")
279
 
280
  def create_templates():
281
+ """创建HTML模板文件,如果不存在的话"""
282
+ template_path = os.path.join(templates_dir, "index.html")
283
+ if not os.path.exists(template_path):
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
+ <link rel="stylesheet" href="{{ url_for('static', path='css/style.css') }}">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
293
  <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
294
  </head>
 
316
  </div>
317
  </div>
318
  </div>
319
+ <script src="{{ url_for('static', path='js/script.js') }}"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  </body>
321
  </html>"""
322
+ with open(template_path, "w", encoding="utf-8") as f:
323
+ f.write(index_html)
324
+ logger.info("Web应用模板文件 'index.html' 不存在,已创建。")
325
+ else:
326
+ logger.info("Web应用模板文件 'index.html' 已存在,跳过创建。")
327
+
328
+
329
+ def get_local_ip():
330
+ """获取本机局域网IP地址"""
331
+ try:
332
+ with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
333
+ # 连接到一个公共DNS服务器的IP(不会真的发送数据)
334
+ s.connect(("8.8.8.8", 80))
335
+ return s.getsockname()[0]
336
+ except Exception:
337
+ return "127.0.0.1" # 如果获取失败,返回本地回环地址
338
+
339
 
340
  if __name__ == "__main__":
341
  os.makedirs(static_dir, exist_ok=True)
342
  os.makedirs(templates_dir, exist_ok=True)
343
  create_templates()
344
+
345
  # 智能端口切换:优先使用环境变量PORT,否则默认为8000
346
  port = int(os.environ.get("PORT", 8000))
347
+ local_ip = get_local_ip()
348
+
349
+ logger.info("="*50)
350
+ logger.info("AI大模型辩论系统已启动")
351
+ logger.info(f" - 本机访问: http://localhost:{port}")
352
+ logger.info(f" - 局域网访问: http://{local_ip}:{port}")
353
+ logger.info("="*50)
354
+
355
  uvicorn.run(app, host="0.0.0.0", port=port)
src/debate_controller.py CHANGED
@@ -86,7 +86,7 @@ class DebateMessage:
86
  class DebateSession:
87
  """辩论会话类"""
88
 
89
- def __init__(self, topic: str, max_rounds: int = 5, first_model: str = 'glm45'):
90
  """
91
  初始化辩论会话
92
 
@@ -94,14 +94,15 @@ class DebateSession:
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')}"
@@ -141,7 +142,6 @@ class DebateSession:
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
@@ -175,7 +175,6 @@ class DebateSession:
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
 
@@ -200,7 +199,10 @@ class DebateSession:
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
@@ -228,7 +230,6 @@ class DebateController:
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
 
@@ -259,10 +260,8 @@ class DebateController:
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)
@@ -271,26 +270,6 @@ class DebateController:
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:
@@ -299,7 +278,6 @@ class DebateController:
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():
@@ -348,12 +326,6 @@ class DebateController:
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
 
 
86
  class DebateSession:
87
  """辩论会话类"""
88
 
89
+ def __init__(self, topic: str, max_rounds: int = 5, first_model: str = 'glm45', initial_prompt: str = ""):
90
  """
91
  初始化辩论会话
92
 
 
94
  topic: 辩论话题
95
  max_rounds: 最大轮数
96
  first_model: 首发模型 ('glm45' 或 'kimi_k2')
97
+ initial_prompt: 自定义初始提示
98
  """
99
  self.topic = topic
100
  self.max_rounds = max_rounds
101
  self.first_model = first_model
102
+ self.initial_prompt = initial_prompt
103
  self.messages: List[DebateMessage] = []
104
  self.current_round = 0
105
  self.is_active = True
 
106
  self.start_time = None
107
  self.end_time = None
108
  self.debate_id = f"debate_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
 
142
  'current_round': self.current_round,
143
  'total_messages': len(self.messages),
144
  'is_active': self.is_active,
 
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
 
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.start_time = datetime.fromisoformat(debate_info['start_time']) if debate_info['start_time'] else None
179
  session.end_time = datetime.fromisoformat(debate_info['end_time']) if debate_info['end_time'] else None
180
 
 
199
 
200
  if not self.messages:
201
  # 辩论开始,第一个发言者
202
+ base_prompt = f"你将作为{role},就以下话题进行辩论:{self.topic}。请提出你的主要论点,陈述你的核心立场和关键论据。"
203
+ if self.initial_prompt:
204
+ return f"{base_prompt}\n\n另外,请特别注意以下指示:{self.initial_prompt}"
205
+ return base_prompt
206
  else:
207
  last_message = self.messages[-1]
208
  opponent_statement = last_message.content
 
230
  self.current_session: Optional[DebateSession] = None
231
  self.debate_thread: Optional[threading.Thread] = None
232
  self.stop_event = threading.Event()
 
233
 
234
  logger.info("辩论控制器初始化完成")
235
 
 
260
  return
261
 
262
  self.current_session.is_active = True
 
263
  self.current_session.start_time = datetime.now()
264
  self.stop_event.clear()
 
265
 
266
  # 启动辩论线程
267
  self.debate_thread = threading.Thread(target=self._debate_loop)
 
270
 
271
  logger.info("辩论开始")
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  def stop_debate(self):
274
  """停止辩论"""
275
  if not self.current_session or not self.current_session.is_active:
 
278
 
279
  self.stop_event.set()
280
  self.current_session.is_active = False
 
281
  self.current_session.end_time = datetime.now()
282
 
283
  if self.debate_thread and self.debate_thread.is_alive():
 
326
 
327
  # 后续轮次
328
  while self._should_continue_debate(session):
 
 
 
 
 
 
329
  session.current_round += 1
330
  self._output_message(f"--- 第{session.current_round}轮 ---\n")
331
 
static/css/style.css ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 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; }
2
+ .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; }
3
+ .main-layout { display: flex; gap: 20px; flex-grow: 1; min-height: 0; }
4
+ .sidebar { flex: 0 0 320px; display: flex; flex-direction: column; gap: 15px; }
5
+ .chat-area { flex: 1; display: flex; flex-direction: column; min-width: 0; }
6
+ .header { text-align: center; margin-bottom: 20px; flex-shrink: 0; }
7
+ .header h1 { color: #1a73e8; }
8
+ .controls, .control-group { margin-bottom: 20px; }
9
+ .controls { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
10
+ .control-group { flex: 1; min-width: 200px; }
11
+ label { display: block; margin-bottom: 5px; font-weight: 600; color: #555; }
12
+ input, select, button, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; box-sizing: border-box; font-size: 16px; }
13
+ textarea { resize: vertical; }
14
+ button { background-color: #1a73e8; color: white; border: none; cursor: pointer; transition: background-color 0.3s; }
15
+ button:hover { background-color: #1558b8; }
16
+ button:disabled { background-color: #ccc; cursor: not-allowed; }
17
+ .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; }
18
+ .status { padding: 10px; border-radius: 6px; margin-bottom: 15px; border: 1px solid; }
19
+ .status.connected { background-color: #e6f4ea; color: #155724; border-color: #c3e6cb; }
20
+ .status.disconnected { background-color: #f8d7da; color: #721c24; border-color: #f5c6cb; }
21
+
22
+ /* 聊天气泡样式 */
23
+ .message { display: flex; align-items: flex-start; gap: 10px; max-width: 80%; }
24
+ .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; }
25
+ .message .content { background-color: #ffffff; padding: 10px 15px; border-radius: 18px; box-shadow: 0 1px 2px rgba(0,0,0,0.1); }
26
+ .message .sender { font-weight: bold; margin-bottom: 5px; color: #333; }
27
+ .message.glm45 { align-self: flex-start; }
28
+ .message.glm45 .avatar { background-color: #34a853; } /* Google Green */
29
+ .message.glm45 .content { border-top-left-radius: 4px; }
30
+ .message.deepseek_v31 { align-self: flex-end; flex-direction: row-reverse; }
31
+ .message.deepseek_v31 .avatar { background-color: #4285f4; } /* Google Blue */
32
+ .message.deepseek_v31 .content { background-color: #e7f3ff; border-top-right-radius: 4px; }
33
+ .message .text { white-space: pre-wrap; word-wrap: break-word; }
34
+ .message .text p { margin: 0 0 10px; }
35
+ .message .text h1, .message .text h2, .message .text h3 { margin: 15px 0 10px; border-bottom: 1px solid #eee; padding-bottom: 5px; }
36
+ .message .text ul, .message .text ol { padding-left: 20px; }
37
+ .message .text code { background-color: #eee; padding: 2px 4px; border-radius: 4px; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; }
38
+ .message .text pre { background-color: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 6px; overflow-x: auto; }
39
+ .message .text pre code { background-color: transparent; padding: 0; }
40
+ .round-separator { text-align: center; color: #888; font-size: 0.9em; margin: 20px 0; font-weight: 600; }
41
+
42
+ /* 响应式设计 - 针对手机等小屏幕设备 */
43
+ @media (max-width: 768px) {
44
+ body { padding: 0; }
45
+ .container { width: 100%; margin: 0; border-radius: 0; padding: 10px; height: 100%; }
46
+ .main-layout { flex-direction: column; }
47
+ .sidebar { flex: 0 0 auto; }
48
+ .header h1 { font-size: 1.5em; }
49
+ .controls { flex-direction: column; }
50
+ .control-group { min-width: unset; }
51
+ }
static/js/script.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let websocket = null, isConnected = false, currentMessageElement = null, currentMessageContent = '';
2
+ const startBtn = document.getElementById('startBtn'), stopBtn = document.getElementById('stopBtn');
3
+ const outputDiv = document.getElementById('output');
4
+
5
+ function handleWebSocketMessage(data) {
6
+ let shouldScroll = Math.abs(outputDiv.scrollHeight - outputDiv.clientHeight - outputDiv.scrollTop) < 10;
7
+
8
+ switch (data.type) {
9
+ case 'debate_started':
10
+ outputDiv.innerHTML = '';
11
+ const topicDiv = document.createElement('div');
12
+ topicDiv.className = 'round-separator';
13
+ topicDiv.innerHTML = `<strong>话题:</strong> ${data.topic}`;
14
+ outputDiv.appendChild(topicDiv);
15
+ break;
16
+ case 'round_info':
17
+ const separator = document.createElement('div');
18
+ separator.className = 'round-separator';
19
+ separator.textContent = data.message.trim();
20
+ outputDiv.appendChild(separator);
21
+ break;
22
+ case 'model_speaking':
23
+ currentMessageContent = ''; // 重置当前消息内容
24
+ const messageDiv = document.createElement('div');
25
+ messageDiv.className = `message ${data.model}`;
26
+ messageDiv.innerHTML = `
27
+ <div class="avatar">${data.model.substring(0, 1).toUpperCase()}</div>
28
+ <div class="content">
29
+ <div class="sender">${data.model} (${data.role})</div>
30
+ <div class="text"><i>正在思考...</i></div>
31
+ </div>`;
32
+ outputDiv.appendChild(messageDiv);
33
+ currentMessageElement = messageDiv.querySelector('.text');
34
+ break;
35
+ case 'stream_content':
36
+ if (currentMessageElement) {
37
+ currentMessageContent += data.content;
38
+ currentMessageElement.innerHTML = DOMPurify.sanitize(marked.parse(currentMessageContent));
39
+ }
40
+ break;
41
+ case 'stream_end':
42
+ currentMessageElement = null;
43
+ break;
44
+ case 'debate_ended':
45
+ case 'debate_stopped':
46
+ const endMsg = document.createElement('div');
47
+ endMsg.className = 'round-separator';
48
+ endMsg.textContent = data.message;
49
+ outputDiv.appendChild(endMsg);
50
+ startBtn.disabled = false; stopBtn.disabled = true;
51
+ break;
52
+ case 'error':
53
+ outputDiv.innerHTML += `<div class="round-separator" style="color: red;">错误: ${data.message}</div>`;
54
+ break;
55
+ }
56
+ if(shouldScroll) {
57
+ outputDiv.scrollTop = outputDiv.scrollHeight;
58
+ }
59
+ }
60
+
61
+ function connect() {
62
+ const wsUrl = `ws://${window.location.host}/ws`;
63
+ websocket = new WebSocket(wsUrl);
64
+ websocket.onopen = () => { isConnected = true; startBtn.disabled = false; };
65
+ websocket.onmessage = (event) => handleWebSocketMessage(JSON.parse(event.data));
66
+ websocket.onclose = () => { isConnected = false; startBtn.disabled = true; stopBtn.disabled = true; };
67
+ websocket.onerror = (error) => { console.error('WebSocket Error:', error); };
68
+ }
69
+
70
+ window.addEventListener('load', connect);
71
+
72
+ startBtn.addEventListener('click', () => {
73
+ if (!websocket) return;
74
+ const message = {
75
+ action: "start_debate",
76
+ topic: document.getElementById('topic').value,
77
+ rounds: parseInt(document.getElementById('rounds').value),
78
+ first_model: document.getElementById('firstModel').value,
79
+ initial_prompt: document.getElementById('initialPrompt').value
80
+ };
81
+ websocket.send(JSON.stringify(message));
82
+ startBtn.disabled = true; stopBtn.disabled = false;
83
+ });
84
+ stopBtn.addEventListener('click', () => {
85
+ if (!websocket) return;
86
+ websocket.send(JSON.stringify({ action: "stop_debate" }));
87
+ });
templates/index.html CHANGED
@@ -5,59 +5,7 @@
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>
@@ -85,94 +33,6 @@
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>
 
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>AI大模型辩论系统</title>
8
+ <link rel="stylesheet" href="{{ url_for('static', path='css/style.css') }}">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
10
  <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
11
  </head>
 
33
  </div>
34
  </div>
35
  </div>
36
+ <script src="{{ url_for('static', path='js/script.js') }}"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  </body>
38
  </html>