Wendong-Fan commited on
Commit
e7c5aed
·
1 Parent(s): 6d73180

update web app and camel version

Browse files
Files changed (8) hide show
  1. README.md +3 -0
  2. README_zh.md +3 -0
  3. owl/app.py +57 -18
  4. owl/app_en.py +865 -0
  5. pyproject.toml +1 -1
  6. requirements.txt +1 -1
  7. run_app_en.py +61 -0
  8. uv.lock +4 -4
README.md CHANGED
@@ -394,6 +394,9 @@ OWL now includes a web-based user interface that makes it easier to interact wit
394
 
395
  ```bash
396
  python run_app.py
 
 
 
397
  ```
398
 
399
  The web interface provides the following features:
 
394
 
395
  ```bash
396
  python run_app.py
397
+
398
+ # For English Version
399
+ python run_app_en.py
400
  ```
401
 
402
  The web interface provides the following features:
README_zh.md CHANGED
@@ -390,6 +390,9 @@ OWL 现在包含一个基于网页的用户界面,使与系统交互变得更
390
 
391
  ```bash
392
  python run_app.py
 
 
 
393
  ```
394
 
395
  网页界面提供以下功能:
 
390
 
391
  ```bash
392
  python run_app.py
393
+
394
+ # 英文版本
395
+ python run_app_en.py
396
  ```
397
 
398
  网页界面提供以下功能:
owl/app.py CHANGED
@@ -198,13 +198,16 @@ def save_env_vars(env_vars):
198
  if value: # 只保存非空值
199
  # 确保值是字符串形式,并用引号包裹
200
  value = str(value) # 确保值是字符串
201
- if not (value.startswith('"') and value.endswith('"')) and not (
202
- value.startswith("'") and value.endswith("'")
203
- ):
204
- value = f'"{value}"'
205
- existing_content[key] = value
206
- # 同时更新当前进程的环境变量
207
- os.environ[key] = value.strip("\"'")
 
 
 
208
 
209
  # 写入.env文件
210
  with open(env_path, "w", encoding="utf-8") as f:
@@ -290,12 +293,21 @@ def delete_custom_env_var(name):
290
  if env_path.exists():
291
  with open(env_path, "r", encoding="utf-8") as f:
292
  lines = f.readlines()
293
-
294
  with open(env_path, "w", encoding="utf-8") as f:
295
  for line in lines:
296
- if not line.strip().startswith(f"{name}="):
297
- f.write(line)
298
-
 
 
 
 
 
 
 
 
 
299
  # 从当前进程的环境变量中删除
300
  if name in os.environ:
301
  del os.environ[name]
@@ -315,8 +327,14 @@ def terminate_process():
315
  if os.name == "nt":
316
  # 获取进程ID
317
  pid = current_process.pid
318
- # 使用taskkill命令终止进程及其子进程
319
- subprocess.run(f"taskkill /F /T /PID {pid}", shell=True)
 
 
 
 
 
 
320
  else:
321
  # 在Unix上使用SIGTERM和SIGKILL
322
  current_process.terminate()
@@ -387,12 +405,24 @@ def run_script(script_dropdown, question, progress=gr.Progress()):
387
  text=True,
388
  bufsize=1,
389
  env=env,
 
390
  )
391
 
392
  # 创建线程来读取输出
393
  def read_output():
394
  try:
395
- with open(log_file, "w", encoding="utf-8") as f:
 
 
 
 
 
 
 
 
 
 
 
396
  for line in iter(current_process.stdout.readline, ""):
397
  if line:
398
  # 写入日志文件
@@ -645,11 +675,17 @@ def create_ui():
645
  )
646
 
647
  # 更改和删除自定义环境变量部分
648
- with gr.Accordion("更改或删除自定义环境变量", open=True, visible=len(ENV_GROUPS["自定义环境变量"]) > 0) as update_delete_accordion:
 
 
 
 
649
  with gr.Row():
650
  # 创建下拉菜单,显示所有自定义环境变量
651
  custom_var_dropdown = gr.Dropdown(
652
- choices=[var["name"] for var in ENV_GROUPS["自定义环境变量"]],
 
 
653
  label="选择环境变量",
654
  interactive=True,
655
  )
@@ -663,7 +699,7 @@ def create_ui():
663
  with gr.Row():
664
  update_var_button = gr.Button("更新环境变量", variant="primary")
665
  delete_var_button = gr.Button("删除环境变量", variant="stop")
666
-
667
  update_var_status = gr.Textbox(label="操作状态", interactive=False)
668
 
669
  # 添加环境变量按钮点击事件
@@ -697,7 +733,10 @@ def create_ui():
697
 
698
  # 当自定义环境变量列表更新时,更新下拉菜单选项
699
  custom_vars_list.change(
700
- fn=lambda vars: {"choices": [var["name"] for var in vars], "value": None},
 
 
 
701
  inputs=[custom_vars_list],
702
  outputs=[custom_var_dropdown],
703
  )
 
198
  if value: # 只保存非空值
199
  # 确保值是字符串形式,并用引号包裹
200
  value = str(value) # 确保值是字符串
201
+
202
+ # 先移除现有的引号(如果有)
203
+ stripped_value = value.strip("\"'")
204
+
205
+ # 用双引号包裹值,确保特殊字符被正确处理
206
+ quoted_value = f'"{stripped_value}"'
207
+ existing_content[key] = quoted_value
208
+
209
+ # 同时更新当前进程的环境变量(使用未引用的值)
210
+ os.environ[key] = stripped_value
211
 
212
  # 写入.env文件
213
  with open(env_path, "w", encoding="utf-8") as f:
 
293
  if env_path.exists():
294
  with open(env_path, "r", encoding="utf-8") as f:
295
  lines = f.readlines()
296
+
297
  with open(env_path, "w", encoding="utf-8") as f:
