Commit
·
d2960de
1
Parent(s):
a1b6884
update wendong
Browse files- owl/webapp_zh.py +122 -296
owl/webapp_zh.py
CHANGED
@@ -16,12 +16,13 @@ import signal
|
|
16 |
import sys
|
17 |
import subprocess
|
18 |
import platform
|
|
|
19 |
|
20 |
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
21 |
|
22 |
# 配置日志系统
|
23 |
def setup_logging():
|
24 |
-
"""
|
25 |
# 创建logs目录(如果不存在)
|
26 |
logs_dir = os.path.join(os.path.dirname(__file__), "logs")
|
27 |
os.makedirs(logs_dir, exist_ok=True)
|
@@ -43,14 +44,18 @@ def setup_logging():
|
|
43 |
file_handler = logging.FileHandler(log_file, encoding='utf-8', mode='a')
|
44 |
file_handler.setLevel(logging.INFO)
|
45 |
|
|
|
|
|
|
|
|
|
46 |
# 创建格式化器
|
47 |
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
48 |
file_handler.setFormatter(formatter)
|
|
|
49 |
|
50 |
# 添加处理器到根日志记录器
|
51 |
root_logger.addHandler(file_handler)
|
52 |
-
|
53 |
-
|
54 |
|
55 |
logging.info("日志系统已初始化,日志文件: %s", log_file)
|
56 |
return log_file
|
@@ -58,10 +63,10 @@ def setup_logging():
|
|
58 |
# 全局变量
|
59 |
LOG_FILE = None
|
60 |
LOG_QUEUE = queue.Queue()
|
|
|
61 |
STOP_LOG_THREAD = threading.Event()
|
62 |
CURRENT_PROCESS = None # 用于跟踪当前运行的进程
|
63 |
STOP_REQUESTED = threading.Event() # 用于标记是否请求停止
|
64 |
-
CONVERSATION_UPDATE_QUEUE = queue.Queue() # 用于实时更新对话历史的队列
|
65 |
|
66 |
# 日志读取和更新函数
|
67 |
def log_reader_thread(log_file):
|
@@ -75,26 +80,29 @@ def log_reader_thread(log_file):
|
|
75 |
line = f.readline()
|
76 |
if line:
|
77 |
LOG_QUEUE.put(line)
|
|
|
78 |
else:
|
79 |
# 没有新行,等待一小段时间
|
80 |
time.sleep(0.1)
|
81 |
except Exception as e:
|
82 |
logging.error(f"日志读取线程出错: {str(e)}")
|
83 |
|
84 |
-
def get_latest_logs(max_lines=100):
|
85 |
"""从队列中获取最新的日志行,如果队列为空则直接从文件读取
|
86 |
|
87 |
Args:
|
88 |
max_lines: 最大返回行数
|
|
|
89 |
|
90 |
Returns:
|
91 |
str: 日志内容
|
92 |
"""
|
93 |
logs = []
|
|
|
94 |
try:
|
95 |
# 尝试从队列中获取所有可用的日志行
|
96 |
-
while not
|
97 |
-
logs.append(
|
98 |
except queue.Empty:
|
99 |
pass
|
100 |
|
@@ -118,7 +126,34 @@ def get_latest_logs(max_lines=100):
|
|
118 |
if not logs:
|
119 |
return "暂无日志记录或日志系统未正确初始化。"
|
120 |
|
121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
|
123 |
# Dictionary containing module descriptions
|
124 |
MODULE_DESCRIPTIONS = {
|
@@ -209,28 +244,7 @@ FIRECRAWL_API_KEY=""
|
|
209 |
#FIRECRAWL_API_URL="https://api.firecrawl.dev"
|
210 |
"""
|
211 |
|
212 |
-
|
213 |
-
"""将聊天历史格式化为Gradio聊天组件可接受的格式
|
214 |
-
|
215 |
-
Args:
|
216 |
-
chat_history: 原始聊天历史
|
217 |
-
|
218 |
-
Returns:
|
219 |
-
List[List[str]]: 格式化后的聊天历史
|
220 |
-
"""
|
221 |
-
formatted_history = []
|
222 |
-
for message in chat_history:
|
223 |
-
user_msg = message.get("user", "")
|
224 |
-
assistant_msg = message.get("assistant", "")
|
225 |
-
|
226 |
-
if user_msg:
|
227 |
-
formatted_history.append([user_msg, None])
|
228 |
-
if assistant_msg and formatted_history:
|
229 |
-
formatted_history[-1][1] = assistant_msg
|
230 |
-
elif assistant_msg:
|
231 |
-
formatted_history.append([None, assistant_msg])
|
232 |
-
|
233 |
-
return formatted_history
|
234 |
|
235 |
def validate_input(question: str) -> bool:
|
236 |
"""验证用户输入是否有效
|
@@ -246,7 +260,7 @@ def validate_input(question: str) -> bool:
|
|
246 |
return False
|
247 |
return True
|
248 |
|
249 |
-
def run_owl(question: str, example_module: str) -> Tuple[str,
|
250 |
"""运行OWL系统并返回结果
|
251 |
|
252 |
Args:
|
@@ -254,23 +268,15 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
254 |
example_module: 要导入的示例模块名(如 "run_terminal_zh" 或 "run_deep")
|
255 |
|
256 |
Returns:
|
257 |
-
Tuple[...]:
|
258 |
"""
|
259 |
-
global CURRENT_PROCESS
|
260 |
-
|
261 |
-
# 清空对话更新队列
|
262 |
-
while not CONVERSATION_UPDATE_QUEUE.empty():
|
263 |
-
try:
|
264 |
-
CONVERSATION_UPDATE_QUEUE.get_nowait()
|
265 |
-
except queue.Empty:
|
266 |
-
break
|
267 |
|
268 |
# 验证输入
|
269 |
if not validate_input(question):
|
270 |
logging.warning("用户提交了无效的输入")
|
271 |
return (
|
272 |
"请输入有效的问题",
|
273 |
-
[],
|
274 |
"0",
|
275 |
"❌ 错误: 输入无效"
|
276 |
)
|
@@ -285,7 +291,6 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
285 |
logging.error(f"用户选择了不支持的模块: {example_module}")
|
286 |
return (
|
287 |
f"所选模块 '{example_module}' 不受支持",
|
288 |
-
[],
|
289 |
"0",
|
290 |
f"❌ 错误: 不支持的模块"
|
291 |
)
|
@@ -299,7 +304,6 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
299 |
logging.error(f"无法导入模块 {module_path}: {str(ie)}")
|
300 |
return (
|
301 |
f"无法导入模块: {module_path}",
|
302 |
-
[],
|
303 |
"0",
|
304 |
f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}"
|
305 |
)
|
@@ -307,7 +311,6 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
307 |
logging.error(f"导入模块 {module_path} 时发生错误: {str(e)}")
|
308 |
return (
|
309 |
f"导入模块时发生错误: {module_path}",
|
310 |
-
[],
|
311 |
"0",
|
312 |
f"❌ 错误: {str(e)}"
|
313 |
)
|
@@ -317,7 +320,6 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
317 |
logging.error(f"模块 {module_path} 中未找到 construct_society 函数")
|
318 |
return (
|
319 |
f"模块 {module_path} 中未找到 construct_society 函数",
|
320 |
-
[],
|
321 |
"0",
|
322 |
f"❌ 错误: 模块接口不兼容"
|
323 |
)
|
@@ -327,25 +329,11 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
327 |
logging.info("正在构建社会模拟...")
|
328 |
society = module.construct_society(question)
|
329 |
|
330 |
-
|
331 |
-
if hasattr(society, 'set_message_callback'):
|
332 |
-
def message_callback(role, content):
|
333 |
-
"""对话消息回调函数"""
|
334 |
-
try:
|
335 |
-
# 将消息添加到队列
|
336 |
-
CONVERSATION_UPDATE_QUEUE.put((role, content))
|
337 |
-
logging.info(f"对话回调: {role} - {content[:30]}...")
|
338 |
-
except Exception as e:
|
339 |
-
logging.error(f"对话回调处理错误: {str(e)}")
|
340 |
-
|
341 |
-
# 设置回调
|
342 |
-
society.set_message_callback(message_callback)
|
343 |
-
logging.info("已设置对话更新回调")
|
344 |
except Exception as e:
|
345 |
logging.error(f"构建社会模拟时发生错误: {str(e)}")
|
346 |
return (
|
347 |
f"构建社会模拟时发生错误: {str(e)}",
|
348 |
-
[],
|
349 |
"0",
|
350 |
f"❌ 错误: 构建失败 - {str(e)}"
|
351 |
)
|
@@ -359,18 +347,11 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
359 |
logging.error(f"运行社会模拟时发生错误: {str(e)}")
|
360 |
return (
|
361 |
f"运行社会模拟时发生错误: {str(e)}",
|
362 |
-
[],
|
363 |
"0",
|
364 |
f"❌ 错误: 运行失败 - {str(e)}"
|
365 |
)
|
366 |
|
367 |
-
|
368 |
-
try:
|
369 |
-
formatted_chat_history = format_chat_history(chat_history)
|
370 |
-
except Exception as e:
|
371 |
-
# 如果格式化失败,返回空历史记录但继续处理
|
372 |
-
logging.error(f"格式化聊天历史时发生错误: {str(e)}")
|
373 |
-
formatted_chat_history = []
|
374 |
|
375 |
# 安全地获取令牌计数
|
376 |
if not isinstance(token_info, dict):
|
@@ -384,7 +365,6 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
384 |
|
385 |
return (
|
386 |
answer,
|
387 |
-
formatted_chat_history,
|
388 |
f"完成令牌: {completion_tokens:,} | 提示令牌: {prompt_tokens:,} | 总计: {total_tokens:,}",
|
389 |
"✅ 成功完成"
|
390 |
)
|
@@ -393,7 +373,6 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
393 |
logging.error(f"处理问题时发生未捕获的错误: {str(e)}")
|
394 |
return (
|
395 |
f"发生错误: {str(e)}",
|
396 |
-
[],
|
397 |
"0",
|
398 |
f"❌ 错误: {str(e)}"
|
399 |
)
|
@@ -512,6 +491,10 @@ def create_ui():
|
|
512 |
"""获取最新日志并返回给前端显示"""
|
513 |
return get_latest_logs(100)
|
514 |
|
|
|
|
|
|
|
|
|
515 |
def clear_log_file():
|
516 |
"""清空日志文件内容"""
|
517 |
try:
|
@@ -525,6 +508,12 @@ def create_ui():
|
|
525 |
LOG_QUEUE.get_nowait()
|
526 |
except queue.Empty:
|
527 |
break
|
|
|
|
|
|
|
|
|
|
|
|
|
528 |
return "日志文件已清空"
|
529 |
else:
|
530 |
return "日志文件不存在或未设置"
|
@@ -534,181 +523,43 @@ def create_ui():
|
|
534 |
|
535 |
# 创建一个实时日志更新函数
|
536 |
def process_with_live_logs(question, module_name):
|
537 |
-
"""
|
538 |
-
global CURRENT_PROCESS
|
539 |
|
540 |
# 创建一个后台线程来处理问题
|
541 |
result_queue = queue.Queue()
|
542 |
-
# 创建一个队列用于实时更新对话历史
|
543 |
-
chat_history_queue = queue.Queue()
|
544 |
-
|
545 |
-
# 初始化对话历史,添加用户问题
|
546 |
-
current_chat_history = [[question, None]]
|
547 |
-
|
548 |
-
# 创建一个函数来监听日志中的对话更新
|
549 |
-
def monitor_logs_for_chat_updates():
|
550 |
-
"""监控日志中的对话更新并将其添加到队列中"""
|
551 |
-
try:
|
552 |
-
# 创建一个单独的日志队列用于监控对话
|
553 |
-
chat_log_queue = queue.Queue()
|
554 |
-
|
555 |
-
# 打开日志文件进行监控
|
556 |
-
with open(LOG_FILE, 'r', encoding='utf-8') as f:
|
557 |
-
# 移动到文件末尾
|
558 |
-
f.seek(0, 2)
|
559 |
-
|
560 |
-
while True:
|
561 |
-
line = f.readline()
|
562 |
-
if line:
|
563 |
-
# 尝试多种模式来检测对话信息
|
564 |
-
|
565 |
-
# 模式1: 检查标准的Agent对话格式
|
566 |
-
if "Agent:" in line and ":" in line.split("Agent:")[1]:
|
567 |
-
try:
|
568 |
-
agent_part = line.split("Agent:")[1].strip()
|
569 |
-
agent_name = agent_part.split(":")[0].strip()
|
570 |
-
message = ":".join(agent_part.split(":")[1:]).strip()
|
571 |
-
|
572 |
-
# 将对话信息添加到队列
|
573 |
-
chat_history_queue.put((agent_name, message))
|
574 |
-
logging.info(f"检测到对话更新(模式1): {agent_name} - {message[:30]}...")
|
575 |
-
except Exception as e:
|
576 |
-
logging.error(f"解析对话信息时出错(模式1): {str(e)}")
|
577 |
-
|
578 |
-
# 模式2: 检查包含角色名和消息的格式
|
579 |
-
elif " - " in line and any(role in line for role in ["用户", "助手", "系统", "User", "Assistant", "System"]):
|
580 |
-
try:
|
581 |
-
parts = line.split(" - ", 1)
|
582 |
-
if len(parts) >= 2:
|
583 |
-
# 尝试提取角色名
|
584 |
-
log_prefix = parts[0]
|
585 |
-
message_part = parts[1]
|
586 |
-
|
587 |
-
# 尝试从日志前缀中提取角色名
|
588 |
-
role_candidates = ["用户", "助手", "系统", "User", "Assistant", "System"]
|
589 |
-
agent_name = None
|
590 |
-
for role in role_candidates:
|
591 |
-
if role in log_prefix:
|
592 |
-
agent_name = role
|
593 |
-
break
|
594 |
-
|
595 |
-
if agent_name and message_part.strip():
|
596 |
-
chat_history_queue.put((agent_name, message_part.strip()))
|
597 |
-
logging.info(f"检测到对话更新(模式2): {agent_name} - {message_part[:30]}...")
|
598 |
-
except Exception as e:
|
599 |
-
logging.error(f"解析对话信息时出错(模式2): {str(e)}")
|
600 |
-
|
601 |
-
# 模式3: 检查JSON格式的对话记录
|
602 |
-
elif '"role"' in line and '"content"' in line and ('"user"' in line.lower() or '"assistant"' in line.lower() or '"system"' in line.lower()):
|
603 |
-
try:
|
604 |
-
# 尝试提取JSON部分
|
605 |
-
json_start = line.find("{")
|
606 |
-
json_end = line.rfind("}")
|
607 |
-
|
608 |
-
if json_start >= 0 and json_end > json_start:
|
609 |
-
json_str = line[json_start:json_end+1]
|
610 |
-
message_data = json.loads(json_str)
|
611 |
-
|
612 |
-
if "role" in message_data and "content" in message_data:
|
613 |
-
agent_name = message_data["role"].capitalize()
|
614 |
-
message = message_data["content"]
|
615 |
-
|
616 |
-
chat_history_queue.put((agent_name, message))
|
617 |
-
logging.info(f"检测到对话更新(模式3): {agent_name} - {message[:30]}...")
|
618 |
-
except Exception as e:
|
619 |
-
# JSON解析错误是常见的,所以这里不记录为错误
|
620 |
-
pass
|
621 |
-
else:
|
622 |
-
# 没有新行,等待一小段时间
|
623 |
-
time.sleep(0.1)
|
624 |
-
except Exception as e:
|
625 |
-
logging.error(f"对话监控线程出错: {str(e)}")
|
626 |
|
627 |
def process_in_background():
|
628 |
try:
|
629 |
result = run_owl(question, module_name)
|
630 |
result_queue.put(result)
|
631 |
except Exception as e:
|
632 |
-
result_queue.put((f"发生错误: {str(e)}",
|
633 |
-
|
634 |
-
# 启动对话监控线程
|
635 |
-
chat_monitor_thread = threading.Thread(target=monitor_logs_for_chat_updates, daemon=True)
|
636 |
-
chat_monitor_thread.start()
|
637 |
|
638 |
# 启动后台处理线程
|
639 |
bg_thread = threading.Thread(target=process_in_background)
|
640 |
CURRENT_PROCESS = bg_thread # 记录当前进程
|
641 |
bg_thread.start()
|
642 |
|
643 |
-
#
|
644 |
while bg_thread.is_alive():
|
645 |
-
# 检查是否有新的对话更新(从日志解析)
|
646 |
-
updated = False
|
647 |
-
while not chat_history_queue.empty():
|
648 |
-
try:
|
649 |
-
agent_name, message = chat_history_queue.get_nowait()
|
650 |
-
|
651 |
-
# 如果是新的对话,添加到历史记录
|
652 |
-
if not current_chat_history or current_chat_history[-1][1] is not None:
|
653 |
-
# 添加新的对话条目
|
654 |
-
current_chat_history.append([f"[{agent_name}]", message])
|
655 |
-
else:
|
656 |
-
# 更新最后一个对话的回复
|
657 |
-
current_chat_history[-1][1] = message
|
658 |
-
|
659 |
-
updated = True
|
660 |
-
except queue.Empty:
|
661 |
-
break
|
662 |
-
|
663 |
-
# 检查是否有新的对话更新(从回调机制)
|
664 |
-
while not CONVERSATION_UPDATE_QUEUE.empty():
|
665 |
-
try:
|
666 |
-
role, content = CONVERSATION_UPDATE_QUEUE.get_nowait()
|
667 |
-
|
668 |
-
# 格式化角色名称
|
669 |
-
if role.lower() == "user":
|
670 |
-
role_display = "用户"
|
671 |
-
elif role.lower() == "assistant":
|
672 |
-
role_display = "助手"
|
673 |
-
else:
|
674 |
-
role_display = role
|
675 |
-
|
676 |
-
# 如果是新的对话,添加到历史记录
|
677 |
-
if not current_chat_history or current_chat_history[-1][1] is not None:
|
678 |
-
# 添加新的对话条目
|
679 |
-
current_chat_history.append([f"[{role_display}]", content])
|
680 |
-
else:
|
681 |
-
# 更新最后一个对话的回复
|
682 |
-
current_chat_history[-1][1] = content
|
683 |
-
|
684 |
-
updated = True
|
685 |
-
logging.info(f"从回调更新对话: {role_display} - {content[:30]}...")
|
686 |
-
except queue.Empty:
|
687 |
-
break
|
688 |
-
|
689 |
# 更新日志显示
|
690 |
logs = get_latest_logs(100)
|
|
|
691 |
|
692 |
-
#
|
693 |
-
|
694 |
-
yield None, current_chat_history, None, "<span class='status-indicator status-running'></span> 处理中...", logs
|
695 |
|
696 |
time.sleep(1)
|
697 |
|
698 |
# 处理完成,获取结果
|
699 |
if not result_queue.empty():
|
700 |
result = result_queue.get()
|
701 |
-
answer,
|
702 |
-
|
703 |
-
# 如果有完整的聊天历史,使用它替换我们的临时历史
|
704 |
-
if chat_history and len(chat_history) > 0:
|
705 |
-
# 但首先确保用户问题已包含在内
|
706 |
-
if not any(item[0] == question for item in chat_history):
|
707 |
-
chat_history.insert(0, [question, None])
|
708 |
-
current_chat_history = chat_history
|
709 |
|
710 |
# 最后一次更新日志
|
711 |
logs = get_latest_logs(100)
|
|
|
712 |
|
713 |
# 根据状态设置不同的指示器
|
714 |
if "错误" in status:
|
@@ -716,10 +567,11 @@ def create_ui():
|
|
716 |
else:
|
717 |
status_with_indicator = f"<span class='status-indicator status-success'></span> {status}"
|
718 |
|
719 |
-
yield answer,
|
720 |
else:
|
721 |
logs = get_latest_logs(100)
|
722 |
-
|
|
|
723 |
|
724 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app:
|
725 |
gr.Markdown(
|
@@ -741,28 +593,7 @@ def create_ui():
|
|
741 |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
742 |
}
|
743 |
|
744 |
-
|
745 |
-
.chat-container .user-message {
|
746 |
-
background-color: #e6f7ff;
|
747 |
-
border-radius: 18px 18px 0 18px;
|
748 |
-
padding: 10px 15px;
|
749 |
-
margin: 5px 0;
|
750 |
-
}
|
751 |
-
|
752 |
-
/* 助手消息样式 */
|
753 |
-
.chat-container .assistant-message {
|
754 |
-
background-color: #f0f0f0;
|
755 |
-
border-radius: 18px 18px 18px 0;
|
756 |
-
padding: 10px 15px;
|
757 |
-
margin: 5px 0;
|
758 |
-
}
|
759 |
-
|
760 |
-
/* 角色名称样式 */
|
761 |
-
.chat-container .role-name {
|
762 |
-
font-weight: bold;
|
763 |
-
margin-bottom: 5px;
|
764 |
-
}
|
765 |
-
|
766 |
/* 改进标签页样式 */
|
767 |
.tabs .tab-nav {
|
768 |
background-color: #f5f5f5;
|
@@ -815,26 +646,7 @@ def create_ui():
|
|
815 |
line-height: 1.4;
|
816 |
}
|
817 |
|
818 |
-
|
819 |
-
.chat-container .avatar {
|
820 |
-
display: flex !important;
|
821 |
-
align-items: center;
|
822 |
-
justify-content: center;
|
823 |
-
width: 40px !important;
|
824 |
-
height: 40px !important;
|
825 |
-
border-radius: 50%;
|
826 |
-
font-size: 20px;
|
827 |
-
margin-right: 10px;
|
828 |
-
}
|
829 |
-
|
830 |
-
.chat-container .avatar.user {
|
831 |
-
background-color: #e6f7ff;
|
832 |
-
}
|
833 |
-
|
834 |
-
.chat-container .avatar.assistant {
|
835 |
-
background-color: #f0f0f0;
|
836 |
-
}
|
837 |
-
|
838 |
@keyframes pulse {
|
839 |
0% { opacity: 1; }
|
840 |
50% { opacity: 0.5; }
|
@@ -885,7 +697,29 @@ def create_ui():
|
|
885 |
|
886 |
|
887 |
|
888 |
-
with gr.Tabs():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
889 |
with gr.TabItem("系统日志"):
|
890 |
# 添加日志显示区域
|
891 |
log_display = gr.Textbox(
|
@@ -914,38 +748,7 @@ def create_ui():
|
|
914 |
elem_classes="answer-box"
|
915 |
)
|
916 |
|
917 |
-
|
918 |
-
chat_output = gr.Chatbot(
|
919 |
-
label="完整对话记录",
|
920 |
-
elem_classes="chat-container",
|
921 |
-
height=500,
|
922 |
-
|
923 |
-
bubble_full_width=False, # 气泡不占满宽度
|
924 |
-
show_copy_button=True # 显示复制按钮
|
925 |
-
)
|
926 |
-
|
927 |
-
# 添加自动滚动到底部的JavaScript
|
928 |
-
gr.HTML("""
|
929 |
-
<script>
|
930 |
-
// 自动滚动聊天记录到底部
|
931 |
-
function scrollChatToBottom() {
|
932 |
-
const chatContainer = document.querySelector('.chat-container .chatbot');
|
933 |
-
if (chatContainer) {
|
934 |
-
chatContainer.scrollTop = chatContainer.scrollHeight;
|
935 |
-
}
|
936 |
-
}
|
937 |
-
|
938 |
-
// 每秒检查并滚动
|
939 |
-
setInterval(scrollChatToBottom, 1000);
|
940 |
-
|
941 |
-
// 监听标签页切换,当切换到对话历史标签时滚动到底部
|
942 |
-
document.addEventListener('click', function(e) {
|
943 |
-
if (e.target && e.target.closest('[id="chat-history-tab"]')) {
|
944 |
-
setTimeout(scrollChatToBottom, 100);
|
945 |
-
}
|
946 |
-
});
|
947 |
-
</script>
|
948 |
-
""")
|
949 |
|
950 |
|
951 |
|
@@ -1047,7 +850,7 @@ def create_ui():
|
|
1047 |
# 示例问题
|
1048 |
examples = [
|
1049 |
"打开百度搜索,总结一下camel-ai的camel框架的github star、fork数目等,并把数字用plot包写成python文件保存到本地,用本地终端执行python文件显示图出来给我",
|
1050 |
-
"请分析GitHub上CAMEL-AI
|
1051 |
"浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格",
|
1052 |
"写一个hello world的python文件,保存到本地",
|
1053 |
|
@@ -1075,7 +878,7 @@ def create_ui():
|
|
1075 |
run_button.click(
|
1076 |
fn=process_with_live_logs,
|
1077 |
inputs=[question_input, module_dropdown],
|
1078 |
-
outputs=[answer_output,
|
1079 |
)
|
1080 |
|
1081 |
# 模块选择更新描述
|
@@ -1091,11 +894,21 @@ def create_ui():
|
|
1091 |
outputs=[log_display]
|
1092 |
)
|
1093 |
|
|
|
|
|
|
|
|
|
|
|
1094 |
clear_logs_button.click(
|
1095 |
fn=clear_log_file,
|
1096 |
outputs=[log_display]
|
1097 |
)
|
1098 |
|
|
|
|
|
|
|
|
|
|
|
1099 |
# 自动刷新控制
|
1100 |
def toggle_auto_refresh(enabled):
|
1101 |
if enabled:
|
@@ -1109,6 +922,12 @@ def create_ui():
|
|
1109 |
outputs=[log_display]
|
1110 |
)
|
1111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
1112 |
# 设置自动刷新(默认每3秒刷新一次)
|
1113 |
if auto_refresh_checkbox.value:
|
1114 |
app.load(
|
@@ -1116,6 +935,13 @@ def create_ui():
|
|
1116 |
outputs=[log_display],
|
1117 |
every=2
|
1118 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1119 |
|
1120 |
return app
|
1121 |
|
|
|
16 |
import sys
|
17 |
import subprocess
|
18 |
import platform
|
19 |
+
import re
|
20 |
|
21 |
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
22 |
|
23 |
# 配置日志系统
|
24 |
def setup_logging():
|
25 |
+
"""配置日志系统,将日志输出到文件和内存队列以及控制台"""
|
26 |
# 创建logs目录(如果不存在)
|
27 |
logs_dir = os.path.join(os.path.dirname(__file__), "logs")
|
28 |
os.makedirs(logs_dir, exist_ok=True)
|
|
|
44 |
file_handler = logging.FileHandler(log_file, encoding='utf-8', mode='a')
|
45 |
file_handler.setLevel(logging.INFO)
|
46 |
|
47 |
+
# 创建控制台处理器
|
48 |
+
console_handler = logging.StreamHandler()
|
49 |
+
console_handler.setLevel(logging.INFO)
|
50 |
+
|
51 |
# 创建格式化器
|
52 |
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
53 |
file_handler.setFormatter(formatter)
|
54 |
+
console_handler.setFormatter(formatter)
|
55 |
|
56 |
# 添加处理器到根日志记录器
|
57 |
root_logger.addHandler(file_handler)
|
58 |
+
root_logger.addHandler(console_handler)
|
|
|
59 |
|
60 |
logging.info("日志系统已初始化,日志文件: %s", log_file)
|
61 |
return log_file
|
|
|
63 |
# 全局变量
|
64 |
LOG_FILE = None
|
65 |
LOG_QUEUE = queue.Queue()
|
66 |
+
LOG_QUEUE2 = queue.Queue() # 对话记录的队列
|
67 |
STOP_LOG_THREAD = threading.Event()
|
68 |
CURRENT_PROCESS = None # 用于跟踪当前运行的进程
|
69 |
STOP_REQUESTED = threading.Event() # 用于标记是否请求停止
|
|
|
70 |
|
71 |
# 日志读取和更新函数
|
72 |
def log_reader_thread(log_file):
|
|
|
80 |
line = f.readline()
|
81 |
if line:
|
82 |
LOG_QUEUE.put(line)
|
83 |
+
LOG_QUEUE2.put(line) # 同时添加到第二个队列
|
84 |
else:
|
85 |
# 没有新行,等待一小段时间
|
86 |
time.sleep(0.1)
|
87 |
except Exception as e:
|
88 |
logging.error(f"日志读取线程出错: {str(e)}")
|
89 |
|
90 |
+
def get_latest_logs(max_lines=100, queue_source=None):
|
91 |
"""从队列中获取最新的日志行,如果队列为空则直接从文件读取
|
92 |
|
93 |
Args:
|
94 |
max_lines: 最大返回行数
|
95 |
+
queue_source: 指定使用哪个队列,默认为LOG_QUEUE
|
96 |
|
97 |
Returns:
|
98 |
str: 日志内容
|
99 |
"""
|
100 |
logs = []
|
101 |
+
log_queue = queue_source if queue_source else LOG_QUEUE
|
102 |
try:
|
103 |
# 尝试从队列中获取所有可用的日志行
|
104 |
+
while not log_queue.empty() and len(logs) < max_lines:
|
105 |
+
logs.append(log_queue.get_nowait())
|
106 |
except queue.Empty:
|
107 |
pass
|
108 |
|
|
|
126 |
if not logs:
|
127 |
return "暂无日志记录或日志系统未正确初始化。"
|
128 |
|
129 |
+
# 格式化日志输出,确保每个日志条目有适当的换行和分隔
|
130 |
+
formatted_logs = []
|
131 |
+
for log in logs:
|
132 |
+
# 移除开头和结尾的多余空白字符
|
133 |
+
log = log.strip()
|
134 |
+
|
135 |
+
# 处理包含JSON或代码片段的日志,确保它们有正确的换行和缩进
|
136 |
+
if '"]"\n}' in log or '\n}\n\n' in log:
|
137 |
+
# 替换不合理的换行为更清晰的格式
|
138 |
+
log = log.replace('"]"\n}', '"]" }').replace('\n}\n\n', ' }\n')
|
139 |
+
|
140 |
+
# 检测日期时间格式的开头,这通常表示一个新的日志条目
|
141 |
+
# 例如:2025-03-14 18:49:31,008 - httpx - INFO
|
142 |
+
if re.match(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}', log):
|
143 |
+
# 在新的日志条目前添加一个空行,使日志更易读
|
144 |
+
formatted_logs.append('\n')
|
145 |
+
|
146 |
+
# 确保每个日志条目以换行符结束
|
147 |
+
if not log.endswith('\n'):
|
148 |
+
log += '\n'
|
149 |
+
|
150 |
+
formatted_logs.append(log)
|
151 |
+
|
152 |
+
# 移除第一个可能的额外空行
|
153 |
+
if formatted_logs and formatted_logs[0] == '\n':
|
154 |
+
formatted_logs.pop(0)
|
155 |
+
|
156 |
+
return "".join(formatted_logs)
|
157 |
|
158 |
# Dictionary containing module descriptions
|
159 |
MODULE_DESCRIPTIONS = {
|
|
|
244 |
#FIRECRAWL_API_URL="https://api.firecrawl.dev"
|
245 |
"""
|
246 |
|
247 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
|
249 |
def validate_input(question: str) -> bool:
|
250 |
"""验证用户输入是否有效
|
|
|
260 |
return False
|
261 |
return True
|
262 |
|
263 |
+
def run_owl(question: str, example_module: str) -> Tuple[str, str, str]:
|
264 |
"""运行OWL系统并返回结果
|
265 |
|
266 |
Args:
|
|
|
268 |
example_module: 要导入的示例模块名(如 "run_terminal_zh" 或 "run_deep")
|
269 |
|
270 |
Returns:
|
271 |
+
Tuple[...]: 回答、令牌计数、状态
|
272 |
"""
|
273 |
+
global CURRENT_PROCESS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
274 |
|
275 |
# 验证输入
|
276 |
if not validate_input(question):
|
277 |
logging.warning("用户提交了无效的输入")
|
278 |
return (
|
279 |
"请输入有效的问题",
|
|
|
280 |
"0",
|
281 |
"❌ 错误: 输入无效"
|
282 |
)
|
|
|
291 |
logging.error(f"用户选择了不支持的模块: {example_module}")
|
292 |
return (
|
293 |
f"所选模块 '{example_module}' 不受支持",
|
|
|
294 |
"0",
|
295 |
f"❌ 错误: 不支持的模块"
|
296 |
)
|
|
|
304 |
logging.error(f"无法导入模块 {module_path}: {str(ie)}")
|
305 |
return (
|
306 |
f"无法导入模块: {module_path}",
|
|
|
307 |
"0",
|
308 |
f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}"
|
309 |
)
|
|
|
311 |
logging.error(f"导入模块 {module_path} 时发生错误: {str(e)}")
|
312 |
return (
|
313 |
f"导入模块时发生错误: {module_path}",
|
|
|
314 |
"0",
|
315 |
f"❌ 错误: {str(e)}"
|
316 |
)
|
|
|
320 |
logging.error(f"模块 {module_path} 中未找到 construct_society 函数")
|
321 |
return (
|
322 |
f"模块 {module_path} 中未找到 construct_society 函数",
|
|
|
323 |
"0",
|
324 |
f"❌ 错误: 模块接口不兼容"
|
325 |
)
|
|
|
329 |
logging.info("正在构建社会模拟...")
|
330 |
society = module.construct_society(question)
|
331 |
|
332 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
333 |
except Exception as e:
|
334 |
logging.error(f"构建社会模拟时发生错误: {str(e)}")
|
335 |
return (
|
336 |
f"构建社会模拟时发生错误: {str(e)}",
|
|
|
337 |
"0",
|
338 |
f"❌ 错误: 构建失败 - {str(e)}"
|
339 |
)
|
|
|
347 |
logging.error(f"运行社会模拟时发生错误: {str(e)}")
|
348 |
return (
|
349 |
f"运行社会模拟时发生错误: {str(e)}",
|
|
|
350 |
"0",
|
351 |
f"❌ 错误: 运行失败 - {str(e)}"
|
352 |
)
|
353 |
|
354 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
355 |
|
356 |
# 安全地获取令牌计数
|
357 |
if not isinstance(token_info, dict):
|
|
|
365 |
|
366 |
return (
|
367 |
answer,
|
|
|
368 |
f"完成令牌: {completion_tokens:,} | 提示令牌: {prompt_tokens:,} | 总计: {total_tokens:,}",
|
369 |
"✅ 成功完成"
|
370 |
)
|
|
|
373 |
logging.error(f"处理问题时发生未捕获的错误: {str(e)}")
|
374 |
return (
|
375 |
f"发生错误: {str(e)}",
|
|
|
376 |
"0",
|
377 |
f"❌ 错误: {str(e)}"
|
378 |
)
|
|
|
491 |
"""获取最新日志并返回给前端显示"""
|
492 |
return get_latest_logs(100)
|
493 |
|
494 |
+
def update_logs2():
|
495 |
+
"""获取最新对话记录并返回给前端显示"""
|
496 |
+
return get_latest_logs(100, LOG_QUEUE2)
|
497 |
+
|
498 |
def clear_log_file():
|
499 |
"""清空日志文件内容"""
|
500 |
try:
|
|
|
508 |
LOG_QUEUE.get_nowait()
|
509 |
except queue.Empty:
|
510 |
break
|
511 |
+
# 清空第二个日志队列
|
512 |
+
while not LOG_QUEUE2.empty():
|
513 |
+
try:
|
514 |
+
LOG_QUEUE2.get_nowait()
|
515 |
+
except queue.Empty:
|
516 |
+
break
|
517 |
return "日志文件已清空"
|
518 |
else:
|
519 |
return "日志文件不存在或未设置"
|
|
|
523 |
|
524 |
# 创建一个实时日志更新函数
|
525 |
def process_with_live_logs(question, module_name):
|
526 |
+
"""处理问题并实时更新日志"""
|
527 |
+
global CURRENT_PROCESS
|
528 |
|
529 |
# 创建一个后台线程来处理问题
|
530 |
result_queue = queue.Queue()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
531 |
|
532 |
def process_in_background():
|
533 |
try:
|
534 |
result = run_owl(question, module_name)
|
535 |
result_queue.put(result)
|
536 |
except Exception as e:
|
537 |
+
result_queue.put((f"发生错误: {str(e)}", "0", f"❌ 错误: {str(e)}"))
|
|
|
|
|
|
|
|
|
538 |
|
539 |
# 启动后台处理线程
|
540 |
bg_thread = threading.Thread(target=process_in_background)
|
541 |
CURRENT_PROCESS = bg_thread # 记录当前进程
|
542 |
bg_thread.start()
|
543 |
|
544 |
+
# 在等待处理完成的同时,每秒更新一次日志
|
545 |
while bg_thread.is_alive():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
546 |
# 更新日志显示
|
547 |
logs = get_latest_logs(100)
|
548 |
+
logs2 = get_latest_logs(100, LOG_QUEUE2)
|
549 |
|
550 |
+
# 始终更新状态
|
551 |
+
yield None, "0", "<span class='status-indicator status-running'></span> 处理中...", logs, logs2
|
|
|
552 |
|
553 |
time.sleep(1)
|
554 |
|
555 |
# 处理完成,获取结果
|
556 |
if not result_queue.empty():
|
557 |
result = result_queue.get()
|
558 |
+
answer, token_count, status = result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
559 |
|
560 |
# 最后一次更新日志
|
561 |
logs = get_latest_logs(100)
|
562 |
+
logs2 = get_latest_logs(100, LOG_QUEUE2)
|
563 |
|
564 |
# 根据状态设置不同的指示器
|
565 |
if "错误" in status:
|
|
|
567 |
else:
|
568 |
status_with_indicator = f"<span class='status-indicator status-success'></span> {status}"
|
569 |
|
570 |
+
yield answer, token_count, status_with_indicator, logs, logs2
|
571 |
else:
|
572 |
logs = get_latest_logs(100)
|
573 |
+
logs2 = get_latest_logs(100, LOG_QUEUE2)
|
574 |
+
yield "操作未完成", "0", "<span class='status-indicator status-error'></span> 已终止", logs, logs2
|
575 |
|
576 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app:
|
577 |
gr.Markdown(
|
|
|
593 |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
594 |
}
|
595 |
|
596 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
597 |
/* 改进标签页样式 */
|
598 |
.tabs .tab-nav {
|
599 |
background-color: #f5f5f5;
|
|
|
646 |
line-height: 1.4;
|
647 |
}
|
648 |
|
649 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
650 |
@keyframes pulse {
|
651 |
0% { opacity: 1; }
|
652 |
50% { opacity: 0.5; }
|
|
|
697 |
|
698 |
|
699 |
|
700 |
+
with gr.Tabs(selected=1): # 设置对话记录为默认选中的标签页
|
701 |
+
with gr.TabItem("对话记录"):
|
702 |
+
# 添加对话记录显示区域
|
703 |
+
log_display2 = gr.Textbox(
|
704 |
+
label="对话记录",
|
705 |
+
lines=25,
|
706 |
+
max_lines=100,
|
707 |
+
interactive=False,
|
708 |
+
autoscroll=True,
|
709 |
+
show_copy_button=True,
|
710 |
+
elem_classes="log-display",
|
711 |
+
container=True
|
712 |
+
)
|
713 |
+
|
714 |
+
with gr.Row():
|
715 |
+
refresh_logs_button2 = gr.Button("刷新记录")
|
716 |
+
auto_refresh_checkbox2 = gr.Checkbox(
|
717 |
+
label="自动刷新",
|
718 |
+
value=True,
|
719 |
+
interactive=True
|
720 |
+
)
|
721 |
+
clear_logs_button2 = gr.Button("清空记录", variant="secondary")
|
722 |
+
|
723 |
with gr.TabItem("系统日志"):
|
724 |
# 添加日志显示区域
|
725 |
log_display = gr.Textbox(
|
|
|
748 |
elem_classes="answer-box"
|
749 |
)
|
750 |
|
751 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
752 |
|
753 |
|
754 |
|
|
|
850 |
# 示例问题
|
851 |
examples = [
|
852 |
"打开百度搜索,总结一下camel-ai的camel框架的github star、fork数目等,并把数字用plot包写成python文件保存到本地,用本地终端执行python文件显示图出来给我",
|
853 |
+
"请分析GitHub上CAMEL-AI项目的最新统计数据。找出该项目的星标数量、贡献者名称,把内容整理成一个markdown文件保存到本地",
|
854 |
"浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格",
|
855 |
"写一个hello world的python文件,保存到本地",
|
856 |
|
|
|
878 |
run_button.click(
|
879 |
fn=process_with_live_logs,
|
880 |
inputs=[question_input, module_dropdown],
|
881 |
+
outputs=[answer_output, token_count_output, status_output, log_display, log_display2]
|
882 |
)
|
883 |
|
884 |
# 模块选择更新描述
|
|
|
894 |
outputs=[log_display]
|
895 |
)
|
896 |
|
897 |
+
refresh_logs_button2.click(
|
898 |
+
fn=update_logs2,
|
899 |
+
outputs=[log_display2]
|
900 |
+
)
|
901 |
+
|
902 |
clear_logs_button.click(
|
903 |
fn=clear_log_file,
|
904 |
outputs=[log_display]
|
905 |
)
|
906 |
|
907 |
+
clear_logs_button2.click(
|
908 |
+
fn=clear_log_file,
|
909 |
+
outputs=[log_display2]
|
910 |
+
)
|
911 |
+
|
912 |
# 自动刷新控制
|
913 |
def toggle_auto_refresh(enabled):
|
914 |
if enabled:
|
|
|
922 |
outputs=[log_display]
|
923 |
)
|
924 |
|
925 |
+
auto_refresh_checkbox2.change(
|
926 |
+
fn=toggle_auto_refresh,
|
927 |
+
inputs=[auto_refresh_checkbox2],
|
928 |
+
outputs=[log_display2]
|
929 |
+
)
|
930 |
+
|
931 |
# 设置自动刷新(默认每3秒刷新一次)
|
932 |
if auto_refresh_checkbox.value:
|
933 |
app.load(
|
|
|
935 |
outputs=[log_display],
|
936 |
every=2
|
937 |
)
|
938 |
+
|
939 |
+
if auto_refresh_checkbox2.value:
|
940 |
+
app.load(
|
941 |
+
fn=update_logs2,
|
942 |
+
outputs=[log_display2],
|
943 |
+
every=2
|
944 |
+
)
|
945 |
|
946 |
return app
|
947 |
|