feat: implement ERP AI Assistant Phase 1

Backend (FastAPI + SQLAlchemy + Claude API + RAG):
- Config management with Pydantic v2
- Database engine with connection pooling and SQL injection prevention
- AI engine with Claude API integration (support custom base URL)
- RAG engine with ChromaDB and sentence-transformers
- Requirement analysis service
- Config generation service
- Executor engine with SQL validation
- REST API endpoints: /analyze, /generate, /execute

Frontend (Vue 3 + Element Plus + Pinia):
- Complete 3-step workflow: analyze → generate → execute
- Step indicator with progress visualization
- Analysis result display with field table
- SQL preview with monospace font
- Execute confirmation dialog with safety warning
- Execution result display
- State management with Pinia
- API service integration

Security:
- SQL injection prevention with parameterized queries
- Dangerous SQL operation blocking
- Database password URL encoding
- Transaction auto-rollback
- Pydantic config validation

Features:
- Natural language requirement analysis
- Automated SQL configuration generation
- Safe execution with human review
- LAN access support
- Custom Claude API endpoint support

Documentation:
- README with quick start guide
- Quick start guide
- LAN access configuration
- Dependency fixes guide
- Claude API configuration
- Git operation guide
- Implementation report

Dependencies fixed:
- numpy<2.0.0 for chromadb compatibility
- sentence-transformers==2.7.0 for huggingface_hub compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 14:23:20 +00:00
commit acd73431ae
60 changed files with 11284 additions and 0 deletions

104
.gitignore vendored Normal file
View File

