zjrwtx commited on
Commit
9dffbe6
·
1 Parent(s): d4ada08

update web log

Browse files
Files changed (2) hide show
  1. owl/nextwebapp.py +813 -0
  2. owl/webapp_zh.py +223 -0
owl/nextwebapp.py ADDED
@@ -0,0 +1,813 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Import from the correct module path
2
+ from owl.utils import run_society
3
+ import os
4
+ import gradio as gr
5
+ import time
6
+ import json
7
+ from typing import Tuple, List, Dict, Any
8
+ import importlib
9
+ from dotenv import load_dotenv, set_key, find_dotenv, unset_key
10
+
11
+ os.environ['PYTHONIOENCODING'] = 'utf-8'
12
+
13
+ # Enhanced CSS with navigation bar and additional styling
14
+ custom_css = """
15
+ :root {
16
+ --primary-color: #4a89dc;
17
+ --secondary-color: #5d9cec;
18
+ --accent-color: #7baaf7;
19
+ --light-bg: #f8f9fa;
20
+ --border-color: #e4e9f0;
21
+ --text-muted: #8a9aae;
22
+ }
23
+
24
+ .container {
25
+ max-width: 1200px;
26
+ margin: 0 auto;
27
+ }
28
+
29
+ .navbar {
30
+ display: flex;
31
+ justify-content: space-between;
32
+ align-items: center;
33
+ padding: 15px 30px;
34
+ background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
35
+ color: white;
36
+ border-radius: 10px 10px 0 0;
37
+ margin-bottom: 0;
38
+ box-shadow: 0 2px 10px rgba(74, 137, 220, 0.15);
39
+ }
40
+
41
+ .navbar-logo {
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 10px;
45
+ font-size: 1.5em;
46
+ font-weight: bold;
47
+ }
48
+
49
+ .navbar-menu {
50
+ display: flex;
51
+ gap: 20px;
52
+ }
53
+
54
+ /* Navbar styles moved to a more specific section below */
55
+
56
+ .header {
57
+ text-align: center;
58
+ margin-bottom: 20px;
59
+ background: linear-gradient(180deg, var(--secondary-color), var(--accent-color));
60
+ color: white;
61
+ padding: 40px 20px;
62
+ border-radius: 0 0 10px 10px;
63
+ box-shadow: 0 4px 6px rgba(93, 156, 236, 0.12);
64
+ }
65
+
66
+ .module-info {
67
+ background-color: var(--light-bg);
68
+ border-left: 5px solid var(--primary-color);
69
+ padding: 10px 15px;
70
+ margin-top: 10px;
71
+ border-radius: 5px;
72
+ font-size: 0.9em;
73
+ }
74
+
75
+ .answer-box {
76
+ background-color: var(--light-bg);
77
+ border-left: 5px solid var(--secondary-color);
78
+ padding: 15px;
79
+ margin-bottom: 20px;
80
+ border-radius: 5px;
81
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
82
+ }
83
+
84
+ .token-count {
85
+ background-color: #e9ecef;
86
+ padding: 10px;
87
+ border-radius: 5px;
88
+ text-align: center;
89
+ font-weight: bold;
90
+ margin-bottom: 20px;
91
+ }
92
+
93
+ .chat-container {
94
+ border: 1px solid var(--border-color);
95
+ border-radius: 5px;
96
+ max-height: 500px;
97
+ overflow-y: auto;
98
+ margin-bottom: 20px;
99
+ }
100
+
101
+ .footer {
102
+ text-align: center;
103
+ margin-top: 20px;
104
+ color: var(--text-muted);
105
+ font-size: 0.9em;
106
+ padding: 20px;
107
+ border-top: 1px solid var(--border-color);
108
+ }
109
+
110
+ .features-section {
111
+ display: grid;
112
+ grid-template-columns: repeat(3, 1fr);
113
+ gap: 20px;
114
+ margin: 20px 0;
115
+ }
116
+
117
+ @media (max-width: 1200px) {
118
+ .features-section {
119
+ grid-template-columns: repeat(2, 1fr);
120
+ }
121
+ }
122
+
123
+ @media (max-width: 768px) {
124
+ .features-section {
125
+ grid-template-columns: 1fr;
126
+ }
127
+ }
128
+
129
+ .feature-card {
130
+ background-color: white;
131
+ border-radius: 8px;
132
+ padding: 20px;
133
+ box-shadow: 0 2px 8px rgba(74, 137, 220, 0.08);
134
+ transition: transform 0.3s, box-shadow 0.3s;
135
+ height: 100%;
136
+ display: flex;
137
+ flex-direction: column;
138
+ border: 1px solid rgba(228, 233, 240, 0.6);
139
+ }
140
+
141
+ .feature-card:hover {
142
+ transform: translateY(-5px);
143
+ box-shadow: 0 5px 15px rgba(74, 137, 220, 0.15);
144
+ border-color: rgba(93, 156, 236, 0.3);
145
+ }
146
+
147
+ .feature-icon {
148
+ font-size: 2em;
149
+ color: var(--primary-color);
150
+ margin-bottom: 10px;
151
+ text-shadow: 0 1px 2px rgba(74, 137, 220, 0.1);
152
+ }
153
+
154
+ .feature-card h3 {
155
+ margin-top: 10px;
156
+ margin-bottom: 10px;
157
+ }
158
+
159
+ .feature-card p {
160
+ flex-grow: 1;
161
+ font-size: 0.95em;
162
+ line-height: 1.5;
163
+ }
164
+
165
+ /* Navbar link styles - ensuring consistent colors */
166
+ .navbar-menu a {
167
+ color: #ffffff !important;
168
+ text-decoration: none;
169
+ padding: 5px 10px;
170
+ border-radius: 5px;
171
+ transition: background-color 0.3s, color 0.3s;
172
+ font-weight: 500;
173
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
174
+ }
175
+
176
+ .navbar-menu a:hover {
177
+ background-color: rgba(255, 255, 255, 0.15);
178
+ color: #ffffff !important;
179
+ }
180
+
181
+ /* Improved button and input styles */
182
+ button.primary {
183
+ background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
184
+ transition: all 0.3s;
185
+ }
186
+
187
+ button.primary:hover {
188
+ background: linear-gradient(90deg, var(--secondary-color), var(--primary-color));
189
+ transform: translateY(-2px);
190
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
191
+ }
192
+
193
+ .env-section {
194
+ background-color: var(--light-bg);
195
+ border-radius: 8px;
196
+ padding: 20px;
197
+ margin: 20px 0;
198
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
199
+ }
200
+
201
+ .env-table {
202
+ width: 100%;
203
+ border-collapse: collapse;
204
+ margin-top: 15px;
205
+ }
206
+
207
+ .env-table th, .env-table td {
208
+ padding: 10px;
209
+ border: 1px solid var(--border-color);
210
+ }
211
+
212
+ .env-table th {
213
+ background-color: var(--primary-color);
214
+ color: white;
215
+ text-align: left;
216
+ }
217
+
218
+ .env-table tr:nth-child(even) {
219
+ background-color: rgba(0, 0, 0, 0.02);
220
+ }
221
+
222
+ .env-actions {
223
+ display: flex;
224
+ gap: 10px;
225
+ }
226
+
227
+ .env-var-input {
228
+ margin-bottom: 15px;
229
+ }
230
+
231
+ .env-save-status {
232
+ margin-top: 15px;
233
+ padding: 10px;
234
+ border-radius: 5px;
235
+ }
236
+
237
+ .success {
238
+ background-color: #d4edda;
239
+ color: #155724;
240
+ border: 1px solid #c3e6cb;
241
+ }
242
+
243
+ .error {
244
+ background-color: #f8d7da;
245
+ color: #721c24;
246
+ border: 1px solid #f5c6cb;
247
+ }
248
+ """
249
+
250
+ # Dictionary containing module descriptions
251
+ MODULE_DESCRIPTIONS = {
252
+ "run": "默认模式:使用OpenAI模型的默认的智能体协作模式,适合大多数任务。",
253
+ "run_mini":"使用使用OpenAI模型最小化配置处理任务",
254
+ "run_deepseek_zh":"使用deepseek模型处理中文任务",
255
+ "run_terminal_zh": "终端模式:可执行命令行操作,支持网络搜索、文件处理等功能。适合需要系统交互的任务,使用OpenAI模型",
256
+ "run_gaia_roleplaying":"GAIA基准测试实现,用于评估Agent能力",
257
+ "run_openai_compatiable_model":"使用openai兼容模型处理任务",
258
+ "run_ollama":"使用本地ollama模型处理任务",
259
+ "run_qwen_mini_zh":"使用qwen模型最小化配置处理任务",
260
+ "run_qwen_zh":"使用qwen模型处理任务",
261
+ }
262
+
263
+ # 默认环境变量模板
264
+ DEFAULT_ENV_TEMPLATE = """# MODEL & API (See https://docs.camel-ai.org/key_modules/models.html#)
265
+
266
+ # OPENAI API
267
+ # OPENAI_API_KEY= ""
268
+ # OPENAI_API_BASE_URL=""
269
+
270
+ # Qwen API (https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key)
271
+ # QWEN_API_KEY=""
272
+
273
+ # DeepSeek API (https://platform.deepseek.com/api_keys)
274
+ # DEEPSEEK_API_KEY=""
275
+
276
+ #===========================================
277
+ # Tools & Services API
278
+ #===========================================
279
+
280
+ # Google Search API (https://developers.google.com/custom-search/v1/overview)
281
+ GOOGLE_API_KEY=""
282
+ SEARCH_ENGINE_ID=""
283
+
284
+ # Hugging Face API (https://huggingface.co/join)
285
+ HF_TOKEN=""
286
+
287
+ # Chunkr API (https://chunkr.ai/)
288
+ CHUNKR_API_KEY=""
289
+
290
+ # Firecrawl API (https://www.firecrawl.dev/)
291
+ FIRECRAWL_API_KEY=""
292
+ #FIRECRAWL_API_URL="https://api.firecrawl.dev"
293
+ """
294
+
295
+ def format_chat_history(chat_history: List[Dict[str, str]]) -> List[List[str]]:
296
+ """将聊天历史格式化为Gradio聊天组件可接受的格式
297
+
298
+ Args:
299
+ chat_history: 原始聊天历史
300
+
301
+ Returns:
302
+ List[List[str]]: 格式化后的聊天历史
303
+ """
304
+ formatted_history = []
305
+ for message in chat_history:
306
+ user_msg = message.get("user", "")
307
+ assistant_msg = message.get("assistant", "")
308
+
309
+ if user_msg:
310
+ formatted_history.append([user_msg, None])
311
+ if assistant_msg and formatted_history:
312
+ formatted_history[-1][1] = assistant_msg
313
+ elif assistant_msg:
314
+ formatted_history.append([None, assistant_msg])
315
+
316
+ return formatted_history
317
+
318
+ def validate_input(question: str) -> bool:
319
+ """验证用户输入是否有效
320
+
321
+ Args:
322
+ question: 用户问题
323
+
324
+ Returns:
325
+ bool: 输入是否有效
326
+ """
327
+ # 检查输入是否为空或只包含空格
328
+ if not question or question.strip() == "":
329
+ return False
330
+ return True
331
+
332
+ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], str, str]:
333
+ """运行OWL系统并返回结果
334
+
335
+ Args:
336
+ question: 用户问题
337
+ example_module: 要导入的示例模块名(如 "run_terminal_zh" 或 "run_deep")
338
+
339
+ Returns:
340
+ Tuple[...]: 回答、聊天历史、令牌计数、状态
341
+ """
342
+ # 验证输入
343
+ if not validate_input(question):
344
+ return (
345
+ "请输入有效的问题",
346
+ [],
347
+ "0",
348
+ "❌ 错误: 输入无效"
349
+ )
350
+
351
+ try:
352
+ # 确保环境变量已加载
353
+ load_dotenv(find_dotenv(), override=True)
354
+ # 检查模块是否在MODULE_DESCRIPTIONS中
355
+ if example_module not in MODULE_DESCRIPTIONS:
356
+ return (
357
+ f"所选模块 '{example_module}' 不受支持",
358
+ [],
359
+ "0",
360
+ f"❌ 错误: 不支持的模块"
361
+ )
362
+
363
+ # 动态导入目标模块
364
+ module_path = f"owl.examples.{example_module}"
365
+ try:
366
+ module = importlib.import_module(module_path)
367
+ except ImportError as ie:
368
+ return (
369
+ f"无法导入模块: {module_path}",
370
+ [],
371
+ "0",
372
+ f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}"
373
+ )
374
+ except Exception as e:
375
+ return (
376
+ f"导入模块时发生错误: {module_path}",
377
+ [],
378
+ "0",
379
+ f"❌ 错误: {str(e)}"
380
+ )
381
+
382
+ # 检查是否包含construct_society函数
383
+ if not hasattr(module, "construct_society"):
384
+ return (
385
+ f"模块 {module_path} 中未找到 construct_society 函数",
386
+ [],
387
+ "0",
388
+ f"❌ 错误: 模块接口不兼容"
389
+ )
390
+
391
+ # 构建社会模��
392
+ try:
393
+ society = module.construct_society(question)
394
+ except Exception as e:
395
+ return (
396
+ f"构建社会模拟时发生错误: {str(e)}",
397
+ [],
398
+ "0",
399
+ f"❌ 错误: 构建失败 - {str(e)}"
400
+ )
401
+
402
+ # 运行社会模拟
403
+ try:
404
+ answer, chat_history, token_info = run_society(society)
405
+ except Exception as e:
406
+ return (
407
+ f"运行社会模拟时发生错误: {str(e)}",
408
+ [],
409
+ "0",
410
+ f"❌ 错误: 运行失败 - {str(e)}"
411
+ )
412
+
413
+ # 格式化聊天历史
414
+ try:
415
+ formatted_chat_history = format_chat_history(chat_history)
416
+ except Exception as e:
417
+ # 如果格式化失败,返回空历史记录但继续处理
418
+ formatted_chat_history = []
419
+
420
+ # 安全地获取令牌计数
421
+ if not isinstance(token_info, dict):
422
+ token_info = {}
423
+
424
+ completion_tokens = token_info.get("completion_token_count", 0)
425
+ prompt_tokens = token_info.get("prompt_token_count", 0)
426
+ total_tokens = completion_tokens + prompt_tokens
427
+
428
+ return (
429
+ answer,
430
+ formatted_chat_history,
431
+ f"完成令牌: {completion_tokens:,} | 提示令牌: {prompt_tokens:,} | 总计: {total_tokens:,}",
432
+ "✅ 成功完成"
433
+ )
434
+
435
+ except Exception as e:
436
+ return (
437
+ f"发生错误: {str(e)}",
438
+ [],
439
+ "0",
440
+ f"❌ 错误: {str(e)}"
441
+ )
442
+
443
+ def update_module_description(module_name: str) -> str:
444
+ """返回所选模块的描述"""
445
+ return MODULE_DESCRIPTIONS.get(module_name, "无可用描述")
446
+
447
+ # 环境变量管理功能
448
+ def init_env_file():
449
+ """初始化.env文件如果不存在"""
450
+ dotenv_path = find_dotenv()
451
+ if not dotenv_path:
452
+ with open(".env", "w") as f:
453
+ f.write(DEFAULT_ENV_TEMPLATE)
454
+ dotenv_path = find_dotenv()
455
+ return dotenv_path
456
+
457
+ def load_env_vars():
458
+ """加载环境变量并返回字典格式"""
459
+ dotenv_path = init_env_file()
460
+ load_dotenv(dotenv_path, override=True)
461
+
462
+ env_vars = {}
463
+ with open(dotenv_path, "r") as f:
464
+ for line in f:
465
+ line = line.strip()
466
+ if line and not line.startswith("#"):
467
+ if "=" in line:
468
+ key, value = line.split("=", 1)
469
+ env_vars[key.strip()] = value.strip().strip('"\'')
470
+
471
+ return env_vars
472
+
473
+ def save_env_vars(env_vars):
474
+ """保存环境变量到.env文件"""
475
+ try:
476
+ dotenv_path = init_env_file()
477
+
478
+ # 保存每个环境变量
479
+ for key, value in env_vars.items():
480
+ if key and key.strip(): # 确保键不为空
481
+ set_key(dotenv_path, key.strip(), value.strip())
482
+
483
+ # 重新加载环境变量以确保生效
484
+ load_dotenv(dotenv_path, override=True)
485
+
486
+ return True, "环境变量已成功保存!"
487
+ except Exception as e:
488
+ return False, f"保存环境变量时出错: {str(e)}"
489
+
490
+ def add_env_var(key, value):
491
+ """添加或更新单个环境变量"""
492
+ try:
493
+ if not key or not key.strip():
494
+ return False, "变量名不能为空"
495
+
496
+ dotenv_path = init_env_file()
497
+ set_key(dotenv_path, key.strip(), value.strip())
498
+ load_dotenv(dotenv_path, override=True)
499
+
500
+ return True, f"环境变量 {key} 已成功添加/更新!"
501
+ except Exception as e:
502
+ return False, f"添加环境变量时出错: {str(e)}"
503
+
504
+ def delete_env_var(key):
505
+ """删除环境变量"""
506
+ try:
507
+ if not key or not key.strip():
508
+ return False, "变量名不能为空"
509
+
510
+ dotenv_path = init_env_file()
511
+ unset_key(dotenv_path, key.strip())
512
+
513
+ # 从当前进程环境中也删除
514
+ if key in os.environ:
515
+ del os.environ[key]
516
+
517
+ return True, f"环境变量 {key} 已成功删除!"
518
+ except Exception as e:
519
+ return False, f"删除环境变量时出错: {str(e)}"
520
+
521
+ def mask_sensitive_value(key: str, value: str) -> str:
522
+ """对敏感信息进行掩码处理
523
+
524
+ Args:
525
+ key: 环境变量名
526
+ value: 环境变量值
527
+
528
+ Returns:
529
+ str: 处理后的值
530
+ """
531
+ # 定义需要掩码的敏感关键词
532
+ sensitive_keywords = ['key', 'token', 'secret', 'password', 'api']
533
+
534
+ # 检查是否包含敏感关键词(不区分大小写)
535
+ is_sensitive = any(keyword in key.lower() for keyword in sensitive_keywords)
536
+
537
+ if is_sensitive and value:
538
+ # 如果是敏感信息且有值,则显示掩码
539
+ return '*' * 8
540
+ return value
541
+
542
+ def update_env_table():
543
+ """更新环境变量表格显示,对敏感信息进行掩码处理"""
544
+ env_vars = load_env_vars()
545
+ # 对敏感值进行掩码处理
546
+ masked_env_vars = [[k, mask_sensitive_value(k, v)] for k, v in env_vars.items()]
547
+ return masked_env_vars
548
+
549
+ def create_ui():
550
+ """创建增强版Gradio界面"""
551
+ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="blue")) as app:
552
+ with gr.Column(elem_classes="container"):
553
+ gr.HTML("""
554
+ <div class="navbar">
555
+ <div class="navbar-logo">
556
+ 🦉 OWL 多智能体协作系统
557
+ </div>
558
+ <div class="navbar-menu">
559
+ <a href="#home">首页</a>
560
+ <a href="#env-settings">环境设置</a>
561
+ <a href="https://github.com/camel-ai/owl/blob/main/README.md#-community">加入交流群</a>
562
+ <a href="https://github.com/camel-ai/owl/blob/main/README.md">OWL文档</a>
563
+ <a href="https://github.com/camel-ai/camel">CAMEL框架</a>
564
+ <a href="https://camel-ai.org">CAMEL-AI官网</a>
565
+ </div>
566
+ </div>
567
+ <div class="header" id="home">
568
+
569
+ <p>我们的愿景是彻底改变AI代理协作解决现实世界任务的方式。通过利用动态代理交互,OWL能够在多个领域实现更自然、高效和稳健的任务自动化。</p>
570
+ </div>
571
+ """)
572
+
573
+ with gr.Row(elem_id="features"):
574
+ gr.HTML("""
575
+ <div class="features-section">
576
+ <div class="feature-card">
577
+ <div class="feature-icon">🔍</div>
578
+ <h3>实时信息检索</h3>
579
+ <p>利用维基百科、谷歌搜索和其他在线资源获取最新信息。</p>
580
+ </div>
581
+ <div class="feature-card">
582
+ <div class="feature-icon">📹</div>
583
+ <h3>多模态处理</h3>
584
+ <p>支持处理互联网或本地的视频、图像和音频数据。</p>
585
+ </div>
586
+ <div class="feature-card">
587
+ <div class="feature-icon">🌐</div>
588
+ <h3>浏览器自动化</h3>
589
+ <p>使用Playwright框架模拟浏览器交互,实现网页操作自动化。</p>
590
+ </div>
591
+ <div class="feature-card">
592
+ <div class="feature-icon">📄</div>
593
+ <h3>文档解析</h3>
594
+ <p>从各种文档格式中提取内容,并转换为易于处理的格式。</p>
595
+ </div>
596
+ <div class="feature-card">
597
+ <div class="feature-icon">💻</div>
598
+ <h3>代码执行</h3>
599
+ <p>使用解释器编写和运行Python代码,实现自动化数据处理。</p>
600
+ </div>
601
+ <div class="feature-card">
602
+ <div class="feature-icon">🧰</div>
603
+ <h3>内置工具包</h3>
604
+ <p>提供丰富的工具包,支持搜索、数据分析、代码执行等多种功能。</p>
605
+ </div>
606
+ <div class="feature-card">
607
+ <div class="feature-icon">🔑</div>
608
+ <h3>环境变量管理</h3>
609
+ <p>便捷管理API密钥和环境配置,安全存储敏感信息。</p>
610
+ </div>
611
+ </div>
612
+ """)
613
+
614
+ with gr.Row():
615
+ with gr.Column(scale=2):
616
+ question_input = gr.Textbox(
617
+ lines=5,
618
+ placeholder="请输入您的问题...",
619
+ label="问题",
620
+ elem_id="question_input",
621
+ show_copy_button=True,
622
+ )
623
+
624
+ # 增强版模块选择下拉菜单
625
+ # 只包含MODULE_DESCRIPTIONS中定义的模块
626
+ module_dropdown = gr.Dropdown(
627
+ choices=list(MODULE_DESCRIPTIONS.keys()),
628
+ value="run_terminal_zh",
629
+ label="选择功能模块",
630
+ interactive=True
631
+ )
632
+
633
+ # 模块描述文本框
634
+ module_description = gr.Textbox(
635
+ value=MODULE_DESCRIPTIONS["run_terminal_zh"],
636
+ label="模块描述",
637
+ interactive=False,
638
+ elem_classes="module-info"
639
+ )
640
+
641
+ run_button = gr.Button("运行", variant="primary", elem_classes="primary")
642
+
643
+ with gr.Column(scale=1):
644
+ gr.Markdown("""
645
+ ### 使用指南
646
+
647
+ 1. **选择适合的模块**:根据您的任务需求选择合适的功能模块
648
+ 2. **详细描述您的需求**:在输入框中清晰描述您的问题或任务
649
+ 3. **启动智能处理**:点击"运行"按钮开始多智能体协作处理
650
+ 4. **查看结果**:在下方标签页查看回答和完整对话历史
651
+
652
+ > **高级提示**: 对于复杂任务,可以尝试指定具体步骤和预期结果
653
+ """)
654
+
655
+ status_output = gr.Textbox(label="状态", interactive=False)
656
+
657
+ with gr.Tabs():
658
+ with gr.TabItem("回答"):
659
+ answer_output = gr.Textbox(
660
+ label="回答",
661
+ lines=10,
662
+ elem_classes="answer-box"
663
+ )
664
+
665
+ with gr.TabItem("对话历史"):
666
+ chat_output = gr.Chatbot(
667
+ label="完整对话记录",
668
+ elem_classes="chat-container",
669
+ height=500
670
+ )
671
+
672
+
673
+
674
+ token_count_output = gr.Textbox(
675
+ label="令牌计数",
676
+ interactive=False,
677
+ elem_classes="token-count"
678
+ )
679
+
680
+ # 示例问题
681
+ examples = [
682
+ "打开百度搜索,总结一下camel-ai的camel框架的github star、fork数目等,并把数字用plot包写成python文件保存到本地,用本地终端执行python文件显示图出来给我",
683
+ "请分析GitHub上CAMEL-AI项目的最新统计数据。找出该项目的星标数量、贡献者数量和最近的活跃度。",
684
+ "浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格",
685
+ "写一个hello world的python文件,保存到本地",
686
+
687
+ ]
688
+
689
+ gr.Examples(
690
+ examples=examples,
691
+ inputs=question_input
692
+ )
693
+ # 新增: 环境变量管理选项卡
694
+ with gr.TabItem("环境变量管理", id="env-settings"):
695
+ gr.Markdown("""
696
+ ## 环境变量管理
697
+
698
+ 在此处设置模型API密钥和其他服务凭证。这些信息将保存在本地的`.env`文件中,确保您的API密钥安全存储且不会上传到网络。
699
+ """)
700
+
701
+ # 环境变量表格
702
+ env_table = gr.Dataframe(
703
+ headers=["变量名", "值"],
704
+ datatype=["str", "str"],
705
+ row_count=10,
706
+ col_count=(2, "fixed"),
707
+ value=update_env_table,
708
+ label="当前环境变量",
709
+ interactive=False
710
+ )
711
+
712
+ with gr.Row():
713
+ with gr.Column(scale=1):
714
+ new_env_key = gr.Textbox(label="变量名", placeholder="例如: OPENAI_API_KEY")
715
+ with gr.Column(scale=2):
716
+ new_env_value = gr.Textbox(label="值", placeholder="输入API密钥或其他配置值")
717
+
718
+ with gr.Row():
719
+ add_env_button = gr.Button("添加/更新变量", variant="primary")
720
+ refresh_button = gr.Button("刷新变量列表")
721
+ delete_env_button = gr.Button("删除选定变量", variant="stop")
722
+
723
+ env_status = gr.Textbox(label="状态", interactive=False)
724
+
725
+ # 变量选择器(用于删除)
726
+ env_var_to_delete = gr.Dropdown(
727
+ choices=[],
728
+ label="选择要删除的变量",
729
+ interactive=True
730
+ )
731
+
732
+ # 更新变量选择器的选项
733
+ def update_delete_dropdown():
734
+ env_vars = load_env_vars()
735
+ return gr.Dropdown.update(choices=list(env_vars.keys()))
736
+
737
+ # 连接事件处理函数
738
+ add_env_button.click(
739
+ fn=lambda k, v: add_env_var(k, v),
740
+ inputs=[new_env_key, new_env_value],
741
+ outputs=[env_status]
742
+ ).then(
743
+ fn=update_env_table,
744
+ outputs=[env_table]
745
+ ).then(
746
+ fn=update_delete_dropdown,
747
+ outputs=[env_var_to_delete]
748
+ ).then(
749
+ fn=lambda: ("", ""), # 修改为返回两个空字符串的元组
750
+ outputs=[new_env_key, new_env_value]
751
+ )
752
+
753
+ refresh_button.click(
754
+ fn=update_env_table,
755
+ outputs=[env_table]
756
+ ).then(
757
+ fn=update_delete_dropdown,
758
+ outputs=[env_var_to_delete]
759
+ )
760
+
761
+ delete_env_button.click(
762
+ fn=lambda k: delete_env_var(k),
763
+ inputs=[env_var_to_delete],
764
+ outputs=[env_status]
765
+ ).then(
766
+ fn=update_env_table,
767
+ outputs=[env_table]
768
+ ).then(
769
+ fn=update_delete_dropdown,
770
+ outputs=[env_var_to_delete]
771
+ )
772
+
773
+
774
+
775
+ gr.HTML("""
776
+ <div class="footer" id="about">
777
+ <h3>关于 OWL 多智能体协作系统</h3>
778
+ <p>OWL 是一个基于CAMEL框架开发的先进多智能体协作系统,旨在通过智能体协作解决复杂问题。</p>
779
+ <p>© 2025 CAMEL-AI.org. 基于Apache License 2.0开源协议</p>
780
+ <p><a href="https://github.com/camel-ai/owl" target="_blank">GitHub</a></p>
781
+ </div>
782
+ """)
783
+
784
+ # 设置事件处理
785
+ run_button.click(
786
+ fn=run_owl,
787
+ inputs=[question_input, module_dropdown],
788
+ outputs=[answer_output, chat_output, token_count_output, status_output]
789
+ )
790
+
791
+ # 模块选择更新描述
792
+ module_dropdown.change(
793
+ fn=update_module_description,
794
+ inputs=module_dropdown,
795
+ outputs=module_description
796
+ )
797
+
798
+ return app
799
+
800
+ # 主函数
801
+ def main():
802
+ try:
803
+ # 初始化.env文件(如果不存在)
804
+ init_env_file()
805
+ app = create_ui()
806
+ app.launch(share=False)
807
+ except Exception as e:
808
+ print(f"启动应用程序时发生错误: {str(e)}")
809
+ import traceback
810
+ traceback.print_exc()
811
+
812
+ if __name__ == "__main__":
813
+ main()
owl/webapp_zh.py CHANGED
@@ -4,13 +4,131 @@ import os
4
  import gradio as gr
