674 lines
27 KiB
Python
674 lines
27 KiB
Python
# @line_count 500
|
||
"""测试专家系统 - Streamlit Web应用"""
|
||
import streamlit as st
|
||
import json
|
||
import os
|
||
from pathlib import Path
|
||
from typing import List, Dict, Any, Optional
|
||
import yaml
|
||
|
||
# 导入自定义模块
|
||
from modules.json_parser import JSONParser
|
||
from modules.api_client import APIClient
|
||
from modules.prompt_manager import PromptManager
|
||
from modules.test_generator import TestGenerator
|
||
from modules.output_formatter import OutputFormatter
|
||
from process_doc_file import convert_docx_to_json
|
||
|
||
|
||
# 页面配置
|
||
st.set_page_config(
|
||
page_title="测试专家系统",
|
||
page_icon="🧪",
|
||
layout="wide",
|
||
initial_sidebar_state="expanded"
|
||
)
|
||
|
||
|
||
def init_session_state():
|
||
"""初始化会话状态"""
|
||
if 'api_client' not in st.session_state:
|
||
st.session_state.api_client = None
|
||
if 'prompt_manager' not in st.session_state:
|
||
st.session_state.prompt_manager = None
|
||
if 'json_parser' not in st.session_state:
|
||
st.session_state.json_parser = None
|
||
if 'test_items' not in st.session_state:
|
||
st.session_state.test_items = []
|
||
if 'test_cases' not in st.session_state:
|
||
st.session_state.test_cases = []
|
||
if 'function_points' not in st.session_state:
|
||
st.session_state.function_points = []
|
||
if 'document_info' not in st.session_state:
|
||
st.session_state.document_info = {}
|
||
if 'standard_manager' not in st.session_state:
|
||
st.session_state.standard_manager = None
|
||
# 用户自定义Prompt支持
|
||
if 'custom_prompts' not in st.session_state:
|
||
st.session_state.custom_prompts = {} # {func_point_index: custom_prompt}
|
||
if 'custom_prompt_template' not in st.session_state:
|
||
st.session_state.custom_prompt_template = None # 批量模板
|
||
if 'use_custom_prompt' not in st.session_state:
|
||
st.session_state.use_custom_prompt = False # 是否启用自定义prompt
|
||
|
||
|
||
def load_config():
|
||
"""加载配置"""
|
||
try:
|
||
if st.session_state.api_client is None:
|
||
st.session_state.api_client = APIClient()
|
||
if st.session_state.prompt_manager is None:
|
||
st.session_state.prompt_manager = PromptManager()
|
||
except Exception as e:
|
||
st.error(f"配置加载失败: {str(e)}")
|
||
|
||
|
||
def show_home_page():
|
||
"""显示首页"""
|
||
st.title("🧪 测试专家系统")
|
||
st.markdown("---")
|
||
|
||
st.markdown("""
|
||
### 欢迎使用测试专家系统
|
||
|
||
本系统基于大模型AI技术,能够自动从软件使用说明文档中提取功能点,并生成全面的测试项和测试用例。
|
||
|
||
#### 主要功能
|
||
|
||
1. **文档解析**: 自动解析JSON格式的软件使用说明书,提取功能点和操作步骤
|
||
2. **智能生成**: 基于大模型API,为每个功能点生成专业的测试项和测试用例
|
||
3. **多格式输出**: 支持JSON、Markdown、Excel多种格式导出
|
||
4. **Prompt优化**: 支持自定义和优化测试生成Prompt模板
|
||
|
||
#### 使用流程
|
||
|
||
1. **配置API**: 在"配置"页面设置大模型API密钥和选择模型
|
||
2. **上传文档**: 在"生成测试"页面上传JSON格式的文档
|
||
3. **生成测试**: 选择功能点,点击生成按钮
|
||
4. **下载结果**: 预览生成结果,下载所需格式的文件
|
||
|
||
#### 支持的API提供商
|
||
|
||
- DeepSeek (默认)
|
||
- 通义千问
|
||
- OpenAI/ChatGPT
|
||
|
||
---
|
||
|
||
**开始使用**: 请先前往"配置"页面设置API密钥
|
||
""")
|
||
|
||
|
||
def show_config_page():
|
||
"""显示配置页面"""
|
||
st.title("⚙️ 系统配置")
|
||
st.markdown("---")
|
||
|
||
load_config()
|
||
|
||
# API配置
|
||
st.header("📡 API配置")
|
||
|
||
api_client = st.session_state.api_client
|
||
providers = api_client.config.get('providers', {})
|
||
current_provider = api_client.current_provider
|
||
|
||
# 选择API提供商
|
||
provider_options = list(providers.keys())
|
||
selected_provider = st.selectbox(
|
||
"选择API提供商",
|
||
provider_options,
|
||
index=provider_options.index(current_provider) if current_provider in provider_options else 0
|
||
)
|
||
|
||
if selected_provider != current_provider:
|
||
api_client.set_provider(selected_provider)
|
||
st.success(f"已切换到: {selected_provider}")
|
||
|
||
# 配置API密钥
|
||
provider_config = providers[selected_provider]
|
||
st.subheader(f"{selected_provider.upper()} 配置")
|
||
|
||
api_key = st.text_input(
|
||
"API密钥",
|
||
value=provider_config.get('api_key', '') or os.getenv(f'{selected_provider.upper()}_API_KEY', ''),
|
||
type="password",
|
||
help="输入API密钥,或通过环境变量设置"
|
||
)
|
||
|
||
if st.button("保存API密钥"):
|
||
try:
|
||
api_client.update_api_key(selected_provider, api_key)
|
||
st.success("API密钥已保存")
|
||
except Exception as e:
|
||
st.error(f"保存失败: {str(e)}")
|
||
|
||
# 显示其他配置信息
|
||
with st.expander("查看配置详情"):
|
||
st.json(provider_config)
|
||
|
||
# 提示信息
|
||
st.info("💡 提示:Prompt和测试规范配置将在上传文档后进行设置")
|
||
|
||
|
||
def show_generate_page():
|
||
"""显示生成页面"""
|
||
st.title("🚀 生成测试")
|
||
st.markdown("---")
|
||
|
||
load_config()
|
||
|
||
# 上传文档(支持 JSON 和 Word)
|
||
st.header("📄 上传文档")
|
||
uploaded_file = st.file_uploader(
|
||
"选择文档文件(支持 JSON 或 Word)",
|
||
type=['json', 'docx'],
|
||
help="可以直接上传Word使用说明书(.docx),系统会自动转换为JSON;也可以上传已转换好的JSON文件"
|
||
)
|
||
|
||
if uploaded_file is not None:
|
||
try:
|
||
# 保存上传的文件
|
||
upload_dir = Path("uploads")
|
||
upload_dir.mkdir(exist_ok=True)
|
||
file_path = upload_dir / uploaded_file.name
|
||
|
||
with open(file_path, 'wb') as f:
|
||
f.write(uploaded_file.getbuffer())
|
||
|
||
# 如果是Word文档,先转换为JSON
|
||
if file_path.suffix.lower() == ".docx":
|
||
json_path = upload_dir / f"{file_path.stem}.json"
|
||
convert_docx_to_json(str(file_path), str(json_path))
|
||
parser_target_path = json_path
|
||
else:
|
||
# 直接使用上传的JSON文件
|
||
parser_target_path = file_path
|
||
|
||
# 解析JSON
|
||
parser = JSONParser(str(parser_target_path))
|
||
st.session_state.json_parser = parser
|
||
|
||
# 只有在上传新文档时才清空自定义prompts(检查路径是否变化)
|
||
current_doc_path = str(parser_target_path)
|
||
previous_doc_path = st.session_state.get('last_uploaded_doc_path', '')
|
||
|
||
if current_doc_path != previous_doc_path:
|
||
# 新文档,清空之前的自定义prompts
|
||
st.session_state.custom_prompts.clear()
|
||
st.session_state.custom_prompt_template = None
|
||
st.session_state.use_custom_prompt = False
|
||
st.session_state.last_uploaded_doc_path = current_doc_path
|
||
|
||
# 获取文档信息
|
||
doc_info = parser.get_document_info()
|
||
st.session_state.document_info = doc_info
|
||
|
||
# 显示文档信息
|
||
st.success("文档上传成功!")
|
||
col1, col2, col3 = st.columns(3)
|
||
with col1:
|
||
st.metric("文档标题", doc_info.get('title', 'N/A'))
|
||
with col2:
|
||
st.metric("章节数量", doc_info.get('section_count', 0))
|
||
with col3:
|
||
st.metric("版本", doc_info.get('version', 'N/A'))
|
||
|
||
# 提取功能点
|
||
function_points = parser.extract_function_points()
|
||
st.session_state.function_points = function_points
|
||
|
||
# 显示功能点
|
||
if function_points:
|
||
# ========== 新增:Prompt/测试规范配置区域 ==========
|
||
st.markdown("---")
|
||
st.header("📝 Prompt/测试规范配置")
|
||
|
||
# 1. 需求类型统计
|
||
requirement_types = {}
|
||
for fp in function_points:
|
||
req_type = fp.get('requirement_type', '未知')
|
||
requirement_types[req_type] = requirement_types.get(req_type, 0) + 1
|
||
|
||
if requirement_types:
|
||
st.subheader("需求类型统计")
|
||
cols = st.columns(len(requirement_types))
|
||
for idx, (req_type, count) in enumerate(requirement_types.items()):
|
||
with cols[idx]:
|
||
st.metric(req_type, count)
|
||
|
||
# 2. Prompt策略选择
|
||
use_standards = st.checkbox(
|
||
"使用测试规范(推荐)",
|
||
value=True,
|
||
help="启用后,系统会根据需求类型自动选择适用的测试规范,生成符合行业标准的测试用例"
|
||
)
|
||
|
||
# 3. 测试规范选择详情(如果启用)
|
||
if use_standards:
|
||
try:
|
||
from modules.test_standard_manager import TestStandardManager
|
||
|
||
# 初始化测试规范管理器
|
||
if st.session_state.standard_manager is None:
|
||
st.session_state.standard_manager = TestStandardManager(
|
||
api_client=st.session_state.api_client
|
||
)
|
||
|
||
standard_manager = st.session_state.standard_manager
|
||
|
||
# 显示测试规范选择详情
|
||
with st.expander("📋 查看测试规范选择详情"):
|
||
for fp in function_points:
|
||
requirement = _convert_to_requirement(fp)
|
||
standards = standard_manager.get_applicable_standards(
|
||
requirement,
|
||
use_ai=False # 先不使用AI,避免额外调用
|
||
)
|
||
|
||
req_type = requirement.get('requirement_type', '未知')
|
||
req_id = requirement.get('requirement_id', fp.get('function_name', ''))
|
||
|
||
st.markdown(f"**{req_id}** ({req_type})")
|
||
if standards:
|
||
st.write(f"选择的测试规范: {', '.join(standards)}")
|
||
else:
|
||
st.write("⚠️ 未找到适用的测试规范")
|
||
st.markdown("---")
|
||
|
||
# 4. Prompt预览与编辑
|
||
preview_prompt = st.checkbox("预览规范化Prompt")
|
||
if preview_prompt and function_points:
|
||
sample_fp = function_points[0]
|
||
requirement = _convert_to_requirement(sample_fp)
|
||
standards = standard_manager.get_applicable_standards(requirement, use_ai=False)
|
||
|
||
if standards:
|
||
# 生成默认prompt
|
||
default_prompt = standard_manager.build_prompt(requirement, standards)
|
||
|
||
# 获取当前索引(第一个功能点)
|
||
sample_idx = 0
|
||
|
||
# 如果没有自定义prompt,使用默认值
|
||
if sample_idx not in st.session_state.custom_prompts:
|
||
st.session_state.custom_prompts[sample_idx] = default_prompt
|
||
|
||
# 可编辑的Prompt文本框
|
||
edited_prompt = st.text_area(
|
||
"Prompt预览(可编辑,以第一个功能点为例)",
|
||
value=st.session_state.custom_prompts[sample_idx],
|
||
height=400,
|
||
key="prompt_editor_preview",
|
||
help="您可以直接修改Prompt内容,修改后的内容将在生成测试时使用"
|
||
)
|
||
|
||
# 更新session state
|
||
st.session_state.custom_prompts[sample_idx] = edited_prompt
|
||
|
||
# 操作按钮
|
||
col1, col2, col3 = st.columns(3)
|
||
with col1:
|
||
if st.button("🔄 恢复默认", key="restore_default_prompt"):
|
||
st.session_state.custom_prompts[sample_idx] = default_prompt
|
||
st.session_state.use_custom_prompt = False
|
||
st.success("已恢复为默认Prompt")
|
||
st.rerun()
|
||
|
||
with col2:
|
||
if st.button("📋 应用到所有功能点", key="apply_to_all"):
|
||
# 将当前编辑的prompt应用到所有功能点
|
||
for idx in range(len(function_points)):
|
||
st.session_state.custom_prompts[idx] = edited_prompt
|
||
st.session_state.use_custom_prompt = True
|
||
st.success(f"✅ 已应用到 {len(function_points)} 个功能点")
|
||
|
||
with col3:
|
||
if st.button("🗑️ 清空所有自定义", key="clear_all_custom"):
|
||
st.session_state.custom_prompts.clear()
|
||
st.session_state.use_custom_prompt = False
|
||
st.success("已清空所有自定义Prompt")
|
||
st.rerun()
|
||
|
||
# 显示状态信息
|
||
if st.session_state.custom_prompts:
|
||
custom_count = len(st.session_state.custom_prompts)
|
||
st.info(f"ℹ️ 当前有 {custom_count} 个功能点使用自定义Prompt")
|
||
else:
|
||
st.warning("无法生成Prompt预览:未找到适用的测试规范")
|
||
|
||
except Exception as e:
|
||
st.warning(f"测试规范功能加载失败: {str(e)},将使用传统Prompt模式")
|
||
use_standards = False
|
||
|
||
# 5. 更新PromptManager配置
|
||
if st.session_state.prompt_manager.use_standards != use_standards:
|
||
st.session_state.prompt_manager.use_standards = use_standards
|
||
if use_standards and st.session_state.standard_manager:
|
||
st.session_state.prompt_manager.standard_manager = st.session_state.standard_manager
|
||
|
||
# ========== 原有功能点选择部分 ==========
|
||
st.markdown("---")
|
||
st.header("📋 功能点列表")
|
||
# 选择功能点
|
||
selected_indices = st.multiselect(
|
||
"选择要生成测试的功能点(留空则选择全部)",
|
||
options=list(range(len(function_points))),
|
||
format_func=lambda x: f"{function_points[x]['module_name']} - {function_points[x]['function_name']}"
|
||
)
|
||
|
||
if not selected_indices:
|
||
selected_function_points = function_points
|
||
else:
|
||
selected_function_points = [function_points[i] for i in selected_indices]
|
||
|
||
# 显示功能点详情
|
||
with st.expander("查看功能点详情"):
|
||
for idx, fp in enumerate(selected_function_points):
|
||
st.markdown(f"### {idx + 1}. {fp['function_name']}")
|
||
st.markdown(f"**模块**: {fp['module_name']}")
|
||
st.markdown(f"**描述**: {fp.get('description', 'N/A')}")
|
||
if fp.get('operation_steps'):
|
||
st.markdown("**操作步骤**:")
|
||
for step in fp['operation_steps']:
|
||
st.markdown(f"- {step}")
|
||
st.markdown("---")
|
||
|
||
# 生成选项
|
||
st.markdown("---")
|
||
st.header("⚙️ 生成选项")
|
||
|
||
generation_mode = st.radio(
|
||
"生成模式",
|
||
['batch', 'separate'],
|
||
format_func=lambda x: {
|
||
'batch': '批量生成(一次性生成测试项和测试用例,更快)',
|
||
'separate': '分步生成(先生成测试项,再生成测试用例,更灵活)'
|
||
}[x]
|
||
)
|
||
|
||
# 开始生成
|
||
if st.button("🚀 开始生成测试", type="primary", use_container_width=True):
|
||
if not st.session_state.api_client.get_provider_config().get('api_key'):
|
||
st.error("请先在配置页面设置API密钥!")
|
||
else:
|
||
generate_tests(selected_function_points, generation_mode)
|
||
else:
|
||
st.warning("未能从文档中提取到功能点,请检查文档格式。")
|
||
|
||
except Exception as e:
|
||
st.error(f"处理文件失败: {str(e)}")
|
||
st.exception(e)
|
||
|
||
else:
|
||
st.info("👆 请上传JSON格式的文档文件")
|
||
|
||
|
||
def generate_tests(function_points: List[Dict[str, Any]], mode: str):
|
||
"""生成测试项和测试用例"""
|
||
api_client = st.session_state.api_client
|
||
prompt_manager = st.session_state.prompt_manager
|
||
parser = st.session_state.json_parser
|
||
|
||
if parser is None:
|
||
st.error("请先上传文档")
|
||
return
|
||
|
||
# 先清空之前的自定义prompts
|
||
prompt_manager.clear_custom_prompts()
|
||
|
||
# 注入用户自定义的prompts到PromptManager
|
||
all_function_points = st.session_state.function_points
|
||
if st.session_state.custom_prompts:
|
||
st.info(f"📝 开始注入 {len(st.session_state.custom_prompts)} 个自定义Prompt")
|
||
|
||
for idx, fp in enumerate(all_function_points):
|
||
if idx in st.session_state.custom_prompts:
|
||
# 获取功能点ID(与TestGenerator._convert_to_requirement保持一致)
|
||
func_id = fp.get('requirement_id', fp.get('function_name', ''))
|
||
custom_prompt = st.session_state.custom_prompts[idx]
|
||
|
||
# 调试信息
|
||
st.write(f" - 索引 {idx}: {fp.get('function_name', 'N/A')}")
|
||
st.write(f" - requirement_id: `{fp.get('requirement_id', 'N/A')}`")
|
||
st.write(f" - 映射到的ID: `{func_id}`")
|
||
st.write(f" - Prompt长度: {len(custom_prompt)} 字符")
|
||
|
||
prompt_manager.set_custom_prompt(func_id, custom_prompt)
|
||
|
||
st.success(f"✅ 已加载 {len(st.session_state.custom_prompts)} 个自定义Prompt到缓存")
|
||
|
||
# 显示缓存内容
|
||
with st.expander("🔍 查看Prompt缓存详情"):
|
||
st.write("当前PromptManager缓存的自定义Prompt:")
|
||
for key, value in prompt_manager.custom_prompts_cache.items():
|
||
st.write(f"- **{key}**: {len(value)} 字符")
|
||
|
||
# 创建生成器
|
||
generator = TestGenerator(
|
||
str(parser.json_path),
|
||
api_client=api_client,
|
||
prompt_manager=prompt_manager
|
||
)
|
||
|
||
progress_bar = st.progress(0)
|
||
status_text = st.empty()
|
||
|
||
try:
|
||
if mode == 'batch':
|
||
# 批量生成
|
||
status_text.text("开始批量生成...")
|
||
result = generator.generate_batch(
|
||
function_points=function_points,
|
||
progress_callback=lambda current, total, msg: (
|
||
progress_bar.progress(current / total),
|
||
status_text.text(f"进度: {current}/{total} - {msg}")
|
||
)
|
||
)
|
||
st.session_state.test_items = result['test_items']
|
||
st.session_state.test_cases = result['test_cases']
|
||
else:
|
||
# 分步生成
|
||
status_text.text("第一步:生成测试项...")
|
||
test_items = generator.generate_test_items(
|
||
function_points=function_points,
|
||
progress_callback=lambda current, total, msg: (
|
||
progress_bar.progress(current / (total * 2)),
|
||
status_text.text(f"生成测试项: {current}/{total} - {msg}")
|
||
)
|
||
)
|
||
st.session_state.test_items = test_items
|
||
|
||
status_text.text("第二步:生成测试用例...")
|
||
test_cases = generator.generate_test_cases(
|
||
progress_callback=lambda current, total, msg: (
|
||
progress_bar.progress(0.5 + current / (total * 2)),
|
||
status_text.text(f"生成测试用例: {current}/{total} - {msg}")
|
||
)
|
||
)
|
||
st.session_state.test_cases = test_cases
|
||
|
||
progress_bar.progress(1.0)
|
||
status_text.text("生成完成!")
|
||
st.success(f"✅ 成功生成 {len(st.session_state.test_items)} 个测试项和 {len(st.session_state.test_cases)} 个测试用例")
|
||
|
||
# 跳转到结果页面
|
||
st.session_state.current_page = 'results'
|
||
st.rerun()
|
||
|
||
except Exception as e:
|
||
st.error(f"生成失败: {str(e)}")
|
||
st.exception(e)
|
||
progress_bar.empty()
|
||
status_text.empty()
|
||
|
||
|
||
def _convert_to_requirement(func_point: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""
|
||
将功能点转换为需求格式(用于测试规范选择)
|
||
|
||
Args:
|
||
func_point: 功能点字典
|
||
|
||
Returns:
|
||
需求字典
|
||
"""
|
||
requirement = {
|
||
'requirement_id': func_point.get('requirement_id', func_point.get('function_name', '')),
|
||
'requirement_type': func_point.get('requirement_type', '功能需求'),
|
||
'description': func_point.get('description', func_point.get('function_name', '')),
|
||
'module_name': func_point.get('module_name', '')
|
||
}
|
||
|
||
# 如果有接口信息,添加进去
|
||
if func_point.get('interface_info'):
|
||
requirement['interface_info'] = func_point['interface_info']
|
||
|
||
return requirement
|
||
|
||
|
||
def show_results_page():
|
||
"""显示结果页面"""
|
||
st.title("📊 生成结果")
|
||
st.markdown("---")
|
||
|
||
test_items = st.session_state.test_items
|
||
test_cases = st.session_state.test_cases
|
||
document_info = st.session_state.document_info
|
||
|
||
if not test_items and not test_cases:
|
||
st.warning('还没有生成测试项和测试用例,请先前往"生成测试"页面')
|
||
return
|
||
|
||
# 统计信息
|
||
col1, col2, col3, col4 = st.columns(4)
|
||
with col1:
|
||
st.metric("测试项数量", len(test_items))
|
||
with col2:
|
||
st.metric("测试用例数量", len(test_cases))
|
||
with col3:
|
||
modules = set(item.get('module_name', '') for item in test_items)
|
||
st.metric("涉及模块", len(modules))
|
||
with col4:
|
||
priorities = {}
|
||
for case in test_cases:
|
||
p = case.get('priority', '中')
|
||
priorities[p] = priorities.get(p, 0) + 1
|
||
high_priority = priorities.get('高', 0)
|
||
st.metric("高优先级用例", high_priority)
|
||
|
||
st.markdown("---")
|
||
|
||
# 预览
|
||
st.header("📋 预览")
|
||
|
||
preview_format = st.selectbox("选择预览格式", ['JSON', 'Markdown'])
|
||
|
||
if preview_format == 'JSON':
|
||
formatter = OutputFormatter(test_items, test_cases, document_info)
|
||
json_str = formatter.to_json()
|
||
st.json(json.loads(json_str))
|
||
else:
|
||
formatter = OutputFormatter(test_items, test_cases, document_info)
|
||
markdown_str = formatter.to_markdown()
|
||
st.markdown(markdown_str)
|
||
|
||
st.markdown("---")
|
||
|
||
# 下载
|
||
st.header("💾 下载结果")
|
||
|
||
col1, col2, col3 = st.columns(3)
|
||
|
||
formatter = OutputFormatter(test_items, test_cases, document_info)
|
||
|
||
with col1:
|
||
if st.button("下载JSON格式", use_container_width=True):
|
||
output_path = "outputs/test_results.json"
|
||
Path("outputs").mkdir(exist_ok=True)
|
||
formatter.to_json(output_path)
|
||
with open(output_path, 'rb') as f:
|
||
st.download_button(
|
||
"⬇️ 下载JSON",
|
||
f.read(),
|
||
"test_results.json",
|
||
"application/json",
|
||
key="download_json"
|
||
)
|
||
|
||
with col2:
|
||
if st.button("下载Markdown格式", use_container_width=True):
|
||
output_path = "outputs/test_results.md"
|
||
Path("outputs").mkdir(exist_ok=True)
|
||
formatter.to_markdown(output_path)
|
||
with open(output_path, 'rb') as f:
|
||
st.download_button(
|
||
"⬇️ 下载Markdown",
|
||
f.read(),
|
||
"test_results.md",
|
||
"text/markdown",
|
||
key="download_md"
|
||
)
|
||
|
||
with col3:
|
||
if st.button("下载Excel格式", use_container_width=True):
|
||
try:
|
||
output_path = "outputs/test_results.xlsx"
|
||
Path("outputs").mkdir(exist_ok=True)
|
||
formatter.to_excel(output_path)
|
||
with open(output_path, 'rb') as f:
|
||
st.download_button(
|
||
"⬇️ 下载Excel",
|
||
f.read(),
|
||
"test_results.xlsx",
|
||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||
key="download_excel"
|
||
)
|
||
except ImportError:
|
||
st.error("需要安装openpyxl: pip install openpyxl")
|
||
|
||
|
||
def main():
|
||
"""主函数"""
|
||
init_session_state()
|
||
|
||
# 侧边栏导航
|
||
with st.sidebar:
|
||
st.title("🧪 测试专家系统")
|
||
st.markdown("---")
|
||
|
||
page = st.radio(
|
||
"导航",
|
||
['home', 'config', 'generate', 'results'],
|
||
format_func=lambda x: {
|
||
'home': '🏠 首页',
|
||
'config': '⚙️ 配置',
|
||
'generate': '🚀 生成测试',
|
||
'results': '📊 结果'
|
||
}[x]
|
||
)
|
||
|
||
st.markdown("---")
|
||
st.markdown("### 使用提示")
|
||
st.info("""
|
||
1. 首次使用请先配置API密钥
|
||
2. 上传JSON格式的文档
|
||
3. 选择功能点并生成测试
|
||
4. 下载生成的结果
|
||
""")
|
||
|
||
# 路由到对应页面
|
||
if page == 'home':
|
||
show_home_page()
|
||
elif page == 'config':
|
||
show_config_page()
|
||
elif page == 'generate':
|
||
show_generate_page()
|
||
elif page == 'results':
|
||
show_results_page()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
|