| import gradio as gr |
| import os |
| import base64 |
| import json |
| import time |
| os.system("pip install openai") |
| from openai import OpenAI, APITimeoutError, APIError |
| import io |
| import logging |
|
|
|
|
| |
| try: |
| from PIL import Image |
| PIL_AVAILABLE = True |
| except ImportError: |
| PIL_AVAILABLE = False |
| logging.warning("未找到pillow库,图片处理功能将不可用") |
|
|
| |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger(__name__) |
| API_KEY = os.getenv('baidu_api_key') |
| |
| client = OpenAI( |
| base_url='https://qianfan.baidubce.com/v2', |
| api_key=API_KEY |
| ) |
|
|
| |
| def read_base64_from_file(file_path): |
| """从指定文本文件读取Base64编码(处理空文件、路径错误等异常)""" |
| try: |
| |
| if not os.path.exists(file_path): |
| logging.warning(f"Base64文件不存在:{file_path}") |
| return None |
| |
| |
| with open(file_path, 'r', encoding='utf-8') as f: |
| base64_str = f.read().strip() |
| |
| |
| if len(base64_str) % 4 != 0: |
| logging.error(f"{file_path} 中Base64编码无效(长度非4的倍数)") |
| return None |
| |
| return base64_str |
| except Exception as e: |
| logging.error(f"读取{file_path}失败:{str(e)}") |
| return None |
|
|
| def base64_to_pil_image(base64_str): |
| """将Base64字符串转换为PIL Image对象(用于Gradio渲染)""" |
| try: |
| if not base64_str: |
| return None |
| |
| |
| image_bytes = base64.b64decode(base64_str) |
| |
| image = Image.open(io.BytesIO(image_bytes)) |
| return image |
| except Exception as e: |
| logging.error(f"Base64转图片失败:{str(e)}") |
| return None |
|
|
| |
| def compress_image(image_path, max_size=(1024, 1024)): |
| """图片压缩处理,增加pillow可用性检查""" |
| if not PIL_AVAILABLE: |
| raise Exception("未安装pillow库,请执行 `pip install pillow` 安装后重试") |
| |
| try: |
| with Image.open(image_path) as img: |
| |
| if img.mode in ('RGBA', 'LA'): |
| background = Image.new(img.mode[:-1], img.size, (255, 255, 255)) |
| background.paste(img, img.split()[-1]) |
| img = background |
| |
| img.thumbnail(max_size, Image.Resampling.LANCZOS) |
| |
| img_byte_arr = io.BytesIO() |
| img.save(img_byte_arr, format='JPEG', quality=80) |
| return base64.b64encode(img_byte_arr.getvalue()).decode('utf-8') |
| except Exception as e: |
| logger.error(f"图片处理失败: {str(e)}") |
| raise Exception(f"图片处理失败: {str(e)}") |
|
|
| def clean_json_response(raw_content): |
| """清理模型返回的响应,移除代码块标记,确保JSON可解析""" |
| |
| if raw_content.startswith('```json') or raw_content.startswith('```JSON'): |
| raw_content = raw_content[7:] |
| |
| if raw_content.endswith('```'): |
| raw_content = raw_content[:-3] |
| |
| return raw_content.strip() |
|
|
| def format_analysis_result(result_json): |
| """将JSON结果格式化为结构化的Markdown文本,用于页面展示""" |
| if not isinstance(result_json, dict): |
| return "⚠️ 分析结果格式异常,无法结构化展示" |
| |
| |
| md_content = "# 智能体截图分析报告\n\n" |
| |
| |
| scoring = result_json.get("页面评分", {}) |
| if scoring: |
| md_content += "## 一、页面评分(1-10分)\n" |
| md_content += "| 评价维度 | 得分 | 评价说明 |\n" |
| md_content += "|----------------|------|---------------------------|\n" |
| |
| |
| score_items = ["overall", "design", "usability", "functionality", "responsiveness"] |
| item_names = { |
| "overall": "整体评价", |
| "design": "设计美感", |
| "usability": "易用性", |
| "functionality": "功能完整性", |
| "responsiveness": "响应式设计" |
| } |
| |
| for item in score_items: |
| score = scoring.get(item, "N/A") |
| comment = scoring.get(f"{item}_comment", "无") |
| md_content += f"| {item_names[item]} | {score} | {comment} |\n" |
| md_content += "\n" |
| |
| |
| capabilities = result_json.get("智能体能力拆解", {}) |
| if capabilities: |
| md_content += "## 二、智能体能力拆解\n" |
| |
| |
| core_funcs = capabilities.get("core_functions", []) |
| if core_funcs: |
| md_content += "### 1. 核心功能\n" |
| for i, func in enumerate(core_funcs, 1): |
| md_content += f"- **{i}.** {func}\n" |
| md_content += "\n" |
| |
| |
| strengths = capabilities.get("strengths", []) |
| if strengths: |
| md_content += "### 2. 优势\n" |
| for i, strength in enumerate(strengths, 1): |
| md_content += f"- **{i}.** {strength}\n" |
| md_content += "\n" |
| |
| |
| weaknesses = capabilities.get("weaknesses", []) |
| if weaknesses: |
| md_content += "### 3. 劣势\n" |
| for i, weakness in enumerate(weaknesses, 1): |
| md_content += f"- **{i}.** {weakness}\n" |
| md_content += "\n" |
| |
| |
| potential_uses = capabilities.get("potential_uses", []) |
| if potential_uses: |
| md_content += "### 4. 潜在用途\n" |
| for i, use in enumerate(potential_uses, 1): |
| md_content += f"- **{i}.** {use}\n" |
| md_content += "\n" |
| |
| |
| improvement_areas = capabilities.get("improvement_areas", []) |
| if improvement_areas: |
| md_content += "### 5. 改进方向\n" |
| for i, area in enumerate(improvement_areas, 1): |
| md_content += f"- **{i}.** {area}\n" |
| md_content += "\n" |
| |
| |
| detailed_analysis = capabilities.get("detailed_analysis", "无") |
| if detailed_analysis != "无": |
| md_content += "### 6. 详细分析\n" |
| md_content += f">{detailed_analysis}\n\n" |
| |
| |
| if not scoring and not capabilities: |
| md_content += "⚠️ 未获取到有效分析结果,请检查图片内容或重试" |
| |
| return md_content |
|
|
| def analyze_image(input_image): |
| """AI分析核心函数:返回【结构化Markdown结果】+【原始JSON】+【状态】""" |
| |
| structured_result = "" |
| raw_json = "" |
| status = "就绪" |
| |
| |
| if not PIL_AVAILABLE: |
| status = "❌ 缺少依赖" |
| structured_result = "未安装pillow库,请关闭应用并执行 `pip install pillow` 安装后重试" |
| yield structured_result, raw_json, status |
| return |
| |
| |
| if not input_image: |
| status = "请先上传图片" |
| structured_result = "请点击左侧「上传截图」区域,选择JPG/PNG格式的智能体网页截图" |
| yield structured_result, raw_json, status |
| return |
| |
| try: |
| |
| status = "正在准备图片..." |
| structured_result = "🔄 正在读取并准备图片文件..." |
| yield structured_result, raw_json, status |
| time.sleep(0.1) |
| |
| |
| status = "正在压缩图片..." |
| structured_result = "🔄 正在压缩图片(确保分析效率,不影响质量)..." |
| yield structured_result, raw_json, status |
| image_base64 = compress_image(input_image) |
| time.sleep(0.1) |
| |
| |
| status = "正在发送分析请求..." |
| structured_result = "🔄 正在向AI模型发送分析请求(约3-10秒)..." |
| yield structured_result, raw_json, status |
| |
| |
| messages = [ |
| { |
| "role": "user", |
| "content": [ |
| { |
| "type": "text", |
| "text": "请分析这张智能体网页的截图,并严格按照以下格式返回JSON:\n\ |
| {\n\ |
| \"页面评分\": {\n\ |
| \"overall\": 分数(1-10),\n\ |
| \"overall_comment\": \"简短评价\",\n\ |
| \"design\": 分数(1-10),\n\ |
| \"design_comment\": \"简短评价\",\n\ |
| \"usability\": 分数(1-10),\n\ |
| \"usability_comment\": \"简短评价\",\n\ |
| \"functionality\": 分数(1-10),\n\ |
| \"functionality_comment\": \"简短评价\",\n\ |
| \"responsiveness\": 分数(1-10),\n\ |
| \"responsiveness_comment\": \"简短评价\"\n\ |
| },\n\ |
| \"智能体能力拆解\": {\n\ |
| \"core_functions\": [\"功能1\", \"功能2\", ...],\n\ |
| \"strengths\": [\"优势1\", \"优势2\", ...],\n\ |
| \"weaknesses\": [\"劣势1\", \"劣势2\", ...],\n\ |
| \"potential_uses\": [\"用途1\", \"用途2\", ...],\n\ |
| \"improvement_areas\": [\"改进1\", \"改进2\", ...],\n\ |
| \"detailed_analysis\": \"详细综合分析文本\"\n\ |
| }\n\ |
| }\n\ |
| 【注意】:仅返回纯JSON,不要包含代码块标记(如```json)、解释文本等额外内容!" |
| }, |
| { |
| "type": "image_url", |
| "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"} |
| } |
| ] |
| } |
| ] |
| |
| |
| status = "AI正在分析(约3-10秒)..." |
| structured_result = f"🔄 AI正在深度分析图片内容...\n\n当前进度:\n- 图片已上传:✅\n- 模型已接收:✅\n- 分析中:⌛" |
| yield structured_result, raw_json, status |
| |
| |
| response = client.chat.completions.create( |
| model="ernie-4.5-turbo-vl-32k", |
| messages=messages, |
| temperature=0.2, |
| top_p=0.8, |
| extra_body={"penalty_score": 1}, |
| timeout=30 |
| ) |
| |
| |
| status = "正在整理分析结果..." |
| structured_result = "🔄 AI分析完成,正在整理结构化报告..." |
| yield structured_result, raw_json, status |
| time.sleep(0.1) |
| |
| |
| raw_content = response.choices[0].message.content |
| cleaned_content = clean_json_response(raw_content) |
| raw_json = json.dumps(json.loads(cleaned_content), ensure_ascii=False, indent=2) |
| |
| |
| result_json = json.loads(cleaned_content) |
| structured_result = format_analysis_result(result_json) |
| status = "✅ 分析完成!" |
| yield structured_result, raw_json, status |
| |
| |
| except APITimeoutError: |
| status = "❌ 分析超时" |
| structured_result = "⚠️ API调用超时(超过30秒)\n\n可能原因:\n1. 网络不稳定\n2. 模型负载较高\n建议:检查网络后重试" |
| yield structured_result, raw_json, status |
| except APIError as e: |
| status = "❌ API错误" |
| structured_result = f"⚠️ 百度千帆API错误\n\n错误详情:\n{str(e)}\n\n建议:检查API密钥是否正确,或前往百度智能云控制台确认服务状态" |
| yield structured_result, raw_json, status |
| except json.JSONDecodeError as e: |
| status = "❌ 格式错误" |
| structured_result = f"⚠️ AI返回结果非标准JSON\n\n原始内容:\n{cleaned_content[:500]}...\n\n错误详情:{str(e)}" |
| yield structured_result, cleaned_content, status |
| except Exception as e: |
| status = "❌ 分析失败" |
| structured_result = f"⚠️ 分析过程出错\n\n错误详情:\n{str(e)}\n\n建议:检查图片格式(仅支持JPG/PNG)或重试" |
| yield structured_result, raw_json, status |
|
|
| |
| with gr.Blocks(title="智能体截图分析工具", theme=gr.themes.Soft()) as demo: |
| |
| raw_json_store = gr.State("") |
| |
| |
| logo_base64 = read_base64_from_file("logo.txt") |
| demo_base64 = read_base64_from_file("demo.txt") |
| logo_image = base64_to_pil_image(logo_base64) |
| demo_image = base64_to_pil_image(demo_base64) |
| |
| |
| with gr.Row(elem_id="logo_row", visible=logo_image is not None): |
| gr.Image( |
| value=logo_image, |
| height=50, width=165, |
| interactive=False, |
| show_label=False, |
| show_download_button=False |
| ) |
| if logo_image is None: |
| gr.Markdown("# 智能体截图分析工具", elem_id="fallback_title") |
| |
| |
| gr.Markdown("### 功能说明", visible=logo_image is not None) |
| gr.Markdown(""" |
| 上传智能体网页截图(JPG/PNG),系统将自动完成: |
| 1. 页面质量评分(整体评价、设计美感等5个维度) |
| 2. 智能体能力拆解(核心功能、优势、潜在用途等) |
| 3. 生成结构化报告,支持下载原始JSON结果 |
| """, elem_id="description") |
| |
| |
| with gr.Row(elem_id="main_content", variant="panel"): |
| |
| with gr.Column(scale=1, elem_id="upload_col"): |
| input_image = gr.Image( |
| type="filepath", |
| label="上传截图", |
| height=300, |
| show_label=True, |
| elem_id="image_upload" |
| ) |
| |
| |
| example_images = [demo_image] if demo_image is not None else [] |
| if example_images: |
| gr.Examples( |
| examples=example_images, |
| inputs=input_image, |
| label="示例截图(点击可直接使用)", |
| elem_id="examples_box" |
| ) |
| |
| analyze_btn = gr.Button( |
| "开始分析", |
| variant="primary", |
| size="lg", |
| elem_id="analyze_btn", |
| interactive=PIL_AVAILABLE |
| ) |
| |
| if not PIL_AVAILABLE: |
| gr.Markdown("⚠️ 未安装pillow库,无法处理图片\n请执行 `pip install pillow` 后重启应用", elem_id="dependency_warning") |
| |
| |
| with gr.Column(scale=2, elem_id="result_col", variant="panel"): |
| |
| status_display = gr.Textbox( |
| label="当前状态", |
| interactive=False, |
| value="就绪:请上传截图并点击「开始分析」", |
| elem_id="status_box", |
| max_lines=2 |
| ) |
| |
| |
| with gr.Tabs(elem_id="result_tabs"): |
| |
| with gr.Tab("结构化分析报告", elem_id="structured_tab"): |
| structured_result = gr.Markdown( |
| value=""" |
| # 等待分析... |
| |
| ## 操作指引 |
| 1. 点击左侧「上传截图」区域,选择智能体网页的JPG/PNG图片 |
| 2. 点击「开始分析」按钮,等待3-10秒 |
| 3. 分析完成后,此处将显示结构化报告 |
| |
| ## 示例图说明 |
| 若左侧有示例图片,可直接点击示例快速测试 |
| """, |
| elem_id="structured_result" |
| ) |
| |
| |
| with gr.Tab("原始JSON结果", elem_id="raw_json_tab"): |
| raw_json_result = gr.Textbox( |
| label=None, |
| lines=20, |
| placeholder="分析完成后,此处将显示格式化的原始JSON结果...", |
| elem_id="raw_json_box", |
| container=True |
| ) |
| |
| |
| with gr.Row(elem_id="action_buttons"): |
| download_btn = gr.DownloadButton( |
| "下载JSON结果", |
| label=None, |
| elem_id="download_btn", |
| visible=False |
| ) |
| clear_btn = gr.Button( |
| "清除结果", |
| variant="secondary", |
| size="sm", |
| elem_id="clear_btn" |
| ) |
| |
| |
| |
| analyze_btn.click( |
| fn=analyze_image, |
| inputs=input_image, |
| outputs=[structured_result, raw_json_result, status_display] |
| ).then( |
| |
| fn=lambda raw_json, status: ( |
| raw_json, |
| gr.update(visible=status.startswith("✅")) |
| ), |
| inputs=[raw_json_result, status_display], |
| outputs=[raw_json_store, download_btn] |
| ) |
| |
| |
| download_btn.click( |
| fn=lambda result: (result, "analysis_result.json"), |
| inputs=raw_json_store, |
| outputs=download_btn, |
| show_progress=False |
| ) |
| |
| |
| clear_btn.click( |
| fn=lambda: ( |
| """ |
| # 等待分析... |
| |
| ## 操作指引 |
| 1. 点击左侧「上传截图」区域,选择智能体网页的JPG/PNG图片 |
| 2. 点击「开始分析」按钮,等待3-10秒 |
| 3. 分析完成后,此处将显示结构化报告 |
| |
| ## 示例图说明 |
| 若左侧有示例图片,可直接点击示例快速测试 |
| """, |
| "", |
| "就绪:请上传截图并点击「开始分析」", |
| gr.update(visible=False), |
| "" |
| ), |
| outputs=[structured_result, raw_json_result, status_display, download_btn, raw_json_store] |
| ) |
| |
| |
| demo.load( |
| None, |
| None, |
| None, |
| js="""() => { |
| // 结果区整体样式 |
| const resultCol = document.getElementById('result_col'); |
| if (resultCol) { |
| resultCol.style.padding = '20px'; |
| resultCol.style.borderRadius = '8px'; |
| } |
| |
| // 状态框样式和动态颜色 |
| const statusBox = document.getElementById('status_box'); |
| if (statusBox) { |
| statusBox.style.marginBottom = '15px'; |
| statusBox.style.padding = '8px'; |
| |
| // 定时检查状态文本并更新颜色 |
| setInterval(() => { |
| const statusText = statusBox.value || ''; |
| if (typeof statusText === 'string') { |
| if (statusText.startsWith('✅')) statusBox.style.color = '#27ae60'; |
| else if (statusText.startsWith('❌')) statusBox.style.color = '#e74c3c'; |
| else if (statusText.includes('分析中') || statusText.includes('🔄')) statusBox.style.color = '#3498db'; |
| else statusBox.style.color = '#34495e'; |
| } |
| }, 100); |
| } |
| |
| // 按钮区样式 |
| const actionButtons = document.getElementById('action_buttons'); |
| if (actionButtons) { |
| actionButtons.style.marginTop = '15px'; |
| actionButtons.style.gap = '10px'; |
| } |
| |
| // 结构化报告样式优化 |
| const structuredResult = document.getElementById('structured_result'); |
| if (structuredResult) { |
| structuredResult.style.padding = '10px 0'; |
| } |
| }""" |
| ) |
|
|
| |
| if __name__ == "__main__": |
| try: |
| |
| required_imports = { |
| "gradio": "gradio", |
| "openai": "openai", |
| "PIL": "pillow", |
| "requests": "requests" |
| } |
| |
| for import_name, package_name in required_imports.items(): |
| try: |
| __import__(import_name) |
| except ImportError: |
| raise ImportError(package_name) |
| |
| |
| demo.launch( |
| |
| |
| |
| |
| |
| ) |
| except ImportError as e: |
| logger.critical(f"缺少依赖包:请执行 `pip install {e.args[0]}` 安装") |
| except Exception as e: |
| logger.critical(f"应用启动失败:{str(e)}") |
|
|