298
  for line in lines:
299
+ # 更精确地匹配环境变量行
300
+ # 检查是否为非注释行且包含变量名=
301
+ line_stripped = line.strip()
302
+ if line_stripped.startswith("#") or "=" not in line_stripped:
303
+ f.write(line) # 保留注释行和不包含=的行
304
+ continue
305
+
306
+ # 提取变量名并检查是否与要删除的变量匹配
307
+ var_name = line_stripped.split("=", 1)[0].strip()
308
+ if var_name != name:
309
+ f.write(line) # 保留不匹配的变量
310
+
311
  # 从当前进程的环境变量中删除
312
  if name in os.environ:
313
  del os.environ[name]
 
327
  if os.name == "nt":
328
  # 获取进程ID
329
  pid = current_process.pid
330
+ # 使用taskkill命令终止进程及其子进程 - 避免使用shell=True以提高安全性
331
+ try:
332
+ subprocess.run(
333
+ ["taskkill", "/F", "/T", "/PID", str(pid)], check=False
334
+ )
335
+ except subprocess.SubprocessError as e:
336
+ log_queue.put(f"终止进程时出错: {str(e)}\n")
337
+ return f"❌ 终止进程时出错: {str(e)}"
338
  else:
339
  # 在Unix上使用SIGTERM和SIGKILL
340
  current_process.terminate()
 
405
  text=True,
406
  bufsize=1,
407
  env=env,
408
+ encoding="utf-8",
409
  )
410
 
411
  # 创建线程来读取输出
412
  def read_output():
413
  try:
414
+ # 使用唯一的时间戳确保日志文件名不重复
415
+ timestamp_unique = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
416
+ unique_log_file = (
417
+ log_dir / f"{script_name.replace('.py', '')}_{timestamp_unique}.log"
418
+ )
419
+
420
+ # 使用这个唯一的文件名写入日志
421
+ with open(unique_log_file, "w", encoding="utf-8") as f:
422
+ # 更新全局日志文件路径
423
+ nonlocal log_file
424
+ log_file = unique_log_file
425
+
426
  for line in iter(current_process.stdout.readline, ""):
427
  if line:
428
  # 写入日志文件
 
675
  )
676
 
677
  # 更改和删除自定义环境变量部分
678
+ with gr.Accordion(
679
+ "更改或删除自定义环境变量",
680
+ open=True,
681
+ visible=len(ENV_GROUPS["自定义环境变量"]) > 0,
682
+ ) as update_delete_accordion:
683
  with gr.Row():
684
  # 创建下拉菜单,显示所有自定义环境变量
685
  custom_var_dropdown = gr.Dropdown(
686
+ choices=[
687
+ var["name"] for var in ENV_GROUPS["自定义环境变量"]
688
+ ],
689
  label="选择环境变量",
690
  interactive=True,
691
  )
 
699
  with gr.Row():
700
  update_var_button = gr.Button("更新环境变量", variant="primary")
701
  delete_var_button = gr.Button("删除环境变量", variant="stop")
702
+
703
  update_var_status = gr.Textbox(label="操作状态", interactive=False)
704
 
705
  # 添加环境变量按钮点击事件
 
733
 
734
  # 当自定义环境变量列表更新时,更新下拉菜单选项
735
  custom_vars_list.change(
736
+ fn=lambda vars: {
737
+ "choices": [var["name"] for var in vars],
738
+ "value": None,
739
+ },
740
  inputs=[custom_vars_list],
741
  outputs=[custom_var_dropdown],
742
  )