5
  import time
6
  import json
 
 
7
  from typing import Tuple, List, Dict, Any
8
  import importlib
9
  from dotenv import load_dotenv, set_key, find_dotenv, unset_key
 
 
 
10
 
11
  os.environ['PYTHONIOENCODING'] = 'utf-8'
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  # Dictionary containing module descriptions
16
  MODULE_DESCRIPTIONS = {
@@ -150,6 +268,7 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
150
  """
151
  # 验证输入
152
  if not validate_input(question):
 
153
  return (
154
  "请输入有效的问题",
155
  [],
@@ -160,8 +279,11 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
160
  try:
161
  # 确保环境变量已加载
162
  load_dotenv(find_dotenv(), override=True)
 
 
163
  # 检查模块是否在MODULE_DESCRIPTIONS中
164
  if example_module not in MODULE_DESCRIPTIONS:
 
165
  return (
166
  f"所选模块 '{example_module}' 不受支持",
167
  [],
@@ -172,8 +294,10 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
172
  # 动态导入目标模块
173
  module_path = f"owl.examples.{example_module}"
174
  try:
 
175
  module = importlib.import_module(module_path)
176
  except ImportError as ie:
 
177
  return (
178
  f"无法导入模块: {module_path}",
179
  [],
@@ -181,6 +305,7 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
181
  f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}"
182
  )
183
  except Exception as e:
 
184
  return (
185
  f"导入模块时发生错误: {module_path}",
186
  [],
@@ -190,6 +315,7 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
190
 
191
  # 检查是否包含construct_society函数
192
  if not hasattr(module, "construct_society"):
 
193
  return (
194
  f"模块 {module_path} 中未找到 construct_society 函数",
195
  [],
@@ -199,8 +325,10 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
199
 
200
  # 构建社会模拟
201
  try:
 
202
  society = module.construct_society(question)
203
  except Exception as e:
 
204
  return (
205
  f"构建社会模拟时发生错误: {str(e)}",
206
  [],
@@ -210,8 +338,11 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
210
 
211
  # 运行社会模拟
212
  try:
 
213
  answer, chat_history, token_info = run_society(society)
 
214
  except Exception as e:
 
215
  return (
216
  f"运行社会模拟时发生错误: {str(e)}",
217
  [],
@@ -224,6 +355,7 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
224
  formatted_chat_history = format_chat_history(chat_history)
225
  except Exception as e:
226
  # 如果格式化失败,返回空历史记录但继续处理
 
227
  formatted_chat_history = []
228
 
229
  # 安全地获取令牌计数
@@ -234,6 +366,8 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
234
  prompt_tokens = token_info.get("prompt_token_count", 0)
235
  total_tokens = completion_tokens + prompt_tokens
236
 
 
 
237
  return (
238
  answer,
239
  formatted_chat_history,
@@ -242,6 +376,7 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
242
  )
243
 
244
  except Exception as e:
 
245
  return (
246
  f"发生错误: {str(e)}",
247
  [],
@@ -357,6 +492,16 @@ def update_env_table():
357
 
358
  def create_ui():
359
  """创建增强版Gradio界面"""
 
 
 
 
 
 
 
 
 
 
360
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app:
361
 
362
 
@@ -413,6 +558,27 @@ def create_ui():
413
  height=500
414
  )
415
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  with gr.TabItem("环境变量管理", id="env-settings"):
417
  gr.Markdown("""
418
  ## 环境变量管理
@@ -540,6 +706,9 @@ def create_ui():
540
  fn=run_owl,
541
  inputs=[question_input, module_dropdown],
542
  outputs=[answer_output, chat_output, token_count_output, status_output]
 
 
 
543
  )