@@ -0,0 +1,104 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual Environment
venv/
env/
ENV/
.venv
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Environment Variables
.env
.env.local
.env.*.local
# Logs
*.log
logs/
# Database
*.db
*.sqlite
*.sqlite3
# Testing
.pytest_cache/
.coverage
htmlcov/
.tox/
# ChromaDB
backend/knowledge_base/chroma_db/
# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Build
frontend/dist/
frontend/dist-ssr/
frontend/*.local
# Editor directories and files
frontend/.vscode/
frontend/.idea/
frontend/*.suo
frontend*.ntvs*
frontend*.njsproj
frontend*.sln
frontend*.sw?
# OS
.DS_Store
Thumbs.db
# Project specific
erp-doc/
backend/knowledge_base/documents/*.docx
backend/knowledge_base/documents/*.xlsx
backend/knowledge_base/documents/*.pdf
backend/knowledge_base/documents/*.pptx
backend/knowledge_base/documents/*.vsdx
backend/knowledge_base/documents/*.xls
# Temporary files
*.tmp
*.temp
*.bak
*.swp
*~
# Archives
*.zip
*.tar.gz
*.rar
*.7z

160
CHANGELOG.md Normal file
View File

@@ -0,0 +1,160 @@
# 更新日志
## [v1.1.0] - 2026-03-21
### 🎉 新增功能
#### 前端完整工作流程
-**需求分析界面**: 输入自然语言需求,展示结构化分析结果
-**配置生成界面**: 预览生成的 SQL 配置语句
-**执行配置界面**: 确认对话框 + 执行结果展示
-**步骤指示器**: 清晰显示当前进度(分析 → 生成 → 执行)
#### API 服务封装
- 创建 `frontend/src/api/index.js`: 封装三个核心 API 调用
- `analyzeRequirement()`: 需求分析
- `generateConfig()`: 配置生成
- `executeConfig()`: 配置执行
#### 状态管理
- 创建 `frontend/src/stores/function.js`: Pinia 状态管理
- 会话管理
- 分析结果存储
- 配置结果存储
- 执行结果存储
- 加载状态管理
#### UI 组件增强
- **分析结果展示**:
- 使用 `el-descriptions` 展示基本信息
- 使用 `el-table` 展示字段列表
- 标签化显示(功能类型、必填项等)
- **SQL 配置预览**:
- 等宽字体显示
- 15 行文本框
- 只读模式
- **执行确认对话框**:
- 安全警告提示
- 二次确认机制
- **执行结果展示**:
- 成功/失败状态图标
- 详细消息展示
### 🔧 功能改进
#### 后端集成
- 更新 `backend/app/api/generate.py`: 自动存储生成的 SQL 到会话存储
- 完善前后端数据流:分析 → 生成 → 存储 → 执行
#### 用户体验优化
- ✅ 所有异步操作添加 loading 状态
- ✅ 友好的错误提示消息
- ✅ 表单必填项验证
- ✅ 危险操作二次确认
- ✅ 支持重新开始整个流程
### 📚 文档更新
- 创建 `docs/QUICK_START.md`: 完整的快速上手指南
- 创建 `docs/FRONTEND_UPDATE.md`: 前端功能说明文档
- 更新 `README.md`: 添加快速上手指引
### 🐛 问题修复
- 修复前端无分析结果展示的问题
- 修复缺少配置生成和执行按钮的问题
- 修复工作流程不完整的问题
---
## [v1.0.0] - 2026-03-21
### 🎉 初始发布
#### 后端核心功能
- ✅ 配置管理Pydantic v2
- ✅ 数据库引擎SQLAlchemy + pyodbc
- ✅ AI 引擎Claude API 集成)
- ✅ Prompt 模板设计
- ✅ RAG 引擎ChromaDB + sentence-transformers
- ✅ 需求解析服务
- ✅ 配置生成服务
- ✅ 执行引擎SQL 安全验证)
#### API 层
- ✅ FastAPI 应用框架
- ✅ 请求/响应模型Pydantic
- ✅ 三个核心端点:
- POST `/api/v1/analyze`
- POST `/api/v1/generate`
- POST `/api/v1/execute`
#### 前端基础
- ✅ Vue 3 + Vite 项目结构
- ✅ Vue Router 路由配置
- ✅ Element Plus UI 集成
- ✅ 基础布局Layout、CreateFunction、History
#### 安全特性
- ✅ SQL 注入防护(参数化查询)
- ✅ 危险 SQL 操作拦截
- ✅ 数据库密码 URL 编码
- ✅ 事务自动回滚
- ✅ Pydantic 配置验证
#### 配置支持
- ✅ 自定义 Claude API base URL支持代理/自托管)
- ✅ 局域网访问支持
- ✅ 环境变量管理
#### 文档
- ✅ README.md
- ✅ 实施报告
- ✗ API 配置指南
- ✗ 局域网访问指南
- ✗ 依赖问题修复指南
---
## 路线图
### [v1.2.0] - 计划中
- [ ] SQL 语法高亮显示
- [ ] 配置导出功能
- [ ] 历史记录页面
- [ ] 数据库元数据查询 API
- [ ] 知识库管理界面
### [v1.3.0] - 计划中
- [ ] 执行日志和审计系统
- [ ] 配置模板库
- [ ] 批量操作支持
- [ ] 错误诊断功能
### [v2.0.0] - 未来
- [ ] 多用户权限管理
- [ ] 配置版本控制
- [ ] CI/CD 集成
- [ ] 性能优化分析工具
---
**版本命名规范**: 遵循 [语义化版本](https://semver.org/lang/zh-CN/)

264
README.md Normal file
View File

@@ -0,0 +1,264 @@
# ERP AI Assistant - 一零软件智能配置助手
基于 Claude AI 的 ERP 平台智能配置助手,支持自然语言需求分析、自动化配置生成和安全执行。
## 🌟 核心功能
- **智能需求分析**: 自然语言输入 → 结构化需求文档
- **自动配置生成**: 基于需求自动生成 SQL 配置语句
- **安全执行机制**: SQL 安全验证 + 人工审核 + 事务保护
- **知识库增强**: RAG 技术提供平台配置知识检索
## 🚀 快速上手
**首次使用请阅读**: [快速上手指南](docs/QUICK_START.md)
### 最快 3 步开始
```bash
# 1. 配置后端
cd backend && pip install -r requirements.txt
cp .env.example .env # 编辑填入数据库和 API 配置
# 2. 启动后端
python -m app.main
# 3. 启动前端
cd ../frontend && npm install && npm run dev
```
访问 http://localhost:5173 开始使用!
### 1. 环境要求
- Python 3.10+
- Node.js 18+
- SQL Server 数据库
- Anthropic API Key
### 2. 后端安装
```bash
cd backend
# 创建虚拟环境
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# 安装依赖
pip install -r requirements.txt
# 配置环境变量
cp .env.example .env
# 编辑 .env 文件,填入配置信息
```
### 3. 前端安装
```bash
cd frontend
# 安装依赖
npm install
```
### 4. 启动服务
**启动后端:**
```bash
cd backend
source venv/bin/activate
python -m app.main
```
后端服务运行在 http://localhost:8000
**启动前端:**
```bash
cd frontend
npm run dev
```
前端服务运行在 http://localhost:5173
### 5. 访问应用
**本地访问:**
打开浏览器访问 http://localhost:5173
**局域网访问:**
其他设备可通过服务器 IP 访问http://192.168.1.100:5173
详细配置请参考 [局域网访问指南](docs/LAN_ACCESS.md)
## 📚 API 文档
启动后端后访问:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
### 核心 API 端点
| 端点 | 方法 | 描述 |
|------|------|------|
| `/api/v1/analyze` | POST | 分析用户需求 |
| `/api/v1/generate` | POST | 生成 SQL 配置 |
| `/api/v1/execute` | POST | 执行 SQL 配置 |
| `/health` | GET | 健康检查 |
## 🏗️ 项目结构
```
erp-ass/
├── backend/ # 后端服务
│ ├── app/
│ │ ├── api/ # API 路由
│ │ ├── core/ # 核心引擎
│ │ ├── models/ # 数据模型
│ │ ├── services/ # 业务服务
│ │ ├── config.py # 配置管理
│ │ └── main.py # 应用入口
│ ├── tests/ # 测试文件
│ ├── knowledge_base/ # 知识库文档
│ └── requirements.txt # Python 依赖
├── frontend/ # 前端应用
│ ├── src/
│ │ ├── router/ # 路由配置
│ │ ├── views/ # 页面组件
│ │ ├── main.js # 应用入口
│ │ └── App.vue # 根组件
│ ├── index.html
│ ├── vite.config.js
│ └── package.json
└── docs/ # 项目文档
```
## 🔧 配置说明
### 后端环境变量 (.env)
```bash
# 应用配置
APP_NAME=ERP AI Assistant
APP_ENV=development
DEBUG=True
SECRET_KEY=your-secret-key
# 数据库配置
DB_DRIVER=ODBC Driver 17 for SQL Server
DB_SERVER=localhost
DB_PORT=1433
DB_NAME=ERP_DB
DB_USER=sa
DB_PASSWORD=your-password
# Claude API 配置
ANTHROPIC_API_KEY=your-api-key
# ANTHROPIC_BASE_URL=https://your-proxy.com # Optional: custom base URL for proxy or self-hosted
CLAUDE_MODEL=claude-sonnet-4-6
CLAUDE_MAX_TOKENS=8192
CLAUDE_TEMPERATURE=0.7
# 知识库配置
KNOWLEDGE_BASE_PATH=./knowledge_base
CHROMA_DB_PATH=./knowledge_base/chroma_db
EMBEDDING_MODEL=all-MiniLM-L6-v2
CHUNK_SIZE=500
CHUNK_OVERLAP=50
```
## 🧪 运行测试
```bash
cd backend
source venv/bin/activate
pytest tests/ -v --cov=app
```
## 📖 使用流程
### 1. 创建新功能
1. 访问前端页面,进入"新建功能"
2. 输入自然语言需求,例如:
```
创建一个销售订单管理页面,包含订单号、客户、订单日期、金额字段
```
3. 点击"分析需求",系统自动生成结构化需求文档
### 2. 生成配置
1. 查看需求分析结果
2. 确认需求正确后,点击"生成配置"
3. 系统自动生成 SQL 配置语句
### 3. 执行配置
1. 预览生成的 SQL 语句
2. 确认无误后,点击"执行"
3. 系统安全执行配置,记录执行日志
## 🔐 安全特性
- ✅ SQL 注入防护(参数化查询)
- ✅ 危险操作拦截DROP、TRUNCATE
- ✅ 事务保护(自动回滚)
- ✅ 人工审核机制
- ✅ 密码安全编码
## 📋 开发计划
### Phase 1 (已完成) ✅
- 基础架构搭建
- 核心引擎实现
- API 层开发
- 前端基础界面
### Phase 2 (计划中)
- 执行日志和审计系统
- 数据库元数据 API
- 知识库管理界面
- 配置预览组件
- 执行监控组件
### Phase 3 (计划中)
- 错误排查系统
- 系统优化功能
- 性能分析工具
## 🛠️ 技术栈
**后端:**
- FastAPI - 现代 Web 框架
- SQLAlchemy - ORM
- Anthropic Claude - AI 引擎
- ChromaDB - 向量数据库
- sentence-transformers - 文本嵌入
**前端:**
- Vue 3 - 渐进式框架
- Vite - 构建工具
- Element Plus - UI 组件库
- Vue Router - 路由管理
- Pinia - 状态管理
**数据库:**
- SQL Server - 业务数据库
- ChromaDB - 知识库向量数据库
## 📄 许可证
本项目仅供内部使用。
## 🤝 贡献
暂不接受外部贡献。
## 📞 联系方式
如有问题,请联系项目负责人。
---
**版本:** 1.0.0
**更新日期:** 2026-03-21

26
backend/.env.example Normal file
View File

@@ -0,0 +1,26 @@
APP_NAME=ERP AI Assistant
APP_ENV=development
DEBUG=True
SECRET_KEY=change-this-in-production
# Database
DB_DRIVER=ODBC Driver 17 for SQL Server
DB_SERVER=192.168.120.19
DB_PORT=1433
DB_NAME=DMPF_HY
DB_USER=sa
DB_PASSWORD=your-password
# Claude API
ANTHROPIC_API_KEY=your-claude-api-key
# ANTHROPIC_BASE_URL=https://api.anthropic.com # Optional: uncomment to use custom base URL (for proxy or self-hosted)
CLAUDE_MODEL=claude-sonnet-4-6
CLAUDE_MAX_TOKENS=8192
CLAUDE_TEMPERATURE=0.7
# Knowledge Base
KNOWLEDGE_BASE_PATH=./knowledge_base
CHROMA_DB_PATH=./knowledge_base/chroma_db
EMBEDDING_MODEL=all-MiniLM-L6-v2
CHUNK_SIZE=500
CHUNK_OVERLAP=50

2
backend/app/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
"""ERP AI Assistant Backend"""
__version__ = "1.0.0"

View File

@@ -0,0 +1 @@
"""API routes for ERP AI Assistant."""

113
backend/app/api/analyze.py Normal file
View File

@@ -0,0 +1,113 @@
"""Analyze API endpoint for requirement analysis.
This module provides the /analyze endpoint for analyzing user requirements.
"""
import uuid
from typing import Dict
from fastapi import APIRouter, HTTPException, status
from loguru import logger
from app.models.request import AnalyzeRequest
from app.models.response import AnalyzeResponse, ErrorResponse
from app.services.requirement_service import RequirementService
# Create router
router = APIRouter()
@router.post(
"/analyze",
response_model=AnalyzeResponse,
responses={
400: {"model": ErrorResponse, "description": "Invalid request"},
500: {"model": ErrorResponse, "description": "Internal server error"}
},
summary="Analyze user requirement",
description="Analyze natural language or structured requirement and return structured specification"
)
async def analyze_requirement(request: AnalyzeRequest) -> AnalyzeResponse:
"""Analyze user requirement and return structured specification.
This endpoint accepts either natural language or structured input,
processes it through Claude AI with RAG knowledge retrieval, and
returns a structured requirement specification.
Args:
request: AnalyzeRequest containing input_type, content, and optional session_id
Returns:
AnalyzeResponse with session_id, status, and structured data
Raises:
HTTPException: 400 for invalid input, 500 for processing errors
"""
# Generate session ID if not provided
session_id = request.session_id or str(uuid.uuid4())
try:
# Validate input type
if request.input_type not in ["natural_language", "structured"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={
"code": "INVALID_INPUT_TYPE",
"message": "input_type must be 'natural_language' or 'structured'",
"session_id": session_id
}
)
# Validate content
if not request.content or not request.content.strip():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={
"code": "EMPTY_CONTENT",
"message": "content cannot be empty",
"session_id": session_id
}
)
logger.info(f"[{session_id}] Processing analyze request: {request.content[:50]}...")
# Create service and analyze
service = RequirementService()
result = await service.analyze(
user_input=request.content,
session_id=session_id
)
logger.success(f"[{session_id}] Analysis completed successfully")
return AnalyzeResponse(
session_id=session_id,
status="success",
data=result
)
except HTTPException:
# Re-raise HTTP exceptions
raise
except ValueError as e:
logger.error(f"[{session_id}] Validation error: {e}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={
"code": "VALIDATION_ERROR",
"message": str(e),
"session_id": session_id
}
)
except Exception as e:
logger.error(f"[{session_id}] Analysis failed: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail={
"code": "ANALYSIS_FAILED",
"message": f"Failed to analyze requirement: {str(e)}",
"session_id": session_id
}
)

151
backend/app/api/execute.py Normal file
View File

@@ -0,0 +1,151 @@
"""Execute API endpoint for SQL configuration execution.
This module provides the /execute endpoint for executing SQL configuration.
"""
import uuid
from typing import Dict, Any
from fastapi import APIRouter, HTTPException, status
from loguru import logger
from app.models.request import ExecuteRequest
from app.models.response import ExecuteResponse, ErrorResponse
from app.core.executor import ConfigExecutor
# Create router
router = APIRouter()
# In-memory storage for SQL lists (should use Redis/database in production)
_session_sql_store: Dict[str, list] = {}
def store_session_sql(session_id: str, sql_list: list) -> None:
"""Store SQL list for a session.
Args:
session_id: Session ID
sql_list: List of SQL statements
"""
_session_sql_store[session_id] = sql_list
logger.debug(f"Stored {len(sql_list)} SQL statements for session {session_id}")
def get_session_sql(session_id: str) -> list:
"""Retrieve SQL list for a session.
Args:
session_id: Session ID
Returns:
List of SQL statements (empty list if not found)
"""
sql_list = _session_sql_store.get(session_id, [])
logger.debug(f"Retrieved {len(sql_list)} SQL statements for session {session_id}")
return sql_list
@router.post(
"/execute",
response_model=ExecuteResponse,
responses={
400: {"model": ErrorResponse, "description": "Invalid request"},
500: {"model": ErrorResponse, "description": "Internal server error"}
},
summary="Execute SQL configuration",
description="Execute SQL configuration after user confirmation"
)
async def execute_config(request: ExecuteRequest) -> ExecuteResponse:
"""Execute SQL configuration after user confirmation.
This endpoint executes the SQL statements associated with the session.
User must set confirmed=True to proceed with execution.
Args:
request: ExecuteRequest with session_id, confirmed, and backup_enabled
Returns:
ExecuteResponse with execution_id, status, and message
Raises:
HTTPException: 400 if not confirmed or invalid, 500 for execution errors
"""
# Generate execution ID
execution_id = str(uuid.uuid4())
try:
# Check user confirmation
if not request.confirmed:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={
"code": "NOT_CONFIRMED",
"message": "User must confirm execution by setting confirmed=True",
"session_id": request.session_id,
"execution_id": execution_id
}
)
logger.info(f"[{request.session_id}] Processing execute request")
# Retrieve SQL list for session
sql_list = get_session_sql(request.session_id)
if not sql_list:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={
"code": "NO_SQL_FOUND",
"message": "No SQL statements found for this session",
"session_id": request.session_id,
"execution_id": execution_id
}
)
logger.info(f"[{request.session_id}] Retrieved {len(sql_list)} SQL statements")
# Create executor
executor = ConfigExecutor()
# Execute configuration
result = executor.execute_config(sql_list, request.session_id)
if result["success"]:
logger.success(
f"[{request.session_id}] Execution completed: {result['message']}"
)
return ExecuteResponse(
execution_id=execution_id,
status="success",
message=result["message"]
)
else:
logger.error(
f"[{request.session_id}] Execution failed: {result['failed']}"
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail={
"code": "EXECUTION_FAILED",
"message": result["message"],
"error": result["failed"],
"session_id": request.session_id,
"execution_id": execution_id
}
)
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
logger.error(f"[{request.session_id}] Execution failed: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail={
"code": "EXECUTION_ERROR",
"message": f"Failed to execute config: {str(e)}",
"session_id": request.session_id,
"execution_id": execution_id
}
)

102
backend/app/api/generate.py Normal file
View File

@@ -0,0 +1,102 @@
"""Generate API endpoint for configuration generation.
This module provides the /generate endpoint for generating SQL configuration.
"""
from fastapi import APIRouter, HTTPException, status
from loguru import logger
from app.models.request import GenerateRequest
from app.models.response import GenerateResponse, ErrorResponse
from app.services.config_service import ConfigService
from app.api.execute import store_session_sql # Import SQL storage function
# Create router
router = APIRouter()
@router.post(
"/generate",
response_model=GenerateResponse,
responses={
400: {"model": ErrorResponse, "description": "Invalid request"},
500: {"model": ErrorResponse, "description": "Internal server error"}
},
summary="Generate SQL configuration",
description="Generate SQL configuration based on structured requirements"
)
async def generate_config(request: GenerateRequest) -> GenerateResponse:
"""Generate SQL configuration based on structured requirements.
This endpoint takes structured requirements from the analysis phase
and generates SQL configuration statements using Claude AI.
Args:
request: GenerateRequest with session_id and requirements
Returns:
GenerateResponse with session_id, status, and generated config
Raises:
HTTPException: 400 for invalid input, 500 for processing errors
"""
try:
# Validate requirements
if not request.requirements:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={
"code": "EMPTY_REQUIREMENTS",
"message": "requirements cannot be empty",
"session_id": request.session_id
}
)
logger.info(f"[{request.session_id}] Processing generate request")
# Create service and generate config
service = ConfigService()
result = await service.generate(
requirements=request.requirements,
session_id=request.session_id
)
# Store generated SQL for later execution
if result and result.get("配置方案") and result["配置方案"].get("sql_list"):
sql_list = result["配置方案"]["sql_list"]
store_session_sql(request.session_id, sql_list)
logger.info(f"[{request.session_id}] Stored {len(sql_list)} SQL statements for execution")
logger.success(f"[{request.session_id}] Config generation completed")
return GenerateResponse(
session_id=request.session_id,
status="success",
data=result
)
except HTTPException:
# Re-raise HTTP exceptions
raise
except ValueError as e:
logger.error(f"[{request.session_id}] Validation error: {e}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={
"code": "VALIDATION_ERROR",
"message": str(e),
"session_id": request.session_id
}
)
except Exception as e:
logger.error(f"[{request.session_id}] Config generation failed: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail={
"code": "GENERATION_FAILED",
"message": f"Failed to generate config: {str(e)}",
"session_id": request.session_id
}
)

67
backend/app/config.py Normal file
View File

@@ -0,0 +1,67 @@
from pydantic_settings import BaseSettings
from pydantic import ConfigDict, field_validator
from functools import lru_cache
from urllib.parse import quote_plus
class Settings(BaseSettings):
# Application
APP_NAME: str = "ERP AI Assistant"
APP_ENV: str = "development"
DEBUG: bool = True
SECRET_KEY: str
# Database
DB_DRIVER: str
DB_SERVER: str
DB_PORT: int = 1433
DB_NAME: str
DB_USER: str
DB_PASSWORD: str
# Claude API
ANTHROPIC_API_KEY: str
ANTHROPIC_BASE_URL: str | None = None # Optional custom base URL for proxy/self-hosted
CLAUDE_MODEL: str = "claude-sonnet-4-6"
CLAUDE_MAX_TOKENS: int = 8192
CLAUDE_TEMPERATURE: float = 0.7
# Knowledge Base
KNOWLEDGE_BASE_PATH: str = "./knowledge_base"
CHROMA_DB_PATH: str = "./knowledge_base/chroma_db"
EMBEDDING_MODEL: str = "all-MiniLM-L6-v2"
CHUNK_SIZE: int = 500
CHUNK_OVERLAP: int = 50
@property
def DATABASE_URL(self) -> str:
"""构建数据库连接 URL密码安全编码"""
password = quote_plus(self.DB_PASSWORD)
return (
f"mssql+pyodbc://{self.DB_USER}:{password}"
f"@{self.DB_SERVER}:{self.DB_PORT}/{self.DB_NAME}"
f"?driver={quote_plus(self.DB_DRIVER)}"
)
@field_validator('CLAUDE_TEMPERATURE')
@classmethod
def validate_temperature(cls, v):
if not 0 <= v <= 2:
raise ValueError('CLAUDE_TEMPERATURE must be between 0 and 2')
return v
@field_validator('CHUNK_OVERLAP')
@classmethod
def validate_chunk_overlap(cls, v, info):
chunk_size = info.data.get('CHUNK_SIZE', 500)
if v >= chunk_size:
raise ValueError('CHUNK_OVERLAP must be less than CHUNK_SIZE')
return v
model_config = ConfigDict(env_file=".env", case_sensitive=True)
@lru_cache()
def get_settings() -> Settings:
"""获取配置单例"""
return Settings()

View File

@@ -0,0 +1 @@
"""Core modules"""

View File

@@ -0,0 +1,120 @@
"""AI Engine for ERP AI Assistant.
This module provides the ClaudeEngine class that wraps Claude API calls
and provides JSON parsing utilities.
"""
import json
import re
from typing import Any
import anthropic
from loguru import logger
from app.config import get_settings
class ClaudeEngine:
"""Engine for interacting with Claude API.
This class wraps the Anthropic Claude API client and provides
utilities for parsing JSON responses from Claude.
"""
def __init__(self) -> None:
"""Initialize Claude engine with settings."""
settings = get_settings()
# Initialize Anthropic client with optional custom base_url
client_kwargs = {"api_key": settings.ANTHROPIC_API_KEY}
if settings.ANTHROPIC_BASE_URL:
client_kwargs["base_url"] = settings.ANTHROPIC_BASE_URL
logger.info(f"Using custom Anthropic base URL: {settings.ANTHROPIC_BASE_URL}")
self.client = anthropic.AsyncAnthropic(**client_kwargs)
self.model = settings.CLAUDE_MODEL
self.max_tokens = settings.CLAUDE_MAX_TOKENS
self.temperature = settings.CLAUDE_TEMPERATURE
def parse_json_response(self, content: str) -> dict[str, Any]:
"""Parse JSON from Claude responses.
Attempts multiple parsing strategies:
1. Direct JSON parse
2. Extract from markdown code blocks
3. Extract any {...} block
Args:
content: The response content from Claude
Returns:
Parsed JSON as a dictionary
Raises:
ValueError: If JSON cannot be parsed using any strategy
"""
if not content or not content.strip():
raise ValueError("Empty content provided")
# Strategy 1: Try direct JSON parse
try:
return json.loads(content)
except json.JSONDecodeError:
pass
# Strategy 2: Try extracting from markdown code blocks
json_code_block_pattern = r'```json\s*(\{.*?\})\s*```'
json_match = re.search(json_code_block_pattern, content, re.DOTALL)
if json_match:
try:
return json.loads(json_match.group(1))
except json.JSONDecodeError:
pass
# Also try any code block (not just json tagged)
code_block_pattern = r'```\s*(\{.*?\})\s*```'
code_block_match = re.search(code_block_pattern, content, re.DOTALL)
if code_block_match:
try:
return json.loads(code_block_match.group(1))
except json.JSONDecodeError:
pass
# Strategy 3: Try extracting any {...} block
# Find balanced braces
brace_pattern = r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'
json_blocks = re.findall(brace_pattern, content, re.DOTALL)
for json_block in json_blocks:
try:
return json.loads(json_block)
except json.JSONDecodeError:
continue
# All strategies failed
logger.error(f"无法解析 Claude 返回的 JSON: {content[:200]}")
raise ValueError("无法解析 Claude 返回的 JSON请检查响应格式")
async def call_claude(
self,
messages: list[dict[str, str]],
temperature: float | None = None
) -> str:
"""Call Claude API.
Args:
messages: List of message dictionaries with 'role' and 'content'
temperature: Optional temperature override (0-2)
Returns:
The text content from Claude's response
Raises:
Exception: If the API call fails
"""
response = await self.client.messages.create(
model=self.model,
max_tokens=self.max_tokens,
temperature=temperature if temperature is not None else self.temperature,
messages=messages
)
return response.content[0].text

View File

@@ -0,0 +1,78 @@
from typing import Optional
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from contextlib import contextmanager
from loguru import logger
from app.config import get_settings
class DatabaseEngine:
"""数据库操作引擎"""
def __init__(self):
settings = get_settings()
self.engine = create_engine(
settings.DATABASE_URL,
pool_size=20,
max_overflow=10,
pool_pre_ping=True,
echo=settings.DEBUG
)
self.Session = sessionmaker(bind=self.engine)
@contextmanager
def get_session(self):
"""获取数据库会话(上下文管理器)"""
session = self.Session()
try:
yield session
session.commit()
except Exception as e:
session.rollback()
logger.error(f"数据库操作失败:{e}")
raise
finally:
session.close()
def execute_sql(self, sql: str, params: Optional[dict] = None) -> list:
"""执行单条 SQL"""
with self.get_session() as session:
result = session.execute(text(sql), params or {})
return result.fetchall()
def execute_transaction(self, sql_list: list, params_list: Optional[list] = None) -> bool:
"""执行事务(多条 SQL"""
params_list = params_list or [None] * len(sql_list)
with self.get_session() as session:
for sql, params in zip(sql_list, params_list):
session.execute(text(sql), params or {})
return True
def get_table_structure(self, table_name: str):
"""获取表结构(安全参数化查询)"""
sql = """
SELECT
COLUMN_NAME,
DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH,
IS_NULLABLE,
COLUMN_DEFAULT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = :table_name
ORDER BY ORDINAL_POSITION
"""
return self.execute_sql(sql, {"table_name": table_name})
def table_exists(self, table_name: str) -> bool:
"""检查表是否存在(安全参数化查询)"""
sql = """
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = :table_name
"""
result = self.execute_sql(sql, {"table_name": table_name})
return result[0][0] > 0
def dispose(self):
"""关闭连接池,释放资源"""
self.engine.dispose()

View File

@@ -0,0 +1,147 @@
"""Config Executor for ERP AI Assistant.
This module provides the ConfigExecutor class for validating and executing
SQL configuration statements with safety checks.
"""
import re
from typing import List, Tuple, Dict, Any
from loguru import logger
from app.core.db_engine import DatabaseEngine
class ConfigExecutor:
"""Executor for SQL configuration statements with safety validation.
This class validates SQL statements against dangerous operations before
execution and provides transaction-based execution with rollback support.
"""
# Dangerous SQL keywords that should be blocked
DANGEROUS_KEYWORDS = [
r"DROP\s+DATABASE",
r"DROP\s+TABLE",
r"TRUNCATE\s+TABLE",
r"DELETE\s+FROM",
r"UPDATE\s+.*\s+SET",
r"ALTER\s+TABLE\s+.*\s+DROP"
]
def __init__(self) -> None:
"""Initialize executor with database engine."""
self.db_engine = DatabaseEngine()
logger.info("ConfigExecutor initialized")
def validate_sql(self, sql: str) -> Tuple[bool, str]:
"""Validate SQL statement for safety.
Checks SQL against a list of dangerous keywords/patterns to prevent
destructive operations.
Args:
sql: SQL statement to validate
Returns:
Tuple of (is_valid, message) where is_valid indicates if SQL is safe
"""
if not sql or not sql.strip():
return False, "SQL语句为空"
sql_upper = sql.upper().strip()
# Check for dangerous operations
for pattern in self.DANGEROUS_KEYWORDS:
if re.search(pattern, sql_upper):
# Extract matched keyword for error message
match = re.search(pattern, sql_upper)
matched_keyword = match.group(0) if match else pattern
logger.warning(f"SQL validation failed: dangerous operation '{matched_keyword}' detected")
return False, f"危险操作被拦截: {matched_keyword}"
logger.debug(f"SQL validation passed: {sql[:50]}...")
return True, "SQL验证通过"
def execute_config(
self,
sql_list: List[str],
session_id: str
) -> Dict[str, Any]:
"""Execute a list of SQL statements in a transaction.
Validates all SQL statements before execution. If any validation fails,
no statements are executed.
Args:
sql_list: List of SQL statements to execute
session_id: Session ID for logging and tracking
Returns:
Dictionary containing:
- success: Boolean indicating overall success
- executed: List of executed SQL statements
- failed: Error message if execution failed, None otherwise
- message: Human-readable result message
"""
logger.info(f"[{session_id}] Starting config execution with {len(sql_list)} SQL statements")
results: Dict[str, Any] = {
"success": True,
"executed": [],
"failed": None,
"message": ""
}
try:
# Step 1: Validate all SQL statements
logger.debug(f"[{session_id}] Validating {len(sql_list)} SQL statements")
for i, sql in enumerate(sql_list):
is_valid, msg = self.validate_sql(sql)
if not is_valid:
error_msg = f"SQL #{i+1} 验证失败: {msg}"
logger.error(f"[{session_id}] {error_msg}")
raise ValueError(error_msg)
# Step 2: Execute transaction
logger.debug(f"[{session_id}] Executing transaction")
self.db_engine.execute_transaction(sql_list)
# Step 3: Record success
results["executed"] = sql_list
results["message"] = f"成功执行 {len(sql_list)} 条SQL"
logger.success(f"[{session_id}] {results['message']}")
except ValueError as e:
# Validation failure
results["success"] = False
results["failed"] = str(e)
results["message"] = f"执行失败: {e}"
logger.error(f"[{session_id}] {results['message']}")
except Exception as e:
# Execution failure
results["success"] = False
results["failed"] = str(e)
results["message"] = f"执行失败: {e}"
logger.error(f"[{session_id}] {results['message']}")
return results
def rollback(self, session_id: str) -> Dict[str, Any]:
"""Rollback executed operations for a session.
This is a placeholder for rollback functionality. Actual implementation
would require recording inverse SQL operations during execution.
Args:
session_id: Session ID to rollback
Returns:
Dictionary with success status and message
"""
logger.warning(f"[{session_id}] Rollback requested but not yet implemented")
return {
"success": False,
"message": "回滚功能待实现"
}

144
backend/app/core/prompts.py Normal file
View File

@@ -0,0 +1,144 @@
"""
Prompt 模板定义
模板说明:
- SYSTEM_PROMPT: 系统提示词,定义 Claude 的角色和专业领域
- ANALYZE_PROMPT_TEMPLATE: 需求解析模板占位符user_input, knowledge_context, existing_tables
- GENERATE_PROMPT_TEMPLATE: 配置生成模板占位符requirements, platform_rules, similar_cases
"""
SYSTEM_PROMPT = """你是一个 ERP 平台配置专家助手,专门帮助开发人员配置一零软件结构化开发平台。
## 你的职责
你是 ERP 系统配置和开发的专业顾问,负责:
1. 分析用户需求,理解业务场景
2. 设计合理的数据库表结构
3. 生成符合平台规范的配置方案
4. 提供完整的 SQL 脚本和配置说明
## 平台知识
你熟悉以下平台概念:
- 窗体类型:基础资料、单据、报表、系统设置等
- 标准字段命名规范F 开头的主键、FPrefix 前缀的自定义字段
- 配置流程:需求分析 → 表结构设计 → 功能配置 → 页面配置 → 菜单配置
- 命名约定:表名以 T_开头功能号以功能类别前缀开头
## 输出要求
1. 提供完整的 SQL 脚本,包括建表语句、函数配置、页面配置等
2. 确保配置符合平台规范和最佳实践
3. 进行风险评估,提示潜在问题
4. 使用 JSON 格式输出结构化结果
5. 所有字段和表名使用英文,注释使用中文
请始终保持专业、严谨的工作态度,确保输出的配置方案可落地执行。"""
ANALYZE_PROMPT_TEMPLATE = """请分析以下用户需求,生成结构化的需求分析文档。
## 用户输入
{user_input}
## 相关知识上下文
{knowledge_context}
## 现有表结构
{existing_tables}
## 分析要求
请输出结构化的需求分析文档,使用 JSON 格式,包含以下字段:
# Note: Use {{ and }} to escape braces for .format() - rendered as literal { and }
```json
{{
"功能名称": "功能的中文名称",
"功能号建议": "建议的功能编号,如 SAL001",
"窗体类型": "基础资料/单据/报表/系统设置",
"主表名建议": "建议的主表名,如 T_SAL_Order",
"从表名建议": "建议的从表名,如 T_SAL_OrderEntry",
"主表字段": [
{{"字段名": "FOrderId", "字段类型": "varchar(50)", "中文名称": "订单编号", "必填": true}},
...
],
"从表字段": [
{{"字段名": "FEntryId", "字段类型": "int", "中文名称": "分录 ID", "必填": true}},
...
],
"业务需求": "详细的业务需求描述",
"关联表": ["相关表 1", "相关表 2"],
"风险提示": ["潜在风险 1", "潜在风险 2"]
}}
```
## 注意事项
1. 字段命名遵循平台规范:主键以 F 开头,使用 PascalCase
2. 表名以 T_开头使用模块前缀
3. 考虑必填字段、默认值、数据长度等约束
4. 识别必要的业务关联关系
5. 评估潜在的数据一致性和性能风险"""
GENERATE_PROMPT_TEMPLATE = """请根据需求分析结果,生成完整的平台配置方案。
## 需求分析结果
{requirements}
## 平台规则
{platform_rules}
## 类似案例参考
{similar_cases}
## 生成要求
请生成完整的配置方案,使用 JSON 格式,包含以下内容:
# Note: Use {{ and }} to escape braces for .format() - rendered as literal { and }
```json
{{
"table_sql": "建表 SQL 语句,包括主表和从表",
"function_config_sql": "功能配置 SQL 语句",
"page_config_sql": "页面配置 SQL 语句",
"menu_config_sql": "菜单配置 SQL 语句",
"ikey_config_sql": "IKEY 配置 SQL 语句",
"config_summary": {{
"created_tables": ["表 1", "表 2"],
"main_entities": ["主要实体 1", "主要实体 2"],
"relationships": "表间关系说明"
}},
"implementation_notes": "实施注意事项",
"validation_rules": ["验证规则 1", "验证规则 2"]
}}
```
## 配置规范
1. **建表 SQL**:
- 主键使用 FId 或 F+ 表名缩写 + Id
- 包含创建时间、创建人、更新时间、更新人等审计字段
- 使用合适的索引提高查询性能
2. **功能配置**:
- 定义功能号、功能名称、功能类型
- 配置数据权限和操作权限
3. **页面配置**:
- 配置表单布局、字段顺序
- 设置字段属性(必填、只读、可见性)
4. **菜单配置**:
- 配置菜单层级、图标、排序
5. **IKEY 配置**:
- 配置编码规则、生成策略
## 注意事项
- 所有 SQL 语句需要语法正确、可直接执行
- 配置需要符合平台规范
- 考虑扩展性和维护性
- 提供必要的注释说明"""

View File

@@ -0,0 +1,251 @@
"""RAG Engine for ERP AI Assistant.
This module provides the RAGEngine class that handles knowledge document
storage and retrieval using ChromaDB and sentence-transformers embeddings.
"""
from typing import Optional
import chromadb
from chromadb.config import Settings as ChromaSettings
from sentence_transformers import SentenceTransformer
from loguru import logger
from app.config import get_settings
class RAGEngine:
"""RAG Engine for knowledge document retrieval.
This class wraps ChromaDB vector database and sentence-transformers
to provide semantic search over knowledge documents.
"""
# Class-level singleton for embedding model (lazy loading)
_embedding_model: Optional[SentenceTransformer] = None
def __init__(self) -> None:
"""Initialize RAG engine with ChromaDB and embedding model."""
settings = get_settings()
# Initialize ChromaDB persistent client
logger.info(f"Initializing ChromaDB at: {settings.CHROMA_DB_PATH}")
self.chroma_client = chromadb.PersistentClient(
path=settings.CHROMA_DB_PATH,
settings=ChromaSettings(anonymized_telemetry=False)
)
# Load sentence-transformers embedding model (lazy loading, singleton)
logger.info(f"Loading embedding model: {settings.EMBEDDING_MODEL}")
self.embedding_model = self._get_embedding_model(settings.EMBEDDING_MODEL)
# Get or create documents collection
self.documents_collection = self.chroma_client.get_or_create_collection(
name="documents"
)
# Store chunking settings
self.chunk_size = settings.CHUNK_SIZE
self.chunk_overlap = settings.CHUNK_OVERLAP
logger.info(
f"RAG Engine initialized: chunk_size={self.chunk_size}, "
f"chunk_overlap={self.chunk_overlap}"
)
@classmethod
def _get_embedding_model(cls, model_name: str) -> SentenceTransformer:
"""Get or create the embedding model (lazy loading, singleton).
Args:
model_name: Name of the embedding model to load
Returns:
SentenceTransformer embedding model instance
"""
if cls._embedding_model is None:
logger.info(f"Loading embedding model: {model_name}")
cls._embedding_model = SentenceTransformer(model_name)
return cls._embedding_model
def _split_text(self, text: str) -> list[str]:
"""Split text into overlapping chunks.
Args:
text: The text to split
Returns:
List of chunk strings
"""
if not text:
return []
chunks = []
start = 0
text_length = len(text)
while start < text_length:
end = start + self.chunk_size
chunk = text[start:end]
if chunk.strip(): # Only add non-empty chunks
chunks.append(chunk)
start += self.chunk_size - self.chunk_overlap
# Avoid infinite loop if overlap >= chunk_size
if self.chunk_overlap >= self.chunk_size:
start += 1
return chunks
def _delete_chunks_for_doc(self, doc_id: str) -> None:
"""Delete all chunks associated with a document.
Args:
doc_id: The document ID to delete chunks for
"""
try:
# Find all chunks for this document
results = self.documents_collection.get(
where={"doc_id": doc_id},
include=[]
)
if results and results.get("ids"):
self.documents_collection.delete(ids=results["ids"])
logger.debug(f"Deleted {len(results['ids'])} chunks for document '{doc_id}'")
except Exception as e:
logger.warning(f"Failed to delete chunks for document '{doc_id}': {e}")
def add_document(
self,
doc_id: str,
content: str,
metadata: Optional[dict] = None
) -> int:
"""Add a document to the knowledge base.
Args:
doc_id: Unique identifier for the document
content: The document content to index
metadata: Optional metadata dict to store with the document
Returns:
Number of chunks added
Raises:
ValueError: If content is empty
"""
if not content or not content.strip():
raise ValueError("Cannot add empty document")
try:
# Delete existing chunks for this doc_id (handles duplicates)
self._delete_chunks_for_doc(doc_id)
# Split content into chunks
chunks = self._split_text(content)
logger.info(f"Split document '{doc_id}' into {len(chunks)} chunks")
if not chunks:
return 0
# Generate embeddings for all chunks
logger.debug(f"Generating embeddings for {len(chunks)} chunks")
embeddings = self.embedding_model.encode(chunks)
# Prepare chunk IDs and metadata
chunk_ids = [f"{doc_id}_chunk_{i}" for i in range(len(chunks))]
# Add metadata to each chunk
chunk_metadata = []
base_metadata = metadata or {}
for i, chunk in enumerate(chunks):
meta = {
**base_metadata,
"doc_id": doc_id,
"chunk_index": i,
"total_chunks": len(chunks)
}
chunk_metadata.append(meta)
# Add to ChromaDB
self.documents_collection.add(
ids=chunk_ids,
embeddings=embeddings.tolist(),
documents=chunks,
metadatas=chunk_metadata
)
logger.info(f"Added {len(chunks)} chunks for document '{doc_id}'")
return len(chunks)
except Exception as e:
logger.error(f"Failed to add document '{doc_id}': {e}")
raise
def search(self, query: str, top_k: int = 3) -> list[dict]:
"""Search for relevant document chunks.
Args:
query: The search query
top_k: Number of results to return (default: 3, max: 100)
Returns:
List of dicts with 'content', 'metadata', and 'distance'
Raises:
ValueError: If top_k exceeds maximum limit
"""
# Validate top_k
if top_k > 100:
raise ValueError(f"top_k cannot exceed 100 (got: {top_k})")
if not query or not query.strip():
logger.warning("Empty search query provided")
return []
try:
# Generate embedding for query
logger.debug(f"Generating embedding for query: {query[:50]}...")
query_embedding = self.embedding_model.encode([query])
# Query ChromaDB
results = self.documents_collection.query(
query_embeddings=query_embedding.tolist(),
n_results=top_k,
include=["documents", "metadatas", "distances"]
)
# Format results
formatted_results = []
if results and results.get("documents"):
documents = results["documents"][0]
metadatas = results["metadatas"][0] if results.get("metadatas") else []
distances = results["distances"][0] if results.get("distances") else []
for i, content in enumerate(documents):
formatted_results.append({
"content": content,
"metadata": metadatas[i] if i < len(metadatas) else {},
"distance": distances[i] if i < len(distances) else None
})
logger.info(f"Found {len(formatted_results)} results for query")
return formatted_results
except Exception as e:
logger.error(f"Search failed: {e}")
raise
def close(self) -> None:
"""Release resources and cleanup the RAG engine.
This method should be called when the engine is no longer needed
to free up memory and other resources.
"""
logger.info("Closing RAG engine and releasing resources")
self.embedding_model = None
self.documents_collection = None
self.chroma_client = None

78
backend/app/main.py Normal file
View File

@@ -0,0 +1,78 @@
"""FastAPI application entry point for ERP AI Assistant.
This module creates and configures the main FastAPI application instance.
"""
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.config import get_settings
from app.api import analyze, generate, execute
# Get application settings
settings = get_settings()
# Create FastAPI application instance
app = FastAPI(
title=settings.APP_NAME,
version="1.0.0",
debug=settings.DEBUG,
description="AI-powered assistant for ERP platform configuration"
)
# Configure CORS middleware for frontend communication
# For development: allow all origins with port 5173
# For production: configure specific origins in environment
app.add_middleware(
CORSMiddleware,
allow_origins=["*"] if settings.DEBUG else [
"http://localhost:5173",
"http://127.0.0.1:5173",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Register API routers
app.include_router(analyze.router, prefix="/api/v1", tags=["Analysis"])
app.include_router(generate.router, prefix="/api/v1", tags=["Generation"])
app.include_router(execute.router, prefix="/api/v1", tags=["Execution"])
@app.get("/", tags=["Root"])
async def root() -> dict:
"""Root endpoint returning application info.
Returns:
Dictionary with application name, version, and status
"""
return {
"message": settings.APP_NAME,
"version": "1.0.0",
"status": "running"
}
@app.get("/health", tags=["Health"])
async def health_check() -> dict:
"""Health check endpoint for monitoring.
Returns:
Dictionary with health status
"""
return {
"status": "healthy",
"version": "1.0.0"
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"app.main:app",
host="0.0.0.0",
port=8000,
reload=settings.DEBUG
)

View File

@@ -0,0 +1 @@
"""Pydantic models for ERP AI Assistant."""

View File

@@ -0,0 +1,89 @@
"""Request models for ERP AI Assistant API.
This module defines Pydantic models for API request validation.
"""
from typing import Optional
from pydantic import BaseModel, Field
class AnalyzeRequest(BaseModel):
"""Request model for requirement analysis.
Attributes:
input_type: Type of input - 'natural_language' or 'structured'
content: Requirement content text
session_id: Optional session ID for context continuity
"""
input_type: str = Field(
...,
description="输入类型: natural_language | structured"
)
content: str = Field(..., description="需求内容")
session_id: Optional[str] = Field(None, description="会话ID")
model_config = {
"json_schema_extra": {
"examples": [
{
"input_type": "natural_language",
"content": "创建一个销售订单管理页面",
"session_id": "session-123"
}
]
}
}
class GenerateRequest(BaseModel):
"""Request model for config generation.
Attributes:
session_id: Session ID from previous analysis
requirements: Structured requirements from analysis
"""
session_id: str = Field(..., description="会话ID")
requirements: dict = Field(..., description="结构化需求")
model_config = {
"json_schema_extra": {
"examples": [
{
"session_id": "session-123",
"requirements": {
"功能名称": "销售订单管理",
"功能类型": "列表页面"
}
}
]
}
}
class ExecuteRequest(BaseModel):
"""Request model for config execution.
Attributes:
session_id: Session ID for tracking
confirmed: User confirmation flag
backup_enabled: Whether to create backup before execution
"""
session_id: str = Field(..., description="会话ID")
confirmed: bool = Field(False, description="用户确认标识")
backup_enabled: bool = Field(True, description="是否启用备份")
model_config = {
"json_schema_extra": {
"examples": [
{
"session_id": "session-123",
"confirmed": True,
"backup_enabled": True
}
]
}
}

View File

@@ -0,0 +1,133 @@
"""Response models for ERP AI Assistant API.
This module defines Pydantic models for API response formatting.
"""
from typing import Any, Optional
from pydantic import BaseModel, Field
class AnalyzeResponse(BaseModel):
"""Response model for requirement analysis.
Attributes:
session_id: Session ID for this analysis
status: Processing status
data: Structured requirement analysis result
"""
session_id: str = Field(..., description="会话ID")
status: str = Field(..., description="处理状态")
data: dict = Field(..., description="结构化需求分析结果")
model_config = {
"json_schema_extra": {
"examples": [
{
"session_id": "session-123",
"status": "success",
"data": {
"功能名称": "销售订单管理",
"功能类型": "列表页面"
}
}
]
}
}
class GenerateResponse(BaseModel):
"""Response model for config generation.
Attributes:
session_id: Session ID
status: Processing status
data: Generated SQL configuration
"""
session_id: str = Field(..., description="会话ID")
status: str = Field(..., description="处理状态")
data: dict = Field(..., description="生成的SQL配置")
model_config = {
"json_schema_extra": {
"examples": [
{
"session_id": "session-123",
"status": "success",
"data": {
"sql_list": ["INSERT INTO SYS_FORM ...", "INSERT INTO SYS_MENU ..."]
}
}
]
}
}
class ExecuteResponse(BaseModel):
"""Response model for config execution.
Attributes:
execution_id: Unique execution ID
status: Execution status
message: Human-readable result message
"""
execution_id: str = Field(..., description="执行ID")
status: str = Field(..., description="执行状态")
message: str = Field(..., description="执行结果消息")
model_config = {
"json_schema_extra": {
"examples": [
{
"execution_id": "exec-456",
"status": "success",
"message": "成功执行 5 条SQL"
}
]
}
}
class ErrorResponse(BaseModel):
"""Response model for errors.
Attributes:
error: Error details dictionary
"""
error: dict = Field(..., description="错误详情")
model_config = {
"json_schema_extra": {
"examples": [
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": "Field 'input_type' must be 'natural_language' or 'structured'"
}
}
]
}
}
class HealthResponse(BaseModel):
"""Response model for health check.
Attributes:
status: Service health status
version: Application version
"""
status: str = Field(..., description="服务状态")
version: str = Field(default="1.0.0", description="版本号")
model_config = {
"json_schema_extra": {
"examples": [{"status": "healthy", "version": "1.0.0"}]
}
}

View File

@@ -0,0 +1 @@
"""Service modules for ERP AI Assistant."""

View File

@@ -0,0 +1,135 @@
"""Config Generation Service.
This module provides the ConfigService class for generating ERP platform
configuration SQL based on structured requirements.
"""
from typing import Dict, Any
from loguru import logger
from app.core.ai_engine import ClaudeEngine
from app.core.rag_engine import RAGEngine
from app.core.prompts import SYSTEM_PROMPT, GENERATE_PROMPT_TEMPLATE
from app.core.db_engine import DatabaseEngine
class ConfigService:
"""Service for generating ERP platform configuration.
This service uses Claude AI with RAG knowledge retrieval to generate
SQL configuration statements based on structured requirements.
"""
def __init__(self) -> None:
"""Initialize config service with required engines."""
self.ai_engine = ClaudeEngine()
self.rag_engine = RAGEngine()
self.db_engine = DatabaseEngine()
logger.info("ConfigService initialized")
async def generate(
self,
requirements: Dict[str, Any],
session_id: str
) -> Dict[str, Any]:
"""Generate configuration SQL based on requirements.
Args:
requirements: Structured requirement specification
session_id: Session ID for tracking
Returns:
Configuration plan with SQL statements
Raises:
ValueError: If requirements are invalid
Exception: If generation fails
"""
if not requirements:
raise ValueError("Requirements cannot be empty")
function_name = requirements.get("功能名称", "Unknown")
logger.info(f"[{session_id}] Starting config generation for: {function_name}")
try:
# Step 1: Retrieve platform rules for form type
form_type = requirements.get("窗体类型", "0")
logger.debug(f"[{session_id}] Retrieving platform rules for form type: {form_type}")
platform_rules = self._get_platform_rules(form_type)
logger.info(f"[{session_id}] Retrieved platform rules")
# Step 2: Retrieve similar cases
logger.debug(f"[{session_id}] Retrieving similar cases")
similar_cases = self._get_similar_cases(function_name)
logger.info(f"[{session_id}] Retrieved similar cases")
# Step 3: Build prompt
prompt = GENERATE_PROMPT_TEMPLATE.format(
requirements=str(requirements),
platform_rules=platform_rules,
similar_cases=similar_cases
)
messages = [
{"role": "user", "content": SYSTEM_PROMPT},
{"role": "assistant", "content": "我已了解,请提供需求信息。"},
{"role": "user", "content": prompt}
]
# Step 4: Call Claude API
logger.debug(f"[{session_id}] Calling Claude API for config generation")
response = await self.ai_engine.call_claude(messages, temperature=0.5)
# Step 5: Parse JSON response
result = self.ai_engine.parse_json_response(response)
logger.success(f"[{session_id}] Config generation completed")
return result
except Exception as e:
logger.error(f"[{session_id}] Config generation failed: {e}")
raise
def _get_platform_rules(self, form_type: str) -> str:
"""Retrieve platform configuration rules for specific form type.
Args:
form_type: Form type code
Returns:
Platform rules text
"""
try:
results = self.rag_engine.search(
f"窗体类型{form_type}配置规则",
top_k=2
)
if not results:
return "未找到相关配置规则"
return "\n\n".join([r["content"] for r in results])
except Exception as e:
logger.warning(f"Failed to retrieve platform rules: {e}")
return "无法获取平台配置规则"
def _get_similar_cases(self, keywords: str) -> str:
"""Retrieve similar configuration cases from knowledge base.
Args:
keywords: Search keywords
Returns:
Similar cases text
"""
try:
results = self.rag_engine.search(keywords, top_k=2)
if not results:
return "未找到相似案例"
return "\n\n".join([r["content"] for r in results])
except Exception as e:
logger.warning(f"Failed to retrieve similar cases: {e}")
return "无法获取相似案例"

View File

@@ -0,0 +1,147 @@
"""Requirement Analysis Service.
This module provides the RequirementService class for analyzing user requirements
using Claude AI with RAG knowledge retrieval.
"""
from typing import Optional, Dict, Any
import uuid
from loguru import logger
from app.core.ai_engine import ClaudeEngine
from app.core.rag_engine import RAGEngine
from app.core.prompts import SYSTEM_PROMPT, ANALYZE_PROMPT_TEMPLATE
from app.core.db_engine import DatabaseEngine
class RequirementService:
"""Service for analyzing user requirements with AI assistance.
This service integrates Claude AI, RAG knowledge retrieval, and database
metadata to provide comprehensive requirement analysis.
"""
def __init__(self) -> None:
"""Initialize requirement service with required engines."""
self.ai_engine = ClaudeEngine()
self.rag_engine = RAGEngine()
self.db_engine = DatabaseEngine()
logger.info("RequirementService initialized")
async def analyze(
self,
user_input: str,
session_id: Optional[str] = None
) -> Dict[str, Any]:
"""Analyze user requirement and generate structured specification.
Args:
user_input: Natural language requirement from user
session_id: Session ID for context management (auto-generated if None)
Returns:
Structured requirement document as dictionary
Raises:
ValueError: If user_input is empty
Exception: If AI analysis fails
"""
# Validate input
if not user_input or not user_input.strip():
raise ValueError("User input cannot be empty")
# Generate session ID if not provided
session_id = session_id or str(uuid.uuid4())
logger.info(f"[{session_id}] Starting requirement analysis: {user_input[:50]}...")
try:
# Step 1: Retrieve relevant knowledge from RAG
logger.debug(f"[{session_id}] Searching knowledge base")
knowledge_results = self.rag_engine.search(user_input, top_k=3)
knowledge_context = self._format_knowledge_context(knowledge_results)
logger.info(f"[{session_id}] Retrieved {len(knowledge_results)} knowledge chunks")
# Step 2: Query existing database tables
logger.debug(f"[{session_id}] Querying existing tables")
existing_tables = self._get_existing_tables(user_input)
logger.info(f"[{session_id}] Retrieved existing table information")
# Step 3: Build prompt
prompt = ANALYZE_PROMPT_TEMPLATE.format(
user_input=user_input,
knowledge_context=knowledge_context,
existing_tables=existing_tables
)
messages = [
{"role": "user", "content": SYSTEM_PROMPT},
{"role": "assistant", "content": "我已了解平台配置规范,请告诉我您的需求。"},
{"role": "user", "content": prompt}
]
# Step 4: Call Claude API
logger.debug(f"[{session_id}] Calling Claude API")
response = await self.ai_engine.call_claude(messages, temperature=0.7)
# Step 5: Parse JSON response
result = self.ai_engine.parse_json_response(response)
function_name = result.get("功能名称", "Unknown")
logger.success(f"[{session_id}] Requirement analysis completed: {function_name}")
return result
except Exception as e:
logger.error(f"[{session_id}] Requirement analysis failed: {e}")
raise
def _format_knowledge_context(self, knowledge_results: list) -> str:
"""Format knowledge search results into context string.
Args:
knowledge_results: List of knowledge search results
Returns:
Formatted knowledge context string
"""
if not knowledge_results:
return "未找到相关知识库内容"
context_parts = []
for result in knowledge_results:
source = result.get("metadata", {}).get("source", "文档")
content = result.get("content", "")
if content:
context_parts.append(f"{source}\n{content}")
return "\n\n".join(context_parts) if context_parts else "未找到相关知识库内容"
def _get_existing_tables(self, user_input: str) -> str:
"""Query existing database tables relevant to user input.
Args:
user_input: User requirement text
Returns:
Formatted string listing existing tables
"""
try:
# Query top 10 tables (simplified version - could be enhanced with relevance matching)
sql = """
SELECT TOP 10 TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
ORDER BY TABLE_NAME
"""
tables = self.db_engine.execute_sql(sql)
if not tables:
return "未找到现有数据表"
table_list = [f"- {t[0]}" for t in tables]
return "\n".join(table_list)
except Exception as e:
logger.warning(f"Failed to query existing tables: {e}")
return "无法获取现有表信息"

View File

@@ -0,0 +1,31 @@
#!/bin/bash
# 一键修复依赖问题脚本
echo "🔧 开始修复依赖问题..."
cd backend
# 激活虚拟环境
if [ -d "venv" ]; then
echo "✓ 找到虚拟环境"
source venv/bin/activate
else
echo "✗ 未找到虚拟环境,正在创建..."
python3 -m venv venv
source venv/bin/activate
fi
echo "📦 卸载冲突的包..."
pip uninstall -y numpy sentence-transformers huggingface-hub
echo "📥 重新安装所有依赖..."
pip install -r requirements.txt
echo "✅ 验证安装..."
python -c "import numpy; print(f'NumPy version: {numpy.__version__}')"
python -c "import sentence_transformers; print(f'SentenceTransformers installed successfully')"
python -c "import chromadb; print(f'ChromaDB installed successfully')"
echo ""
echo "✨ 修复完成!现在可以运行后端服务:"
echo " python -m app.main"

8
backend/pytest.ini Normal file
View File

@@ -0,0 +1,8 @@
[pytest]
asyncio_mode = auto
asyncio_default_fixture_loop_scope = function
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --cov=app --cov-report=term-missing

21
backend/requirements.txt Normal file
View File

@@ -0,0 +1,21 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
pyodbc==5.0.1
anthropic==0.18.1
chromadb==0.4.18
sentence-transformers==2.7.0
pydantic==2.5.0
pydantic-settings==2.1.0
python-dotenv==1.0.0
loguru==0.7.2
tenacity==8.2.3
python-jose[cryptography]==3.3.0
pytest==7.4.3
pytest-asyncio==0.21.1
httpx==0.25.2
pytest-cov==4.1.0
pytest-mock==3.12.0
# Fix NumPy compatibility issue with chromadb
numpy<2.0.0

View File

@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""Initialize knowledge base with sample documents.
This script adds sample knowledge documents to the RAG engine
for the ERP AI Assistant.
"""
import sys
from pathlib import Path
# NOTE: Development workaround to enable direct script execution.
# For production, use: python -m backend.scripts.init_knowledge
sys.path.insert(0, str(Path(__file__).parent.parent))
from loguru import logger
from app.core.rag_engine import RAGEngine
# Sample document: Platform basics
PLATFORM_BASICS_CONTENT = """
# ERP 平台基础知识
## 窗体类型 (Form Types)
ERP 系统支持以下几种窗体类型:
1. **标准窗体 (Standard Form)**
- 用于单一数据实体的 CRUD 操作
- 包含字段、按钮、表格等基本控件
- 适用于简单的数据录入和查询场景
2. **列表窗体 (List Form)**
- 用于展示多条记录的列表
- 支持排序、筛选、分页功能
- 可配置列显示和隐藏
3. **报表窗体 (Report Form)**
- 用于生成统计报表
- 支持图表展示
- 可导出 Excel、PDF 格式
4. **流程窗体 (Workflow Form)**
- 用于业务流程处理
- 包含审批、流转、会签等功能
- 支持流程状态跟踪
## 标准字段 (Standard Fields)
系统预定义以下标准字段类型:
1. **文本字段**
- ShortText: 短文本 (最多 255 字符)
- LongText: 长文本 (最多 4000 字符)
- Memo: 备注文本 (不限长度)
2. **数值字段**
- Integer: 整数
- Decimal: 小数 (可配置精度)
- Currency: 货币 (带币种符号)
3. **日期字段**
- Date: 日期
- DateTime: 日期时间
- Time: 时间
4. **选择字段**
- Dropdown: 下拉选择
- Radio: 单选
- Checkbox: 复选框
- MultiSelect: 多选
5. **关联字段**
- Lookup: 查找关联
- Reference: 引用关联
- Master-Detail: 主从关联
## 配置流程 (Configuration Process)
### 1. 需求分析
- 明确业务场景
- 确定窗体类型
- 梳理字段清单
### 2. 窗体设计
- 创建新窗体
- 配置窗体属性
- 添加字段控件
### 3. 字段配置
- 选择字段类型
- 设置字段属性 (必填、只读、默认值等)
- 配置验证规则
### 4. 权限设置
- 配置角色权限
- 设置数据访问范围
- 配置操作权限
### 5. 测试验证
- 功能测试
- 权限测试
- 性能测试
### 6. 发布上线
- 提交发布申请
- 通过审批流程
- 正式发布
## 常用术语 (Common Terms)
- **窗体 (Form)**: 用户界面的基本单元,用于数据展示和操作
- **字段 (Field)**: 窗体中的数据项,对应数据库列
- **控件 (Control)**: 窗体上的可视化元素
- **数据源 (Data Source)**: 窗体绑定的数据表或查询
- **动作 (Action)**: 窗体上的操作按钮
- **验证 (Validation)**: 数据输入的合法性检查
- **权限 (Permission)**: 用户对资源的访问控制
- **工作流 (Workflow)**: 业务流程的自动化流转
## 最佳实践 (Best Practices)
1. **字段命名规范**
- 使用英文命名,遵循下划线分隔
- 字段名应清晰表达业务含义
- 避免使用系统保留字
2. **性能优化**
- 列表窗体配置合理的分页大小
- 为常用查询字段建立索引
- 避免在窗体中加载过多数据
3. **用户体验**
- 必填字段应明确标识
- 提供清晰的错误提示
- 常用操作应放在明显位置
4. **安全性**
- 敏感数据应设置访问权限
- 用户输入应进行验证
- 定期审计权限配置
"""
def main() -> None:
"""Initialize knowledge base with sample documents."""
logger.info("Starting knowledge base initialization...")
try:
# Initialize RAG engine
logger.info("Initializing RAG engine...")
rag = RAGEngine()
# Add platform basics document
logger.info("Adding platform basics document...")
metadata = {
"title": "平台基础知识",
"category": "platform",
"language": "zh-CN",
"version": "1.0"
}
chunks_added = rag.add_document(
doc_id="platform_basics",
content=PLATFORM_BASICS_CONTENT,
metadata=metadata
)
logger.success(
f"Knowledge base initialized successfully! "
f"Added {chunks_added} chunks from 'platform_basics' document."
)
except Exception as e:
logger.error(f"Failed to initialize knowledge base: {e}")
raise
if __name__ == "__main__":
main()

View File

@@ -0,0 +1 @@
"""Tests"""

26
backend/tests/conftest.py Normal file
View File

@@ -0,0 +1,26 @@
import pytest
from app.config import get_settings
@pytest.fixture
def test_settings():
"""Test settings"""
return get_settings()
@pytest.fixture
def mock_db_engine(mocker):
"""Mock database engine"""
from app.core.db_engine import DatabaseEngine
return mocker.MagicMock(spec=DatabaseEngine)
@pytest.fixture
def mock_ai_engine(mocker):
"""Mock AI engine with default parse_json_response behavior"""
from app.core.ai_engine import ClaudeEngine
mock_engine = mocker.MagicMock(spec=ClaudeEngine)
# Default behavior: returns a test function dict
# Can be overridden in individual tests via mock_ai_engine.parse_json_response.return_value = {...}
mock_engine.parse_json_response.return_value = {"function_name": "test_function"}
return mock_engine

View File

@@ -0,0 +1,148 @@
import pytest
from app.core.ai_engine import ClaudeEngine
@pytest.fixture
def mock_settings(mocker):
"""Mock settings for test isolation."""
mock_settings = mocker.MagicMock()
mock_settings.ANTHROPIC_API_KEY = "test-key"
mock_settings.CLAUDE_MODEL = "claude-sonnet-4-6"
mock_settings.CLAUDE_MAX_TOKENS = 1024
mock_settings.CLAUDE_TEMPERATURE = 0.7
mocker.patch('app.core.ai_engine.get_settings', return_value=mock_settings)
return mock_settings
@pytest.fixture
def mock_anthropic_client(mocker):
"""Mock Anthropic async client."""
mock_client = mocker.AsyncMock()
mocker.patch('app.core.ai_engine.anthropic.AsyncAnthropic', return_value=mock_client)
return mock_client
def test_claude_engine_init(mocker, mock_settings):
"""测试 Claude 引擎初始化"""
engine = ClaudeEngine()
assert engine.client is not None
assert engine.model == "claude-sonnet-4-6"
assert engine.max_tokens == 1024
assert engine.temperature == 0.7
def test_parse_json_response(mocker, mock_settings):
"""测试 JSON 解析"""
engine = ClaudeEngine()
# 测试纯 JSON
json_str = '{"name": "test", "value": 123}'
result = engine.parse_json_response(json_str)
assert result["name"] == "test"
assert result["value"] == 123
# 测试 markdown 代码块
md_str = '```json\n{"name": "test"}\n```'
result = engine.parse_json_response(md_str)
assert result["name"] == "test"
def test_parse_json_response_empty_content(mocker, mock_settings):
"""测试空内容错误处理"""
engine = ClaudeEngine()
with pytest.raises(ValueError, match="Empty content provided"):
engine.parse_json_response("")
with pytest.raises(ValueError, match="Empty content provided"):
engine.parse_json_response(" ")
def test_parse_json_response_invalid_json(mocker, mock_settings):
"""测试无效 JSON 错误处理"""
engine = ClaudeEngine()
# 无效 JSON 且无法提取任何代码块
invalid_str = "This is not JSON at all"
with pytest.raises(ValueError, match="无法解析 Claude 返回的 JSON"):
engine.parse_json_response(invalid_str)
# 无效的 JSON 代码块
invalid_json_block = '```json\n{invalid json}\n```'
with pytest.raises(ValueError, match="无法解析 Claude 返回的 JSON"):
engine.parse_json_response(invalid_json_block)
def test_parse_json_response_code_block(mocker, mock_settings):
"""测试代码块 JSON 解析"""
engine = ClaudeEngine()
# 普通代码块(无 json 标签)
code_block = '```\n{"status": "ok"}\n```'
result = engine.parse_json_response(code_block)
assert result["status"] == "ok"
def test_parse_json_response_nested_json(mocker, mock_settings):
"""测试嵌套 JSON 解析"""
engine = ClaudeEngine()
# 带有一些额外文本的 JSON
text_with_json = 'Some text before {"key": "value"} and after'
result = engine.parse_json_response(text_with_json)
assert result["key"] == "value"
# 嵌套 JSON
nested_json = '{"outer": {"inner": "value"}}'
result = engine.parse_json_response(nested_json)
assert result["outer"]["inner"] == "value"
@pytest.mark.asyncio
async def test_call_claude(mocker, mock_settings, mock_anthropic_client):
"""测试 call_claude 方法"""
# 设置 mock 响应
mock_response = mocker.MagicMock()
mock_response.content = [mocker.MagicMock(text="Hello, I am Claude")]
mock_anthropic_client.messages.create.return_value = mock_response
engine = ClaudeEngine()
messages = [{"role": "user", "content": "Hello"}]
result = await engine.call_claude(messages)
assert result == "Hello, I am Claude"
mock_anthropic_client.messages.create.assert_called_once_with(
model="claude-sonnet-4-6",
max_tokens=1024,
temperature=0.7,
messages=messages
)
@pytest.mark.asyncio
async def test_call_claude_with_temperature(mocker, mock_settings, mock_anthropic_client):
"""测试 call_claude 带温度参数"""
mock_response = mocker.MagicMock()
mock_response.content = [mocker.MagicMock(text="Response")]
mock_anthropic_client.messages.create.return_value = mock_response
engine = ClaudeEngine()
messages = [{"role": "user", "content": "Hello"}]
result = await engine.call_claude(messages, temperature=1.5)
assert result == "Response"
call_args = mock_anthropic_client.messages.create.call_args
assert call_args.kwargs["temperature"] == 1.5
@pytest.mark.asyncio
async def test_call_claude_error(mocker, mock_settings, mock_anthropic_client):
"""测试 call_claude 错误处理"""
# 设置 mock 抛出异常
mock_anthropic_client.messages.create.side_effect = Exception("API Error")
engine = ClaudeEngine()
messages = [{"role": "user", "content": "Hello"}]
with pytest.raises(Exception, match="API Error"):
await engine.call_claude(messages)

View File

@@ -0,0 +1,94 @@
"""Tests for Config Service.
This module tests the ConfigService class for configuration generation.
"""
import pytest
from unittest.mock import MagicMock, AsyncMock, patch
from app.services.config_service import ConfigService
@pytest.mark.asyncio
async def test_generate_config():
"""Test config generation with mocked dependencies."""
with patch('app.services.config_service.ClaudeEngine') as MockClaudeEngine, \
patch('app.services.config_service.RAGEngine') as MockRAGEngine, \
patch('app.services.config_service.DatabaseEngine') as MockDBEngine:
# Setup mocks
mock_ai_engine = MagicMock()
mock_ai_engine.call_claude = AsyncMock(return_value='{"配置方案": {"sql_list": ["INSERT INTO SYS_FORM..."]}}')
mock_ai_engine.parse_json_response = MagicMock(return_value={
"配置方案": {
"sql_list": ["INSERT INTO SYS_FORM VALUES (...)"]
}
})
MockClaudeEngine.return_value = mock_ai_engine
mock_rag_engine = MagicMock()
mock_rag_engine.search = MagicMock(return_value=[
{"content": "Sample rule", "metadata": {}}
])
MockRAGEngine.return_value = mock_rag_engine
MockDBEngine.return_value = MagicMock()
# Create service and test
service = ConfigService()
requirements = {
"功能名称": "销售订单",
"功能号建议": "11-001",
"窗体类型": "5",
"主表名建议": "SA_ORDER",
"主表字段": [
{"字段名": "订单号", "字段类型": "varchar(50)", "必填": True}
]
}
result = await service.generate(requirements, "test-session")
assert result is not None
assert "配置方案" in result
@pytest.mark.asyncio
async def test_get_platform_rules():
"""Test platform rules retrieval."""
with patch('app.services.config_service.ClaudeEngine'), \
patch('app.services.config_service.RAGEngine') as MockRAGEngine, \
patch('app.services.config_service.DatabaseEngine'):
mock_rag_engine = MagicMock()
mock_rag_engine.search = MagicMock(return_value=[
{"content": "Rule 1"},
{"content": "Rule 2"}
])
MockRAGEngine.return_value = mock_rag_engine
service = ConfigService()
rules = service._get_platform_rules("5")
assert "Rule 1" in rules
assert "Rule 2" in rules
@pytest.mark.asyncio
async def test_get_similar_cases():
"""Test similar cases retrieval."""
with patch('app.services.config_service.ClaudeEngine'), \
patch('app.services.config_service.RAGEngine') as MockRAGEngine, \
patch('app.services.config_service.DatabaseEngine'):
mock_rag_engine = MagicMock()
mock_rag_engine.search = MagicMock(return_value=[
{"content": "Case 1"},
{"content": "Case 2"}
])
MockRAGEngine.return_value = mock_rag_engine
service = ConfigService()
cases = service._get_similar_cases("销售订单")
assert "Case 1" in cases
assert "Case 2" in cases

View File

@@ -0,0 +1,25 @@
import pytest
from app.core.db_engine import DatabaseEngine
def test_database_engine_init():
"""测试数据库引擎初始化"""
engine = DatabaseEngine()
assert engine.engine is not None
assert engine.Session is not None
def test_execute_sql_select():
"""测试执行 SELECT 查询"""
engine = DatabaseEngine()
result = engine.execute_sql("SELECT 1 AS test")
assert result is not None
assert len(result) > 0
def test_table_exists():
"""测试表存在性检查"""
engine = DatabaseEngine()
# 假设 SYS_FORM 表存在
exists = engine.table_exists("SYS_FORM")
assert exists is True

View File

@@ -0,0 +1,141 @@
"""Tests for Config Executor.
This module tests the ConfigExecutor class for SQL validation and execution.
"""
import pytest
from unittest.mock import MagicMock, patch
from app.core.executor import ConfigExecutor
def test_executor_init():
"""Test executor initialization."""
executor = ConfigExecutor()
assert executor.db_engine is not None
def test_validate_sql_safe():
"""Test validation of safe SQL statements."""
executor = ConfigExecutor()
# Test SELECT
is_valid, msg = executor.validate_sql("SELECT * FROM SYS_FORM")
assert is_valid is True
assert "验证通过" in msg
# Test INSERT
is_valid, msg = executor.validate_sql(
"INSERT INTO SYS_FORM (IKEY, FORM_NAME) VALUES (1, 'Test')"
)
assert is_valid is True
# Test UPDATE with safe WHERE clause
is_valid, msg = executor.validate_sql(
"UPDATE SYS_FORM SET FORM_NAME = 'Test' WHERE IKEY = 1"
)
assert is_valid is True
def test_validate_sql_dangerous():
"""Test validation catches dangerous SQL statements."""
executor = ConfigExecutor()
# Test DROP DATABASE
is_valid, msg = executor.validate_sql("DROP DATABASE test_db")
assert is_valid is False
assert "危险操作" in msg
# Test DROP TABLE
is_valid, msg = executor.validate_sql("DROP TABLE users")
assert is_valid is False
assert "危险操作" in msg
# Test TRUNCATE
is_valid, msg = executor.validate_sql("TRUNCATE TABLE important_data")
assert is_valid is False
assert "危险操作" in msg
# Test DELETE without WHERE
is_valid, msg = executor.validate_sql("DELETE FROM users")
assert is_valid is False
assert "危险操作" in msg
def test_execute_config_success():
"""Test successful execution of SQL list."""
with patch('app.core.executor.DatabaseEngine') as MockDBEngine:
mock_db_engine = MagicMock()
mock_db_engine.execute_transaction = MagicMock(return_value=True)
MockDBEngine.return_value = mock_db_engine
executor = ConfigExecutor()
sql_list = [
"INSERT INTO SYS_FORM (IKEY, FORM_NAME) VALUES (1, 'Test1')",
"INSERT INTO SYS_FORM (IKEY, FORM_NAME) VALUES (2, 'Test2')"
]
result = executor.execute_config(sql_list, session_id="test-session")
assert result["success"] is True
assert len(result["executed"]) == 2
assert result["failed"] is None
assert "成功执行" in result["message"]
def test_execute_config_validation_failure():
"""Test execution fails when SQL validation fails."""
executor = ConfigExecutor()
sql_list = [
"SELECT * FROM SYS_FORM",
"DROP DATABASE test" # Dangerous SQL
]
result = executor.execute_config(sql_list, session_id="test-session")
assert result["success"] is False
assert result["failed"] is not None
assert "验证失败" in result["message"]
assert len(result["executed"]) == 0
def test_execute_config_execution_failure():
"""Test execution handles database errors."""
with patch('app.core.executor.DatabaseEngine') as MockDBEngine:
mock_db_engine = MagicMock()
mock_db_engine.execute_transaction = MagicMock(
side_effect=Exception("Database connection error")
)
MockDBEngine.return_value = mock_db_engine
executor = ConfigExecutor()
sql_list = ["SELECT * FROM SYS_FORM"]
result = executor.execute_config(sql_list, session_id="test-session")
assert result["success"] is False
assert result["failed"] is not None
assert "执行失败" in result["message"]
def test_rollback_placeholder():
"""Test rollback functionality placeholder."""
executor = ConfigExecutor()
result = executor.rollback(session_id="test-session")
assert result["success"] is False
assert "待实现" in result["message"]
def test_dangerous_keywords_exist():
"""Test that dangerous keywords list is properly defined."""
executor = ConfigExecutor()
assert hasattr(executor, 'DANGEROUS_KEYWORDS')
assert len(executor.DANGEROUS_KEYWORDS) > 0
assert "DROP DATABASE" in executor.DANGEROUS_KEYWORDS
assert "DROP TABLE" in executor.DANGEROUS_KEYWORDS
assert "TRUNCATE TABLE" in executor.DANGEROUS_KEYWORDS

View File

@@ -0,0 +1,36 @@
"""Tests for prompt templates."""
from app.core.prompts import SYSTEM_PROMPT, ANALYZE_PROMPT_TEMPLATE, GENERATE_PROMPT_TEMPLATE
def test_system_prompt_exists():
"""测试系统 Prompt 存在"""
assert SYSTEM_PROMPT is not None
assert len(SYSTEM_PROMPT) > 100
# Test for stable characteristics rather than exact wording
assert "ERP" in SYSTEM_PROMPT
assert "配置" in SYSTEM_PROMPT
def test_analyze_prompt_template():
"""测试需求解析模板"""
rendered = ANALYZE_PROMPT_TEMPLATE.format(
user_input="创建销售订单",
knowledge_context="测试知识",
existing_tables="测试表"
)
assert "创建销售订单" in rendered
assert "测试知识" in rendered
assert "测试表" in rendered
def test_generate_prompt_template():
"""测试配置生成模板"""
rendered = GENERATE_PROMPT_TEMPLATE.format(
requirements="需求分析结果",
platform_rules="平台规则",
similar_cases="类似案例"
)
assert "需求分析结果" in rendered
assert "平台规则" in rendered
assert "类似案例" in rendered

View File

@@ -0,0 +1,156 @@
"""Tests for RAG Engine.
This module tests the RAGEngine class for document indexing and retrieval.
"""
import pytest
from unittest.mock import MagicMock, patch
from app.core.rag_engine import RAGEngine
def test_rag_engine_init():
"""Test RAG engine initialization."""
engine = RAGEngine()
assert engine.chroma_client is not None
assert engine.documents_collection is not None
assert engine.chunk_size > 0
assert engine.chunk_overlap >= 0
assert engine.chunk_overlap < engine.chunk_size
def test_split_text_basic():
"""Test basic text splitting functionality."""
engine = RAGEngine()
# Test with text longer than chunk_size
long_text = "A" * 1000
chunks = engine._split_text(long_text)
assert len(chunks) > 0
assert all(len(chunk) <= engine.chunk_size for chunk in chunks)
assert all(chunk.strip() for chunk in chunks) # No empty chunks
def test_split_text_empty():
"""Test splitting empty text."""
engine = RAGEngine()
# Test with empty text
assert engine._split_text("") == []
assert engine._split_text(" ") == []
def test_split_text_overlap():
"""Test text splitting with overlap."""
engine = RAGEngine()
# Test that chunks overlap correctly
text = "A" * 600
chunks = engine._split_text(text)
if len(chunks) > 1:
# Check overlap exists between consecutive chunks
# (This is a basic check; actual overlap content depends on implementation)
assert len(chunks) > 1
def test_add_document_success():
"""Test adding a document to the knowledge base."""
engine = RAGEngine()
# Mock the collection's add method
engine.documents_collection.add = MagicMock()
doc_id = "test_doc_1"
content = "This is a test document for the knowledge base."
metadata = {"source": "test", "type": "sample"}
num_chunks = engine.add_document(doc_id, content, metadata)
assert num_chunks > 0
assert engine.documents_collection.add.called
# Verify add was called with correct parameters
call_args = engine.documents_collection.add.call_args
assert "ids" in call_args.kwargs
assert "embeddings" in call_args.kwargs
assert "documents" in call_args.kwargs
assert "metadatas" in call_args.kwargs
def test_add_document_empty_content():
"""Test that adding empty document raises ValueError."""
engine = RAGEngine()
with pytest.raises(ValueError, match="Cannot add empty document"):
engine.add_document("test_doc", "")
with pytest.raises(ValueError, match="Cannot add empty document"):
engine.add_document("test_doc", " ")
def test_search_basic():
"""Test basic search functionality."""
engine = RAGEngine()
# Mock the collection's query method
mock_results = {
"documents": [["Result 1", "Result 2"]],
"metadatas": [[{"doc_id": "doc1"}, {"doc_id": "doc2"}]],
"distances": [[0.1, 0.2]]
}
engine.documents_collection.query = MagicMock(return_value=mock_results)
results = engine.search("test query", top_k=2)
assert len(results) == 2
assert results[0]["content"] == "Result 1"
assert results[0]["metadata"]["doc_id"] == "doc1"
assert results[0]["distance"] == 0.1
assert engine.documents_collection.query.called
def test_search_empty_query():
"""Test search with empty query returns empty results."""
engine = RAGEngine()
results = engine.search("", top_k=3)
assert results == []
results = engine.search(" ", top_k=3)
assert results == []
def test_search_invalid_top_k():
"""Test that search with invalid top_k raises ValueError."""
engine = RAGEngine()
with pytest.raises(ValueError, match="top_k cannot exceed 100"):
engine.search("test", top_k=101)
def test_delete_chunks_for_doc():
"""Test deleting chunks for a document."""
engine = RAGEngine()
# Mock the get and delete methods
engine.documents_collection.get = MagicMock(return_value={
"ids": ["doc1_chunk_0", "doc1_chunk_1"]
})
engine.documents_collection.delete = MagicMock()
engine._delete_chunks_for_doc("doc1")
assert engine.documents_collection.get.called
assert engine.documents_collection.delete.called
def test_close():
"""Test closing the RAG engine releases resources."""
engine = RAGEngine()
engine.close()
assert engine.embedding_model is None
assert engine.documents_collection is None
assert engine.chroma_client is None

View File

@@ -0,0 +1,116 @@
"""Tests for Requirement Service.
This module tests the RequirementService class for requirement analysis.
"""
import pytest
from unittest.mock import MagicMock, AsyncMock, patch
from app.services.requirement_service import RequirementService
@pytest.mark.asyncio
async def test_analyze_requirement():
"""Test requirement analysis with mocked dependencies."""
# Create service with mocked engines
with patch('app.services.requirement_service.ClaudeEngine') as MockClaudeEngine, \
patch('app.services.requirement_service.RAGEngine') as MockRAGEngine, \
patch('app.services.requirement_service.DatabaseEngine') as MockDBEngine:
# Setup mocks
mock_ai_engine = MagicMock()
mock_ai_engine.call_claude = AsyncMock(return_value='{"功能名称": "销售订单管理", "功能类型": "列表页面"}')
mock_ai_engine.parse_json_response = MagicMock(return_value={
"功能名称": "销售订单管理",
"功能类型": "列表页面"
})
MockClaudeEngine.return_value = mock_ai_engine
mock_rag_engine = MagicMock()
mock_rag_engine.search = MagicMock(return_value=[
{"content": "Sample knowledge", "metadata": {"source": "docs"}}
])
MockRAGEngine.return_value = mock_rag_engine
mock_db_engine = MagicMock()
mock_db_engine.execute_sql = MagicMock(return_value=[("SYS_FORM",), ("SYS_MENU",)])
MockDBEngine.return_value = mock_db_engine
# Create service and test
service = RequirementService()
result = await service.analyze(
user_input="创建一个销售订单管理页面",
session_id="test-session"
)
assert result is not None
assert "功能名称" in result
assert result["功能名称"] == "销售订单管理"
@pytest.mark.asyncio
async def test_analyze_requirement_without_session_id():
"""Test that session_id is auto-generated if not provided."""
with patch('app.services.requirement_service.ClaudeEngine') as MockClaudeEngine, \
patch('app.services.requirement_service.RAGEngine') as MockRAGEngine, \
patch('app.services.requirement_service.DatabaseEngine') as MockDBEngine:
# Setup mocks
mock_ai_engine = MagicMock()
mock_ai_engine.call_claude = AsyncMock(return_value='{"功能名称": "测试功能"}')
mock_ai_engine.parse_json_response = MagicMock(return_value={"功能名称": "测试功能"})
MockClaudeEngine.return_value = mock_ai_engine
mock_rag_engine = MagicMock()
mock_rag_engine.search = MagicMock(return_value=[])
MockRAGEngine.return_value = mock_rag_engine
mock_db_engine = MagicMock()
mock_db_engine.execute_sql = MagicMock(return_value=[])
MockDBEngine.return_value = mock_db_engine
# Test without session_id
service = RequirementService()
result = await service.analyze(user_input="测试输入")
assert result is not None
assert "功能名称" in result
@pytest.mark.asyncio
async def test_get_existing_tables_success():
"""Test successful retrieval of existing tables."""
with patch('app.services.requirement_service.ClaudeEngine'), \
patch('app.services.requirement_service.RAGEngine'), \
patch('app.services.requirement_service.DatabaseEngine') as MockDBEngine:
mock_db_engine = MagicMock()
mock_db_engine.execute_sql = MagicMock(return_value=[
("SYS_FORM",),
("SYS_MENU",),
("SYS_USER",)
])
MockDBEngine.return_value = mock_db_engine
service = RequirementService()
tables = service._get_existing_tables("测试")
assert "SYS_FORM" in tables
assert "SYS_MENU" in tables
assert "SYS_USER" in tables
@pytest.mark.asyncio
async def test_get_existing_tables_failure():
"""Test handling of database query failure."""
with patch('app.services.requirement_service.ClaudeEngine'), \
patch('app.services.requirement_service.RAGEngine'), \
patch('app.services.requirement_service.DatabaseEngine') as MockDBEngine:
mock_db_engine = MagicMock()
mock_db_engine.execute_sql = MagicMock(side_effect=Exception("DB Error"))
MockDBEngine.return_value = mock_db_engine
service = RequirementService()
tables = service._get_existing_tables("测试")
assert "无法获取现有表信息" in tables

146
docs/CLAUDE_API_CONFIG.md Normal file
View File

@@ -0,0 +1,146 @@
# Claude API 配置指南
## 概述
本项目支持自定义 Anthropic API 的 base URL允许您使用代理服务或自托管服务。
## 配置方法
### 1. 使用官方 Anthropic API默认
`.env` 文件中配置:
```bash
ANTHROPIC_API_KEY=your-api-key-here
# 不需要设置 ANTHROPIC_BASE_URL将自动使用官方 API
```
### 2. 使用代理服务
如果您使用代理服务(例如 OpenRouter、AWS Bedrock 代理等),配置如下:
```bash
ANTHROPIC_API_KEY=your-api-key-here
ANTHROPIC_BASE_URL=https://your-proxy-service.com/v1
```
### 3. 使用自托管服务
如果您部署了自托管的 Claude API 兼容服务:
```bash
ANTHROPIC_API_KEY=your-custom-key
ANTHROPIC_BASE_URL=http://localhost:8080/v1
```
## 配置参数说明
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| `ANTHROPIC_API_KEY` | string | ✅ 是 | - | Anthropic API 密钥或自定义密钥 |
| `ANTHROPIC_BASE_URL` | string | ❌ 否 | `None` | 自定义 API 端点 URL |
| `CLAUDE_MODEL` | string | ❌ 否 | `claude-sonnet-4-6` | 使用的模型名称 |
| `CLAUDE_MAX_TOKENS` | int | ❌ 否 | `8192` | 最大生成 token 数 |
| `CLAUDE_TEMPERATURE` | float | ❌ 否 | `0.7` | 生成温度0-2 |
## 支持的代理服务示例
### OpenRouter
```bash
ANTHROPIC_API_KEY=sk-or-xxx
ANTHROPIC_BASE_URL=https://openrouter.ai/api/v1
CLAUDE_MODEL=anthropic/claude-sonnet-4-6
```
### AWS Bedrock (通过代理)
```bash
ANTHROPIC_API_KEY=your-aws-key
ANTHROPIC_BASE_URL=https://bedrock.us-east-1.amazonaws.com
CLAUDE_MODEL=anthropic.claude-3-sonnet-20240229-v1:0
```
### Azure OpenAI (Claude 兼容)
```bash
ANTHROPIC_API_KEY=your-azure-key
ANTHROPIC_BASE_URL=https://your-resource.openai.azure.com
CLAUDE_MODEL=claude-sonnet-4-6
```
## 代码实现
配置在以下文件中实现:
1. **配置定义**: `backend/app/config.py`
```python
ANTHROPIC_BASE_URL: str | None = None # Optional custom base URL
```
2. **API 客户端初始化**: `backend/app/core/ai_engine.py`
```python
client_kwargs = {"api_key": settings.ANTHROPIC_API_KEY}
if settings.ANTHROPIC_BASE_URL:
client_kwargs["base_url"] = settings.ANTHROPIC_BASE_URL
self.client = anthropic.AsyncAnthropic(**client_kwargs)
```
## 验证配置
启动后端服务后,检查日志输出:
```
[INFO] Using custom Anthropic base URL: https://your-proxy.com
```
如果看到此日志,说明自定义 base URL 已生效。
## 故障排查
### 问题 1: 连接超时
**症状**: API 调用超时或连接失败
**解决方案**:
- 检查 `ANTHROPIC_BASE_URL` 是否正确
- 确认代理服务可访问
- 检查网络防火墙设置
### 问题 2: 认证失败
**症状**: 401 Unauthorized 错误
**解决方案**:
- 验证 `ANTHROPIC_API_KEY` 是否正确
- 确认代理服务的认证方式
- 检查 API key 是否有权限访问指定模型
### 问题 3: 模型不存在
**症状**: 404 Not Found - Model not found
**解决方案**:
- 确认代理服务支持您指定的 `CLAUDE_MODEL`
- 检查模型名称格式(不同服务可能使用不同的命名)
## 安全建议
1. **不要提交 `.env` 文件到 Git**
- `.env` 文件已在 `.gitignore` 中
- 只提交 `.env.example` 模板
2. **生产环境配置**
- 使用环境变量或密钥管理服务
- 不要在代码中硬编码 API key
3. **API Key 权限**
- 使用最小权限原则
- 定期轮换 API key
- 监控 API 使用情况
## 相关文档
- [Anthropic API 文档](https://docs.anthropic.com/)
- [OpenRouter 文档](https://openrouter.ai/docs)
- [项目 README](./README.md)

288
docs/DEPENDENCY_FIXES.md Normal file
View File

@@ -0,0 +1,288 @@
# 依赖问题修复指南
## 🐛 已知问题及解决方案
### 问题 1: NumPy 2.0 兼容性错误 ✅ 已修复
**错误信息:**
```
AttributeError: `np.float_` was removed in the NumPy 2.0 release. Use `np.float64` instead.
```
**原因:**
- ChromaDB 0.4.18 依赖旧版 NumPy API
- NumPy 2.0 移除了 `np.float_` 等类型别名
- 版本冲突导致运行失败
**解决方案:**
已在 `requirements.txt` 中添加约束:
```txt
numpy<2.0.0
```
### 问题 2: Sentence-Transformers 与 HuggingFace Hub 不兼容 ✅ 已修复
**错误信息:**
```
ImportError: cannot import name 'cached_download' from 'huggingface_hub'
```
**原因:**
- sentence-transformers 2.2.2 使用了已弃用的 `cached_download` API
- 新版 huggingface_hub 移除了该 API
**解决方案:**
已升级到兼容版本:
```txt
sentence-transformers==2.7.0 # 从 2.2.2 升级
```
**修复步骤:**
```bash
cd backend
source venv/bin/activate # Windows: venv\Scripts\activate
# 卸载当前 numpy
pip uninstall numpy -y
# 重新安装依赖(会安装 numpy 1.x
pip install -r requirements.txt
# 验证 numpy 版本
python -c "import numpy; print(numpy.__version__)"
# 应输出: 1.x.x (小于 2.0)
```
---
## 📦 依赖版本说明
### 核心依赖版本
| 包名 | 版本 | 说明 |
|------|------|------|
| numpy | < 2.0.0 | 降级以兼容 chromadb |
| chromadb | 0.4.18 | 向量数据库 |
| sentence-transformers | 2.2.2 | 文本嵌入模型 |
| fastapi | 0.104.1 | Web 框架 |
| anthropic | 0.18.1 | Claude API SDK |
| pydantic | 2.5.0 | 数据验证 |
### 为什么锁定版本?
1. **兼容性保证**: 避免主版本升级引入破坏性更改
2. **可重现构建**: 确保开发和生产环境一致
3. **安全性**: 便于安全审计和漏洞修复
---
## 🔄 更新依赖
### 更新单个包
```bash
pip install --upgrade package-name
pip freeze > requirements.txt # 更新版本
```
### 更新所有包(谨慎)
```bash
pip install --upgrade pip
pip-review --auto # 需要 pip-review 工具
```
### 安全更新
仅更新补丁版本(修复 bug 和安全漏洞):
```bash
pip install --upgrade package-name==1.2.* # 保持在 1.2.x
```
---
## ⚠️ 常见依赖问题
### 问题 1: pip 安装超时
**解决方案:**
```bash
# 使用国内镜像
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
# 或配置全局镜像
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
```
### 问题 2: pyodbc 安装失败
**原因:** 缺少 ODBC 驱动开发包
**解决方案:**
**Ubuntu/Debian:**
```bash
sudo apt-get install unixodbc-dev
pip install pyodbc
```
**CentOS/RHEL:**
```bash
sudo yum install unixODBC-devel
pip install pyodbc
```
**Windows:**
无需额外操作,直接安装即可。
### 问题 3: sentence-transformers 下载模型慢
**原因:** HuggingFace 模型下载速度慢
**解决方案:**
```bash
# 使用镜像站
export HF_ENDPOINT=https://hf-mirror.com
# 或在 Python 代码中设置
import os
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
```
### 问题 4: ChromaDB 初始化错误
**可能原因:**
- 权限问题
- SQLite 版本过低
- 磁盘空间不足
**解决方案:**
```bash
# 检查目录权限
chmod -R 755 backend/knowledge_base/
# 检查 SQLite 版本(需要 >= 3.35.0
python -c "import sqlite3; print(sqlite3.sqlite_version)"
# 清理并重新初始化
rm -rf backend/knowledge_base/chroma_db
```
---
## 🔍 依赖诊断命令
### 检查已安装版本
```bash
# 列出所有包
pip list
# 查看特定包信息
pip show numpy
# 检查过时的包
pip list --outdated
# 检查安全漏洞
pip audit # 需要 pip-audit 工具
```
### 依赖树分析
```bash
# 安装 pipdeptree
pip install pipdeptree
# 查看依赖树
pipdeptree
# 查看反向依赖(谁依赖了这个包)
pipdeptree -r -p numpy
```
### 冲突检测
```bash
# 检查依赖冲突
pip check
# 查看版本约束
pip-compile --dry-run requirements.txt
```
---
## 📝 最佳实践
### 1. 使用虚拟环境
```bash
# 创建虚拟环境
python3 -m venv venv
# 激活虚拟环境
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
# 确认使用虚拟环境
which python # Linux/Mac
where python # Windows
```
### 2. 定期更新依赖
```bash
# 每月检查更新
pip list --outdated
# 更新前查看变更日志
pip install --upgrade package-name --dry-run
```
### 3. 锁定依赖版本
```bash
# 生成精确版本
pip freeze > requirements.txt
# 或使用 pip-tools
pip-compile requirements.in
```
### 4. 分离开发和生产依赖
创建 `requirements-dev.txt`:
```txt
-r requirements.txt
pytest==7.4.3
pytest-asyncio==0.21.1
pytest-cov==4.1.0
pytest-mock==3.12.0
httpx==0.25.2
```
---
## 🆘 依赖问题排查流程
1. **确认错误信息** → 找到具体的包名和错误类型
2. **检查版本冲突**`pip check`
3. **查看依赖关系**`pipdeptree -r -p package-name`
4. **搜索已知问题** → GitHub Issues / Stack Overflow
5. **尝试降级/升级** → 调整版本约束
6. **清理重新安装**`pip cache purge && pip install -r requirements.txt`
---
## 📚 相关资源
- [NumPy 2.0 迁移指南](https://numpy.org/doc/stable/numpy_2_0_migration_guide.html)
- [ChromaDB 文档](https://docs.trychroma.com/)
- [Python 依赖管理最佳实践](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/)
---
**更新时间**: 2026-03-21
**适用版本**: v1.0.0+

236
docs/FRONTEND_UPDATE.md Normal file
View File

@@ -0,0 +1,236 @@
# 前端功能完善说明
## 🎉 已完成的功能
### 1. 完整的工作流程
前端现在支持完整的三个步骤工作流程:
**步骤 1: 需求分析**
- 输入自然语言需求
- 调用后端 `/api/v1/analyze` 接口
- 展示结构化的分析结果
**步骤 2: 配置生成**
- 基于分析结果生成 SQL 配置
- 调用后端 `/api/v1/generate` 接口
- 展示 SQL 配置语句(只读预览)
**步骤 3: 执行配置**
- 确认对话框(带安全警告)
- 调用后端 `/api/v1/execute` 接口
- 展示执行结果(成功/失败)
### 2. 新增功能组件
#### 📦 API 服务 (`frontend/src/api/index.js`)
封装了三个核心 API 调用:
```javascript
analyzeRequirement(data) // 分析需求
generateConfig(data) // 生成配置
executeConfig(data) // 执行配置
```
#### 🗃️ 状态管理 (`frontend/src/stores/function.js`)
使用 Pinia 管理全局状态:
- `currentSession`: 当前会话 ID
- `analysisResult`: 需求分析结果
- `configResult`: 配置生成结果
- `executeResult`: 执行结果
- `loading`: 各步骤加载状态
#### 🎨 UI 组件增强 (`frontend/src/views/CreateFunction.vue`)
**步骤指示器:**
- 清晰展示当前进度
- 三步流程可视化
**需求分析结果展示:**
- 使用 `el-descriptions` 展示基本信息
- 使用 `el-table` 展示字段列表
- 支持标签显示(功能类型、必填项等)
**SQL 配置预览:**
- 使用等宽字体显示 SQL
- 15 行高度文本框
- 只读模式,防止误修改
**执行确认对话框:**
- 醒目的安全警告
- 列出注意事项
- 确认/取消按钮
**执行结果展示:**
- 成功/失败状态图标
- 详细消息展示
- "创建新功能"按钮重新开始
### 3. 交互优化
**加载状态**: 所有异步操作都有 loading 状态
**错误处理**: 友好的错误提示消息
**表单验证**: 必填项验证
**操作确认**: 危险操作二次确认
**状态重置**: 支持重新开始整个流程
## 📋 使用指南
### 启动前端服务
```bash
cd frontend
npm install
npm run dev
```
### 访问应用
- 本地: http://localhost:5173
- 局域网: http://192.168.1.100:5173
### 完整操作流程
1. **输入需求**
```
创建一个销售订单管理页面,包含订单号、客户、订单日期、金额字段
```
2. **点击"开始分析需求"**
- 等待 1-3 秒(取决于 Claude API 响应时间)
- 查看分析结果(功能名称、类型、字段等)
3. **点击"生成配置方案"**
- 等待 AI 生成 SQL 配置
- 仔细检查 SQL 语句
4. **点击"确认并执行"**
- 阅读安全警告
- 点击"确认执行"按钮
- 查看执行结果
5. **完成**
- 成功: 显示成功消息
- 失败: 显示错误详情
## 🔧 技术实现
### 状态管理流程
```
用户输入
分析需求 → 保存 analysisResult + sessionId
生成配置 → 保存 configResult (包含 SQL)
执行配置 → 保存 executeResult (成功/失败)
```
### API 调用示例
```javascript
// 1. 分析需求
const analyzeResult = await analyzeRequirement({
content: '创建销售订单管理页面',
session_id: null // 后端自动生成
})
// 2. 生成配置
const configResult = await generateConfig({
session_id: analyzeResult.session_id,
requirements: analyzeResult.data
})
// 3. 执行配置
const executeResult = await executeConfig({
session_id: analyzeResult.session_id,
confirmed: true,
backup_enabled: true
})
```
## ⚠️ 注意事项
### 1. 后端必须先启动
前端依赖后端 API请确保
```bash
cd backend
python -m app.main
```
### 2. 环境配置
确保后端 `.env` 已配置:
- `ANTHROPIC_API_KEY`: Claude API 密钥
- `DB_*`: 数据库连接信息
### 3. 测试数据
首次测试建议使用简单的需求:
```
创建一个简单的用户管理页面
```
### 4. 跨域问题
开发环境已配置 CORS
- 后端允许所有来源 (`DEBUG=True`)
- 前端代理 `/api` 到后端
## 🐛 已知问题
### 问题 1: API 超时
**现象**: 分析需求时超时
**原因**: Claude API 响应慢或网络问题
**解决**:
- 检查网络连接
- 确认 API Key 有效
- 查看后端日志
### 问题 2: 数据库连接失败
**现象**: 执行配置失败
**原因**: 数据库配置错误或服务未启动
**解决**:
- 检查 `.env` 数据库配置
- 确认 SQL Server 运行中
- 测试数据库连接
## 🚀 后续优化建议
### 功能增强
- [ ] SQL 语法高亮
- [ ] 配置导出功能
- [ ] 历史记录查看
- [ ] 配置模板库
- [ ] 批量操作
### UI/UX 优化
- [ ] 深色模式
- [ ] 响应式布局优化
- [ ] 加载动画优化
- [ ] 快捷键支持
### 性能优化
- [ ] API 响应缓存
- [ ] 长文本虚拟滚动
- [ ] 懒加载优化
## 📚 相关文档
- [后端 API 文档](http://localhost:8000/docs)
- [局域网访问配置](./LAN_ACCESS.md)
- [依赖问题修复](./DEPENDENCY_FIXES.md)
---
**更新时间**: 2026-03-21
**版本**: v1.1.0

266
docs/GIT_GUIDE.md Normal file
View File

@@ -0,0 +1,266 @@
# Git 操作指南 - 撤回和重新添加文件
## ✅ 已完成的操作
### 1. 撤回暂存区
已执行 `git reset` 命令,清空了暂存区的所有文件。
## 📝 修改的 .gitignore
已在 `.gitignore` 中添加以下规则:
```gitignore
# Project specific - 忽略知识库文档
backend/knowledge_base/documents/*.docx
backend/knowledge_base/documents/*.xlsx
backend/knowledge_base/documents/*.pdf
backend/knowledge_base/documents/*.pptx
backend/knowledge_base/documents/*.vsdx
backend/knowledge_base/documents/*.xls
# Temporary files - 忽略临时文件
*.tmp
*.temp
*.bak
*.swp
*~
# Archives - 忽略压缩包
*.zip
*.tar.gz
*.rar
*.7z
```
## 🔍 下一步操作
### 1. 查看将要提交的文件
先查看哪些文件会被添加:
```bash
# 查看所有未跟踪的文件
git status
# 或者更详细地查看
git status --short
```
### 2. 重新添加文件
确认文件列表无误后:
```bash
# 方式 1: 添加所有文件(推荐)
git add .
# 方式 2: 逐个添加文件类型(更安全)
git add backend/app/
git add backend/tests/
git add backend/*.txt backend/*.ini
git add backend/.env.example
git add frontend/src/
git add frontend/*.json frontend/*.js frontend/*.html
git add *.md .gitignore
```
### 3. 检查暂存区
添加后再次检查:
```bash
# 查看暂存区状态
git status
# 查看暂存区的文件列表
git diff --cached --name-only
# 查看具体改动
git diff --cached
```
### 4. 提交
确认无误后提交:
```bash
git commit -m "feat: implement ERP AI Assistant Phase 1
- Backend: FastAPI + SQLAlchemy + Claude API + RAG
- Frontend: Vue 3 + Element Plus + Pinia
- Features: requirement analysis, config generation, safe execution
- Security: SQL injection prevention, parameterized queries
- Support: LAN access, custom API endpoint"
```
## 🎯 建议忽略的文件
如果发现还有其他不需要提交的文件,可以在 `.gitignore` 中添加:
### 常见需要忽略的文件类型
```gitignore
# 大文件
*.dmg
*.iso
*.img
# 编译产物
*.class
*.exe
*.dll
*.so
*.dylib
# 包文件
*.jar
*.war
*.ear
# 日志文件
logs/
*.log
# 数据文件
*.csv
*.dat
*.out
# 模型文件(如果很大的话)
*.model
*.pkl
*.h5
*.pt
*.pth
```
### 项目特定的忽略规则
```gitignore
# 知识库向量数据库(可能很大)
backend/knowledge_base/chroma_db/
# 用户上传的文档
backend/knowledge_base/documents/
# 测试覆盖率报告
htmlcov/
.coverage
# 本地配置
.env.local
.env.*.local
```
## 🔧 Git 常用命令
### 撤回操作
```bash
# 撤回所有已 add 的文件
git reset
# 撤回特定文件
git reset <file>
# 撤回最近一次 commit保留修改
git reset --soft HEAD~1
# 撤回最近一次 commit丢弃修改慎用
git reset --hard HEAD~1
```
### 查看状态
```bash
# 查看当前状态
git status
# 查看简洁状态
git status -s
# 查看分支信息
git branch -a
# 查看提交历史
git log --oneline --graph
```
### 删除已跟踪的文件
如果某个文件已经被 git 跟踪,想要从版本控制中移除:
```bash
# 从 Git 中移除但保留本地文件
git rm --cached <file>
# 从 Git 和本地都删除
git rm <file>
# 从 Git 中移除整个文件夹
git rm -r --cached <directory>
```
## 📋 快速检查清单
提交前检查:
- [ ] 是否包含 `.env` 文件?(不应包含)
- [ ] 是否包含 `venv/``node_modules/`?(不应包含)
- [ ] 是否包含大文件?(不应包含)
- [ ] 是否包含临时文件?(不应包含)
- [ ] 是否包含日志文件?(不应包含)
- [ ] 是否包含个人配置?(不应包含)
## 🚨 常见问题
### 问题 1: 文件已经在 .gitignore 中,但仍然被跟踪
**原因**: 文件在添加 .gitignore 规则之前就已经被 git 跟踪了
**解决方案**:
```bash
# 从 Git 中移除但保留本地文件
git rm --cached <file>
# 然后提交
git commit -m "chore: remove tracked file from version control"
```
### 问题 2: 添加了不该添加的文件
**解决方案**:
```bash
# 1. 撤回添加
git reset
# 2. 更新 .gitignore
# 3. 重新添加
git add .
```
### 问题 3: 如何只提交特定文件
**解决方案**:
```bash
# 只提交代码文件
git add *.py *.vue *.js
# 只提交特定目录
git add backend/app/ frontend/src/
# 交互式添加
git add -p
```
## 📚 相关资源
- [Git 官方文档](https://git-scm.com/doc)
- [.gitignore 模板集合](https://github.com/github/gitignore)
- [Git Flow 工作流](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow)
---
**更新时间**: 2026-03-21

View File

@@ -0,0 +1,289 @@
# ERP AI Assistant - Phase 1 实施完成报告
## 📋 项目概述
已完成 Phase 1 的所有核心任务,建立了 ERP AI 助手的基础架构和核心功能。
## ✅ 已完成任务
### 后端基础设施 (Tasks 1-7)
1. **项目初始化和配置管理**
- 创建 `backend/requirements.txt` - 所有 Python 依赖
- 创建 `backend/.env.example` - 环境变量模板
- 创建 `backend/app/config.py` - Pydantic v2 配置管理
- 实现配置验证和安全编码
2. **Pytest 配置和测试基础设施**
- 创建 `backend/pytest.ini` - pytest 配置
- 创建 `backend/tests/conftest.py` - 测试固件和 mocks
- 配置 asyncio 支持和覆盖率报告
3. **数据库引擎实现**
- 创建 `backend/app/core/db_engine.py`
- 实现连接池管理 (pool_size=20, max_overflow=10)
- 实现上下文管理器会话管理
- **安全加固**: 使用参数化查询防止 SQL 注入
4. **AI 引擎基础实现**
- 创建 `backend/app/core/ai_engine.py`
- 集成 Claude API (Anthropic SDK)
- 实现 JSON 响应解析(支持纯 JSON、markdown 代码块、{} 块)
5. **Prompt 模板设计**
- 创建 `backend/app/core/prompts.py`
- 定义系统 Prompt 和分析/生成模板
6. **RAG 引擎基础实现**
- 创建 `backend/app/core/rag_engine.py`
- 集成 ChromaDB 向量数据库
- 集成 sentence-transformers 嵌入模型
- 实现文档分块、嵌入和语义搜索
7. **需求解析服务实现**
- 创建 `backend/app/services/requirement_service.py`
- 整合 AI 引擎 + RAG 引擎 + 数据库引擎
- 实现需求分析流程(知识检索 → 表结构查询 → AI 分析)
8. **执行引擎实现**
- 创建 `backend/app/core/executor.py`
- 实现 SQL 安全验证(拦截 DROP/TRUNCATE/DELETE 等)
- 实现事务执行和错误处理
### API 层实现 (Tasks 8-10)
9. **API 基础结构**
- 创建 `backend/app/models/request.py` - 请求模型
- 创建 `backend/app/models/response.py` - 响应模型
- 创建 `backend/app/main.py` - FastAPI 应用入口
- 配置 CORS 中间件
10. **需求解析 API**
- 创建 `backend/app/api/analyze.py`
- POST `/api/v1/analyze` - 分析用户需求
### 前端实现 (Tasks 11-12)
11. **前端项目初始化**
- 创建 `frontend/package.json` - Vue 3 + Vite
- 创建 `frontend/vite.config.js` - 开发服务器和代理配置
- 创建 `frontend/src/main.js` - 应用入口
- 创建 `frontend/src/App.vue` - 根组件
12. **前端路由和布局**
- 创建 `frontend/src/router/index.js` - 路由配置
- 创建 `frontend/src/views/Layout.vue` - 主布局(侧边栏 + 头部)
- 创建 `frontend/src/views/CreateFunction.vue` - 功能创建页面
- 创建 `frontend/src/views/History.vue` - 历史记录页面
### 执行和配置服务 (Tasks 13-15)
13. **配置生成服务**
- 创建 `backend/app/services/config_service.py`
- 创建 `backend/tests/test_config_service.py`
- 实现基于需求的 SQL 配置生成
14. **执行配置 API**
- 创建 `backend/app/api/generate.py` - POST `/api/v1/generate`
- 创建 `backend/app/api/execute.py` - POST `/api/v1/execute`
- 更新 `backend/app/main.py` 注册路由
## 📁 项目结构
```
/data/erp-ass/
├── backend/
│ ├── app/
│ │ ├── api/
│ │ │ ├── __init__.py
│ │ │ ├── analyze.py
│ │ │ ├── generate.py
│ │ │ └── execute.py
│ │ ├── core/
│ │ │ ├── __init__.py
│ │ │ ├── db_engine.py
│ │ │ ├── ai_engine.py
│ │ │ ├── prompts.py
│ │ │ ├── rag_engine.py
│ │ │ └── executor.py
│ │ ├── models/
│ │ │ ├── __init__.py
│ │ │ ├── request.py
│ │ │ └── response.py
│ │ ├── services/
│ │ │ ├── __init__.py
│ │ │ ├── requirement_service.py
│ │ │ └── config_service.py
│ │ ├── config.py
│ │ └── main.py
│ ├── tests/
│ │ ├── conftest.py
│ │ ├── test_db_engine.py
│ │ ├── test_ai_engine.py
│ │ ├── test_prompts.py
│ │ ├── test_rag_engine.py
│ │ ├── test_requirement_service.py
│ │ ├── test_config_service.py
│ │ └── test_executor.py
│ ├── knowledge_base/
│ │ └── documents/
│ ├── scripts/
│ ├── requirements.txt
│ ├── pytest.ini
│ └── .env.example
└── frontend/
├── src/
│ ├── router/
│ │ └── index.js
│ ├── views/
│ │ ├── Layout.vue
│ │ ├── CreateFunction.vue
│ │ └── History.vue
│ ├── main.js
│ └── App.vue
├── index.html
├── vite.config.js
└── package.json
```
## 🔐 安全特性
1. **SQL 注入防护**
- 所有数据库查询使用参数化查询
- 危险 SQL 操作拦截DROP、TRUNCATE、DELETE without WHERE
2. **配置安全**
- 数据库密码 URL 编码(支持特殊字符)
- 环境变量管理敏感信息
- Pydantic 配置验证
3. **事务保护**
- 自动提交/回滚机制
- 上下文管理器管理会话
## 🚀 后续步骤
### 立即需要完成
1. **安装依赖**
```bash
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cd ../frontend
npm install
```
2. **配置环境变量**
```bash
cd backend
cp .env.example .env
# 编辑 .env 文件,填入真实的数据库和 Claude API 配置
```
3. **局域网访问配置** ✅ 已完成
- 前端已配置 `host: '0.0.0.0'`
- 后端已配置 `host="0.0.0.0"` 和 CORS 策略
- 详细配置参考: [docs/LAN_ACCESS.md](LAN_ACCESS.md)
4. **初始化知识库**
- 将平台文档放入 `backend/knowledge_base/documents/`
- 运行知识库初始化脚本(需要创建)
5. **Git 版本控制**
```bash
git init
git add .
git commit -m "feat: implement Phase 1 - ERP AI Assistant foundation"
```
### Phase 2 功能增强
1. **执行日志和审计系统**
- 创建 ExecutionLog 数据模型
- 实现审计服务记录所有操作
- 前端展示执行历史
2. **数据库元数据 API**
- 提供表结构查询接口
- 支持智能表推荐
3. **知识库管理界面**
- 文档上传和管理
- 知识库更新和版本控制
4. **配置预览组件**
- SQL 高亮显示
- Monaco Editor 集成
- 配置对比和修改
5. **执行监控组件**
- 实时进度显示
- 错误详情展示
- 回滚功能实现
### Phase 3 高级功能
1. **错误排查系统**
- SQL 日志监控
- 智能错误诊断
- 修复建议生成
2. **系统优化**
- 性能分析
- 缓存管理
- 权限优化
## 📝 注意事项
1. **环境限制**
- 当前环境无法执行 bash 命令(权限超时)
- 需要手动执行依赖安装和测试
2. **数据库配置**
- 需要 SQL Server 数据库连接
- 确保数据库有 SYS_FORM、SYS_MENU 等系统表
3. **Claude API**
- 需要有效的 Anthropic API Key
- 推荐使用 `claude-sonnet-4-6` 模型
4. **测试覆盖**
- 所有测试文件已创建
- 使用 mock 避免真实 API 调用
- 需要配置环境后运行集成测试
## 🎯 技术栈总结
**后端:**
- Python 3.10+
- FastAPI 0.104.1
- SQLAlchemy 2.0.23
- Anthropic SDK 0.18.1
- ChromaDB 0.4.18
- sentence-transformers 2.2.2
- Pydantic 2.5.0
**前端:**
- Vue 3.3.8
- Vite 5.0.4
- Vue Router 4.2.5
- Pinia 2.1.7
- Element Plus 2.4.3
- Axios 1.6.2
**数据库:**
- SQL Server (通过 pyodbc 5.0.1)
- ChromaDB (向量数据库)
**AI 引擎:**
- Claude Sonnet 4.6
- all-MiniLM-L6-v2 (嵌入模型)
---
**实施日期:** 2026-03-21
**完成状态:** Phase 1 全部完成 ✓
**下一阶段:** Phase 2 - 功能增强

319
docs/LAN_ACCESS.md Normal file
View File

@@ -0,0 +1,319 @@
# 局域网访问配置指南
## 🌐 配置说明
本项目已配置支持局域网访问,允许团队在同一局域网内访问前端和后端服务。
## ✅ 已完成的配置
### 1. 前端配置 (Vite)
**文件**: `frontend/vite.config.js`
```javascript
server: {
host: '0.0.0.0', // 监听所有网络接口,允许局域网访问
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true
}
}
}
```
### 2. 后端配置 (FastAPI)
**文件**: `backend/app/main.py`
- **服务监听**: `host="0.0.0.0"` (已配置,监听所有网络接口)
- **CORS 策略**:
- 开发模式 (`DEBUG=True`): 允许所有来源 (`allow_origins=["*"]`)
- 生产模式: 仅允许 localhost
## 🚀 使用步骤
### 1. 获取服务器 IP 地址
在运行服务的机器上执行:
**Windows:**
```bash
ipconfig
```
查找 "IPv4 地址",例如:`192.168.1.100`
**Linux/Mac:**
```bash
ifconfig
# 或
ip addr show
```
查找 `inet` 地址,例如:`inet 192.168.1.100`
### 2. 启动服务
**启动后端:**
```bash
cd backend
source venv/bin/activate # Windows: venv\Scripts\activate
python -m app.main
```
后端将监听:
- `http://0.0.0.0:8000` (所有网络接口)
- 可通过 `http://localhost:8000``http://192.168.1.100:8000` 访问
**启动前端:**
```bash
cd frontend
npm run dev
```
Vite 将输出:
```
VITE v5.0.4 ready in xxx ms
➜ Local: http://localhost:5173/
➜ Network: http://192.168.1.100:5173/
```
### 3. 局域网访问
在同一局域网的其他设备上,使用服务器 IP 地址访问:
- **前端**: `http://192.168.1.100:5173`
- **后端 API**: `http://192.168.1.100:8000`
- **API 文档**: `http://192.168.1.100:8000/docs`
## 🔥 防火墙配置
如果局域网内无法访问,需要检查防火墙设置。
### Windows 防火墙
**方法 1: 允许端口**
```powershell
# 以管理员身份运行 PowerShell
netsh advfirewall firewall add rule name="ERP AI Assistant - Frontend" dir=in action=allow protocol=tcp localport=5173
netsh advfirewall firewall add rule name="ERP AI Assistant - Backend" dir=in action=allow protocol=tcp localport=8000
```
**方法 2: 允许应用**
1. 打开 "Windows Defender 防火墙"
2. 点击 "允许应用通过防火墙"
3. 添加 `node.exe``python.exe`
### Linux 防火墙
**UFW (Ubuntu):**
```bash
sudo ufw allow 5173/tcp
sudo ufw allow 8000/tcp
sudo ufw reload
```
**Firewalld (CentOS/RHEL):**
```bash
sudo firewall-cmd --permanent --add-port=5173/tcp
sudo firewall-cmd --permanent --add-port=8000/tcp
sudo firewall-cmd --reload
```
### 云服务器安全组
如果运行在云服务器上,需要在安全组中开放端口:
- 入站规则: 允许 TCP 端口 5173 和 8000
## 📱 测试访问
### 1. 本地测试
在服务器机器上测试:
```bash
# 测试后端
curl http://localhost:8000/health
# 测试前端(浏览器访问)
http://localhost:5173
```
### 2. 局域网测试
在其他设备上测试:
```bash
# 测试后端(替换为实际 IP
curl http://192.168.1.100:8000/health
# 测试前端(浏览器访问)
http://192.168.1.100:5173
```
## ⚠️ 安全注意事项
### 开发环境
当前配置适合开发环境:
- ✅ CORS 允许所有来源 (`allow_origins=["*"]`)
- ✅ 方便团队协作和测试
### 生产环境
**强烈建议**生产环境进行以下调整:
1. **设置 `DEBUG=False`**
```bash
# .env
DEBUG=False
```
2. **配置具体允许的域名**
修改 `backend/app/main.py`:
```python
allow_origins=[
"https://your-domain.com",
"https://erp.your-company.com",
]
```
3. **使用 HTTPS**
- 配置 SSL 证书
- 使用 Nginx 反向代理
4. **使用环境变量管理 CORS**
在 `.env` 中配置:
```bash
ALLOWED_ORIGINS=https://domain1.com,https://domain2.com
```
## 🔧 高级配置
### 自定义端口
如果默认端口被占用,可以修改:
**前端** (`frontend/vite.config.js`):
```javascript
server: {
host: '0.0.0.0',
port: 3000, // 自定义端口
// ...
}
```
**后端** (`backend/app/main.py`):
```python
uvicorn.run(
"app.main:app",
host="0.0.0.0",
port=8888, # 自定义端口
reload=settings.DEBUG
)
```
### 使用 Nginx 反向代理
生产环境推荐使用 Nginx
```nginx
server {
listen 80;
server_name erp.your-company.com;
# 前端
location / {
proxy_pass http://127.0.0.1:5173;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 后端 API
location /api/ {
proxy_pass http://127.0.0.1:8000/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
## 📊 访问日志
查看访问日志,确认服务正常运行:
**前端日志:**
```bash
# Vite 会显示访问日志
npm run dev
```
**后端日志:**
```bash
# FastAPI/Uvicorn 会显示请求日志
python -m app.main
```
## 🐛 故障排查
### 问题 1: 无法访问
**症状**: 局域网内其他设备无法访问
**检查清单:**
- ✅ 服务器 IP 地址是否正确
- ✅ 防火墙是否开放端口
- ✅ 服务是否正在运行
- ✅ 是否在同一局域网内
**诊断命令:**
```bash
# 测试端口是否开放
telnet 192.168.1.100 5173
telnet 192.168.1.100 8000
# 或使用 nc
nc -zv 192.168.1.100 5173
nc -zv 192.168.1.100 8000
```
### 问题 2: CORS 错误
**症状**: 浏览器控制台显示 CORS 错误
**解决方案:**
- 确认 `DEBUG=True` 在 `.env` 中
- 检查后端 CORS 配置
- 查看浏览器控制台错误详情
### 问题 3: API 代理失败
**症状**: 前端无法调用后端 API
**解决方案:**
- 检查后端是否在 `localhost:8000` 运行
- 确认 Vite proxy 配置正确
- 查看浏览器网络请求
## 📝 快速命令参考
```bash
# 查看本机 IP
ipconfig # Windows
ifconfig # Linux/Mac
ip addr show # Linux
# 测试端口
netstat -an | grep 5173 # Linux/Mac
netstat -an | findstr 5173 # Windows
# 开放防火墙端口
sudo ufw allow 5173/tcp # Ubuntu
sudo firewall-cmd --add-port=5173/tcp # CentOS
```
---
**更新时间**: 2026-03-21
**适用版本**: v1.0.0+

58
docs/QUICK_REFERENCE.md Normal file
View File

@@ -0,0 +1,58 @@
# 快速配置卡片
## 🎯 局域网访问配置
### ✅ 已配置项目
1. **前端** - `frontend/vite.config.js`
```javascript
host: '0.0.0.0' // ✓ 已添加
```
2. **后端** - `backend/app/main.py`
```python
host="0.0.0.0" // ✓ 已配置
allow_origins=["*"] if settings.DEBUG // ✓ 开发模式允许所有来源
```
### 📋 使用步骤
```bash
# 1. 查看服务器 IP
ipconfig # Windows
ifconfig # Linux/Mac
# 2. 启动后端
cd backend && python -m app.main
# 3. 启动前端
cd frontend && npm run dev
# 4. 局域网访问
# 前端: http://192.168.1.100:5173
# 后端: http://192.168.1.100:8000
```
### 🔥 防火墙快速配置
**Windows:**
```powershell
# 管理员 PowerShell
netsh advfirewall firewall add rule name="ERP Frontend" dir=in action=allow protocol=tcp localport=5173
netsh advfirewall firewall add rule name="ERP Backend" dir=in action=allow protocol=tcp localport=8000
```
**Linux (Ubuntu):**
```bash
sudo ufw allow 5173/tcp
sudo ufw allow 8000/tcp
```
### ⚠️ 安全提醒
- **开发环境**: `DEBUG=True` (允许所有来源)
- **生产环境**: `DEBUG=False` + 配置具体域名
---
详细说明: [docs/LAN_ACCESS.md](LAN_ACCESS.md)

386
docs/QUICK_START.md Normal file
View File

@@ -0,0 +1,386 @@
# 🚀 快速上手指南
## 📋 前置条件
### 1. 后端配置
确保已完成后端配置和依赖安装:
```bash
cd backend
# 安装依赖(如果遇到问题,参考 docs/DEPENDENCY_FIXES.md
pip install -r requirements.txt
# 配置环境变量
cp .env.example .env
# 编辑 .env 文件,配置数据库和 Claude API
```
**关键配置项:**
```bash
# 数据库配置
DB_SERVER=192.168.120.19
DB_PORT=1433
DB_NAME=DMPF_HY
DB_USER=sa
DB_PASSWORD=your-password
# Claude API 配置
ANTHROPIC_API_KEY=your-claude-api-key
# 可选:使用自定义 API 端点
# ANTHROPIC_BASE_URL=https://your-proxy.com/v1
```
### 2. 前端配置
```bash
cd frontend
# 安装依赖
npm install
```
## 🎯 启动服务
### 启动后端
```bash
cd backend
source venv/bin/activate # Windows: venv\Scripts\activate
python -m app.main
```
**成功输出:**
```
INFO: Uvicorn running on http://0.0.0.0:8000
INFO: Application startup complete.
```
### 启动前端
**新终端窗口:**
```bash
cd frontend
npm run dev
```
**成功输出:**
```
➜ Local: http://localhost:5173/
➜ Network: http://192.168.1.100:5173/
```
## 📱 使用流程
### 步骤 1: 访问应用
打开浏览器访问:
- **本地**: http://localhost:5173
- **局域网**: http://192.168.1.100:5173 (替换为实际 IP
### 步骤 2: 输入需求
在"步骤 1: 输入需求"卡片中,输入自然语言需求,例如:
```
创建一个销售订单管理页面,包含订单号、客户、订单日期、金额、备注字段
```
**示例需求:**
**简单示例:**
```
创建一个部门管理页面,包含部门名称、负责人、联系电话
```
**中等复杂度:**
```
创建一个客户档案管理页面,包含客户名称、联系人、电话、地址、客户类型,支持按客户类型筛选
```
**复杂示例:**
```
创建一个采购申请单,单据类型,包含申请单号、申请日期、申请人、部门、总金额,明细表包含物料编码、物料名称、数量、单价、金额
```
### 步骤 3: 分析需求
点击 **"开始分析需求"** 按钮:
- ⏳ 等待 1-3 秒(取决于 API 响应时间)
- ✅ 成功后显示结构化分析结果
- 📊 包含:功能名称、功能类型、窗体类型、字段列表等
**示例输出:**
```
功能名称: 销售订单管理
功能类型: 列表页面
功能号建议: 11-001
窗体类型: 5
主表字段:
- 订单号 (varchar(50), 必填)
- 客户 (varchar(100), 必填)
- 订单日期 (datetime, 必填)
- 金额 (decimal(18,2), 必填)
- 备注 (varchar(500), 选填)
```
### 步骤 4: 生成配置
确认分析结果无误后,点击 **"生成配置方案"** 按钮:
- ⏳ 等待 2-5 秒
- ✅ 成功后显示生成的 SQL 配置
- 📝 可以预览所有 SQL 语句
**SQL 预览示例:**
```sql
-- 创建功能号
INSERT INTO SYS_FUNCTION (FUNCTION_ID, FUNCTION_NAME, ...)
VALUES ('11-001', '销售订单管理', ...);
-- 创建页面配置
INSERT INTO SYS_FORM (FORM_ID, FORM_NAME, ...)
VALUES (...);
-- 创建表结构
CREATE TABLE SA_ORDER (
IKEY INT IDENTITY(1,1) PRIMARY KEY,
ORDER_NO VARCHAR(50) NOT NULL,
...
);
```
### 步骤 5: 执行配置
仔细检查 SQL 语句后,点击 **"确认并执行"** 按钮:
1. **安全警告对话框**
- ⚠️ 阅读警告信息
- ✅ 确认无误后点击"确认执行"
2. **执行过程**
- ⏳ 等待执行完成
- 📊 实时显示执行结果
3. **执行结果**
- ✅ 成功: 显示"执行成功"消息
- ❌ 失败: 显示错误详情
### 步骤 6: 完成
- 🎉 查看执行结果
- 🔄 点击"创建新功能"开始下一个功能
## 🎨 界面说明
### 步骤指示器
顶部显示当前进度:
```
○ 需求分析 → ○ 配置生成 → ○ 执行配置
```
- **当前步骤**: 高亮显示
- **已完成**: 绿色勾选
- **未完成**: 灰色
### 卡片区域
#### 需求输入卡片
- 文本框:输入自然语言需求
- "开始分析需求"按钮
#### 分析结果卡片
- 基本信息:功能名称、类型等
- 字段列表:表格展示
- 操作按钮:重新分析、生成配置
#### 配置方案卡片
- SQL 预览:只读文本框
- 操作按钮:返回修改、确认执行
#### 执行结果卡片
- 成功/失败图标
- 详细消息
- "创建新功能"按钮
## ⚠️ 注意事项
### 1. 需求描述建议
**✅ 好的描述:**
- 清晰说明功能目的
- 列出主要字段
- 说明特殊需求
**示例:**
```
创建一个库存预警设置页面,包含物料编码、物料名称、安全库存、预警阈值,
当库存低于预警阈值时自动标记为红色
```
**❌ 不好的描述:**
```
做一个库存页面
```
(太模糊,缺少关键信息)
### 2. 检查分析结果
在生成配置前,务必检查:
- ✅ 功能名称是否正确
- ✅ 字段是否完整
- ✅ 字段类型是否合理
- ✅ 必填项标记是否正确
如有问题,点击"重新分析"修改需求。
### 3. SQL 审查
执行前必须检查:
- ✅ 表名是否合理
- ✅ 字段名是否符合规范
- ✅ 数据类型是否正确
- ✅ 外键关联是否正确
### 4. 数据库备份
**强烈建议:**
- 在生产环境执行前备份数据库
- 先在测试环境验证
- 记录执行的 SQL 语句
## 🐛 常见问题
### 问题 1: 分析需求超时
**现象:** 点击按钮后一直加载
**可能原因:**
- Claude API 响应慢
- 网络连接问题
- API Key 无效
**解决方案:**
1. 检查网络连接
2. 验证 `ANTHROPIC_API_KEY` 是否有效
3. 查看后端日志:`tail -f backend/logs/app.log`
4. 尝试更简单的需求
### 问题 2: 数据库执行失败
**现象:** 执行配置时显示失败
**可能原因:**
- 数据库连接失败
- SQL 语法错误
- 权限不足
- 表已存在
**解决方案:**
1. 检查 `.env` 数据库配置
2. 确认数据库服务运行中
3. 检查 SQL Server 日志
4. 使用数据库管理工具测试 SQL
### 问题 3: 前端无法连接后端
**现象:** 前端显示网络错误
**解决方案:**
1. 确认后端已启动:访问 http://localhost:8000/health
2. 检查前端代理配置:`frontend/vite.config.js`
3. 查看浏览器控制台网络请求
4. 确认 CORS 配置:后端 `DEBUG=True`
### 问题 4: 分析结果不准确
**现象:** 生成的字段或类型不符合预期
**解决方案:**
- 更详细地描述需求
- 在需求中明确字段类型
- 使用更具体的业务术语
- 多次尝试不同的描述方式
## 📚 进阶使用
### 自定义 API 端点
如需使用代理或自托管服务:
```bash
# .env
ANTHROPIC_BASE_URL=https://your-proxy.com/v1
```
参考 [Claude API 配置指南](./CLAUDE_API_CONFIG.md)
### 局域网访问
团队成员可通过局域网访问:
```bash
# 查看服务器 IP
ipconfig # Windows
ifconfig # Linux/Mac
# 其他设备访问
http://192.168.1.100:5173
```
参考 [局域网访问配置](./LAN_ACCESS.md)
### 查看历史记录
"历史记录"页面(即将推出):
- 查看所有执行记录
- 重新执行历史配置
- 导出配置方案
## 🎓 最佳实践
### 1. 开发流程
```
需求分析 → 代码评审 → 测试环境验证 → 生产环境执行
```
### 2. 命名规范
建议在需求中说明:
- 功能号格式:`模块-序号` (如:`11-001`)
- 表名前缀:业务含义 (如:`SA_` 销售)
- 字段名:有意义的英文
### 3. 版本管理
使用 Git 记录配置变更:
```bash
# 执行成功后
git add .
git commit -m "feat: add 销售订单管理功能"
```
### 4. 团队协作
- 使用相同的测试数据库
- 共享需求模板
- 定期同步配置规范
## 📞 获取帮助
遇到问题时:
1. 📖 查看本文档
2. 🔍 检查日志文件
3. 📋 查看后端 API 文档: http://localhost:8000/docs
4. 💬 联系项目负责人
---
**祝您使用愉快!🎉**

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

13
frontend/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ERP AI Assistant</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

1750
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
frontend/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "erp-ai-assistant-frontend",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.3.8",
"vue-router": "^4.2.5",
"pinia": "^2.1.7",
"axios": "^1.6.2",
"element-plus": "^2.4.3",
"@element-plus/icons-vue": "^2.3.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.5.1",
"vite": "^5.0.4"
}
}

22
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,22 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script setup>
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
font-family: 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
height: 100vh;
margin: 0;
}
</style>

52
frontend/src/api/index.js Normal file
View File

@@ -0,0 +1,52 @@
import axios from 'axios'
const api = axios.create({
baseURL: '/api/v1',
timeout: 60000,
headers: {
'Content-Type': 'application/json'
}
})
// 响应拦截器
api.interceptors.response.use(
response => response.data,
error => {
console.error('API Error:', error)
return Promise.reject(error)
}
)
/**
* 分析用户需求
*/
export const analyzeRequirement = (data) => {
return api.post('/analyze', {
input_type: 'natural_language',
content: data.content,
session_id: data.session_id
})
}
/**
* 生成配置
*/
export const generateConfig = (data) => {
return api.post('/generate', {
session_id: data.session_id,
requirements: data.requirements
})
}
/**
* 执行配置
*/
export const executeConfig = (data) => {
return api.post('/execute', {
session_id: data.session_id,
confirmed: data.confirmed || true,
backup_enabled: data.backup_enabled || true
})
}
export default api