owl/app_en.py ADDED
@@ -0,0 +1,865 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ import os
15
+ import sys
16
+ import gradio as gr
17
+ import subprocess
18
+ import threading
19
+ import time
20
+ from datetime import datetime
21
+ import queue
22
+ from pathlib import Path
23
+ import json
24
+ import signal
25
+ import dotenv
26
+
27
+ # Set up log queue
28
+ log_queue: queue.Queue[str] = queue.Queue()
29
+
30
+ # Currently running process
31
+ current_process = None
32
+ process_lock = threading.Lock()
33
+
34
+ # Script options
35
+ SCRIPTS = {
36
+ "Qwen Mini (Chinese)": "run_qwen_mini_zh.py",
37
+ "Qwen (Chinese)": "run_qwen_zh.py",
38
+ "Mini": "run_mini.py",
39
+ "DeepSeek (Chinese)": "run_deepseek_zh.py",
40
+ "Default": "run.py",
41
+ "GAIA Roleplaying": "run_gaia_roleplaying.py",
42
+ "OpenAI Compatible": "run_openai_compatiable_model.py",
43
+ "Ollama": "run_ollama.py",
44
+ }
45
+
46
+ # Script descriptions
47
+ SCRIPT_DESCRIPTIONS = {
48
+ "Qwen Mini (Chinese)": "Uses the Chinese version of Alibaba Cloud's Qwen model, suitable for Chinese Q&A and tasks",
49
+ "Qwen (Chinese)": "Uses Alibaba Cloud's Qwen model, supports various tools and functions",
50
+ "Mini": "Lightweight version, uses OpenAI GPT-4o model",
51
+ "DeepSeek (Chinese)": "Uses DeepSeek model, suitable for non-multimodal tasks",
52
+ "Default": "Default OWL implementation, uses OpenAI GPT-4o model and full set of tools",
53
+ "GAIA Roleplaying": "GAIA benchmark implementation, used to evaluate model capabilities",
54
+ "OpenAI Compatible": "Uses third-party models compatible with OpenAI API, supports custom API endpoints",
55
+ "Ollama": "Uses Ollama API",
56
+ }
57
+
58
+ # Environment variable groups
59
+ ENV_GROUPS = {
60
+ "Model API": [
61
+ {
62
+ "name": "OPENAI_API_KEY",
63
+ "label": "OpenAI API Key",
64
+ "type": "password",
65
+ "required": False,
66
+ "help": "OpenAI API key for accessing GPT models. Get it from: https://platform.openai.com/api-keys",
67
+ },
68
+ {
69
+ "name": "OPENAI_API_BASE_URL",
70
+ "label": "OpenAI API Base URL",
71
+ "type": "text",
72
+ "required": False,
73
+ "help": "Base URL for OpenAI API, optional. Set this if using a proxy or custom endpoint.",
74
+ },
75
+ {
76
+ "name": "QWEN_API_KEY",
77
+ "label": "Alibaba Cloud Qwen API Key",
78
+ "type": "password",
79
+ "required": False,
80
+ "help": "Alibaba Cloud Qwen API key for accessing Qwen models. Get it from: https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key",
81
+ },
82
+ {
83
+ "name": "DEEPSEEK_API_KEY",
84
+ "label": "DeepSeek API Key",
85
+ "type": "password",
86
+ "required": False,
87
+ "help": "DeepSeek API key for accessing DeepSeek models. Get it from: https://platform.deepseek.com/api_keys",
88
+ },
89
+ ],
90
+ "Search Tools": [
91
+ {
92
+ "name": "GOOGLE_API_KEY",
93
+ "label": "Google API Key",
94
+ "type": "password",
95
+ "required": False,
96
+ "help": "Google Search API key for web search functionality. Get it from: https://developers.google.com/custom-search/v1/overview",
97
+ },
98
+ {
99
+ "name": "SEARCH_ENGINE_ID",
100
+ "label": "Search Engine ID",
101
+ "type": "text",
102
+ "required": False,
103
+ "help": "Google Custom Search Engine ID, used with Google API key. Get it from: https://developers.google.com/custom-search/v1/overview",
104
+ },
105
+ ],
106
+ "Other Tools": [
107
+ {
108
+ "name": "HF_TOKEN",
109
+ "label": "Hugging Face Token",
110
+ "type": "password",
111
+ "required": False,
112
+ "help": "Hugging Face API token for accessing Hugging Face models and datasets. Get it from: https://huggingface.co/join",
113
+ },
114
+ {
115
+ "name": "CHUNKR_API_KEY",
116
+ "label": "Chunkr API Key",
117
+ "type": "password",
118
+ "required": False,
119
+ "help": "Chunkr API key for document processing functionality. Get it from: https://chunkr.ai/",
120
+ },
121
+ {
122
+ "name": "FIRECRAWL_API_KEY",
123
+ "label": "Firecrawl API Key",
124
+ "type": "password",
125
+ "required": False,
126
+ "help": "Firecrawl API key for web crawling functionality. Get it from: https://www.firecrawl.dev/",
127
+ },
128
+ ],
129
+ "Custom Environment Variables": [], # User-defined environment variables will be stored here
130
+ }
131
+
132
+
133
+ def get_script_info(script_name):
134
+ """Get detailed information about the script"""
135
+ return SCRIPT_DESCRIPTIONS.get(script_name, "No description available")
136
+
137
+
138
+ def load_env_vars():
139
+ """Load environment variables"""
140
+ env_vars = {}
141
+ # Try to load from .env file
142
+ dotenv.load_dotenv()
143
+
144
+ # Get all environment variables
145
+ for group in ENV_GROUPS.values():
146
+ for var in group:
147
+ env_vars[var["name"]] = os.environ.get(var["name"], "")
148
+
149
+ # Load other environment variables that may exist in the .env file
150
+ if Path(".env").exists():
151
+ with open(".env", "r", encoding="utf-8") as f:
152
+ for line in f:
153
+ line = line.strip()
154
+ if line and not line.startswith("#") and "=" in line:
155
+ key, value = line.split("=", 1)
156
+ key = key.strip()
157
+ value = value.strip().strip("\"'")
158
+
159
+ # Check if it's a known environment variable
160
+ known_var = False
161
+ for group in ENV_GROUPS.values():
162
+ if any(var["name"] == key for var in group):
163
+ known_var = True
164
+ break
165
+
166
+ # If it's not a known environment variable, add it to the custom environment variables group
167
+ if not known_var and key not in env_vars:
168
+ ENV_GROUPS["Custom Environment Variables"].append(
169
+ {
170
+ "name": key,
171
+ "label": key,
172
+ "type": "text",
173
+ "required": False,
174
+ "help": "User-defined environment variable",
175
+ }
176
+ )
177
+ env_vars[key] = value
178
+
179
+ return env_vars
180
+
181
+
182
+ def save_env_vars(env_vars):
183
+ """Save environment variables to .env file"""
184
+ # Read existing .env file content
185
+ env_path = Path(".env")
186
+ existing_content = {}
187
+
188
+ if env_path.exists():
189
+ with open(env_path, "r", encoding="utf-8") as f:
190
+ for line in f:
191
+ line = line.strip()
192
+ if line and not line.startswith("#") and "=" in line:
193
+ key, value = line.split("=", 1)
194
+ existing_content[key.strip()] = value.strip()
195
+
196
+ # Update environment variables
197
+ for key, value in env_vars.items():
198
+ if value: # Only save non-empty values
199
+ # Ensure the value is a string and wrapped in quotes
200
+ value = str(value) # Ensure the value is a string
201
+
202
+ # First remove existing quotes (if any)
203
+ stripped_value = value.strip("\"'")
204
+
205
+ # Wrap the value in double quotes to ensure special characters are handled correctly
206
+ quoted_value = f'"{stripped_value}"'
207
+ existing_content[key] = quoted_value
208
+
209
+ # Also update the environment variable for the current process (using the unquoted value)
210
+ os.environ[key] = stripped_value
211
+
212
+ # Write to .env file
213
+ with open(env_path, "w", encoding="utf-8") as f:
214
+ for key, value in existing_content.items():
215
+ f.write(f"{key}={value}\n")
216
+
217
+ return "✅ Environment variables saved"
218
+
219
+
220
+ def add_custom_env_var(name, value, var_type):
221
+ """Add custom environment variable"""
222
+ if not name:
223
+ return "❌ Environment variable name cannot be empty", None
224
+
225
+ # Check if an environment variable with the same name already exists
226
+ for group in ENV_GROUPS.values():
227
+ if any(var["name"] == name for var in group):
228
+ return f"❌ Environment variable {name} already exists", None
229
+
230
+ # Add to custom environment variables group
231
+ ENV_GROUPS["自定义环境变量"].append(
232
+ {
233
+ "name": name,
234
+ "label": name,
235
+ "type": var_type,
236
+ "required": False,
237
+ "help": "User-defined environment variable",
238
+ }
239
+ )
240
+
241
+ # Save environment variables
242
+ env_vars = {name: value}
243
+ save_env_vars(env_vars)
244
+
245
+ # Return success message and updated environment variable group
246
+ return f"✅ Added environment variable {name}", ENV_GROUPS[
247
+ "Custom Environment Variables"
248
+ ]
249
+
250
+
251
+ def update_custom_env_var(name, value, var_type):
252
+ """Update custom environment variable"""
253
+ if not name:
254
+ return "❌ Environment variable name cannot be empty", None
255
+
256
+ # Check if the environment variable exists in the custom environment variables group
257
+ found = False
258
+ for i, var in enumerate(ENV_GROUPS["Custom Environment Variables"]):
259
+ if var["name"] == name:
260
+ # Update type
261
+ ENV_GROUPS["Custom Environment Variables"][i]["type"] = var_type
262
+ found = True
263
+ break
264
+
265
+ if not found:
266
+ return f"❌ Custom environment variable {name} does not exist", None
267
+
268
+ # Save environment variable value
269
+ env_vars = {name: value}
270
+ save_env_vars(env_vars)
271
+
272
+ # Return success message and updated environment variable group
273
+ return f"✅ Updated environment variable {name}", ENV_GROUPS[
274
+ "Custom Environment Variables"
275
+ ]
276
+
277
+
278
+ def delete_custom_env_var(name):
279
+ """Delete custom environment variable"""
280
+ if not name:
281
+ return "❌ Environment variable name cannot be empty", None
282
+
283
+ # Check if the environment variable exists in the custom environment variables group
284
+ found = False
285
+ for i, var in enumerate(ENV_GROUPS["Custom Environment Variables"]):
286
+ if var["name"] == name:
287
+ # Delete from custom environment variables group
288
+ del ENV_GROUPS["Custom Environment Variables"][i]
289
+ found = True
290
+ break
291
+
292
+ if not found:
293
+ return f"❌ Custom environment variable {name} does not exist", None
294
+
295
+ # Delete the environment variable from .env file
296
+ env_path = Path(".env")
297
+ if env_path.exists():
298
+ with open(env_path, "r", encoding="utf-8") as f:
299
+ lines = f.readlines()
300
+
301
+ with open(env_path, "w", encoding="utf-8") as f:
302
+ for line in lines:
303
+ # More precisely match environment variable lines
304
+ # Check if it's a non-comment line and contains variable_name=
305
+ line_stripped = line.strip()
306
+ if line_stripped.startswith("#") or "=" not in line_stripped:
307
+ f.write(line) # Keep comment lines and lines without =
308
+ continue
309
+
310
+ # Extract variable name and check if it matches the variable to be deleted
311
+ var_name = line_stripped.split("=", 1)[0].strip()
312
+ if var_name != name:
313
+ f.write(line) # Keep variables that don't match
314
+
315
+ # Delete from current process environment variables
316
+ if name in os.environ:
317
+ del os.environ[name]
318
+
319
+ # Return success message and updated environment variable group
320
+ return f"✅ Deleted environment variable {name}", ENV_GROUPS[
321
+ "Custom Environment Variables"
322
+ ]
323
+
324
+
325
+ def terminate_process():
326
+ """Terminate the currently running process"""
327
+ global current_process
328
+
329
+ with process_lock:
330
+ if current_process is not None and current_process.poll() is None:
331
+ try:
332
+ # On Windows, use taskkill to forcibly terminate the process tree
333
+ if os.name == "nt":
334
+ # Get process ID
335
+ pid = current_process.pid
336
+ # Use taskkill command to terminate the process and its children - avoid using shell=True for better security
337
+ try:
338
+ subprocess.run(
339
+ ["taskkill", "/F", "/T", "/PID", str(pid)], check=False
340
+ )
341
+ except subprocess.SubprocessError as e:
342
+ log_queue.put(f"Error terminating process: {str(e)}\n")
343
+ return f"❌ Error terminating process: {str(e)}"
344
+ else:
345
+ # On Unix, use SIGTERM and SIGKILL
346
+ current_process.terminate()
347
+ try:
348
+ current_process.wait(timeout=3)
349
+ except subprocess.TimeoutExpired:
350
+ current_process.kill()
351
+
352
+ # Wait for process to terminate
353
+ try:
354
+ current_process.wait(timeout=2)
355
+ except subprocess.TimeoutExpired:
356
+ pass # Already tried to force terminate, ignore timeout
357
+
358
+ log_queue.put("Process terminated\n")
359
+ return "✅ Process terminated"
360
+ except Exception as e:
361
+ log_queue.put(f"Error terminating process: {str(e)}\n")
362
+ return f"❌ Error terminating process: {str(e)}"
363
+ else:
364
+ return "❌ No process is currently running"
365
+
366
+
367
+ def run_script(script_dropdown, question, progress=gr.Progress()):
368
+ """Run the selected script and return the output"""
369
+ global current_process
370
+
371
+ script_name = SCRIPTS.get(script_dropdown)
372
+ if not script_name:
373
+ return "❌ Invalid script selection", "", "", "", None
374
+
375
+ if not question.strip():
376
+ return "Please enter a question!", "", "", "", None
377
+
378
+ # Clear the log queue
379
+ while not log_queue.empty():
380
+ log_queue.get()
381
+
382
+ # Create log directory
383
+ log_dir = Path("logs")
384
+ log_dir.mkdir(exist_ok=True)
385
+
386
+ # Create log file with timestamp
387
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
388
+ log_file = log_dir / f"{script_name.replace('.py', '')}_{timestamp}.log"
389
+
390
+ # Build command
391
+ cmd = [
392
+ sys.executable,
393
+ os.path.join("owl", "script_adapter.py"),
394
+ os.path.join("owl", script_name),
395
+ ]
396
+
397
+ # Create a copy of environment variables and add the question
398
+ env = os.environ.copy()
399
+ # Ensure question is a string type
400
+ if not isinstance(question, str):
401
+ question = str(question)
402
+ # Preserve newlines, but ensure it's a valid string
403
+ env["OWL_QUESTION"] = question
404
+
405
+ # Start the process
406
+ with process_lock:
407
+ current_process = subprocess.Popen(
408
+ cmd,
409
+ stdout=subprocess.PIPE,
410
+ stderr=subprocess.STDOUT,
411
+ text=True,
412
+ bufsize=1,
413
+ env=env,
414
+ encoding="utf-8",
415
+ )
416
+
417
+ # Create thread to read output
418
+ def read_output():
419
+ try:
420
+ # Use a unique timestamp to ensure log filename is not duplicated
421
+ timestamp_unique = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
422
+ unique_log_file = (
423
+ log_dir / f"{script_name.replace('.py', '')}_{timestamp_unique}.log"
424
+ )
425
+
426
+ # Use this unique filename to write logs
427
+ with open(unique_log_file, "w", encoding="utf-8") as f:
428
+ # Update global log file path
429
+ nonlocal log_file
430
+ log_file = unique_log_file
431
+
432
+ for line in iter(current_process.stdout.readline, ""):
433
+ if line:
434
+ # Write to log file
435
+ f.write(line)
436
+ f.flush()
437
+ # Add to queue
438
+ log_queue.put(line)
439
+ except Exception as e:
440
+ log_queue.put(f"Error reading output: {str(e)}\n")
441
+
442
+ # Start the reading thread
443
+ threading.Thread(target=read_output, daemon=True).start()
444
+
445
+ # Collect logs
446
+ logs = []
447
+ progress(0, desc="Running...")
448
+
449
+ # Wait for process to complete or timeout
450
+ start_time = time.time()
451
+ timeout = 1800 # 30 minutes timeout
452
+
453
+ while current_process.poll() is None:
454
+ # Check if timeout
455
+ if time.time() - start_time > timeout:
456
+ with process_lock:
457
+ if current_process.poll() is None:
458
+ if os.name == "nt":
459
+ current_process.send_signal(signal.CTRL_BREAK_EVENT)
460
+ else:
461
+ current_process.terminate()
462
+ log_queue.put("Execution timeout, process terminated\n")
463
+ break
464
+
465
+ # Get logs from queue
466
+ while not log_queue.empty():
467
+ log = log_queue.get()
468
+ logs.append(log)
469
+
470
+ # Update progress
471
+ elapsed = time.time() - start_time
472
+ progress(min(elapsed / 300, 0.99), desc="Running...")
473
+
474
+ # Short sleep to reduce CPU usage
475
+ time.sleep(0.1)
476
+
477
+ # Update log display once per second
478
+ yield (
479
+ status_message(current_process),
480
+ extract_answer(logs),
481
+ "".join(logs),
482
+ str(log_file),
483
+ None,
484
+ )
485
+
486
+ # Get remaining logs
487
+ while not log_queue.empty():
488
+ logs.append(log_queue.get())
489
+
490
+ # Extract chat history (if any)
491
+ chat_history = extract_chat_history(logs)
492
+
493
+ # Return final status and logs
494
+ return (
495
+ status_message(current_process),
496
+ extract_answer(logs),
497
+ "".join(logs),
498
+ str(log_file),
499
+ chat_history,
500
+ )
501
+
502
+
503
+ def status_message(process):
504
+ """Return status message based on process status"""
505
+ if process.poll() is None:
506
+ return "⏳ Running..."
507
+ elif process.returncode == 0:
508
+ return "✅ Execution successful"
509
+ else:
510
+ return f"❌ Execution failed (return code: {process.returncode})"
511
+
512
+
513
+ def extract_answer(logs):
514
+ """Extract answer from logs"""
515
+ answer = ""
516
+ for log in logs:
517
+ if "Answer:" in log:
518
+ answer = log.split("Answer:", 1)[1].strip()
519
+ break
520
+ return answer
521
+
522
+
523
+ def extract_chat_history(logs):
524
+ """Try to extract chat history from logs"""
525
+ try:
526
+ chat_json_str = ""
527
+ capture_json = False
528
+
529
+ for log in logs:
530
+ if "chat_history" in log:
531
+ # Start capturing JSON
532
+ start_idx = log.find("[")
533
+ if start_idx != -1:
534
+ capture_json = True
535
+ chat_json_str = log[start_idx:]
536
+ elif capture_json:
537
+ # Continue capturing JSON until finding the matching closing bracket
538
+ chat_json_str += log
539
+ if "]" in log:
540
+ # Found closing bracket, try to parse JSON
541
+ end_idx = chat_json_str.rfind("]") + 1
542
+ if end_idx > 0:
543
+ try:
544
+ # Clean up possible extra text
545
+ json_str = chat_json_str[:end_idx].strip()
546
+ chat_data = json.loads(json_str)
547
+
548
+ # Format for use with Gradio chat component
549
+ formatted_chat = []
550
+ for msg in chat_data:
551
+ if "role" in msg and "content" in msg:
552
+ role = (
553
+ "User" if msg["role"] == "user" else "Assistant"
554
+ )
555
+ formatted_chat.append([role, msg["content"]])
556
+ return formatted_chat
557
+ except json.JSONDecodeError:
558
+ # If parsing fails, continue capturing
559
+ pass
560
+ except Exception:
561
+ # Other errors, stop capturing
562
+ capture_json = False
563
+ except Exception:
564
+ pass
565
+ return None
566
+
567
+
568
+ def create_ui():
569
+ """Create Gradio interface"""
570
+ # Load environment variables
571
+ env_vars = load_env_vars()
572
+
573
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app:
574
+ gr.Markdown(
575
+ """
576
+ # 🦉 OWL Intelligent Assistant Platform
577
+
578
+ Select a model and enter your question, the system will run the corresponding script and display the results.
579
+ """
580
+ )
581
+
582
+ with gr.Tabs():
583
+ with gr.TabItem("Run Mode"):
584
+ with gr.Row():
585
+ with gr.Column(scale=1):
586
+ # Ensure default value is a key that exists in SCRIPTS
587
+ default_script = list(SCRIPTS.keys())[0] if SCRIPTS else None
588
+ script_dropdown = gr.Dropdown(
589
+ choices=list(SCRIPTS.keys()),
590
+ value=default_script,
591
+ label="Select Mode",
592
+ )
593
+
594
+ script_info = gr.Textbox(
595
+ value=get_script_info(default_script)
596
+ if default_script
597
+ else "",
598
+ label="Model Description",
599
+ interactive=False,
600
+ )
601
+
602
+ script_dropdown.change(
603
+ fn=lambda x: get_script_info(x),
604
+ inputs=script_dropdown,
605
+ outputs=script_info,
606
+ )
607
+
608
+ question_input = gr.Textbox(
609
+ lines=8,
610
+ placeholder="Please enter your question...",
611
+ label="Question",
612
+ elem_id="question_input",
613
+ show_copy_button=True,
614
+ )
615
+
616
+ gr.Markdown(
617
+ """
618
+ > **Note**: Your question will replace the default question in the script. The system will automatically handle the replacement, ensuring your question is used correctly.
619
+ > Multi-line input is supported, line breaks will be preserved.
620
+ """
621
+ )
622
+
623
+ with gr.Row():
624
+ run_button = gr.Button("Run", variant="primary")
625
+ stop_button = gr.Button("Stop", variant="stop")
626
+
627
+ with gr.Column(scale=2):
628
+ with gr.Tabs():
629
+ with gr.TabItem("Results"):
630
+ status_output = gr.Textbox(label="Status")
631
+ answer_output = gr.Textbox(label="Answer", lines=10)
632
+ log_file_output = gr.Textbox(label="Log File Path")
633
+
634
+ with gr.TabItem("Run Logs"):
635
+ log_output = gr.Textbox(label="Complete Logs", lines=25)
636
+
637
+ with gr.TabItem("Chat History"):
638
+ chat_output = gr.Chatbot(label="Conversation History")
639
+
640
+ # Example questions
641
+ examples = [
642
+ [
643
+ "Qwen Mini (Chinese)",
644
+ "Browse Amazon and find a product that is attractive to programmers. Please provide the product name and price.",
645
+ ],
646
+ [
647
+ "DeepSeek (Chinese)",
648
+ "Please analyze the latest statistics of the CAMEL-AI project on GitHub. Find out the number of stars, number of contributors, and recent activity of the project. Then, create a simple Excel spreadsheet to display this data and generate a bar chart to visualize these metrics. Finally, summarize the popularity and development trends of the CAMEL project.",
649
+ ],
650
+ [
651
+ "Default",
652
+ "Navigate to Amazon.com and identify one product that is attractive to coders. Please provide me with the product name and price. No need to verify your answer.",
653
+ ],
654
+ ]
655
+
656
+ gr.Examples(examples=examples, inputs=[script_dropdown, question_input])
657
+
658
+ with gr.TabItem("Environment Variable Configuration"):
659
+ env_inputs = {}
660
+ save_status = gr.Textbox(label="Save Status", interactive=False)
661
+
662
+ # Add custom environment variables section
663
+ with gr.Accordion("Add Custom Environment Variables", open=True):
664
+ with gr.Row():
665
+ new_var_name = gr.Textbox(
666
+ label="Environment Variable Name",
667
+ placeholder="Example: MY_CUSTOM_API_KEY",
668
+ )
669
+ new_var_value = gr.Textbox(
670
+ label="Environment Variable Value",
671
+ placeholder="Enter value",
672
+ )
673
+ new_var_type = gr.Dropdown(
674
+ choices=["text", "password"], value="text", label="Type"
675
+ )
676
+
677
+ add_var_button = gr.Button(
678
+ "Add Environment Variable", variant="primary"
679
+ )
680
+ add_var_status = gr.Textbox(label="Add Status", interactive=False)
681
+
682
+ # Custom environment variables list
683
+ custom_vars_list = gr.JSON(
684
+ value=ENV_GROUPS["Custom Environment Variables"],
685
+ label="Added Custom Environment Variables",
686
+ visible=len(ENV_GROUPS["Custom Environment Variables"]) > 0,
687
+ )
688
+
689
+ # Update and delete custom environment variables section
690
+ with gr.Accordion(
691
+ "Update or Delete Custom Environment Variables",
692
+ open=True,
693
+ visible=len(ENV_GROUPS["Custom Environment Variables"]) > 0,
694
+ ) as update_delete_accordion:
695
+ with gr.Row():
696
+ # Create dropdown menu to display all custom environment variables
697
+ custom_var_dropdown = gr.Dropdown(
698
+ choices=[
699
+ var["name"]
700
+ for var in ENV_GROUPS["Custom Environment Variables"]
701
+ ],
702
+ label="Select Environment Variable",
703
+ interactive=True,
704
+ )
705
+ update_var_value = gr.Textbox(
706
+ label="New Environment Variable Value",
707
+ placeholder="Enter new value",
708
+ )
709
+ update_var_type = gr.Dropdown(
710
+ choices=["text", "password"], value="text", label="Type"
711
+ )
712
+
713
+ with gr.Row():
714
+ update_var_button = gr.Button(
715
+ "Update Environment Variable", variant="primary"
716
+ )
717
+ delete_var_button = gr.Button(
718
+ "Delete Environment Variable", variant="stop"
719
+ )
720
+
721
+ update_var_status = gr.Textbox(
722
+ label="Operation Status", interactive=False
723
+ )
724
+
725
+ # Add environment variable button click event
726
+ add_var_button.click(
727
+ fn=add_custom_env_var,
728
+ inputs=[new_var_name, new_var_value, new_var_type],
729
+ outputs=[add_var_status, custom_vars_list],
730
+ ).then(
731
+ fn=lambda vars: {"visible": len(vars) > 0},
732
+ inputs=[custom_vars_list],
733
+ outputs=[update_delete_accordion],
734
+ )
735
+
736
+ # Update environment variable button click event
737
+ update_var_button.click(
738
+ fn=update_custom_env_var,
739
+ inputs=[custom_var_dropdown, update_var_value, update_var_type],
740
+ outputs=[update_var_status, custom_vars_list],
741
+ )
742
+
743
+ # Delete environment variable button click event
744
+ delete_var_button.click(
745
+ fn=delete_custom_env_var,
746
+ inputs=[custom_var_dropdown],
747
+ outputs=[update_var_status, custom_vars_list],
748
+ ).then(
749
+ fn=lambda vars: {"visible": len(vars) > 0},
750
+ inputs=[custom_vars_list],
751
+ outputs=[update_delete_accordion],
752
+ )
753
+
754
+ # When custom environment variables list is updated, update dropdown menu options
755
+ custom_vars_list.change(
756
+ fn=lambda vars: {
757
+ "choices": [var["name"] for var in vars],
758
+ "value": None,
759
+ },
760
+ inputs=[custom_vars_list],
761
+ outputs=[custom_var_dropdown],
762
+ )
763
+
764
+ # Existing environment variable configuration
765
+ for group_name, vars in ENV_GROUPS.items():
766
+ if (
767
+ group_name != "Custom Environment Variables" or len(vars) > 0
768
+ ): # Only show non-empty custom environment variable groups
769
+ with gr.Accordion(
770
+ group_name,
771
+ open=(group_name != "Custom Environment Variables"),
772
+ ):
773
+ for var in vars:
774
+ # Add help information
775
+ gr.Markdown(f"**{var['help']}**")
776
+
777
+ if var["type"] == "password":
778
+ env_inputs[var["name"]] = gr.Textbox(
779
+ value=env_vars.get(var["name"], ""),
780
+ label=var["label"],
781
+ placeholder=f"Please enter {var['label']}",
782
+ type="password",
783
+ )
784
+ else:
785
+ env_inputs[var["name"]] = gr.Textbox(
786
+ value=env_vars.get(var["name"], ""),
787
+ label=var["label"],
788
+ placeholder=f"Please enter {var['label']}",
789
+ )
790
+
791
+ save_button = gr.Button("Save Environment Variables", variant="primary")
792
+
793
+ # Save environment variables
794
+ save_inputs = [
795
+ env_inputs[var_name]
796
+ for group in ENV_GROUPS.values()
797
+ for var in group
798
+ for var_name in [var["name"]]
799
+ if var_name in env_inputs
800
+ ]
801
+ save_button.click(
802
+ fn=lambda *values: save_env_vars(
803
+ dict(
804
+ zip(
805
+ [
806
+ var["name"]
807
+ for group in ENV_GROUPS.values()
808
+ for var in group
809
+ if var["name"] in env_inputs
810
+ ],
811
+ values,
812
+ )
813
+ )
814
+ ),
815
+ inputs=save_inputs,
816
+ outputs=save_status,
817
+ )
818
+
819
+ # Run script
820
+ run_button.click(
821
+ fn=run_script,
822
+ inputs=[script_dropdown, question_input],
823
+ outputs=[
824
+ status_output,
825
+ answer_output,
826
+ log_output,
827
+ log_file_output,
828
+ chat_output,
829
+ ],
830
+ show_progress=True,
831
+ )
832
+
833
+ # Terminate execution
834
+ stop_button.click(fn=terminate_process, inputs=[], outputs=[status_output])
835
+
836
+ # Add footer
837
+ gr.Markdown(
838
+ """
839
+ ### 📝 Instructions
840
+
841
+ - Select a model and enter your question
842
+ - Click the "Run" button to start execution
843
+ - To stop execution, click the "Stop" button
844
+ - View execution status and answers in the "Results" tab
845
+ - View complete logs in the "Run Logs" tab
846
+ - View conversation history in the "Chat History" tab (if available)
847
+ - Configure API keys and other environment variables in the "Environment Variable Configuration" tab
848
+ - You can add custom environment variables to meet special requirements
849
+
850
+ ### ⚠️ Notes
851
+
852
+ - Running some models may require API keys, please make sure you have set the corresponding environment variables in the "Environment Variable Configuration" tab
853
+ - Some scripts may take a long time to run, please be patient
854
+ - If execution exceeds 30 minutes, the process will automatically terminate
855
+ - Your question will replace the default question in the script, ensure the question is compatible with the selected model
856
+ """
857
+ )
858
+
859
+ return app
860
+
861
+
862
+ if __name__ == "__main__":
863
+ # Create and launch the application
864
+ app = create_ui()
865
+ app.queue().launch(share=True)
pyproject.toml CHANGED
@@ -21,7 +21,7 @@ keywords = [
21
  "learning-systems"
22
  ]