544
 
545
  # 模块选择更新描述
@@ -548,20 +717,74 @@ def create_ui():
548
  inputs=module_dropdown,
549
  outputs=module_description
550
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
 
552
  return app
553
 
554
  # 主函数
555
  def main():
556
  try:
 
 
 
 
 
 
 
 
 
 
557
  # 初始化.env文件(如果不存在)
558
  init_env_file()
559
  app = create_ui()
 
 
 
 
 
 
560
  app.launch(share=False)
561
  except Exception as e:
 
562
  print(f"启动应用程序时发生错误: {str(e)}")
563
  import traceback
564
  traceback.print_exc()
 
 
 
 
 
565
 
566
  if __name__ == "__main__":
567
  main()
 
4
  import gradio as gr
5
  import time
6
  import json
7
+ import logging
8
+ import datetime
9
  from typing import Tuple, List, Dict, Any
10
  import importlib
11
  from dotenv import load_dotenv, set_key, find_dotenv, unset_key
12
+ import threading
13
+ import queue
14
+ import time
15
 
16
  os.environ['PYTHONIOENCODING'] = 'utf-8'
17
 
18
+ # 配置日志系统
19
+ def setup_logging():
20
+ """配置日志系统,将日志输出到文件和内存队列"""
21
+ # 创建logs目录(如果不存在)
22
+ logs_dir = os.path.join(os.path.dirname(__file__), "logs")
23
+ os.makedirs(logs_dir, exist_ok=True)
24
+
25
+ # 生成日志文件名(使用当前日期)
26
+ current_date = datetime.datetime.now().strftime("%Y-%m-%d")
27
+ log_file = os.path.join(logs_dir, f"gradio_log_{current_date}.txt")
28
+
29
+ # 配置根日志记录器(捕获所有日志)
30
+ root_logger = logging.getLogger()
31
+
32
+ # 清除现有的处理器,避免重复日志
33
+ for handler in root_logger.handlers[:]:
34
+ root_logger.removeHandler(handler)
35
+
36
+ root_logger.setLevel(logging.INFO)
37
+
38
+ # 创建文件处理器
39
+ file_handler = logging.FileHandler(log_file, encoding='utf-8', mode='a')
40
+ file_handler.setLevel(logging.INFO)
41
+
42
+ # 创建格式化器
43
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
44
+ file_handler.setFormatter(formatter)
45
+
46
+ # 添加处理器到根日志记录器
47
+ root_logger.addHandler(file_handler)
48
+
49
+ # 配置CAMEL日志系统(如果可用)
50
+ try:
51
+ from camel.logger import set_log_file, set_log_level, enable_logging
52
+ # 启用CAMEL日志
53
+ enable_logging()
54
+ # 设置CAMEL日志文件为同一个文件
55
+ set_log_file(log_file)
56
+ # 设置日志级别
57
+ set_log_level("INFO")
58
+ logging.info("CAMEL日志系统已配置")
59
+ except ImportError:
60
+ logging.warning("无法导入CAMEL日志模块,将只使用Python标准日志")
61
+ except Exception as e:
62
+ logging.warning(f"配置CAMEL日志时出错: {str(e)}")
63
+
64
+ # 配置第三方库的日志级别
65
+ for logger_name in ['urllib3', 'requests', 'gradio']:
66
+ logging.getLogger(logger_name).setLevel(logging.WARNING)
67
+
68
+ logging.info("日志系统已初始化,日志文件: %s", log_file)
69
+ return log_file
70
+
71
+ # 全局变量
72
+ LOG_FILE = None
73
+ LOG_QUEUE = queue.Queue()
74
+ STOP_LOG_THREAD = threading.Event()
75
 
