more simple version
Browse files- owl/webapp_zh.py +305 -270
owl/webapp_zh.py
CHANGED
@@ -2,208 +2,105 @@
|
|
2 |
from owl.utils import run_society
|
3 |
import os
|
4 |
import gradio as gr
|
|
|
|
|
5 |
from typing import Tuple, List, Dict, Any
|
6 |
import importlib
|
|
|
7 |
|
8 |
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
9 |
|
10 |
-
# Enhanced CSS with navigation bar and additional styling
|
11 |
-
custom_css = """
|
12 |
-
:root {
|
13 |
-
--primary-color: #4a89dc;
|
14 |
-
--secondary-color: #5d9cec;
|
15 |
-
--accent-color: #7baaf7;
|
16 |
-
--light-bg: #f8f9fa;
|
17 |
-
--border-color: #e4e9f0;
|
18 |
-
--text-muted: #8a9aae;
|
19 |
-
}
|
20 |
-
|
21 |
-
.container {
|
22 |
-
max-width: 1200px;
|
23 |
-
margin: 0 auto;
|
24 |
-
}
|
25 |
-
|
26 |
-
.navbar {
|
27 |
-
display: flex;
|
28 |
-
justify-content: space-between;
|
29 |
-
align-items: center;
|
30 |
-
padding: 15px 30px;
|
31 |
-
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
|
32 |
-
color: white;
|
33 |
-
border-radius: 10px 10px 0 0;
|
34 |
-
margin-bottom: 0;
|
35 |
-
box-shadow: 0 2px 10px rgba(74, 137, 220, 0.15);
|
36 |
-
}
|
37 |
-
|
38 |
-
.navbar-logo {
|
39 |
-
display: flex;
|
40 |
-
align-items: center;
|
41 |
-
gap: 10px;
|
42 |
-
font-size: 1.5em;
|
43 |
-
font-weight: bold;
|
44 |
-
}
|
45 |
-
|
46 |
-
.navbar-menu {
|
47 |
-
display: flex;
|
48 |
-
gap: 20px;
|
49 |
-
}
|
50 |
-
|
51 |
-
/* Navbar styles moved to a more specific section below */
|
52 |
-
|
53 |
-
.header {
|
54 |
-
text-align: center;
|
55 |
-
margin-bottom: 20px;
|
56 |
-
background: linear-gradient(180deg, var(--secondary-color), var(--accent-color));
|
57 |
-
color: white;
|
58 |
-
padding: 40px 20px;
|
59 |
-
border-radius: 0 0 10px 10px;
|
60 |
-
box-shadow: 0 4px 6px rgba(93, 156, 236, 0.12);
|
61 |
-
}
|
62 |
-
|
63 |
-
.module-info {
|
64 |
-
background-color: var(--light-bg);
|
65 |
-
border-left: 5px solid var(--primary-color);
|
66 |
-
padding: 10px 15px;
|
67 |
-
margin-top: 10px;
|
68 |
-
border-radius: 5px;
|
69 |
-
font-size: 0.9em;
|
70 |
-
}
|
71 |
|
72 |
-
.answer-box {
|
73 |
-
background-color: var(--light-bg);
|
74 |
-
border-left: 5px solid var(--secondary-color);
|
75 |
-
padding: 15px;
|
76 |
-
margin-bottom: 20px;
|
77 |
-
border-radius: 5px;
|
78 |
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
79 |
-
}
|
80 |
-
|
81 |
-
.token-count {
|
82 |
-
background-color: #e9ecef;
|
83 |
-
padding: 10px;
|
84 |
-
border-radius: 5px;
|
85 |
-
text-align: center;
|
86 |
-
font-weight: bold;
|
87 |
-
margin-bottom: 20px;
|
88 |
-
}
|
89 |
-
|
90 |
-
.chat-container {
|
91 |
-
border: 1px solid var(--border-color);
|
92 |
-
border-radius: 5px;
|
93 |
-
max-height: 500px;
|
94 |
-
overflow-y: auto;
|
95 |
-
margin-bottom: 20px;
|
96 |
-
}
|
97 |
-
|
98 |
-
.footer {
|
99 |
-
text-align: center;
|
100 |
-
margin-top: 20px;
|
101 |
-
color: var(--text-muted);
|
102 |
-
font-size: 0.9em;
|
103 |
-
padding: 20px;
|
104 |
-
border-top: 1px solid var(--border-color);
|
105 |
-
}
|
106 |
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
}
|
119 |
|
120 |
-
|
121 |
-
|
122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
}
|
124 |
}
|
125 |
|
126 |
-
|
127 |
-
|
128 |
-
border-radius: 8px;
|
129 |
-
padding: 20px;
|
130 |
-
box-shadow: 0 2px 8px rgba(74, 137, 220, 0.08);
|
131 |
-
transition: transform 0.3s, box-shadow 0.3s;
|
132 |
-
height: 100%;
|
133 |
-
display: flex;
|
134 |
-
flex-direction: column;
|
135 |
-
border: 1px solid rgba(228, 233, 240, 0.6);
|
136 |
-
}
|
137 |
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
border-color: rgba(93, 156, 236, 0.3);
|
142 |
-
}
|
143 |
|
144 |
-
.
|
145 |
-
|
146 |
-
color: var(--primary-color);
|
147 |
-
margin-bottom: 10px;
|
148 |
-
text-shadow: 0 1px 2px rgba(74, 137, 220, 0.1);
|
149 |
-
}
|
150 |
|
151 |
-
|
152 |
-
|
153 |
-
margin-bottom: 10px;
|
154 |
-
}
|
155 |
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
line-height: 1.5;
|
160 |
-
}
|
161 |
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
text-decoration: none;
|
166 |
-
padding: 5px 10px;
|
167 |
-
border-radius: 5px;
|
168 |
-
transition: background-color 0.3s, color 0.3s;
|
169 |
-
font-weight: 500;
|
170 |
-
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
171 |
-
}
|
172 |
|
173 |
-
|
174 |
-
|
175 |
-
color: #ffffff !important;
|
176 |
-
}
|
177 |
|
178 |
-
|
179 |
-
|
180 |
-
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
|
181 |
-
transition: all 0.3s;
|
182 |
-
}
|
183 |
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
box-shadow: 0 4px 8px rgba(74, 137, 220, 0.2);
|
188 |
-
}
|
189 |
"""
|
190 |
|
191 |
-
# Dictionary containing module descriptions
|
192 |
-
MODULE_DESCRIPTIONS = {
|
193 |
-
"run": "默认模式:使用OpenAI模型的默认的智能体协作模式,适合大多数任务。",
|
194 |
-
"run_mini":"使用使用OpenAI模型最小化配置处理任务",
|
195 |
-
"run_deepseek_zh":"使用deepseek模型处理中文任务",
|
196 |
-
"run_terminal_zh": "终端模式:可执行命令行操作,支持网络搜索、文件处理等功能。适合需要系统交互的任务,使用OpenAI模型",
|
197 |
-
"run_gaia_roleplaying":"GAIA基准测试实现,用于评估Agent能力",
|
198 |
-
"run_openai_compatiable_model":"使用openai兼容模型处理任务",
|
199 |
-
"run_ollama":"使用本地ollama模型处理任务",
|
200 |
-
"run_qwen_mini_zh":"使用qwen模型最小化配置处理任务",
|
201 |
-
"run_qwen_zh":"使用qwen模型处理任务",
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
}
|
206 |
-
|
207 |
def format_chat_history(chat_history: List[Dict[str, str]]) -> List[List[str]]:
|
208 |
"""将聊天历史格式化为Gradio聊天组件可接受的格式
|
209 |
|
@@ -261,6 +158,8 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
261 |
)
|
262 |
|
263 |
try:
|
|
|
|
|
264 |
# 检查模块是否在MODULE_DESCRIPTIONS中
|
265 |
if example_module not in MODULE_DESCRIPTIONS:
|
266 |
return (
|
@@ -354,67 +253,115 @@ def update_module_description(module_name: str) -> str:
|
|
354 |
"""返回所选模块的描述"""
|
355 |
return MODULE_DESCRIPTIONS.get(module_name, "无可用描述")
|
356 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
357 |
def create_ui():
|
358 |
"""创建增强版Gradio界面"""
|
359 |
-
with gr.Blocks(
|
360 |
-
|
361 |
-
gr.HTML("""
|
362 |
-
<div class="navbar">
|
363 |
-
<div class="navbar-logo">
|
364 |
-
🦉 OWL 多智能体协作系统
|
365 |
-
</div>
|
366 |
-
<div class="navbar-menu">
|
367 |
-
<a href="#home">首页</a>
|
368 |
-
<a href="https://github.com/camel-ai/owl/blob/main/README.md#-community">加入交流群</a>
|
369 |
-
<a href="https://github.com/camel-ai/owl/blob/main/README.md">OWL文档</a>
|
370 |
-
<a href="https://github.com/camel-ai/camel">CAMEL框架</a>
|
371 |
-
<a href="https://camel-ai.org">CAMEL-AI官网</a>
|
372 |
-
</div>
|
373 |
-
</div>
|
374 |
-
<div class="header" id="home">
|
375 |
-
|
376 |
-
<p>我们的愿景是彻底改变AI代理协作解决现实世界任务的方式。通过利用动态代理交互,OWL能够在多个领域实现更自然、高效和稳健的任务自动化。</p>
|
377 |
-
</div>
|
378 |
-
""")
|
379 |
-
|
380 |
-
with gr.Row(elem_id="features"):
|
381 |
-
gr.HTML("""
|
382 |
-
<div class="features-section">
|
383 |
-
<div class="feature-card">
|
384 |
-
<div class="feature-icon">🔍</div>
|
385 |
-
<h3>实时信息检索</h3>
|
386 |
-
<p>利用维基百科、谷歌搜索和其他在线资源获取最新信息。</p>
|
387 |
-
</div>
|
388 |
-
<div class="feature-card">
|
389 |
-
<div class="feature-icon">📹</div>
|
390 |
-
<h3>多模态处理</h3>
|
391 |
-
<p>支持处理互联网或本地的视频、图像和音频数据。</p>
|
392 |
-
</div>
|
393 |
-
<div class="feature-card">
|
394 |
-
<div class="feature-icon">🌐</div>
|
395 |
-
<h3>浏览器自动化</h3>
|
396 |
-
<p>使用Playwright框架模拟浏览器交互,实现网页操作自动化。</p>
|
397 |
-
</div>
|
398 |
-
<div class="feature-card">
|
399 |
-
<div class="feature-icon">📄</div>
|
400 |
-
<h3>文档解析</h3>
|
401 |
-
<p>从各种文档格式中提取内容,并转换为易于处理的格式。</p>
|
402 |
-
</div>
|
403 |
-
<div class="feature-card">
|
404 |
-
<div class="feature-icon">💻</div>
|
405 |
-
<h3>代码执行</h3>
|
406 |
-
<p>使用解释器编写和运行Python代码,实现自动化数据处理。</p>
|
407 |
-
</div>
|
408 |
-
<div class="feature-card">
|
409 |
-
<div class="feature-icon">🧰</div>
|
410 |
-
<h3>内置工具包</h3>
|
411 |
-
<p>提供丰富的工具包,支持搜索、数据分析、代码执行等多种功能。</p>
|
412 |
-
</div>
|
413 |
-
</div>
|
414 |
-
""")
|
415 |
|
416 |
with gr.Row():
|
417 |
-
with gr.Column(scale=
|
418 |
question_input = gr.Textbox(
|
419 |
lines=5,
|
420 |
placeholder="请输入您的问题...",
|
@@ -441,43 +388,125 @@ def create_ui():
|
|
441 |
)
|
442 |
|
443 |
run_button = gr.Button("运行", variant="primary", elem_classes="primary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
444 |
|
445 |
-
|
446 |
-
gr.Markdown("""
|
447 |
-
### 使用指南
|
448 |
-
|
449 |
-
1. **选择适合的模块**:根据您的任务需求选择合适的功能模块
|
450 |
-
2. **详细描述您的需求**:在输入框中清晰描述您的问题或任务
|
451 |
-
3. **启动智能处理**:点击"运行"按钮开始多智能体协作处理
|
452 |
-
4. **查看结果**:在下方标签页查看回答和完整对话历史
|
453 |
-
|
454 |
-
> **高级提示**: 对于复杂任务,可以尝试指定具体步骤和预期结果
|
455 |
-
""")
|
456 |
-
|
457 |
-
status_output = gr.Textbox(label="状态", interactive=False)
|
458 |
-
|
459 |
-
with gr.Tabs():
|
460 |
-
with gr.TabItem("回答"):
|
461 |
-
answer_output = gr.Textbox(
|
462 |
-
label="回答",
|
463 |
-
lines=10,
|
464 |
-
elem_classes="answer-box"
|
465 |
-
)
|
466 |
|
467 |
-
with gr.
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
473 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
474 |
|
475 |
-
|
476 |
-
token_count_output = gr.Textbox(
|
477 |
-
label="令牌计数",
|
478 |
-
interactive=False,
|
479 |
-
elem_classes="token-count"
|
480 |
-
)
|
481 |
|
482 |
# 示例问题
|
483 |
examples = [
|
@@ -492,6 +521,10 @@ def create_ui():
|
|
492 |
examples=examples,
|
493 |
inputs=question_input
|
494 |
)
|
|
|
|
|
|
|
|
|
495 |
|
496 |
gr.HTML("""
|
497 |
<div class="footer" id="about">
|
@@ -521,6 +554,8 @@ def create_ui():
|
|
521 |
# 主函数
|
522 |
def main():
|
523 |
try:
|
|
|
|
|
524 |
app = create_ui()
|
525 |
app.launch(share=False)
|
526 |
except Exception as e:
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
+
# Dictionary containing module descriptions
|
16 |
+
MODULE_DESCRIPTIONS = {
|
17 |
+
"run": "默认模式:使用OpenAI模型的默认的智能体协作模式,适合大多数任务。",
|
18 |
+
"run_mini":"使用使用OpenAI模型最小化配置处理任务",
|
19 |
+
"run_deepseek_zh":"使用deepseek模型处理中文任务",
|
20 |
+
"run_terminal_zh": "终端模式:可执行命令行操作,支持网络搜索、文件处理等功能。适合需要系统交互的任务,使用OpenAI模型",
|
21 |
+
"run_gaia_roleplaying":"GAIA基准测试实现,用于评估Agent能力",
|
22 |
+
"run_openai_compatiable_model":"使用openai兼容模型处理任务",
|
23 |
+
"run_ollama":"使用本地ollama模型处理任务",
|
24 |
+
"run_qwen_mini_zh":"使用qwen模型最小化配置处理任务",
|
25 |
+
"run_qwen_zh":"使用qwen模型处理任务",
|
26 |
}
|
27 |
|
28 |
+
# API帮助信息
|
29 |
+
API_HELP_INFO = {
|
30 |
+
"OPENAI_API_KEY": {
|
31 |
+
"name": "OpenAI API",
|
32 |
+
"desc": "OpenAI API密钥,用于访问GPT系列模型",
|
33 |
+
"url": "https://platform.openai.com/api-keys"
|
34 |
+
},
|
35 |
+
"QWEN_API_KEY": {
|
36 |
+
"name": "通义千问 API",
|
37 |
+
"desc": "阿里云通义千问API密钥",
|
38 |
+
"url": "https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key"
|
39 |
+
},
|
40 |
+
"DEEPSEEK_API_KEY": {
|
41 |
+
"name": "DeepSeek API",
|
42 |
+
"desc": "DeepSeek API密钥",
|
43 |
+
"url": "https://platform.deepseek.com/api_keys"
|
44 |
+
},
|
45 |
+
"GOOGLE_API_KEY": {
|
46 |
+
"name": "Google Search API",
|
47 |
+
"desc": "Google自定义搜索API密钥",
|
48 |
+
"url": "https://developers.google.com/custom-search/v1/overview"
|
49 |
+
},
|
50 |
+
"SEARCH_ENGINE_ID": {
|
51 |
+
"name": "Google Search Engine ID",
|
52 |
+
"desc": "Google自定义搜索引擎ID",
|
53 |
+
"url": "https://developers.google.com/custom-search/v1/overview"
|
54 |
+
},
|
55 |
+
"HF_TOKEN": {
|
56 |
+
"name": "Hugging Face API",
|
57 |
+
"desc": "Hugging Face API令牌",
|
58 |
+
"url": "https://huggingface.co/join"
|
59 |
+
},
|
60 |
+
"CHUNKR_API_KEY": {
|
61 |
+
"name": "Chunkr API",
|
62 |
+
"desc": "Chunkr API密钥",
|
63 |
+
"url": "https://chunkr.ai/"
|
64 |
+
},
|
65 |
+
"FIRECRAWL_API_KEY": {
|
66 |
+
"name": "Firecrawl API",
|
67 |
+
"desc": "Firecrawl API密钥",
|
68 |
+
"url": "https://www.firecrawl.dev/"
|
69 |
}
|
70 |
}
|
71 |
|
72 |
+
# 默认环境变量模板
|
73 |
+
DEFAULT_ENV_TEMPLATE = """# MODEL & API (See https://docs.camel-ai.org/key_modules/models.html#)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
|
75 |
+
# OPENAI API
|
76 |
+
# OPENAI_API_KEY= ""
|
77 |
+
# OPENAI_API_BASE_URL=""
|
|
|
|
|
78 |
|
79 |
+
# Qwen API (https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key)
|
80 |
+
# QWEN_API_KEY=""
|
|
|
|
|
|
|
|
|
81 |
|
82 |
+
# DeepSeek API (https://platform.deepseek.com/api_keys)
|
83 |
+
# DEEPSEEK_API_KEY=""
|
|
|
|
|
84 |
|
85 |
+
#===========================================
|
86 |
+
# Tools & Services API
|
87 |
+
#===========================================
|
|
|
|
|
88 |
|
89 |
+
# Google Search API (https://developers.google.com/custom-search/v1/overview)
|
90 |
+
GOOGLE_API_KEY=""
|
91 |
+
SEARCH_ENGINE_ID=""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
+
# Hugging Face API (https://huggingface.co/join)
|
94 |
+
HF_TOKEN=""
|
|
|
|
|
95 |
|
96 |
+
# Chunkr API (https://chunkr.ai/)
|
97 |
+
CHUNKR_API_KEY=""
|
|
|
|
|
|
|
98 |
|
99 |
+
# Firecrawl API (https://www.firecrawl.dev/)
|
100 |
+
FIRECRAWL_API_KEY=""
|
101 |
+
#FIRECRAWL_API_URL="https://api.firecrawl.dev"
|
|
|
|
|
102 |
"""
|
103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
def format_chat_history(chat_history: List[Dict[str, str]]) -> List[List[str]]:
|
105 |
"""将聊天历史格式化为Gradio聊天组件可接受的格式
|
106 |
|
|
|
158 |
)
|
159 |
|
160 |
try:
|
161 |
+
# 确保环境变量已加载
|
162 |
+
load_dotenv(find_dotenv(), override=True)
|
163 |
# 检查模块是否在MODULE_DESCRIPTIONS中
|
164 |
if example_module not in MODULE_DESCRIPTIONS:
|
165 |
return (
|
|
|
253 |
"""返回所选模块的描述"""
|
254 |
return MODULE_DESCRIPTIONS.get(module_name, "无可用描述")
|
255 |
|
256 |
+
# 环境变量管理功能
|
257 |
+
def init_env_file():
|
258 |
+
"""初始化.env文件如果不存在"""
|
259 |
+
dotenv_path = find_dotenv()
|
260 |
+
if not dotenv_path:
|
261 |
+
with open(".env", "w") as f:
|
262 |
+
f.write(DEFAULT_ENV_TEMPLATE)
|
263 |
+
dotenv_path = find_dotenv()
|
264 |
+
return dotenv_path
|
265 |
+
|
266 |
+
def load_env_vars():
|
267 |
+
"""加载环境变量并返回字典格式"""
|
268 |
+
dotenv_path = init_env_file()
|
269 |
+
load_dotenv(dotenv_path, override=True)
|
270 |
+
|
271 |
+
env_vars = {}
|
272 |
+
with open(dotenv_path, "r") as f:
|
273 |
+
for line in f:
|
274 |
+
line = line.strip()
|
275 |
+
if line and not line.startswith("#"):
|
276 |
+
if "=" in line:
|
277 |
+
key, value = line.split("=", 1)
|
278 |
+
env_vars[key.strip()] = value.strip().strip('"\'')
|
279 |
+
|
280 |
+
return env_vars
|
281 |
+
|
282 |
+
def save_env_vars(env_vars):
|
283 |
+
"""保存环境变量到.env文件"""
|
284 |
+
try:
|
285 |
+
dotenv_path = init_env_file()
|
286 |
+
|
287 |
+
# 保存每个环境变量
|
288 |
+
for key, value in env_vars.items():
|
289 |
+
if key and key.strip(): # 确保键不为空
|
290 |
+
set_key(dotenv_path, key.strip(), value.strip())
|
291 |
+
|
292 |
+
# 重新加载环境变量以确保��效
|
293 |
+
load_dotenv(dotenv_path, override=True)
|
294 |
+
|
295 |
+
return True, "环境变量已成功保存!"
|
296 |
+
except Exception as e:
|
297 |
+
return False, f"保存环境变量时出错: {str(e)}"
|
298 |
+
|
299 |
+
def add_env_var(key, value):
|
300 |
+
"""添加或更新单个环境变量"""
|
301 |
+
try:
|
302 |
+
if not key or not key.strip():
|
303 |
+
return False, "变量名不能为空"
|
304 |
+
|
305 |
+
dotenv_path = init_env_file()
|
306 |
+
set_key(dotenv_path, key.strip(), value.strip())
|
307 |
+
load_dotenv(dotenv_path, override=True)
|
308 |
+
|
309 |
+
return True, f"环境变量 {key} 已成功添加/更新!"
|
310 |
+
except Exception as e:
|
311 |
+
return False, f"添加环境变量时出错: {str(e)}"
|
312 |
+
|
313 |
+
def delete_env_var(key):
|
314 |
+
"""删除环境变量"""
|
315 |
+
try:
|
316 |
+
if not key or not key.strip():
|
317 |
+
return False, "变量名不能为空"
|
318 |
+
|
319 |
+
dotenv_path = init_env_file()
|
320 |
+
unset_key(dotenv_path, key.strip())
|
321 |
+
|
322 |
+
# 从当前进程环境中也删除
|
323 |
+
if key in os.environ:
|
324 |
+
del os.environ[key]
|
325 |
+
|
326 |
+
return True, f"环境变量 {key} 已成功删除!"
|
327 |
+
except Exception as e:
|
328 |
+
return False, f"删除环境变量时出错: {str(e)}"
|
329 |
+
|
330 |
+
def mask_sensitive_value(key: str, value: str) -> str:
|
331 |
+
"""对敏感信息进行掩码处理
|
332 |
+
|
333 |
+
Args:
|
334 |
+
key: 环境变量名
|
335 |
+
value: 环境变量值
|
336 |
+
|
337 |
+
Returns:
|
338 |
+
str: 处理后的值
|
339 |
+
"""
|
340 |
+
# 定义需要掩码的敏感关键词
|
341 |
+
sensitive_keywords = ['key', 'token', 'secret', 'password', 'api']
|
342 |
+
|
343 |
+
# 检查是否包含敏感关键词(不区分大小写)
|
344 |
+
is_sensitive = any(keyword in key.lower() for keyword in sensitive_keywords)
|
345 |
+
|
346 |
+
if is_sensitive and value:
|
347 |
+
# 如果是敏感信息且有值,则显示掩码
|
348 |
+
return '*' * 8
|
349 |
+
return value
|
350 |
+
|
351 |
+
def update_env_table():
|
352 |
+
"""更新环境变量表格显示,对敏感信息进行掩码处理"""
|
353 |
+
env_vars = load_env_vars()
|
354 |
+
# 对敏感值进行掩码处理
|
355 |
+
masked_env_vars = [[k, mask_sensitive_value(k, v)] for k, v in env_vars.items()]
|
356 |
+
return masked_env_vars
|
357 |
+
|
358 |
def create_ui():
|
359 |
"""创建增强版Gradio界面"""
|
360 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app:
|
361 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
362 |
|
363 |
with gr.Row():
|
364 |
+
with gr.Column(scale=1):
|
365 |
question_input = gr.Textbox(
|
366 |
lines=5,
|
367 |
placeholder="请输入您的问题...",
|
|
|
388 |
)
|
389 |
|
390 |
run_button = gr.Button("运行", variant="primary", elem_classes="primary")
|
391 |
+
|
392 |
+
status_output = gr.Textbox(label="状态", interactive=False)
|
393 |
+
token_count_output = gr.Textbox(
|
394 |
+
label="令牌计数",
|
395 |
+
interactive=False,
|
396 |
+
elem_classes="token-count"
|
397 |
+
)
|
398 |
|
399 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
|
401 |
+
with gr.Tabs(scale=2):
|
402 |
+
with gr.TabItem("回答"):
|
403 |
+
answer_output = gr.Textbox(
|
404 |
+
label="回答",
|
405 |
+
lines=10,
|
406 |
+
elem_classes="answer-box"
|
407 |
+
)
|
408 |
+
|
409 |
+
with gr.TabItem("对话历史"):
|
410 |
+
chat_output = gr.Chatbot(
|
411 |
+
label="完整对话记录",
|
412 |
+
elem_classes="chat-container",
|
413 |
+
height=500
|
414 |
+
)
|
415 |
|
416 |
+
with gr.TabItem("环境变量管理", id="env-settings"):
|
417 |
+
gr.Markdown("""
|
418 |
+
## 环境变量管理
|
419 |
+
|
420 |
+
在此处设置模型API密钥和其他服务凭证。这些信息将保存在本地的`.env`文件中,确保您的API密钥安全存储且不会上传到网络。
|
421 |
+
""")
|
422 |
+
|
423 |
+
# 添加API密钥获取指南
|
424 |
+
gr.Markdown("### API密钥获取指南")
|
425 |
+
|
426 |
+
for key, info in API_HELP_INFO.items():
|
427 |
+
with gr.Accordion(f"{info['name']} ({key})", open=False):
|
428 |
+
gr.Markdown(f"""
|
429 |
+
- **说明**: {info['desc']}
|
430 |
+
- **获取地址**: [{info['url']}]({info['url']})
|
431 |
+
""")
|
432 |
+
|
433 |
+
gr.Markdown("---")
|
434 |
+
|
435 |
+
# 环境变量表格
|
436 |
+
env_table = gr.Dataframe(
|
437 |
+
headers=["变量名", "值"],
|
438 |
+
datatype=["str", "str"],
|
439 |
+
row_count=10,
|
440 |
+
col_count=(2, "fixed"),
|
441 |
+
value=update_env_table,
|
442 |
+
label="当前环境变量",
|
443 |
+
interactive=False
|
444 |
+
)
|
445 |
+
|
446 |
+
with gr.Row():
|
447 |
+
with gr.Column(scale=1):
|
448 |
+
new_env_key = gr.Textbox(label="变量名", placeholder="例如: OPENAI_API_KEY")
|
449 |
+
with gr.Column(scale=2):
|
450 |
+
new_env_value = gr.Textbox(label="值", placeholder="��入API密钥或其他配置值")
|
451 |
+
|
452 |
+
with gr.Row():
|
453 |
+
add_env_button = gr.Button("添加/更新变量", variant="primary")
|
454 |
+
refresh_button = gr.Button("刷新变量列表")
|
455 |
+
delete_env_button = gr.Button("删除选定变量", variant="stop")
|
456 |
+
|
457 |
+
env_status = gr.Textbox(label="状态", interactive=False)
|
458 |
+
|
459 |
+
# 变量选择器(用于删除)
|
460 |
+
env_var_to_delete = gr.Dropdown(
|
461 |
+
choices=[],
|
462 |
+
label="选择要删除的变量",
|
463 |
+
interactive=True
|
464 |
+
)
|
465 |
+
|
466 |
+
# 更新变量选择器的选项
|
467 |
+
def update_delete_dropdown():
|
468 |
+
env_vars = load_env_vars()
|
469 |
+
return gr.Dropdown.update(choices=list(env_vars.keys()))
|
470 |
+
|
471 |
+
# 连接事件处理函数
|
472 |
+
add_env_button.click(
|
473 |
+
fn=lambda k, v: add_env_var(k, v),
|
474 |
+
inputs=[new_env_key, new_env_value],
|
475 |
+
outputs=[env_status]
|
476 |
+
).then(
|
477 |
+
fn=update_env_table,
|
478 |
+
outputs=[env_table]
|
479 |
+
).then(
|
480 |
+
fn=update_delete_dropdown,
|
481 |
+
outputs=[env_var_to_delete]
|
482 |
+
).then(
|
483 |
+
fn=lambda: ("", ""), # 修改为返回两个空字符串的元组
|
484 |
+
outputs=[new_env_key, new_env_value]
|
485 |
+
)
|
486 |
+
|
487 |
+
refresh_button.click(
|
488 |
+
fn=update_env_table,
|
489 |
+
outputs=[env_table]
|
490 |
+
).then(
|
491 |
+
fn=update_delete_dropdown,
|
492 |
+
outputs=[env_var_to_delete]
|
493 |
+
)
|
494 |
+
|
495 |
+
delete_env_button.click(
|
496 |
+
fn=lambda k: delete_env_var(k),
|
497 |
+
inputs=[env_var_to_delete],
|
498 |
+
outputs=[env_status]
|
499 |
+
).then(
|
500 |
+
fn=update_env_table,
|
501 |
+
outputs=[env_table]
|
502 |
+
).then(
|
503 |
+
fn=update_delete_dropdown,
|
504 |
+
outputs=[env_var_to_delete]
|
505 |
+
)
|
506 |
+
|
507 |
+
|
508 |
|
509 |
+
|
|
|
|
|
|
|
|
|
|
|
510 |
|
511 |
# 示例问题
|
512 |
examples = [
|
|
|
521 |
examples=examples,
|
522 |
inputs=question_input
|
523 |
)
|
524 |
+
|
525 |
+
|
526 |
+
|
527 |
+
|
528 |
|
529 |
gr.HTML("""
|
530 |
<div class="footer" id="about">
|
|
|
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:
|