14
frontend/src/main.js Normal file
View File

@@ -0,0 +1,14 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
app.mount('#app')

View File

@@ -0,0 +1,31 @@
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
component: () => import('../views/Layout.vue'),
children: [
{
path: '',
redirect: '/create'
},
{
path: 'create',
name: 'CreateFunction',
component: () => import('../views/CreateFunction.vue')
},
{
path: 'history',
name: 'History',
component: () => import('../views/History.vue')
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router

View File

@@ -0,0 +1,61 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useFunctionStore = defineStore('function', () => {
// 状态
const currentSession = ref(null)
const analysisResult = ref(null)
const configResult = ref(null)
const executeResult = ref(null)
const loading = ref({
analyzing: false,
generating: false,
executing: false
})
// 重置状态
const reset = () => {
currentSession.value = null
analysisResult.value = null
configResult.value = null
executeResult.value = null
loading.value = {
analyzing: false,
generating: false,
executing: false
}
}
// 设置会话
const setSession = (sessionId) => {
currentSession.value = sessionId
}
// 设置分析结果
const setAnalysisResult = (result) => {
analysisResult.value = result
}
// 设置配置结果
const setConfigResult = (result) => {
configResult.value = result
}
// 设置执行结果
const setExecuteResult = (result) => {
executeResult.value = result
}
return {
currentSession,
analysisResult,
configResult,
executeResult,
loading,
reset,
setSession,
setAnalysisResult,
setConfigResult,
setExecuteResult
}
})

View File

@@ -0,0 +1,3 @@
import { createPinia } from 'pinia'
export const pinia = createPinia()

View File

@@ -0,0 +1,380 @@
<template>
<div class="create-function">
<!-- 步骤指示器 -->
<el-steps :active="currentStep" finish-status="success" simple class="steps">
<el-step title="需求分析" />
<el-step title="配置生成" />
<el-step title="执行配置" />
</el-steps>
<!-- 步骤 1: 需求输入 -->
<el-card v-if="currentStep === 0" class="section-card">
<template #header>
<div class="card-header">
<span>步骤 1: 输入需求</span>
</div>
</template>
<el-form :model="form" label-width="120px">
<el-form-item label="需求描述">
<el-input
v-model="form.requirement"
type="textarea"
:rows="6"
placeholder="请输入功能需求,例如:创建一个销售订单管理页面,包含订单号、客户、订单日期、金额等字段"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="handleAnalyze"
:loading="functionStore.loading.analyzing"
:disabled="!form.requirement.trim()"
>
开始分析需求
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 步骤 2: 分析结果 -->
<el-card v-if="functionStore.analysisResult" class="section-card">
<template #header>
<div class="card-header">
<span>需求分析结果</span>
<el-tag type="success">已完成</el-tag>
</div>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="功能名称">
{{ functionStore.analysisResult.功能名称 || '-' }}
</el-descriptions-item>
<el-descriptions-item label="功能类型">
{{ functionStore.analysisResult.功能类型 || '-' }}
</el-descriptions-item>
<el-descriptions-item label="功能号建议">
{{ functionStore.analysisResult.功能号建议 || '-' }}
</el-descriptions-item>
<el-descriptions-item label="窗体类型">
{{ functionStore.analysisResult.窗体类型 || '-' }}
</el-descriptions-item>
</el-descriptions>
<div v-if="functionStore.analysisResult.主表字段" class="field-list">
<h4>主表字段</h4>
<el-table :data="functionStore.analysisResult.主表字段" border size="small">
<el-table-column prop="字段名" label="字段名" />
<el-table-column prop="字段类型" label="字段类型" />
<el-table-column prop="必填" label="必填">
<template #default="{ row }">
<el-tag :type="row.必填 ? 'danger' : 'info'" size="small">
{{ row.必填 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
<div class="button-group">
<el-button @click="resetAll">重新分析</el-button>
<el-button
type="primary"
@click="handleGenerate"
:loading="functionStore.loading.generating"
>
生成配置方案
</el-button>
</div>
</el-card>
<!-- 步骤 3: 配置方案 -->
<el-card v-if="functionStore.configResult" class="section-card">
<template #header>
<div class="card-header">
<span>配置方案</span>
<el-tag type="success">已生成</el-tag>
</div>
</template>
<el-alert
title="请仔细检查以下 SQL 配置,确认无误后执行"
type="warning"
:closable="false"
show-icon
class="alert-box"
/>
<div v-if="functionStore.configResult.配置方案" class="config-section">
<h4>SQL 配置语句</h4>
<el-input
v-model="sqlDisplay"
type="textarea"
:rows="15"
readonly
class="sql-textarea"
/>
</div>
<div class="button-group">
<el-button @click="backToAnalysis">返回修改</el-button>
<el-button
type="danger"
@click="showExecuteConfirm"
:loading="functionStore.loading.executing"
>
确认并执行
</el-button>
</div>
</el-card>
<!-- 执行结果 -->
<el-card v-if="functionStore.executeResult" class="section-card">
<template #header>
<div class="card-header">
<span>执行结果</span>
<el-tag :type="functionStore.executeResult.status === 'success' ? 'success' : 'danger'">
{{ functionStore.executeResult.status === 'success' ? '执行成功' : '执行失败' }}
</el-tag>
</div>
</template>
<el-result
:icon="functionStore.executeResult.status === 'success' ? 'success' : 'error'"
:title="functionStore.executeResult.message"
>
<template #extra>
<el-button type="primary" @click="resetAll">创建新功能</el-button>
</template>
</el-result>
</el-card>
<!-- 执行确认对话框 -->
<el-dialog
v-model="executeDialogVisible"
title="确认执行"
width="500px"
>
<el-alert
type="warning"
:closable="false"
show-icon
>
<template #title>
<strong>警告此操作将直接修改数据库</strong>
</template>
<div style="margin-top: 10px;">
<p> 请确认已仔细检查所有 SQL 语句</p>
<p> 建议在生产环境执行前先备份数据库</p>
<p> 执行后可通过历史记录查看详情</p>
</div>
</el-alert>
<template #footer>
<el-button @click="executeDialogVisible = false">取消</el-button>
<el-button type="danger" @click="handleExecute">
确认执行
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { useFunctionStore } from '@/stores/function'
import { analyzeRequirement, generateConfig, executeConfig } from '@/api'
const functionStore = useFunctionStore()
const form = ref({
requirement: ''
})
const executeDialogVisible = ref(false)
// 当前步骤
const currentStep = computed(() => {
if (functionStore.executeResult) return 3
if (functionStore.configResult) return 2
if (functionStore.analysisResult) return 1
return 0
})
// SQL 显示
const sqlDisplay = computed(() => {
if (!functionStore.configResult?.配置方案?.sql_list) return ''
return functionStore.configResult.配置方案.sql_list.join('\n\n')
})
// 分析需求
const handleAnalyze = async () => {
if (!form.value.requirement.trim()) {
ElMessage.warning('请输入需求描述')
return
}
functionStore.loading.analyzing = true
try {
const result = await analyzeRequirement({
content: form.value.requirement,
session_id: null
})
functionStore.setSession(result.session_id)
functionStore.setAnalysisResult(result.data)
ElMessage.success('需求分析成功')
} catch (error) {
console.error('Analysis failed:', error)
ElMessage.error(
'需求分析失败: ' + (error.response?.data?.detail?.message || error.message)
)
} finally {
functionStore.loading.analyzing = false
}
}
// 生成配置
const handleGenerate = async () => {
if (!functionStore.currentSession || !functionStore.analysisResult) {
ElMessage.warning('请先完成需求分析')
return
}
functionStore.loading.generating = true
try {
const result = await generateConfig({
session_id: functionStore.currentSession,
requirements: functionStore.analysisResult
})
functionStore.setConfigResult(result.data)
ElMessage.success('配置生成成功')
} catch (error) {
console.error('Generation failed:', error)
ElMessage.error(
'配置生成失败: ' + (error.response?.data?.detail?.message || error.message)
)
} finally {
functionStore.loading.generating = false
}
}
// 显示执行确认对话框
const showExecuteConfirm = () => {
executeDialogVisible.value = true
}
// 执行配置
const handleExecute = async () => {
if (!functionStore.currentSession) {
ElMessage.warning('会话信息丢失,请重新开始')
return
}
executeDialogVisible.value = false
functionStore.loading.executing = true
try {
const result = await executeConfig({
session_id: functionStore.currentSession,
confirmed: true,
backup_enabled: true
})
functionStore.setExecuteResult(result)
if (result.status === 'success') {
ElMessage.success('配置执行成功')
} else {
ElMessage.error('配置执行失败')
}
} catch (error) {
console.error('Execution failed:', error)
ElMessage.error(
'配置执行失败: ' + (error.response?.data?.detail?.message || error.message)
)
} finally {
functionStore.loading.executing = false
}
}
// 返回分析步骤
const backToAnalysis = () => {
functionStore.setConfigResult(null)
}
// 重置所有状态
const resetAll = () => {
functionStore.reset()
form.value.requirement = ''
}
</script>
<style scoped>
.create-function {
max-width: 1200px;
margin: 0 auto;
}
.steps {
margin-bottom: 20px;
}
.section-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
font-weight: bold;
}
.field-list {
margin-top: 20px;
}
.field-list h4 {
margin-bottom: 10px;
color: #606266;
}
.alert-box {
margin-bottom: 20px;
}
.config-section {
margin-top: 20px;
}
.config-section h4 {
margin-bottom: 10px;
color: #606266;
}
.sql-textarea {
font-family: 'Courier New', monospace;
}
.sql-textarea :deep(textarea) {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
}
.button-group {
display: flex;
gap: 10px;
margin-top: 20px;
justify-content: flex-end;
}
</style>

View File

@@ -0,0 +1,28 @@
<template>
<div class="history">
<el-card>
<template #header>
<div class="card-header">
<span>历史记录</span>
</div>
</template>
<el-empty description="暂无历史记录" />
</el-card>
</div>
</template>
<script setup>
</script>
<style scoped>
.history {
max-width: 1200px;
margin: 0 auto;
}
.card-header {
font-size: 16px;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<el-container class="layout-container">
<el-header class="header">
<div class="logo">ERP智能助手</div>
<div class="user-info">用户: Admin</div>
</el-header>
<el-container>
<el-aside width="200px" class="sidebar">
<el-menu
:default-active="$route.path"
router
>
<el-menu-item index="/create">
<el-icon><Edit /></el-icon>
<span>新建功能</span>
</el-menu-item>
<el-menu-item index="/history">
<el-icon><Document /></el-icon>
<span>历史记录</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main class="main-content">
<router-view />
</el-main>
</el-container>
</el-container>
</template>
<script setup>
import { Edit, Document } from '@element-plus/icons-vue'
</script>
<style scoped>
.layout-container {
height: 100vh;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
background: #409EFF;
color: white;
padding: 0 20px;
}
.logo {
font-size: 20px;
font-weight: bold;
}
.user-info {
font-size: 14px;
}
.sidebar {
background: #f5f7fa;
overflow-y: auto;
}
.main-content {
background: #f0f2f5;
padding: 20px;
overflow-y: auto;
}
</style>

17
frontend/vite.config.js Normal file
View File

@@ -0,0 +1,17 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
host: '0.0.0.0', // 允许局域网访问
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true
}
}
}
})