76
+ # 日志读取和更新函数
77
+ def log_reader_thread(log_file):
78
+ """后台线程,持续读取日志文件并将新行添加到队列中"""
79
+ try:
80
+ with open(log_file, 'r', encoding='utf-8') as f:
81
+ # 移动到文件末尾
82
+ f.seek(0, 2)
83
+
84
+ while not STOP_LOG_THREAD.is_set():
85
+ line = f.readline()
86
+ if line:
87
+ LOG_QUEUE.put(line)
88
+ else:
89
+ # 没有新行,等待一小段时间
90
+ time.sleep(0.1)
91
+ except Exception as e:
92
+ logging.error(f"日志读取线程出错: {str(e)}")
93
+
94
+ def get_latest_logs(max_lines=100):
95
+ """从队列中获取最新的日志行,如果队列为空则直接从文件读取
96
+
97
+ Args:
98
+ max_lines: 最大返回行数
99
+
100
+ Returns:
101
+ str: 日志内容
102
+ """
103
+ logs = []
104
+ try:
105
+ # 尝试从队列中获取所有可用的日志行
106
+ while not LOG_QUEUE.empty() and len(logs) < max_lines:
107
+ logs.append(LOG_QUEUE.get_nowait())
108
+ except queue.Empty:
109
+ pass
110
+
111
+ # 如果没有新日志或日志不足,尝试直接从文件读取最后几行
112
+ if len(logs) < max_lines and LOG_FILE and os.path.exists(LOG_FILE):
113
+ try:
114
+ with open(LOG_FILE, 'r', encoding='utf-8') as f:
115
+ all_lines = f.readlines()
116
+ # 如果队列中已有一些日志,只读取剩余需要的行数
117
+ remaining_lines = max_lines - len(logs)
118
+ file_logs = all_lines[-remaining_lines:] if len(all_lines) > remaining_lines else all_lines
119
+ # 将文件日志添加到队列日志之前
120
+ logs = file_logs + logs
121
+ except Exception as e:
122
+ error_msg = f"读取日志文件出错: {str(e)}"
123
+ logging.error(error_msg)
124
+ if not logs: # 只有在没有任何日志的情况下才添加错误消息
125
+ logs = [error_msg]
126
+
127
+ # 如果仍然没有日志,返回提示信息
128
+ if not logs:
129
+ return "暂无日志记录或日志系统未正确初始化。"
130
+
131
+ return "".join(logs)
132
 