23
  dependencies = [
24
- "camel-ai[all]==0.2.24",
25
  "chunkr-ai>=0.0.41",
26
  "docx2markdown>=0.1.1",
27
  "gradio>=3.50.2",
 
21
  "learning-systems"
22
  ]
23
  dependencies = [
24
+ "camel-ai[all]==0.2.25",
25
  "chunkr-ai>=0.0.41",
26
  "docx2markdown>=0.1.1",
27
  "gradio>=3.50.2",
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- camel-ai[all]==0.2.24
2
  chunkr-ai>=0.0.41
3
  docx2markdown>=0.1.1
4
  gradio>=3.50.2
 
1
+ camel-ai[all]==0.2.25
2
  chunkr-ai>=0.0.41
3
  docx2markdown>=0.1.1
4
  gradio>=3.50.2
run_app_en.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ #!/usr/bin/env python
15
+ # -*- coding: utf-8 -*-
16
+
17
+ """
18
+ OWL Intelligent Assistant Platform Launch Script
19
+ """
20
+
21
+ import os
22
+ import sys
23
+ from pathlib import Path
24
+
25
+
26
+ def main():
27
+ """Main function to launch the OWL Intelligent Assistant Platform"""
28
+ # 确保当前目录是项目根目录
29
+ project_root = Path(__file__).resolve().parent
30
+ os.chdir(project_root)
31
+
32
+ # 创建日志目录
33
+ log_dir = project_root / "logs"
34
+ log_dir.mkdir(exist_ok=True)
35
+
36
+ # 导入并运行应用
37
+ sys.path.insert(0, str(project_root))
38
+
39
+ try:
40
+ from owl.app_en import create_ui
41
+
42
+ # 创建并启动应用
43
+ app = create_ui()
44
+ app.queue().launch(share=False)
45
+
46
+ except ImportError as e:
47
+ print(
48
+ f"Error: Unable to import necessary modules. Please ensure all dependencies are installed: {e}"
49
+ )
50
+ print("Tip: Run 'pip install -r requirements.txt' to install all dependencies")
51
+ sys.exit(1)
52
+ except Exception as e:
53
+ print(f"Error occurred while starting the application: {e}")
54
+ import traceback
55
+
56
+ traceback.print_exc()
57
+ sys.exit(1)
58
+
59
+
60
+ if __name__ == "__main__":
61
+ main()
uv.lock CHANGED
@@ -482,7 +482,7 @@ wheels = [
482
 
483
  [[package]]
484
  name = "camel-ai"
485
- version = "0.2.24"
486
  source = { registry = "https://pypi.org/simple" }
487
  dependencies = [
488
  { name = "colorama" },
@@ -499,9 +499,9 @@ dependencies = [
499
  { name = "pyyaml" },
500
  { name = "tiktoken" },
501
  ]
502
- sdist = { url = "https://files.pythonhosted.org/packages/b5/e9/ff1d5c6fda166784d74cef34b2bcae62798b4538bb5a61eb14c34573225c/camel_ai-0.2.24.tar.gz", hash = "sha256:a9db0dba48204d5c273689613440ff798ece82c001de69d0865beb25fe72577a", size = 431619 }
503
  wheels = [
504
- { url = "https://files.pythonhosted.org/packages/40/c4/3d804ad127f5e145e0b4ab383edd245d9192ba1846737067cd72d9480d84/camel_ai-0.2.24-py3-none-any.whl", hash = "sha256:000c16536164403db0cf102e6b8f73eba7f155a38264f55d1d3eab38f58ba472", size = 733656 },
505
  ]
506
 
507
  [package.optional-dependencies]
@@ -3575,7 +3575,7 @@ dependencies = [
3575
 
3576
  [package.metadata]
3577
  requires-dist = [
3578
- { name = "camel-ai", extras = ["all"], specifier = "==0.2.24" },
3579
  { name = "chunkr-ai", specifier = ">=0.0.41" },
3580
  { name = "docx2markdown", specifier = ">=0.1.1" },
3581
  { name = "gradio", specifier = ">=3.50.2" },
 
482
 
483
  [[package]]
484
  name = "camel-ai"
485
+ version = "0.2.25"
486
  source = { registry = "https://pypi.org/simple" }
487
  dependencies = [
488
  { name = "colorama" },
 
499
  { name = "pyyaml" },
500
  { name = "tiktoken" },
501
  ]
502
+ sdist = { url = "https://files.pythonhosted.org/packages/f4/0a/f8572ef160a2464dcf4ee11290d9f892de5fc1010f5508717054ffe40f72/camel_ai-0.2.25.tar.gz", hash = "sha256:07de734dd1b33f1265effdd88242523cfcf5dae3519ab73da75fb474c7c67617", size = 433098 }
503
  wheels = [
504
+ { url = "https://files.pythonhosted.org/packages/13/21/2c38bb4a1e1b4f98d3ecaf78069e179341af9966a3f2a824b0c8968b6862/camel_ai-0.2.25-py3-none-any.whl", hash = "sha256:ca2730d1da94e61cf9f5d26c612249fa87f07b2093871954dc0eb05b628b2a72", size = 735185 },
505
  ]
506
 
507
  [package.optional-dependencies]
 
3575
 
3576
  [package.metadata]
3577
  requires-dist = [
3578
+ { name = "camel-ai", extras = ["all"], specifier = "==0.2.25" },
3579
  { name = "chunkr-ai", specifier = ">=0.0.41" },
3580
  { name = "docx2markdown", specifier = ">=0.1.1" },
3581
  { name = "gradio", specifier = ">=3.50.2" },