zhl
commited on
Commit
·
ed1d2fa
1
Parent(s):
f654931
agent.wang first commit.
Browse files- README.md +113 -14
- __pycache__/openai.cpython-38.pyc +0 -0
- analysis_result.json +55 -0
- analysis_result.py +198 -0
- app.py +558 -0
- demo.txt +0 -0
- logo.txt +1 -0
README.md
CHANGED
|
@@ -1,14 +1,113 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 王艾卷智能体评分系统 agent.wang
|
| 2 |
+
|
| 3 |
+
## 项目介绍
|
| 4 |
+
|
| 5 |
+
`agent.wang 王艾卷智能体评分系统` 是一款基于百度千帆 ERNIE-4.5-Turbo-VL 多模态大模型开发的智能体评估工具。该系统能够对智能体网页截图进行自动化分析,从多个维度进行评分并拆解智能体能力,为开发者和产品经理提供客观、全面的智能体评估报告。
|
| 6 |
+
|
| 7 |
+
## 功能特点
|
| 8 |
+
|
| 9 |
+
- **多维度评分**:从整体评价、设计美感、易用性、功能完整性和响应式设计五个维度进行1-10分制评分
|
| 10 |
+
- **能力拆解**:自动识别智能体的核心功能、优势、劣势、潜在用途和改进方向
|
| 11 |
+
- **结构化输出**:提供JSON格式原始数据和可视化解析结果,方便查看和进一步处理
|
| 12 |
+
- **结果保存**:自动将分析结果保存为JSON文件,便于归档和对比分析
|
| 13 |
+
- **错误处理**:完善的错误提示和处理机制,帮助用户快速定位和解决问题
|
| 14 |
+
|
| 15 |
+
## 环境要求
|
| 16 |
+
|
| 17 |
+
- Python 3.8+
|
| 18 |
+
- openai 库
|
| 19 |
+
|
| 20 |
+
## 安装步骤
|
| 21 |
+
|
| 22 |
+
1. 克隆或下载项目代码到本地
|
| 23 |
+
2. 安装依赖库:
|
| 24 |
+
```bash
|
| 25 |
+
pip install openai
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
## 百度API密钥获取
|
| 29 |
+
|
| 30 |
+
1. 访问百度智能云API密钥管理页面:https://console.bce.baidu.com/iam/#/iam/apikey/list
|
| 31 |
+
2. 登录百度账号(未注册用户需先完成注册和实名认证)
|
| 32 |
+
3. 点击"创建Access Key"生成API密钥
|
| 33 |
+
4. 确保已开通千帆大模型服务(新用户可领取免费调用额度)
|
| 34 |
+
|
| 35 |
+
## 使用指南
|
| 36 |
+
|
| 37 |
+
1. 准备一张智能体网页的截图,保存为PNG或JPG格式
|
| 38 |
+
2. 打开`ernie_analysis_with_parsing.py`文件,修改配置参数:
|
| 39 |
+
```python
|
| 40 |
+
API_KEY = "您的百度智能云Access Key" # 替换为实际API密钥
|
| 41 |
+
IMAGE_PATH = "demo.png" # 替换为您的截图路径
|
| 42 |
+
OUTPUT_FILE = "analysis_result.json" # 结果保存文件名
|
| 43 |
+
```
|
| 44 |
+
3. 运行脚本:
|
| 45 |
+
```bash
|
| 46 |
+
python ernie_analysis_with_parsing.py
|
| 47 |
+
```
|
| 48 |
+
4. 查看结果:
|
| 49 |
+
- 控制台将显示原始JSON数据和解析后的结构化结果
|
| 50 |
+
- 分析结果同时会保存到指定的JSON文件中
|
| 51 |
+
|
| 52 |
+
## 输出结果说明
|
| 53 |
+
|
| 54 |
+
### 控制台输出
|
| 55 |
+
|
| 56 |
+
1. **原始JSON结果**:完整展示模型返回的结构化数据,包含页面评分和智能体能力拆解的全部信息
|
| 57 |
+
2. **解析后的结果**:格式化展示分析结果,包括:
|
| 58 |
+
- 页面评分:各维度得分及评论
|
| 59 |
+
- 智能体能力拆解:核心功能、优势、劣势、潜在用途、改进方向和详细分析
|
| 60 |
+
|
| 61 |
+
### JSON文件输出
|
| 62 |
+
|
| 63 |
+
分析结果会保存到指定的JSON文件中,可用于:
|
| 64 |
+
- 结果归档和版本对比
|
| 65 |
+
- 导入其他工具进行进一步分析
|
| 66 |
+
- 集成到报告生成系统
|
| 67 |
+
|
| 68 |
+
## 常见问题
|
| 69 |
+
|
| 70 |
+
### API密钥相关问题
|
| 71 |
+
|
| 72 |
+
- **Q: 提示"API密钥验证失败"怎么办?**
|
| 73 |
+
A: 检查API_KEY是否正确填写,确保没有多余空格;确认百度智能云账号已完成实名认证并开通千帆大模型服务;检查账号是否有可用的调用额度。
|
| 74 |
+
|
| 75 |
+
### 图像相关问题
|
| 76 |
+
|
| 77 |
+
- **Q: 提示"未找到图像文件"如何解决?**
|
| 78 |
+
A: 检查IMAGE_PATH是否正确,确保路径中包含正确的文件名和扩展名;如果使用相对路径,确认图像文件与脚本在同一目录下。
|
| 79 |
+
|
| 80 |
+
### 结果解析问题
|
| 81 |
+
|
| 82 |
+
- **Q: 模型返回结果格式异常怎么办?**
|
| 83 |
+
A: 系统已内置格式清理功能,会自动处理常见的格式问题。如果仍解析失败,可尝试重新运行脚本。
|
| 84 |
+
|
| 85 |
+
## 自定义扩展
|
| 86 |
+
|
| 87 |
+
### 调整评分维度
|
| 88 |
+
|
| 89 |
+
如需增加或修改评分维度,可修改脚本中messages里的文本内容,例如:
|
| 90 |
+
"text": "请分析这张智能体网页的截图,并完成以下任务:\n\
|
| 91 |
+
1. 页面评分(每项1-10分,并给出简短评论):\n\
|
| 92 |
+
- 整体评价(overall)\n\
|
| 93 |
+
- 设计美感(design)\n\
|
| 94 |
+
- 易用性(usability)\n\
|
| 95 |
+
- 功能完整性(functionality)\n\
|
| 96 |
+
- 响应式设计(responsiveness)\n\
|
| 97 |
+
- 安全性(security)\n # 新增维度\
|
| 98 |
+
..."
|
| 99 |
+
### 修改输出文件路径
|
| 100 |
+
|
| 101 |
+
如需将结果保存到指定路径,可修改OUTPUT_FILE参数:
|
| 102 |
+
OUTPUT_FILE = "D:/reports/agent_analysis_202409.json" # 绝对路径
|
| 103 |
+
## 注意事项
|
| 104 |
+
|
| 105 |
+
- 使用前请确保已阅读并遵守百度智能云的服务协议
|
| 106 |
+
- 合理使用API调用额度,避免不必要的频繁调用
|
| 107 |
+
- 对于敏感页面截图,请确保已获得必要的授权
|
| 108 |
+
|
| 109 |
+
## 联系方式
|
| 110 |
+
|
| 111 |
+
### 如有任何问题或建议,请联系项目维护团队。
|
| 112 |
+
### url: https://agent.wang
|
| 113 |
+
### exmail: zhl@agent.wang
|
__pycache__/openai.cpython-38.pyc
ADDED
|
Binary file (2.28 kB). View file
|
|
|
analysis_result.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"页面评分": {
|
| 3 |
+
"overall": {
|
| 4 |
+
"score": 8,
|
| 5 |
+
"comment": "整体页面简洁明了,信息传达清晰,用户体验较好。"
|
| 6 |
+
},
|
| 7 |
+
"design": {
|
| 8 |
+
"score": 8,
|
| 9 |
+
"comment": "设计风格简约,色彩搭配和谐,图标和文字布局合理。"
|
| 10 |
+
},
|
| 11 |
+
"usability": {
|
| 12 |
+
"score": 9,
|
| 13 |
+
"comment": "页面操作简单,用户可以轻松找到所需功能,易用性高。"
|
| 14 |
+
},
|
| 15 |
+
"functionality": {
|
| 16 |
+
"score": 8,
|
| 17 |
+
"comment": "功能明确,能够满足用户识别物体等基本需求。"
|
| 18 |
+
},
|
| 19 |
+
"responsiveness": {
|
| 20 |
+
"score": 8,
|
| 21 |
+
"comment": "在不同设备上应该都能有较好的显示效果,响应式设计良好。"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"智能体能力拆解": {
|
| 25 |
+
"core_functions": [
|
| 26 |
+
"识别周围物体并提供名称",
|
| 27 |
+
"提供物体的属性信息",
|
| 28 |
+
"提供物体的用途信息",
|
| 29 |
+
"回答用户关于物体的相关问题"
|
| 30 |
+
],
|
| 31 |
+
"strengths": [
|
| 32 |
+
"功能明确,专注于物体识别和信息提供",
|
| 33 |
+
"操作简单,用户易上手",
|
| 34 |
+
"信息提供较为全面",
|
| 35 |
+
"页面设计简洁,用户体验好"
|
| 36 |
+
],
|
| 37 |
+
"weaknesses": [
|
| 38 |
+
"功能相对单一,缺乏更多扩展功能",
|
| 39 |
+
"对于复杂物体的识别和信息提供可能存在不足",
|
| 40 |
+
"缺乏用户个性化设置",
|
| 41 |
+
"没有明显的反馈机制,用户无法对识别结果进行评价"
|
| 42 |
+
],
|
| 43 |
+
"potential_uses": [
|
| 44 |
+
"日常生活中的物体识别,如识别植物、电子设备等",
|
| 45 |
+
"学习场景中,帮助学生识别和了解各种物体",
|
| 46 |
+
"旅游时,识别陌生物体获取相关信息"
|
| 47 |
+
],
|
| 48 |
+
"improvement_areas": [
|
| 49 |
+
"增加更多功能,如物体分类、相似物体推荐等",
|
| 50 |
+
"提供用户反馈机制,以便改进识别准确率",
|
| 51 |
+
"增加个性化设置,满足不同用户需求"
|
| 52 |
+
],
|
| 53 |
+
"detailed_analysis": "该智能体网页整体设计简洁,以用户需求为核心,专注于物体识别和信息提供。页面布局合理,操作简单,用户可以轻松输入问题并获取相关信息。然而,功能相对单一,缺乏更多扩展功能,对于复杂物体的识别和信息提供可能存在不足。此外,缺乏用户个性化设置和反馈机制,无法满足不同用户的需求和改进识别准确率。未来可以增加更多功能,提供用户反馈机制和个性化设置,以提升用户体验和智能体的实用性。"
|
| 54 |
+
}
|
| 55 |
+
}
|
analysis_result.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import base64
|
| 2 |
+
import json
|
| 3 |
+
from openai import OpenAI
|
| 4 |
+
from openai import APIError, AuthenticationError, NotFoundError
|
| 5 |
+
|
| 6 |
+
def image_to_base64(image_path):
|
| 7 |
+
"""将图像文件转换为base64编码字符串"""
|
| 8 |
+
try:
|
| 9 |
+
with open(image_path, 'rb') as image_file:
|
| 10 |
+
image_data = image_file.read()
|
| 11 |
+
return base64.b64encode(image_data).decode('utf-8')
|
| 12 |
+
except FileNotFoundError:
|
| 13 |
+
print(f"错误:未找到图像文件 '{image_path}'")
|
| 14 |
+
return None
|
| 15 |
+
except Exception as e:
|
| 16 |
+
print(f"转换图像时出错:{str(e)}")
|
| 17 |
+
return None
|
| 18 |
+
|
| 19 |
+
def clean_json_response(raw_response):
|
| 20 |
+
"""清理模型返回的响应,移除 ```json 和 ``` 代码块标记"""
|
| 21 |
+
# 移除开头的 ```json(不区分大小写)
|
| 22 |
+
if raw_response.startswith('```json') or raw_response.startswith('```JSON'):
|
| 23 |
+
raw_response = raw_response[7:]
|
| 24 |
+
# 移除结尾的 ```
|
| 25 |
+
if raw_response.endswith('```'):
|
| 26 |
+
raw_response = raw_response[:-3]
|
| 27 |
+
# 去除首尾多余空格/换行
|
| 28 |
+
return raw_response.strip()
|
| 29 |
+
|
| 30 |
+
def parse_analysis_result(result):
|
| 31 |
+
"""解析分析结果并以结构化方式展示"""
|
| 32 |
+
if not result:
|
| 33 |
+
return
|
| 34 |
+
|
| 35 |
+
print("\n" + "="*50)
|
| 36 |
+
print("📊 图像分析结果解析")
|
| 37 |
+
print("="*50)
|
| 38 |
+
|
| 39 |
+
# 解析页面评分
|
| 40 |
+
print("\n【页面评分】")
|
| 41 |
+
scoring = result.get("页面评分", {})
|
| 42 |
+
for key, value in scoring.items():
|
| 43 |
+
if key.endswith("_comment"):
|
| 44 |
+
# 处理评论(对应前面的评分项)
|
| 45 |
+
score_key = key.replace("_comment", "")
|
| 46 |
+
print(f" - {score_key}: {scoring.get(score_key, 'N/A')}分 - {value}")
|
| 47 |
+
|
| 48 |
+
# 解析智能体能力
|
| 49 |
+
print("\n【智能体能力拆解】")
|
| 50 |
+
capabilities = result.get("智能体能力拆解", {})
|
| 51 |
+
|
| 52 |
+
# 核心功能
|
| 53 |
+
print("\n核心功能:")
|
| 54 |
+
for i, func in enumerate(capabilities.get("core_functions", []), 1):
|
| 55 |
+
print(f" {i}. {func}")
|
| 56 |
+
|
| 57 |
+
# 优势
|
| 58 |
+
print("\n优势:")
|
| 59 |
+
for i, strength in enumerate(capabilities.get("strengths", []), 1):
|
| 60 |
+
print(f" {i}. {strength}")
|
| 61 |
+
|
| 62 |
+
# 劣势
|
| 63 |
+
print("\n劣势:")
|
| 64 |
+
for i, weakness in enumerate(capabilities.get("weaknesses", []), 1):
|
| 65 |
+
print(f" {i}. {weakness}")
|
| 66 |
+
|
| 67 |
+
# 潜在用途
|
| 68 |
+
print("\n潜在用途:")
|
| 69 |
+
for i, use in enumerate(capabilities.get("potential_uses", []), 1):
|
| 70 |
+
print(f" {i}. {use}")
|
| 71 |
+
|
| 72 |
+
# 改进方向
|
| 73 |
+
print("\n改进方向:")
|
| 74 |
+
for i, area in enumerate(capabilities.get("improvement_areas", []), 1):
|
| 75 |
+
print(f" {i}. {area}")
|
| 76 |
+
|
| 77 |
+
# 详细分析
|
| 78 |
+
print("\n【详细分析】")
|
| 79 |
+
print(capabilities.get("detailed_analysis", "无详细分析信息"))
|
| 80 |
+
|
| 81 |
+
print("\n" + "="*50)
|
| 82 |
+
|
| 83 |
+
def analyze_image_with_ernie(api_key, image_path):
|
| 84 |
+
"""使用百度ERNIE-VL模型分析图像并返回结果"""
|
| 85 |
+
# 转换图像为base64
|
| 86 |
+
base64_string = image_to_base64(image_path)
|
| 87 |
+
if not base64_string:
|
| 88 |
+
return None
|
| 89 |
+
|
| 90 |
+
try:
|
| 91 |
+
# 初始化客户端
|
| 92 |
+
client = OpenAI(
|
| 93 |
+
base_url='https://qianfan.baidubce.com/v2',
|
| 94 |
+
api_key=api_key
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
# 构建消息
|
| 98 |
+
messages = [
|
| 99 |
+
{
|
| 100 |
+
"role": "user",
|
| 101 |
+
"content": [
|
| 102 |
+
{
|
| 103 |
+
"type": "text",
|
| 104 |
+
"text": "请分析这张智能体网页的截图,并完成以下任务:\n\
|
| 105 |
+
1. 页面评分(每项1-10分,并给出简短评论):\n\
|
| 106 |
+
- 整体评价(overall)\n\
|
| 107 |
+
- 设计美感(design)\n\
|
| 108 |
+
- 易用性(usability)\n\
|
| 109 |
+
- 功能完整性(functionality)\n\
|
| 110 |
+
- 响应式设计(responsiveness)\n\
|
| 111 |
+
2. 智能体能力拆解:\n\
|
| 112 |
+
- 核心功能(core_functions):列出3-5个主要功能\n\
|
| 113 |
+
- 优势(strengths):列出3-4个主要优势\n\
|
| 114 |
+
- 劣势(weaknesses):列出3-4个潜在劣势\n\
|
| 115 |
+
- 潜在用途(potential_uses):列出3-4个可能的应用场景\n\
|
| 116 |
+
- 改进方向(improvement_areas):列出2-3个可以改进的地方\n\
|
| 117 |
+
- 详细分析(detailed_analysis):一段详细的综合分析\n\
|
| 118 |
+
【输出要求】:仅返回纯JSON字符串,不要包含任何代码块标记(如```json、```),确保JSON结构可直接被Python的json.loads()解析。"
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"type": "image_url",
|
| 122 |
+
"image_url": {"url": f"data:image/jpeg;base64,{base64_string}"}
|
| 123 |
+
}
|
| 124 |
+
]
|
| 125 |
+
}
|
| 126 |
+
]
|
| 127 |
+
|
| 128 |
+
# 发送请求
|
| 129 |
+
print("正在分析图像,请稍候...")
|
| 130 |
+
response = client.chat.completions.create(
|
| 131 |
+
model="ernie-4.5-turbo-vl",
|
| 132 |
+
messages=messages,
|
| 133 |
+
temperature=0.8,
|
| 134 |
+
top_p=0.2,
|
| 135 |
+
extra_body={"penalty_score": 1}
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
# 处理响应
|
| 139 |
+
raw_content = response.choices[0].message.content
|
| 140 |
+
cleaned_content = clean_json_response(raw_content)
|
| 141 |
+
|
| 142 |
+
try:
|
| 143 |
+
result = json.loads(cleaned_content)
|
| 144 |
+
return result
|
| 145 |
+
except json.JSONDecodeError as e:
|
| 146 |
+
print(f"错误:清理后的内容仍不是有效JSON格式,错误详情:{str(e)}")
|
| 147 |
+
print(f"清理后内容:{cleaned_content}")
|
| 148 |
+
return None
|
| 149 |
+
|
| 150 |
+
except AuthenticationError:
|
| 151 |
+
print("错误:API密钥验证失败,请检查您的百度智能云API密钥是否正确")
|
| 152 |
+
return None
|
| 153 |
+
except NotFoundError:
|
| 154 |
+
print("错误:未找到指定的模型,请确认模型名称是否正确")
|
| 155 |
+
return None
|
| 156 |
+
except APIError as e:
|
| 157 |
+
print(f"API请求失败:{str(e)}")
|
| 158 |
+
return None
|
| 159 |
+
except Exception as e:
|
| 160 |
+
print(f"发生未知错误:{str(e)}")
|
| 161 |
+
return None
|
| 162 |
+
|
| 163 |
+
def save_analysis_result(result, output_file="analysis_result.json"):
|
| 164 |
+
"""保存分析结果到JSON文件"""
|
| 165 |
+
if result:
|
| 166 |
+
try:
|
| 167 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
| 168 |
+
json.dump(result, f, ensure_ascii=False, indent=2)
|
| 169 |
+
print(f"\n分析结果已保存到 {output_file}")
|
| 170 |
+
except Exception as e:
|
| 171 |
+
print(f"保存结果时出错:{str(e)}")
|
| 172 |
+
|
| 173 |
+
if __name__ == "__main__":
|
| 174 |
+
# 配置
|
| 175 |
+
API_KEY = os.getenv('baidu_api_key') # 替换为实际API密钥
|
| 176 |
+
IMAGE_PATH = "demo.png" # 替换为实际图像路径
|
| 177 |
+
OUTPUT_FILE = "analysis_result.json" # 结果保存文件名
|
| 178 |
+
|
| 179 |
+
# 检查API密钥
|
| 180 |
+
if API_KEY in ["您的百度智能云API密钥", "请替换为您的百度API密钥"]:
|
| 181 |
+
print("⚠️ 请先替换代码中的API_KEY为您自己的百度智能云API密钥!")
|
| 182 |
+
print("获取地址:https://console.bce.baidu.com/iam/#/iam/apikey/list")
|
| 183 |
+
else:
|
| 184 |
+
# 执行分析
|
| 185 |
+
analysis_result = analyze_image_with_ernie(API_KEY, IMAGE_PATH)
|
| 186 |
+
|
| 187 |
+
# 解析并展示结果
|
| 188 |
+
if analysis_result:
|
| 189 |
+
# 打印原始JSON(便于调试)
|
| 190 |
+
print("\n📄 原始分析结果(JSON):")
|
| 191 |
+
print(json.dumps(analysis_result, ensure_ascii=False, indent=2))
|
| 192 |
+
|
| 193 |
+
# 解析并格式化展示
|
| 194 |
+
parse_analysis_result(analysis_result)
|
| 195 |
+
|
| 196 |
+
# 保存结果到文件
|
| 197 |
+
save_analysis_result(analysis_result, OUTPUT_FILE)
|
| 198 |
+
|
app.py
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import base64
|
| 3 |
+
import json
|
| 4 |
+
import time
|
| 5 |
+
from openai import OpenAI, APITimeoutError, APIError
|
| 6 |
+
import io
|
| 7 |
+
import logging
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
# 尝试导入PIL(pillow的实际包名),并处理导入错误
|
| 11 |
+
try:
|
| 12 |
+
from PIL import Image
|
| 13 |
+
PIL_AVAILABLE = True
|
| 14 |
+
except ImportError:
|
| 15 |
+
PIL_AVAILABLE = False
|
| 16 |
+
logging.warning("未找到pillow库,图片处理功能将不可用")
|
| 17 |
+
|
| 18 |
+
# 配置日志
|
| 19 |
+
logging.basicConfig(level=logging.INFO)
|
| 20 |
+
logger = logging.getLogger(__name__)
|
| 21 |
+
API_KEY = os.getenv('baidu_api_key')
|
| 22 |
+
# 初始化OpenAI客户端(替换为你的百度千帆API密钥)
|
| 23 |
+
client = OpenAI(
|
| 24 |
+
base_url='https://qianfan.baidubce.com/v2',
|
| 25 |
+
api_key=API_KEY # 请替换为实际密钥
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
# ---------------------- Base64图片读取函数 ----------------------
|
| 29 |
+
def read_base64_from_file(file_path):
|
| 30 |
+
"""从指定文本文件读取Base64编码(处理空文件、路径错误等异常)"""
|
| 31 |
+
try:
|
| 32 |
+
# 检查文件是否存在
|
| 33 |
+
if not os.path.exists(file_path):
|
| 34 |
+
logging.warning(f"Base64文件不存在:{file_path}")
|
| 35 |
+
return None
|
| 36 |
+
|
| 37 |
+
# 读取文件内容(去除空行和空格)
|
| 38 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
| 39 |
+
base64_str = f.read().strip()
|
| 40 |
+
|
| 41 |
+
# 验证Base64有效性(简单检查长度是否为4的倍数)
|
| 42 |
+
if len(base64_str) % 4 != 0:
|
| 43 |
+
logging.error(f"{file_path} 中Base64编码无效(长度非4的倍数)")
|
| 44 |
+
return None
|
| 45 |
+
|
| 46 |
+
return base64_str
|
| 47 |
+
except Exception as e:
|
| 48 |
+
logging.error(f"读取{file_path}失败:{str(e)}")
|
| 49 |
+
return None
|
| 50 |
+
|
| 51 |
+
def base64_to_pil_image(base64_str):
|
| 52 |
+
"""将Base64字符串转换为PIL Image对象(用于Gradio渲染)"""
|
| 53 |
+
try:
|
| 54 |
+
if not base64_str:
|
| 55 |
+
return None
|
| 56 |
+
|
| 57 |
+
# 解码Base64为字节流
|
| 58 |
+
image_bytes = base64.b64decode(base64_str)
|
| 59 |
+
# 转换为PIL Image
|
| 60 |
+
image = Image.open(io.BytesIO(image_bytes))
|
| 61 |
+
return image
|
| 62 |
+
except Exception as e:
|
| 63 |
+
logging.error(f"Base64转图片失败:{str(e)}")
|
| 64 |
+
return None
|
| 65 |
+
|
| 66 |
+
# ---------------------- 核心工具函数 ----------------------
|
| 67 |
+
def compress_image(image_path, max_size=(1024, 1024)):
|
| 68 |
+
"""图片压缩处理,增加pillow可用性检查"""
|
| 69 |
+
if not PIL_AVAILABLE:
|
| 70 |
+
raise Exception("未安装pillow库,请执行 `pip install pillow` 安装后重试")
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
with Image.open(image_path) as img:
|
| 74 |
+
# 处理透明背景(转为白色背景)
|
| 75 |
+
if img.mode in ('RGBA', 'LA'):
|
| 76 |
+
background = Image.new(img.mode[:-1], img.size, (255, 255, 255))
|
| 77 |
+
background.paste(img, img.split()[-1])
|
| 78 |
+
img = background
|
| 79 |
+
# 按比例缩小图片(不拉伸)
|
| 80 |
+
img.thumbnail(max_size, Image.Resampling.LANCZOS)
|
| 81 |
+
# 保存到内存并转Base64
|
| 82 |
+
img_byte_arr = io.BytesIO()
|
| 83 |
+
img.save(img_byte_arr, format='JPEG', quality=80)
|
| 84 |
+
return base64.b64encode(img_byte_arr.getvalue()).decode('utf-8')
|
| 85 |
+
except Exception as e:
|
| 86 |
+
logger.error(f"图片处理失败: {str(e)}")
|
| 87 |
+
raise Exception(f"图片处理失败: {str(e)}")
|
| 88 |
+
|
| 89 |
+
def clean_json_response(raw_content):
|
| 90 |
+
"""清理模型返回的响应,移除代码块标记,确保JSON可解析"""
|
| 91 |
+
# 移除开头的 ```json/```JSON 标记
|
| 92 |
+
if raw_content.startswith('```json') or raw_content.startswith('```JSON'):
|
| 93 |
+
raw_content = raw_content[7:]
|
| 94 |
+
# 移除结尾的 ``` 标记
|
| 95 |
+
if raw_content.endswith('```'):
|
| 96 |
+
raw_content = raw_content[:-3]
|
| 97 |
+
# 去除首尾多余空格/换行
|
| 98 |
+
return raw_content.strip()
|
| 99 |
+
|
| 100 |
+
def format_analysis_result(result_json):
|
| 101 |
+
"""将JSON结果格式化为结构化的Markdown文本,用于页面展示"""
|
| 102 |
+
if not isinstance(result_json, dict):
|
| 103 |
+
return "⚠️ 分析结果格式异常,无法结构化展示"
|
| 104 |
+
|
| 105 |
+
# 初始化Markdown内容
|
| 106 |
+
md_content = "# 智能体截图分析报告\n\n"
|
| 107 |
+
|
| 108 |
+
# 1. 页面评分模块
|
| 109 |
+
scoring = result_json.get("页面评分", {})
|
| 110 |
+
if scoring:
|
| 111 |
+
md_content += "## 一、页面评分(1-10分)\n"
|
| 112 |
+
md_content += "| 评价维度 | 得分 | 评价说明 |\n"
|
| 113 |
+
md_content += "|----------------|------|---------------------------|\n"
|
| 114 |
+
|
| 115 |
+
# 处理每个评分项(匹配 "维度" 和 "维度_comment")
|
| 116 |
+
score_items = ["overall", "design", "usability", "functionality", "responsiveness"]
|
| 117 |
+
item_names = {
|
| 118 |
+
"overall": "整体评价",
|
| 119 |
+
"design": "设计美感",
|
| 120 |
+
"usability": "易用性",
|
| 121 |
+
"functionality": "功能完整性",
|
| 122 |
+
"responsiveness": "响应式设计"
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
for item in score_items:
|
| 126 |
+
score = scoring.get(item, "N/A")
|
| 127 |
+
comment = scoring.get(f"{item}_comment", "无")
|
| 128 |
+
md_content += f"| {item_names[item]} | {score} | {comment} |\n"
|
| 129 |
+
md_content += "\n"
|
| 130 |
+
|
| 131 |
+
# 2. 智能体能力拆解模块
|
| 132 |
+
capabilities = result_json.get("智能体能力拆解", {})
|
| 133 |
+
if capabilities:
|
| 134 |
+
md_content += "## 二、智能体能力拆解\n"
|
| 135 |
+
|
| 136 |
+
# 核心功能
|
| 137 |
+
core_funcs = capabilities.get("core_functions", [])
|
| 138 |
+
if core_funcs:
|
| 139 |
+
md_content += "### 1. 核心功能\n"
|
| 140 |
+
for i, func in enumerate(core_funcs, 1):
|
| 141 |
+
md_content += f"- **{i}.** {func}\n"
|
| 142 |
+
md_content += "\n"
|
| 143 |
+
|
| 144 |
+
# 优势
|
| 145 |
+
strengths = capabilities.get("strengths", [])
|
| 146 |
+
if strengths:
|
| 147 |
+
md_content += "### 2. 优势\n"
|
| 148 |
+
for i, strength in enumerate(strengths, 1):
|
| 149 |
+
md_content += f"- **{i}.** {strength}\n"
|
| 150 |
+
md_content += "\n"
|
| 151 |
+
|
| 152 |
+
# 劣势
|
| 153 |
+
weaknesses = capabilities.get("weaknesses", [])
|
| 154 |
+
if weaknesses:
|
| 155 |
+
md_content += "### 3. 劣势\n"
|
| 156 |
+
for i, weakness in enumerate(weaknesses, 1):
|
| 157 |
+
md_content += f"- **{i}.** {weakness}\n"
|
| 158 |
+
md_content += "\n"
|
| 159 |
+
|
| 160 |
+
# 潜在用途
|
| 161 |
+
potential_uses = capabilities.get("potential_uses", [])
|
| 162 |
+
if potential_uses:
|
| 163 |
+
md_content += "### 4. 潜在用途\n"
|
| 164 |
+
for i, use in enumerate(potential_uses, 1):
|
| 165 |
+
md_content += f"- **{i}.** {use}\n"
|
| 166 |
+
md_content += "\n"
|
| 167 |
+
|
| 168 |
+
# 改进方向
|
| 169 |
+
improvement_areas = capabilities.get("improvement_areas", [])
|
| 170 |
+
if improvement_areas:
|
| 171 |
+
md_content += "### 5. 改进方向\n"
|
| 172 |
+
for i, area in enumerate(improvement_areas, 1):
|
| 173 |
+
md_content += f"- **{i}.** {area}\n"
|
| 174 |
+
md_content += "\n"
|
| 175 |
+
|
| 176 |
+
# 详细分析
|
| 177 |
+
detailed_analysis = capabilities.get("detailed_analysis", "无")
|
| 178 |
+
if detailed_analysis != "无":
|
| 179 |
+
md_content += "### 6. 详细分析\n"
|
| 180 |
+
md_content += f">{detailed_analysis}\n\n"
|
| 181 |
+
|
| 182 |
+
# 若结果不完整,补充提示
|
| 183 |
+
if not scoring and not capabilities:
|
| 184 |
+
md_content += "⚠️ 未获取到有效分析结果,请检查图片内容或重试"
|
| 185 |
+
|
| 186 |
+
return md_content
|
| 187 |
+
|
| 188 |
+
def analyze_image(input_image):
|
| 189 |
+
"""AI分析核心函数:返回【结构化Markdown结果】+【原始JSON】+【状态】"""
|
| 190 |
+
# 初始化返回值(结构化结果、原始JSON、状态)
|
| 191 |
+
structured_result = ""
|
| 192 |
+
raw_json = ""
|
| 193 |
+
status = "就绪"
|
| 194 |
+
|
| 195 |
+
# 依赖检查
|
| 196 |
+
if not PIL_AVAILABLE:
|
| 197 |
+
status = "❌ 缺少依赖"
|
| 198 |
+
structured_result = "未安装pillow库,请关闭应用并执行 `pip install pillow` 安装后重试"
|
| 199 |
+
yield structured_result, raw_json, status
|
| 200 |
+
return
|
| 201 |
+
|
| 202 |
+
# 图片检查
|
| 203 |
+
if not input_image:
|
| 204 |
+
status = "请先上传图片"
|
| 205 |
+
structured_result = "请点击左侧「上传截图」区域,选择JPG/PNG格式的智能体网页截图"
|
| 206 |
+
yield structured_result, raw_json, status
|
| 207 |
+
return
|
| 208 |
+
|
| 209 |
+
try:
|
| 210 |
+
# 1. 图片准备阶段
|
| 211 |
+
status = "正在准备图片..."
|
| 212 |
+
structured_result = "🔄 正在读取并准备图片文件..."
|
| 213 |
+
yield structured_result, raw_json, status
|
| 214 |
+
time.sleep(0.1)
|
| 215 |
+
|
| 216 |
+
# 2. 图片压缩阶段
|
| 217 |
+
status = "正在压缩图片..."
|
| 218 |
+
structured_result = "🔄 正在压缩图片(确保分析效率,不影响质量)..."
|
| 219 |
+
yield structured_result, raw_json, status
|
| 220 |
+
image_base64 = compress_image(input_image)
|
| 221 |
+
time.sleep(0.1)
|
| 222 |
+
|
| 223 |
+
# 3. 请求发送阶段
|
| 224 |
+
status = "正在发送分析请求..."
|
| 225 |
+
structured_result = "🔄 正在向AI模型发送分析请求(约3-10秒)..."
|
| 226 |
+
yield structured_result, raw_json, status
|
| 227 |
+
|
| 228 |
+
# 构建AI请求(明确输出格式要求,减少清理工作)
|
| 229 |
+
messages = [
|
| 230 |
+
{
|
| 231 |
+
"role": "user",
|
| 232 |
+
"content": [
|
| 233 |
+
{
|
| 234 |
+
"type": "text",
|
| 235 |
+
"text": "请分析这张智能体网页的截图,并严格按照以下格式返回JSON:\n\
|
| 236 |
+
{\n\
|
| 237 |
+
\"页面评分\": {\n\
|
| 238 |
+
\"overall\": 分数(1-10),\n\
|
| 239 |
+
\"overall_comment\": \"简短评价\",\n\
|
| 240 |
+
\"design\": 分数(1-10),\n\
|
| 241 |
+
\"design_comment\": \"简短评价\",\n\
|
| 242 |
+
\"usability\": 分数(1-10),\n\
|
| 243 |
+
\"usability_comment\": \"简短评价\",\n\
|
| 244 |
+
\"functionality\": 分数(1-10),\n\
|
| 245 |
+
\"functionality_comment\": \"简短评价\",\n\
|
| 246 |
+
\"responsiveness\": 分数(1-10),\n\
|
| 247 |
+
\"responsiveness_comment\": \"简短评价\"\n\
|
| 248 |
+
},\n\
|
| 249 |
+
\"智能体能力拆解\": {\n\
|
| 250 |
+
\"core_functions\": [\"功能1\", \"功能2\", ...],\n\
|
| 251 |
+
\"strengths\": [\"优势1\", \"优势2\", ...],\n\
|
| 252 |
+
\"weaknesses\": [\"劣势1\", \"劣势2\", ...],\n\
|
| 253 |
+
\"potential_uses\": [\"用途1\", \"用途2\", ...],\n\
|
| 254 |
+
\"improvement_areas\": [\"改进1\", \"改进2\", ...],\n\
|
| 255 |
+
\"detailed_analysis\": \"详细综合分析文本\"\n\
|
| 256 |
+
}\n\
|
| 257 |
+
}\n\
|
| 258 |
+
【注意】:仅返回纯JSON,不要包含代码块标记(如```json)、解释文本等额外内容!"
|
| 259 |
+
},
|
| 260 |
+
{
|
| 261 |
+
"type": "image_url",
|
| 262 |
+
"image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}
|
| 263 |
+
}
|
| 264 |
+
]
|
| 265 |
+
}
|
| 266 |
+
]
|
| 267 |
+
|
| 268 |
+
# 4. AI分析阶段
|
| 269 |
+
status = "AI正在分析(约3-10秒)..."
|
| 270 |
+
structured_result = f"🔄 AI正在深度分析图片内容...\n\n当前进度:\n- 图片已上传:✅\n- 模型已接收:✅\n- 分析中:⌛"
|
| 271 |
+
yield structured_result, raw_json, status
|
| 272 |
+
|
| 273 |
+
# 调用ERNIE-VL模型
|
| 274 |
+
response = client.chat.completions.create(
|
| 275 |
+
model="ernie-4.5-turbo-vl", # 简化模型名,避免版本兼容性问题
|
| 276 |
+
messages=messages,
|
| 277 |
+
temperature=0.2,
|
| 278 |
+
top_p=0.8,
|
| 279 |
+
extra_body={"penalty_score": 1},
|
| 280 |
+
timeout=30
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
# 5. 结果整理阶段
|
| 284 |
+
status = "正在整理分析结果..."
|
| 285 |
+
structured_result = "🔄 AI分析完成,正在整理结构化报告..."
|
| 286 |
+
yield structured_result, raw_json, status
|
| 287 |
+
time.sleep(0.1)
|
| 288 |
+
|
| 289 |
+
# 处理AI响应
|
| 290 |
+
raw_content = response.choices[0].message.content
|
| 291 |
+
cleaned_content = clean_json_response(raw_content)
|
| 292 |
+
raw_json = json.dumps(json.loads(cleaned_content), ensure_ascii=False, indent=2) # 格式化原始JSON
|
| 293 |
+
|
| 294 |
+
# 生成结构化展示结果
|
| 295 |
+
result_json = json.loads(cleaned_content)
|
| 296 |
+
structured_result = format_analysis_result(result_json)
|
| 297 |
+
status = "✅ 分析完成!"
|
| 298 |
+
yield structured_result, raw_json, status
|
| 299 |
+
|
| 300 |
+
# 异常处理(各阶段错误提示)
|
| 301 |
+
except APITimeoutError:
|
| 302 |
+
status = "❌ 分析超时"
|
| 303 |
+
structured_result = "⚠️ API调用超时(超过30秒)\n\n可能原因:\n1. 网络不稳定\n2. 模型负载较高\n建议:检查网络后重试"
|
| 304 |
+
yield structured_result, raw_json, status
|
| 305 |
+
except APIError as e:
|
| 306 |
+
status = "❌ API错误"
|
| 307 |
+
structured_result = f"⚠️ 百度千帆API错误\n\n错误详情:\n{str(e)}\n\n建议:检查API密钥是否正确,或前往百度智能云控制台确认服务状态"
|
| 308 |
+
yield structured_result, raw_json, status
|
| 309 |
+
except json.JSONDecodeError as e:
|
| 310 |
+
status = "❌ 格式错误"
|
| 311 |
+
structured_result = f"⚠️ AI返回结果非标准JSON\n\n原始内容:\n{cleaned_content[:500]}...\n\n错误详情:{str(e)}"
|
| 312 |
+
yield structured_result, cleaned_content, status # 返回原始错误内容
|
| 313 |
+
except Exception as e:
|
| 314 |
+
status = "❌ 分析失败"
|
| 315 |
+
structured_result = f"⚠️ 分析过程出错\n\n错误详情:\n{str(e)}\n\n建议:检查图片格式(仅支持JPG/PNG)或重试"
|
| 316 |
+
yield structured_result, raw_json, status
|
| 317 |
+
|
| 318 |
+
# ---------------------- 界面设计 ----------------------
|
| 319 |
+
with gr.Blocks(title="智能体截图分析工具", theme=gr.themes.Soft()) as demo:
|
| 320 |
+
# 存储原始JSON结果(用于下载)
|
| 321 |
+
raw_json_store = gr.State("")
|
| 322 |
+
|
| 323 |
+
# 预加载Base64图片(启动时读取一次)
|
| 324 |
+
logo_base64 = read_base64_from_file("logo.txt") # 从logo.txt读取Logo的Base64
|
| 325 |
+
demo_base64 = read_base64_from_file("demo.txt") # 从demo.txt读取示例图的Base64
|
| 326 |
+
logo_image = base64_to_pil_image(logo_base64) # 转换为PIL Image
|
| 327 |
+
demo_image = base64_to_pil_image(demo_base64) # 转换为PIL Image
|
| 328 |
+
|
| 329 |
+
# 顶部Logo/标题区(改用Base64图片)
|
| 330 |
+
with gr.Row(elem_id="logo_row", visible=logo_image is not None):
|
| 331 |
+
gr.Image(
|
| 332 |
+
value=logo_image, # 直接使用Base64转换后的PIL Image
|
| 333 |
+
height=50, width=165,
|
| 334 |
+
interactive=False,
|
| 335 |
+
show_label=False,
|
| 336 |
+
show_download_button=False
|
| 337 |
+
)
|
| 338 |
+
if logo_image is None:
|
| 339 |
+
gr.Markdown("# 智能体截图分析工具", elem_id="fallback_title")
|
| 340 |
+
|
| 341 |
+
# 功能说明(简洁明了)
|
| 342 |
+
gr.Markdown("### 功能说明", visible=logo_image is not None)
|
| 343 |
+
gr.Markdown("""
|
| 344 |
+
上传智能体网页截图(JPG/PNG),系统将自动完成:
|
| 345 |
+
1. 页面质量评分(整体评价、设计美感等5个维度)
|
| 346 |
+
2. 智能体能力拆解(核心功能、优势、潜在用途等)
|
| 347 |
+
3. 生成结构化报告,支持下载原始JSON结果
|
| 348 |
+
""", elem_id="description")
|
| 349 |
+
|
| 350 |
+
# 核心交互区域(左侧上传 + 右侧结果)
|
| 351 |
+
with gr.Row(elem_id="main_content", variant="panel"):
|
| 352 |
+
# 左侧:图片上传区(简洁布局)
|
| 353 |
+
with gr.Column(scale=1, elem_id="upload_col"):
|
| 354 |
+
input_image = gr.Image(
|
| 355 |
+
type="filepath",
|
| 356 |
+
label="上传截图",
|
| 357 |
+
height=300,
|
| 358 |
+
show_label=True,
|
| 359 |
+
elem_id="image_upload"
|
| 360 |
+
)
|
| 361 |
+
|
| 362 |
+
# 示例图片区域(改用Base64加载的示例图)
|
| 363 |
+
example_images = [demo_image] if demo_image is not None else []
|
| 364 |
+
if example_images:
|
| 365 |
+
gr.Examples(
|
| 366 |
+
examples=example_images,
|
| 367 |
+
inputs=input_image,
|
| 368 |
+
label="示例截图(点击可直接使用)",
|
| 369 |
+
elem_id="examples_box"
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
analyze_btn = gr.Button(
|
| 373 |
+
"开始分析",
|
| 374 |
+
variant="primary",
|
| 375 |
+
size="lg",
|
| 376 |
+
elem_id="analyze_btn",
|
| 377 |
+
interactive=PIL_AVAILABLE # 无pillow时禁用按钮
|
| 378 |
+
)
|
| 379 |
+
# 依赖提示(无pillow时显示)
|
| 380 |
+
if not PIL_AVAILABLE:
|
| 381 |
+
gr.Markdown("⚠️ 未安装pillow库,无法处理图片\n请执行 `pip install pillow` 后重启应用", elem_id="dependency_warning")
|
| 382 |
+
|
| 383 |
+
# 右侧:结果展示区
|
| 384 |
+
with gr.Column(scale=2, elem_id="result_col", variant="panel"):
|
| 385 |
+
# 状态显示(顶部固定)
|
| 386 |
+
status_display = gr.Textbox(
|
| 387 |
+
label="当前状态",
|
| 388 |
+
interactive=False,
|
| 389 |
+
value="就绪:请上传截图并点击「开始分析」",
|
| 390 |
+
elem_id="status_box",
|
| 391 |
+
max_lines=2
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
# 结果标签页(结构化报告 + 原始JSON)
|
| 395 |
+
with gr.Tabs(elem_id="result_tabs"):
|
| 396 |
+
# 标签1:结构化报告(默认显示)
|
| 397 |
+
with gr.Tab("结构化分析报告", elem_id="structured_tab"):
|
| 398 |
+
structured_result = gr.Markdown(
|
| 399 |
+
value="""
|
| 400 |
+
# 等待分析...
|
| 401 |
+
|
| 402 |
+
## 操作指引
|
| 403 |
+
1. 点击左侧「上传截图」区域,选择智能体网页的JPG/PNG图片
|
| 404 |
+
2. 点击「开始分析」按钮,等待3-10秒
|
| 405 |
+
3. 分析完成后,此处将显示结构化报告
|
| 406 |
+
|
| 407 |
+
## 示例图说明
|
| 408 |
+
若左侧有示例图片,可直接点击示例快速测试
|
| 409 |
+
""",
|
| 410 |
+
elem_id="structured_result"
|
| 411 |
+
)
|
| 412 |
+
|
| 413 |
+
# 标签2:原始JSON(供开发/调试使用)
|
| 414 |
+
with gr.Tab("原始JSON结果", elem_id="raw_json_tab"):
|
| 415 |
+
raw_json_result = gr.Textbox(
|
| 416 |
+
label=None,
|
| 417 |
+
lines=20,
|
| 418 |
+
placeholder="分析完成后,此处将显示格式化的原始JSON结果...",
|
| 419 |
+
elem_id="raw_json_box",
|
| 420 |
+
container=True
|
| 421 |
+
)
|
| 422 |
+
|
| 423 |
+
# 操作按钮区(下载 + 清除)
|
| 424 |
+
with gr.Row(elem_id="action_buttons"):
|
| 425 |
+
download_btn = gr.DownloadButton(
|
| 426 |
+
"下载JSON结果",
|
| 427 |
+
label=None,
|
| 428 |
+
elem_id="download_btn",
|
| 429 |
+
visible=False # 初始隐藏,分析成功后显示
|
| 430 |
+
)
|
| 431 |
+
clear_btn = gr.Button(
|
| 432 |
+
"清除结果",
|
| 433 |
+
variant="secondary",
|
| 434 |
+
size="sm",
|
| 435 |
+
elem_id="clear_btn"
|
| 436 |
+
)
|
| 437 |
+
|
| 438 |
+
# ---------------------- 交互逻辑 ----------------------
|
| 439 |
+
# 1. 分析按钮:触发分析流程
|
| 440 |
+
analyze_btn.click(
|
| 441 |
+
fn=analyze_image,
|
| 442 |
+
inputs=input_image,
|
| 443 |
+
outputs=[structured_result, raw_json_result, status_display]
|
| 444 |
+
).then(
|
| 445 |
+
# 分析完成后:更新原始JSON存储 + 控制下载按钮显示
|
| 446 |
+
fn=lambda raw_json, status: (
|
| 447 |
+
raw_json, # 更新原始JSON存储
|
| 448 |
+
gr.update(visible=status.startswith("✅")) # 成功时显示下载按钮
|
| 449 |
+
),
|
| 450 |
+
inputs=[raw_json_result, status_display],
|
| 451 |
+
outputs=[raw_json_store, download_btn]
|
| 452 |
+
)
|
| 453 |
+
|
| 454 |
+
# 2. 下载按钮:下载原始JSON结果
|
| 455 |
+
download_btn.click(
|
| 456 |
+
fn=lambda result: (result, "analysis_result.json"),
|
| 457 |
+
inputs=raw_json_store,
|
| 458 |
+
outputs=download_btn,
|
| 459 |
+
show_progress=False
|
| 460 |
+
)
|
| 461 |
+
|
| 462 |
+
# 3. 清除按钮:重置所有结果和状态
|
| 463 |
+
clear_btn.click(
|
| 464 |
+
fn=lambda: (
|
| 465 |
+
"""
|
| 466 |
+
# 等待分析...
|
| 467 |
+
|
| 468 |
+
## 操作指引
|
| 469 |
+
1. 点击左侧「上传截图」区域,选择智能体网页的JPG/PNG��片
|
| 470 |
+
2. 点击「开始分析」按钮,等待3-10秒
|
| 471 |
+
3. 分析完成后,此处将显示结构化报告
|
| 472 |
+
|
| 473 |
+
## 示例图说明
|
| 474 |
+
若左侧有示例图片,可直接点击示例快速测试
|
| 475 |
+
""", # 重置结构化报告
|
| 476 |
+
"", # 清空原始JSON
|
| 477 |
+
"就绪:请上传截图并点击「开始分析」", # 重置状态
|
| 478 |
+
gr.update(visible=False), # 隐藏下载按钮
|
| 479 |
+
"" # 清空原始JSON存储
|
| 480 |
+
),
|
| 481 |
+
outputs=[structured_result, raw_json_result, status_display, download_btn, raw_json_store]
|
| 482 |
+
)
|
| 483 |
+
|
| 484 |
+
# ---------------------- 样式优化 ----------------------
|
| 485 |
+
demo.load(
|
| 486 |
+
None,
|
| 487 |
+
None,
|
| 488 |
+
None,
|
| 489 |
+
js="""() => {
|
| 490 |
+
// 结果区整体样式
|
| 491 |
+
const resultCol = document.getElementById('result_col');
|
| 492 |
+
if (resultCol) {
|
| 493 |
+
resultCol.style.padding = '20px';
|
| 494 |
+
resultCol.style.borderRadius = '8px';
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
// 状态框样式和动态颜色
|
| 498 |
+
const statusBox = document.getElementById('status_box');
|
| 499 |
+
if (statusBox) {
|
| 500 |
+
statusBox.style.marginBottom = '15px';
|
| 501 |
+
statusBox.style.padding = '8px';
|
| 502 |
+
|
| 503 |
+
// 定时检查状态文本并更新颜色
|
| 504 |
+
setInterval(() => {
|
| 505 |
+
const statusText = statusBox.value || '';
|
| 506 |
+
if (typeof statusText === 'string') {
|
| 507 |
+
if (statusText.startsWith('✅')) statusBox.style.color = '#27ae60';
|
| 508 |
+
else if (statusText.startsWith('❌')) statusBox.style.color = '#e74c3c';
|
| 509 |
+
else if (statusText.includes('分析中') || statusText.includes('🔄')) statusBox.style.color = '#3498db';
|
| 510 |
+
else statusBox.style.color = '#34495e';
|
| 511 |
+
}
|
| 512 |
+
}, 100);
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
// 按钮区样式
|
| 516 |
+
const actionButtons = document.getElementById('action_buttons');
|
| 517 |
+
if (actionButtons) {
|
| 518 |
+
actionButtons.style.marginTop = '15px';
|
| 519 |
+
actionButtons.style.gap = '10px';
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
// 结构化报告样式优化
|
| 523 |
+
const structuredResult = document.getElementById('structured_result');
|
| 524 |
+
if (structuredResult) {
|
| 525 |
+
structuredResult.style.padding = '10px 0';
|
| 526 |
+
}
|
| 527 |
+
}"""
|
| 528 |
+
)
|
| 529 |
+
|
| 530 |
+
# ---------------------- 应用启动 ----------------------
|
| 531 |
+
if __name__ == "__main__":
|
| 532 |
+
try:
|
| 533 |
+
# 依赖检测
|
| 534 |
+
required_imports = {
|
| 535 |
+
"gradio": "gradio",
|
| 536 |
+
"openai": "openai",
|
| 537 |
+
"PIL": "pillow", # pillow实际导入的是PIL
|
| 538 |
+
"requests": "requests"
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
for import_name, package_name in required_imports.items():
|
| 542 |
+
try:
|
| 543 |
+
__import__(import_name)
|
| 544 |
+
except ImportError:
|
| 545 |
+
raise ImportError(package_name)
|
| 546 |
+
|
| 547 |
+
# 启动应用(适配旧版本Gradio)
|
| 548 |
+
demo.launch(
|
| 549 |
+
server_name="0.0.0.0",
|
| 550 |
+
server_port=1919,
|
| 551 |
+
share=False,
|
| 552 |
+
max_threads=4,
|
| 553 |
+
quiet=True
|
| 554 |
+
)
|
| 555 |
+
except ImportError as e:
|
| 556 |
+
logger.critical(f"缺少依赖包:请执行 `pip install {e.args[0]}` 安装")
|
| 557 |
+
except Exception as e:
|
| 558 |
+
logger.critical(f"应用启动失败:{str(e)}")
|
demo.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
logo.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
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
|