133
  # Dictionary containing module descriptions
134
  MODULE_DESCRIPTIONS = {
 
268
  """
269
  # 验证输入
270
  if not validate_input(question):
271
+ logging.warning("用户提交了无效的输入")
272
  return (
273
  "请输入有效的问题",
274
  [],
 
279
  try:
280
  # 确保环境变量已加载
281
  load_dotenv(find_dotenv(), override=True)
282
+ logging.info(f"处理问题: '{question}', 使用模块: {example_module}")
283
+
284
  # 检查模块是否在MODULE_DESCRIPTIONS中
285
  if example_module not in MODULE_DESCRIPTIONS:
286
+ logging.error(f"用户选择了不支持的模块: {example_module}")
287
  return (
288
  f"所选模块 '{example_module}' 不受支持",
289
  [],
 
294
  # 动态导入目标模块
295
  module_path = f"owl.examples.{example_module}"
296
  try:
297
+ logging.info(f"正在导入模块: {module_path}")
298
  module = importlib.import_module(module_path)
299
  except ImportError as ie:
300
+ logging.error(f"无法导入模块 {module_path}: {str(ie)}")
301
  return (
302
  f"无法导入模块: {module_path}",
303
  [],
 
305
  f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}"
306
  )
307
  except Exception as e:
308
+ logging.error(f"导入模块 {module_path} 时发生错误: {str(e)}")
309
  return (
310
  f"导入模块时发生错误: {module_path}",
311
  [],
 
315
 
316
  # 检查是否包含construct_society函数
317
  if not hasattr(module, "construct_society"):
318
+ logging.error(f"模块 {module_path} 中未找到 construct_society 函数")
319
  return (
320
  f"模块 {module_path} 中未找到 construct_society 函数",
321
  [],
 
325
 
326
  # 构建社会模拟
327
  try:
328
+ logging.info("正在构建社会模拟...")
329
  society = module.construct_society(question)
330
  except Exception as e:
331
+ logging.error(f"构建社会模拟时发生错误: {str(e)}")
332
  return (
333
  f"构建社会模拟时发生错误: {str(e)}",
334
  [],
 
338
 
339
  # 运行社会模拟
340
  try:
341
+ logging.info("正在运行社会模拟...")
342
  answer, chat_history, token_info = run_society(society)
343
+ logging.info("社会模拟运行完成")
344
  except Exception as e:
345
+ logging.error(f"运行社会模拟时发生错误: {str(e)}")
346
  return (
347
  f"运行社会模拟时发生错误: {str(e)}",
348
  [],
 
355
  formatted_chat_history = format_chat_history(chat_history)
356
  except Exception as e:
357
  # 如果格式化失败,返回空历史记录但继续处理
358
+ logging.error(f"格式化聊天历史时发生错误: {str(e)}")
359
  formatted_chat_history = []
360
 
361
  # 安全地获取令牌计数
 
366
  prompt_tokens = token_info.get("prompt_token_count", 0)
367
  total_tokens = completion_tokens + prompt_tokens
368
 
369
+ logging.info(f"处理完成,令牌使用: 完成={completion_tokens}, 提示={prompt_tokens}, 总计={total_tokens}")
370
+
371
  return (
372
  answer,
373
  formatted_chat_history,
 
376
  )
377
 
378
  except Exception as e:
379
+ logging.error(f"处理问题时发生未捕获的错误: {str(e)}")
380
  return (
381
  f"发生错误: {str(e)}",
382
  [],
 
492
 
493
  def create_ui():
494
  """创建增强版Gradio界面"""
495
+
496
+ # 定义日志更新函数
497
+ def update_logs():
498
+ """获取最新日志并返回给前端显示"""
499
+ return get_latest_logs(100)
500
+
501
+ def clear_log_display():
502
+ """清空日志显示"""
503
+ return ""
504
+
505
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app:
506
 
507
 
 
558
  height=500
559
  )
560
 
561
+ with gr.TabItem("系统日志"):
562
+ # 添加日志显示区域
563
+ log_display = gr.Textbox(
564
+ label="系统日志",
565
+ lines=20,
566
+ max_lines=50,
567
+ interactive=False,
568
+ autoscroll=True,
569
+ show_copy_button=True,
570
+ elem_classes="log-display"
571
+ )
572
+
573
+ with gr.Row():
574
+ refresh_logs_button = gr.Button("刷新日志")
575
+ auto_refresh_checkbox = gr.Checkbox(
576
+ label="自动刷新",
577
+ value=True,
578
+ interactive=True
579
+ )
580
+ clear_logs_button = gr.Button("清空显示", variant="secondary")
581
+
582
  with gr.TabItem("环境变量管理", id="env-settings"):
583
  gr.Markdown("""
584
  ## 环境变量管理
 
706
  fn=run_owl,
707
  inputs=[question_input, module_dropdown],
708
  outputs=[answer_output, chat_output, token_count_output, status_output]
709
+ ).then(
710
+ fn=update_logs, # 任务完成后自动更新日志
711
+ outputs=[log_display]
712
  )
713
 
714
  # 模块选择更新描述
 
717
  inputs=module_dropdown,
718
  outputs=module_description
719
  )
720
+
721
+ # 日志相关事件处理
722
+ refresh_logs_button.click(
723
+ fn=update_logs,
724
+ outputs=[log_display]
725
+ )
726
+
727
+ clear_logs_button.click(
728
+ fn=clear_log_display,
729
+ outputs=[log_display]
730
+ )
731
+
732
+ # 自动刷新控制
733
+ def toggle_auto_refresh(enabled):
734
+ if enabled:
735
+ return gr.update(every=3)
736
+ else:
737
+ return gr.update(every=0)
738
+
739
+ auto_refresh_checkbox.change(
740
+ fn=toggle_auto_refresh,
741
+ inputs=[auto_refresh_checkbox],
742
+ outputs=[log_display]
743
+ )
744
+
745
+ # 设置自动刷新(默认每3秒刷新一次)
746
+ if auto_refresh_checkbox.value:
747
+ app.load(
748
+ fn=update_logs,
749
+ outputs=[log_display],
750
+ every=3
751
+ )
752
 
753
  return app
754
 
755
  # 主函数
756
  def main():
757
  try:
758
+ # 初始化日志系统
759
+ global LOG_FILE
760
+ LOG_FILE = setup_logging()
761
+ logging.info("OWL Web应用程序启动")
762
+
763
+ # 启动日志读取线程
764
+ log_thread = threading.Thread(target=log_reader_thread, args=(LOG_FILE,), daemon=True)
765
+ log_thread.start()
766
+ logging.info("日志读取线程已启动")
767
+
768
  # 初始化.env文件(如果不存在)
769
  init_env_file()
770
  app = create_ui()
771
+
772
+ # 注册应用关闭时的清理函数
773
+ def cleanup():
774
+ STOP_LOG_THREAD.set()
775
+ logging.info("应用程序关闭,停止日志线程")
776
+
777
  app.launch(share=False)
778
  except Exception as e:
779
+ logging.error(f"启动应用程序时发生错误: {str(e)}")
780
  print(f"启动应用程序时发生错误: {str(e)}")
781
  import traceback
782
  traceback.print_exc()
783
+
784
+ finally:
785
+ # 确保日志线程停止
786
+ STOP_LOG_THREAD.set()
787
+ logging.info("应用程序关闭")
788
 
789
  if __name__ == "__main__":
790
  main()