Compare commits
11 Commits
main
...
rjb_win_de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daf7c1867a | ||
|
|
e2c0b6b87a | ||
|
|
5cd7e1eb30 | ||
|
|
53d179dd00 | ||
|
|
eb5056b5f2 | ||
|
|
ba73f8395a | ||
|
|
5dad35de82 | ||
|
|
daa34582e9 | ||
|
|
9a3f15f3e2 | ||
|
|
94cbc7f7c0 | ||
|
|
f19668ca3a |
125
(红头)windows上唯一启动说明.md
Normal file
125
(红头)windows上唯一启动说明.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Windows 上唯一启动说明
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 目标 | 在 Windows 上一致地完成依赖、配置、开发与生产启动 |
|
||||
| 版本号 | v1.0.0 |
|
||||
| 责任人 | 项目组 |
|
||||
| 最后更新日期 | 2026-05-02 |
|
||||
|
||||
**变更记录**
|
||||
|
||||
| 日期 | 修改人 | 修改内容 |
|
||||
|------|--------|----------|
|
||||
| 2026-05-02 | 项目组 | 合并原多份 Windows 批处理与部署文档为本文 + 根目录 `windows启动.bat` |
|
||||
|
||||
**关联资源**
|
||||
|
||||
- 代码库根目录:`windows启动.bat`、`run_dev.py`、`run_production.py`
|
||||
- 环境示例:`.env.example`
|
||||
|
||||
---
|
||||
|
||||
## 1. 唯一入口(推荐)
|
||||
|
||||
在资源管理器中进入项目根目录(含 `requirements.txt` 的目录),**双击 `windows启动.bat`**,按提示选择:
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| 1 | 开发服务器:`run_dev.py`,默认 **http://127.0.0.1:5002**,调试与代码重载开启 |
|
||||
| 2 | 生产服务器:Waitress(`run_production.py`),默认 **http://0.0.0.0:5000**,可用环境变量 `PORT` 覆盖 |
|
||||
| 3 | 退出 |
|
||||
|
||||
若已创建虚拟环境 `.venv`,脚本会自动 `call .venv\Scripts\activate.bat` 再启动。
|
||||
|
||||
---
|
||||
|
||||
## 2. 前置条件
|
||||
|
||||
1. **Python**:建议 3.12,已安装 `pip`。
|
||||
2. **虚拟环境(推荐)**
|
||||
|
||||
```bat
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate.bat
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **配置**:复制 `.env.example` 为 `.env`,填写 `SECRET_KEY`、`DATABASE_URL`、`LLM_API_KEY`、`WX_APPID`、`WX_SECRET` 等(与示例注释一致)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 命令行等价写法(不双击 bat 时)
|
||||
|
||||
在项目根目录、已激活虚拟环境的前提下:
|
||||
|
||||
```bat
|
||||
set PYTHONPATH=%cd%
|
||||
python run_dev.py
|
||||
```
|
||||
|
||||
生产(Waitress,默认端口 5000):
|
||||
|
||||
```bat
|
||||
set FLASK_ENV=production
|
||||
set PYTHONPATH=%cd%
|
||||
python run_production.py
|
||||
```
|
||||
|
||||
端口:可在 `.env` 中设置 `PORT=xxxx`(`create_app` 会加载 dotenv);**`run_dev.py` 内端口固定为 5002**,不受 `PORT` 影响。
|
||||
|
||||
---
|
||||
|
||||
## 4. 可选:安装为 Windows 服务(无单独 bat)
|
||||
|
||||
需 **管理员** 命令提示符,项目根目录、已安装依赖:
|
||||
|
||||
```bat
|
||||
cd /d D:\你的路径\aitsc
|
||||
set PYTHONPATH=%cd%
|
||||
python flask_prompt_master_service.py install
|
||||
python flask_prompt_master_service.py start
|
||||
```
|
||||
|
||||
停止 / 卸载:`stop`、`remove`。服务内当前监听 **5000**(见 `flask_prompt_master_service.py`)。
|
||||
|
||||
**简化版(任务计划程序)**:`python simple_windows_service.py install`(详见该脚本内说明)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 监控与日志(无单独 bat)
|
||||
|
||||
```bat
|
||||
.venv\Scripts\activate.bat
|
||||
python monitor_manager.py
|
||||
```
|
||||
|
||||
更多子命令见 `docs/development/监控和日志系统使用指南.md`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 防火墙(按需)
|
||||
|
||||
PowerShell(管理员),示例放行 5002(开发)与 5000(生产/服务):
|
||||
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "aitsc-dev-5002" -Direction Inbound -LocalPort 5002 -Protocol TCP -Action Allow
|
||||
New-NetFirewallRule -DisplayName "aitsc-prod-5000" -Direction Inbound -LocalPort 5000 -Protocol TCP -Action Allow
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 常见问题
|
||||
|
||||
| 现象 | 处理 |
|
||||
|------|------|
|
||||
| 提示无模块 | 确认在项目根目录执行,且已 `set PYTHONPATH=%cd%` 或使用 `windows启动.bat` |
|
||||
| 端口被占用 | 开发改代码中端口或结束占用进程;生产设置 `PORT` 或改 Waitress 默认 |
|
||||
| `pywin32` 安装失败 | `pip install pywin32==306` 后重试;安装 Windows 服务必须能导入 `win32serviceutil` |
|
||||
| 服务起不来 | 查看 `logs\service.log` |
|
||||
|
||||
---
|
||||
|
||||
## 8. Linux / 服务器
|
||||
|
||||
Windows 以外环境不在本文范围;生产常用 Gunicorn 等见仓库内 `gunicorn.conf.py`、`docker-compose*.yml` 及 `docs/deployment/`。
|
||||
45
(红头)windows启动.bat
Normal file
45
(红头)windows启动.bat
Normal file
@@ -0,0 +1,45 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
cd /d "%~dp0"
|
||||
|
||||
set PYTHONPATH=%cd%
|
||||
|
||||
if exist ".venv\Scripts\activate.bat" (
|
||||
call .venv\Scripts\activate.bat
|
||||
)
|
||||
|
||||
:menu
|
||||
echo.
|
||||
echo ============================================================
|
||||
echo aitsc - Windows 启动(唯一脚本)
|
||||
echo ============================================================
|
||||
echo [1] 开发 run_dev.py http://127.0.0.1:5002
|
||||
echo [2] 生产 Waitress 默认端口 5000 ^(可用环境变量 PORT^)
|
||||
echo [3] 退出
|
||||
echo ============================================================
|
||||
set /p _choice=请选择 (1-3):
|
||||
|
||||
if "%_choice%"=="1" goto run_dev
|
||||
if "%_choice%"=="2" goto run_prod
|
||||
if "%_choice%"=="3" goto eof
|
||||
|
||||
echo 无效选择,请重新输入。
|
||||
goto menu
|
||||
|
||||
:run_dev
|
||||
set FLASK_ENV=development
|
||||
python run_dev.py
|
||||
goto after_run
|
||||
|
||||
:run_prod
|
||||
set FLASK_ENV=production
|
||||
python run_production.py
|
||||
goto after_run
|
||||
|
||||
:after_run
|
||||
echo.
|
||||
echo 进程已结束。
|
||||
pause
|
||||
|
||||
:eof
|
||||
exit /b 0
|
||||
803
(红头)项目分析文档.md
Normal file
803
(红头)项目分析文档.md
Normal file
@@ -0,0 +1,803 @@
|
||||
# 提示词大师 (aitsc) 项目分析文档
|
||||
|
||||
> 分析日期:2026-05-02 | 版本:v1.0.0 | 工作目录:`D:\aaa\aitsc`
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [项目概述](#1-项目概述)
|
||||
2. [技术架构](#2-技术架构)
|
||||
3. [目录结构详解](#3-目录结构详解)
|
||||
4. [核心模块分析](#4-核心模块分析)
|
||||
5. [数据模型设计](#5-数据模型设计)
|
||||
6. [API 路由体系](#6-api-路由体系)
|
||||
7. [配置系统](#7-配置系统)
|
||||
8. [部署架构](#8-部署架构)
|
||||
9. [Vue 前端重构](#9-vue-前端重构)
|
||||
10. [安全问题诊断](#10-安全问题诊断)
|
||||
11. [技术债务清单](#11-技术债务清单)
|
||||
12. [改进建议](#12-改进建议)
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
### 1.1 项目定位
|
||||
|
||||
**提示词大师 (aitsc / flask_prompt_master)** 是一个基于大语言模型 (LLM) 的智能提示词生成与多场景 AI 应用平台,支持 Web 端和微信小程序端。
|
||||
|
||||
### 1.2 核心能力
|
||||
|
||||
- 调用 DeepSeek API 将用户的简短需求转化为高质量、结构化的专业提示词
|
||||
- 提供 15 个垂直 AI 应用场景(饭菜规划、古诗词解析、周报生成、简历优化等)
|
||||
- 支持意图识别 + 专家模板的两阶段提示词优化
|
||||
- Web 用户认证 + 微信小程序登录双通道
|
||||
- Flask-Admin 管理后台(用户管理、数据分析、系统监控)
|
||||
|
||||
### 1.3 版本历史
|
||||
|
||||
| 版本 | 日期 | 主要变更 |
|
||||
|------|------|----------|
|
||||
| 初始版本 | 早期 | 基础提示词生成 |
|
||||
| v1.0.0 | 2026-02 | 完整功能集、配置系统重构、Vue 前端试点 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 技术架构
|
||||
|
||||
### 2.1 技术栈全景
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 前端层 │
|
||||
│ ┌──────────────────┐ ┌──────────────────────────┐ │
|
||||
│ │ 传统 SSR 前端 │ │ Vue 3 SPA (vue-app/) │ │
|
||||
│ │ Bootstrap 5 + │ │ TypeScript + Vite + │ │
|
||||
│ │ jQuery + Jinja2 │ │ Element Plus + Pinia │ │
|
||||
│ └────────┬─────────┘ └───────────┬──────────────┘ │
|
||||
│ │ │ │
|
||||
│ └────────┬───────────────┘ │
|
||||
│ │ HTTP/HTTPS │
|
||||
├────────────────────┼────────────────────────────────┤
|
||||
│ │ Nginx 反向代理 │
|
||||
├────────────────────┼────────────────────────────────┤
|
||||
│ 后端层 │
|
||||
│ ┌─────────────────────────────────────────────┐ │
|
||||
│ │ Flask 应用 (Python 3.12) │ │
|
||||
│ │ ├── 路由层: 20+ 蓝图 │ │
|
||||
│ │ ├── 服务层: Auth/MS/Poetry/PromptQuality │ │
|
||||
│ │ ├── 数据层: SQLAlchemy ORM + Flask-Migrate │ │
|
||||
│ │ └── 管理后台: Flask-Admin + Flask-Login │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │ │ │
|
||||
├───────────┼────────────────────┼─────────────────────┤
|
||||
│ 数据层 AI 服务层 │
|
||||
│ ┌───────┴──────┐ ┌─────────┴─────────┐ │
|
||||
│ │ MySQL 8.0 │ │ DeepSeek API │ │
|
||||
│ │ Redis 7 │ │ (OpenAI 兼容) │ │
|
||||
│ └──────────────┘ └───────────────────┘ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 关键技术指标
|
||||
|
||||
| 维度 | 详情 |
|
||||
|------|------|
|
||||
| 后端框架 | Python Flask >= 2.2.0 |
|
||||
| ORM | Flask-SQLAlchemy + Flask-Migrate |
|
||||
| 数据库 | MySQL 8.0 + Redis 7 |
|
||||
| AI 模型 | DeepSeek Chat (OpenAI 兼容 API) |
|
||||
| AI 超时 | 60 秒,含 3 次指数退避重试 |
|
||||
| 生产服务器 | Waitress (Windows) / Gunicorn (Linux) |
|
||||
| 容器化 | Docker Compose 4 服务编排 |
|
||||
| 传统前端 | Bootstrap 5.1 + jQuery 3.6 (CDN) |
|
||||
| 新前端 | Vue 3.5 + TypeScript 5.9 + Vite 8 + Element Plus 2.13 |
|
||||
|
||||
### 2.3 两个应用入口文件的关系
|
||||
|
||||
项目存在两个 Flask app 实例:
|
||||
|
||||
| 文件 | 用途 | 状态 |
|
||||
|------|------|------|
|
||||
| `src/flask_prompt_master/app.py` | 独立模拟模板 API 服务 (端口 5000) | **弃用/遗留** - Mock 数据,与主应用无关 |
|
||||
| `src/flask_prompt_master/__init__.py` | 主应用工厂 `create_app()` | **活跃** - 所有蓝图注册、扩展初始化 |
|
||||
|
||||
`run_dev.py` 和 `run_production.py` 均使用 `create_app()` 工厂函数。
|
||||
|
||||
---
|
||||
|
||||
## 3. 目录结构详解
|
||||
|
||||
```
|
||||
D:\aaa\aitsc/
|
||||
├── run_dev.py # 开发入口 (Flask dev server, 端口 5002)
|
||||
├── run_production.py # 生产入口 (Waitress WSGI)
|
||||
├── config.py # 旧版配置 (已弃用, 重定向)
|
||||
│
|
||||
├── config/ # 新版多环境配置系统
|
||||
│ ├── __init__.py # 配置工厂 get_config()
|
||||
│ ├── base.py # 基础配置类 (公共项)
|
||||
│ ├── development.py # 开发环境配置
|
||||
│ ├── production.py # 生产环境配置
|
||||
│ ├── testing.py # 测试环境配置
|
||||
│ └── local.py # 本地环境配置
|
||||
│
|
||||
├── src/flask_prompt_master/ # ★ Flask 应用核心
|
||||
│ ├── __init__.py # 应用工厂 create_app() + 蓝图注册
|
||||
│ ├── app.py # 遗留 Mock API (独立 app, 无关)
|
||||
│ ├── config.py # 旧版配置引用
|
||||
│ ├── user_context.py # 当前用户标识获取
|
||||
│ ├── promptsTemplates.py # 4214 行模板数据初始化脚本
|
||||
│ │
|
||||
│ ├── routes/ # ★ 路由层 (20+ 蓝图)
|
||||
│ │ ├── __init__.py # 包导出
|
||||
│ │ ├── routes.py # 主路由 (1467 行, 核心生成/模板/微信)
|
||||
│ │ ├── auth.py # 用户认证 (登录/注册/资料/密码)
|
||||
│ │ ├── favorites.py # 收藏管理
|
||||
│ │ ├── history_routes.py # 历史记录 (484 行)
|
||||
│ │ ├── meal_planning.py # 饭菜规划
|
||||
│ │ ├── poetry.py # 古诗词解析
|
||||
│ │ ├── weekly_report.py # 智能周报生成
|
||||
│ │ ├── travel_planning.py # 旅行攻略规划
|
||||
│ │ ├── meeting_minutes.py # 会议纪要整理
|
||||
│ │ ├── resume_optimization.py # 简历/求职信优化
|
||||
│ │ ├── prompt_optimization.py # 提示词优化 (通用)
|
||||
│ │ ├── smart_prompt_optimization.py # 智能提示词优化
|
||||
│ │ ├── expert_generate_2.py # 专家模式 2 号
|
||||
│ │ ├── expert_generate_3.py # 专家模式 3 号
|
||||
│ │ ├── android_tools.py # Android 工程师专区
|
||||
│ │ ├── prompt_quality_routes.py # 提示词质量评价
|
||||
│ │ ├── comparison_routes.py # 对比评价
|
||||
│ │ ├── evaluation_routes.py # 历史评价
|
||||
│ │ ├── placeholder_apps.py # 占位应用
|
||||
│ │ └── prompts.py # 简短路由
|
||||
│ │
|
||||
│ ├── models/ # ★ 数据模型层
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── models.py # 核心模型 (User/WxUser/Prompt/MealPlan/等)
|
||||
│ │ ├── favorites.py # 收藏模型
|
||||
│ │ ├── poetry_favorites.py # 古诗词收藏模型
|
||||
│ │ ├── history_models.py # 历史记录模型 (PromptHistory/HistoryTag/UserStatistics)
|
||||
│ │ ├── optimization_history.py # 优化历史模型
|
||||
│ │ ├── prompt_quality_models.py # 提示词质量评价记录
|
||||
│ │ └── evaluation_models.py # 评价对比模型
|
||||
│ │
|
||||
│ ├── services/ # ★ 服务层
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── auth_service.py # 用户认证服务 (注册/登录/密码)
|
||||
│ │ ├── favorite_service.py # 收藏管理服务
|
||||
│ │ ├── poetry_favorite_service.py # 古诗词收藏服务
|
||||
│ │ └── prompt_quality_service.py # 提示词质量评价 AI 调用
|
||||
│ │
|
||||
│ ├── admin/ # Flask-Admin 管理后台
|
||||
│ │ ├── __init__.py # init_admin() + 视图注册
|
||||
│ │ ├── models/admin_user.py # 管理员模型
|
||||
│ │ ├── forms/admin_forms.py # 管理员表单
|
||||
│ │ └── views/ # 10 个管理视图
|
||||
│ │ ├── user_admin.py
|
||||
│ │ ├── prompt_admin.py
|
||||
│ │ ├── template_admin.py
|
||||
│ │ ├── system_admin.py
|
||||
│ │ ├── analytics_admin.py
|
||||
│ │ ├── batch_admin.py
|
||||
│ │ ├── monitor_admin.py
|
||||
│ │ ├── report_admin.py
|
||||
│ │ ├── backup_admin.py
|
||||
│ │ └── api_admin.py
|
||||
│ │
|
||||
│ ├── templates/ # Jinja2 模板 (SSR 页面)
|
||||
│ ├── static/ # 静态资源
|
||||
│ ├── forms/ # WTForms 表单
|
||||
│ └── utils/ # 工具函数
|
||||
│
|
||||
├── flask_prompt_master/ # 旧版路由 (遗留)
|
||||
│ └── routes.py # 1176 行旧路由文件
|
||||
│
|
||||
├── vue-app/ # ★ Vue 3 新前端
|
||||
│ ├── src/
|
||||
│ │ ├── main.ts # 应用入口
|
||||
│ │ ├── App.vue # 根组件
|
||||
│ │ ├── router/index.ts # Vue Router (14 路由)
|
||||
│ │ ├── api/ # API 接口层
|
||||
│ │ │ ├── client.ts # Axios 实例 (withCredentials)
|
||||
│ │ │ ├── index.ts # Axios 实例 (dev 直连)
|
||||
│ │ │ ├── modules/ # 14 个 API 模块
|
||||
│ │ │ │ ├── auth.ts, prompt.ts, favorite.ts, history.ts,
|
||||
│ │ │ │ ├── meal.ts, poetry.ts, optimization.ts, resume.ts,
|
||||
│ │ │ │ ├── android.ts, wx.ts, profile.ts, promptQuality.ts,
|
||||
│ │ │ │ ├── comparison.ts, evaluation.ts
|
||||
│ │ │ └── types/ # TypeScript 类型定义
|
||||
│ │ ├── stores/auth.ts # Pinia 认证状态
|
||||
│ │ ├── views/ # 15+ 页面组件
|
||||
│ │ │ ├── HomeView.vue, GenerateView.vue, FavoritesView.vue,
|
||||
│ │ │ ├── HistoryView.vue, MealPlanningView.vue, PoetryView.vue,
|
||||
│ │ │ ├── OptimizationView.vue, AndroidToolsView.vue,
|
||||
│ │ │ ├── ResumeOptimizationView.vue, PromptQualityView.vue,
|
||||
│ │ │ ├── ComparisonEvaluationView.vue, EvaluationHistoryView.vue,
|
||||
│ │ │ ├── ProfileView.vue, Auth/ (LoginView, RegisterView),
|
||||
│ │ │ └── Admin/
|
||||
│ │ ├── components/ # 组件 (common/forms/ui/layout)
|
||||
│ │ ├── layouts/ # DefaultLayout.vue, AuthLayout.vue
|
||||
│ │ └── composables/ # 组合式函数
|
||||
│ ├── vite.config.ts # Vite 代理配置
|
||||
│ └── package.json
|
||||
│
|
||||
├── wechat-miniprogram/ # 微信小程序
|
||||
├── docker/ # Docker 配置 (Nginx/MySQL/SSL)
|
||||
├── k8s/ # Kubernetes 部署
|
||||
├── migrations/ # Flask-Migrate 迁移脚本
|
||||
├── scripts/ # 运维脚本
|
||||
├── docs/ # 文档
|
||||
├── logs/ # 日志输出
|
||||
├── uploads/ # 文件上传
|
||||
├── backups/ # 备份
|
||||
└── docker-compose.yml # 4 服务 Docker 编排
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 核心模块分析
|
||||
|
||||
### 4.1 模块全景图
|
||||
|
||||
项目包含 15 个功能模块,按重要性分为三个层级:
|
||||
|
||||
| 层级 | 模块 | 蓝图/Bp | 路由文件 | 代码规模 |
|
||||
|------|------|---------|----------|----------|
|
||||
| **P0 核心** | 提示词生成 (主) | main | routes.py | 1467 行 |
|
||||
| **P0 核心** | 用户认证 | auth | auth.py | 209 行 |
|
||||
| **P0 核心** | 历史记录 | history | history_routes.py | 484 行 |
|
||||
| **P0 核心** | 收藏管理 | favorites | favorites.py | 97 行 |
|
||||
| **P1 垂直** | 饭菜规划 | meal_planning | meal_planning.py | |
|
||||
| **P1 垂直** | 古诗词解析 | poetry | poetry.py | |
|
||||
| **P1 垂直** | 周报生成 | weekly_report | weekly_report.py | |
|
||||
| **P1 垂直** | 旅行攻略 | travel_planning | travel_planning.py | |
|
||||
| **P1 垂直** | 会议纪要 | meeting_minutes | meeting_minutes.py | |
|
||||
| **P1 垂直** | 简历优化 | resume_optimization | resume_optimization.py | |
|
||||
| **P1 垂直** | 提示词质量评价 | prompt_quality | prompt_quality_routes.py | 198 行 |
|
||||
| **P1 垂直** | 对比评价 | comparison | comparison_routes.py | |
|
||||
| **P2 辅助** | 专家模式 2 号 | expert_generate_2 | expert_generate_2.py | |
|
||||
| **P2 辅助** | 专家模式 3 号 | expert_generate_3 | expert_generate_3.py | |
|
||||
| **P2 辅助** | Android 工具 | android_tools | android_tools.py | |
|
||||
| **P2 辅助** | 占位应用 | placeholder | placeholder_apps.py | |
|
||||
|
||||
### 4.2 提示词生成流程 (核心链路)
|
||||
|
||||
```
|
||||
用户输入描述
|
||||
│
|
||||
▼
|
||||
选择模板 (可选) ──→ get_system_prompt(template_id)
|
||||
│ │
|
||||
│ ├─ 有 TemplateID → PromptTemplate.query.get(id)
|
||||
│ ├─ 无 → 查找 is_default=True 模板
|
||||
│ └─ 无默认 → 硬编码默认系统提示词
|
||||
│
|
||||
▼
|
||||
generate_with_llm(input_text, template_id, max_retries=3)
|
||||
│
|
||||
├─ 构建 messages: [system_prompt, user_input]
|
||||
├─ 调用 OpenAI client (DeepSeek, model=deepseek-chat)
|
||||
├─ 超时: 60s, 温度: 0.7, max_tokens: 500
|
||||
├─ 失败 → 指数退避重试 (2^attempt 秒)
|
||||
│
|
||||
▼
|
||||
save_to_history() → PromptHistory 表
|
||||
│
|
||||
▼
|
||||
保存 Prompt 表 → UserStatistics 更新
|
||||
│
|
||||
▼
|
||||
返回 generated_text (Markdown)
|
||||
```
|
||||
|
||||
### 4.3 LLM 调用参数汇总
|
||||
|
||||
| 场景 | temperature | max_tokens | timeout | 重试 |
|
||||
|------|------------|------------|---------|------|
|
||||
| 通用提示词生成 | 0.7 | 500 | 60s | 3 次 |
|
||||
| 意图识别 | 0.1 | - | - | - |
|
||||
| 专家提示词生成 | 0.7 | 2000 | - | - |
|
||||
| 提示词质量评价 | 0.35 | 4096 | 120s | - |
|
||||
| 饭菜规划 | 0.7 | - | - | - |
|
||||
| 古诗词解析 | 0.7 | - | - | - |
|
||||
|
||||
### 4.4 微信小程序集成架构
|
||||
|
||||
```
|
||||
微信小程序端
|
||||
│
|
||||
├── wx.login() → code
|
||||
│ │
|
||||
│ ▼
|
||||
│ POST /api/wx/login { code }
|
||||
│ │
|
||||
│ ├── 请求微信 jscode2session API
|
||||
│ ├── 获取 openid + session_key
|
||||
│ ├── 查找/创建 WxUser 记录
|
||||
│ └── 返回 token + uid
|
||||
│
|
||||
├── POST /api/wx/generate { uid, input_text, template_id? }
|
||||
│
|
||||
├── GET /api/wx/templates
|
||||
├── GET /api/wx/template/<id>
|
||||
├── GET /api/wx/prompts?uid=xxx
|
||||
└── POST /api/wx/update_userinfo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据模型设计
|
||||
|
||||
### 5.1 核心模型 ER 关系
|
||||
|
||||
```
|
||||
User (uid PK)
|
||||
├── prompts ────────── Prompt (id PK, user_id FK, wx_user_id FK)
|
||||
│ └── feedbacks ─── Feedback (id PK, user_id FK, prompt_id FK)
|
||||
├── feedbacks ──────── Feedback
|
||||
├── meal_plans ─────── MealPlan
|
||||
├── weekly_reports ─── WeeklyReport
|
||||
├── travel_plans ───── TravelPlan
|
||||
├── meeting_minutes ─── MeetingMinutes
|
||||
└── resume_optimizations ── ResumeOptimization
|
||||
|
||||
WxUser (id PK)
|
||||
├── prompts ────────── Prompt
|
||||
├── feedbacks ──────── Feedback
|
||||
└── meal_plans ─────── MealPlan
|
||||
|
||||
PromptTemplate (id PK)
|
||||
└── (独立, 无外键关联)
|
||||
|
||||
PromptHistory (id PK)
|
||||
└── history_tags ──── HistoryTag (history_id FK)
|
||||
|
||||
PromptQualityRecord (id PK)
|
||||
└── (独立评价记录)
|
||||
```
|
||||
|
||||
### 5.2 模型列表与用途
|
||||
|
||||
| 模型 | 表名 | 用途 | 来源文件 |
|
||||
|------|------|------|----------|
|
||||
| User | user | Web 用户 (登录名/密码/昵称/邮箱/手机) | models.py |
|
||||
| WxUser | wx_user | 微信用户 (openid/session_key/unionid) | models.py |
|
||||
| Prompt | prompt | 提示词生成记录 (输入/输出) | models.py |
|
||||
| Feedback | feedback | 对 Prompt 的评分/评论 | models.py |
|
||||
| PromptTemplate | prompt_template | 提示词模板 (分类/行业/系统提示词) | models.py |
|
||||
| MealPlan | meal_plan | 饭菜规划参数与结果 | models.py |
|
||||
| WeeklyReport | weekly_report | 智能周报/日报 | models.py |
|
||||
| TravelPlan | travel_plan | 旅行攻略 | models.py |
|
||||
| MeetingMinutes | meeting_minutes | 会议纪要 | models.py |
|
||||
| ResumeOptimization | resume_optimization | 简历/求职信优化 | models.py |
|
||||
| Favorite | favorite | 通用收藏 | favorites.py |
|
||||
| PoetryFavorite | poetry_favorite | 古诗词收藏 | poetry_favorites.py |
|
||||
| PromptHistory | prompt_history | 提示词历史 (含标签/评分/收藏) | history_models.py |
|
||||
| HistoryTag | history_tags | 历史记录标签 | history_models.py |
|
||||
| UserStatistics | user_statistics | 用户使用统计 | history_models.py |
|
||||
| PromptQualityRecord | prompt_quality_record | 提示词质量评价记录 | prompt_quality_models.py |
|
||||
| AdminUser | admin_user | 管理员 (用户名/密码/角色) | admin/models/ |
|
||||
|
||||
### 5.3 密码安全
|
||||
|
||||
`User.login_pwd` + `User.login_salt` 使用混合哈希策略:
|
||||
|
||||
- **新用户**: `login_salt = 'bcrypt'`, 使用 `bcrypt.hashpw()` 哈希密码
|
||||
- **旧用户**: `login_salt = MD5 随机值`, 使用 `MD5(password + salt)` (高危)
|
||||
|
||||
`WxUser` 使用 `MD5(openid + timestamp)` 生成登录 token。
|
||||
|
||||
---
|
||||
|
||||
## 6. API 路由体系
|
||||
|
||||
### 6.1 路由组织架构
|
||||
|
||||
应用通过 `create_app()` 注册以下蓝图:
|
||||
|
||||
| 蓝图名称 | URL 前缀 | 主要功能 |
|
||||
|---------|---------|---------|
|
||||
| main | `/` | 主页生成、模板 API、微信 API、特色功能 |
|
||||
| auth | `/` | 注册/登录/登出/资料/密码 |
|
||||
| favorites | `/` | 收藏 CRUD |
|
||||
| history | `/` | 历史记录查询/更新/删除/统计 |
|
||||
| meal_planning | `/meal-planning` | 饭菜规划页面+API |
|
||||
| poetry | `/poetry` | 古诗词解析页面+API |
|
||||
| weekly_report | `/` | 周报生成 |
|
||||
| travel_planning | `/` | 旅行攻略 |
|
||||
| meeting_minutes | `/` | 会议纪要 |
|
||||
| resume_optimization | `/` | 简历优化 |
|
||||
| prompt_quality | `/` | 提示词质量评价 |
|
||||
| prompt_optimization | `/` | 通用提示词优化 |
|
||||
| smart_prompt_optimization | `/` | 智能提示词优化 |
|
||||
| expert_generate_2 | `/` | 专家模式 2 |
|
||||
| expert_generate_3 | `/` | 专家模式 3 |
|
||||
| android_tools | `/` | Android 工具区 |
|
||||
| comparison | `/` | 对比评价 |
|
||||
| evaluation | `/` | 历史评价 |
|
||||
| placeholder | `/` | 占位应用 |
|
||||
|
||||
### 6.2 关键 API 端点
|
||||
|
||||
#### 认证类
|
||||
```
|
||||
POST /api/register # 注册 (JSON body)
|
||||
POST /api/login # 登录 (JSON body, session 维持)
|
||||
POST /api/logout # 登出
|
||||
GET /api/check-login # 检查登录状态
|
||||
GET /api/profile # 获取个人资料 (需登录)
|
||||
PUT /api/profile # 更新个人资料 (需登录)
|
||||
POST /api/change-password # 修改密码 (需登录)
|
||||
GET /api/profile/stats # 用户统计 (需登录)
|
||||
```
|
||||
|
||||
#### 生成类
|
||||
```
|
||||
GET / # 主生成页面 (SSR, GET/POST)
|
||||
GET /api/generate/meta # 获取筛选维度元数据 (Vue 用)
|
||||
POST /api/prompt/generate # 生成提示词 (JSON API, Vue 用)
|
||||
GET /api/templates/<category> # 按分类获取模板列表
|
||||
GET /api/template/<id> # 获取单个模板详情
|
||||
DELETE /api/templates/<id> # 删除模板
|
||||
```
|
||||
|
||||
#### 微信类
|
||||
```
|
||||
POST /api/wx/login # 微信登录 (code → openid+token)
|
||||
POST /api/wx/generate # 微信端生成提示词
|
||||
GET /api/wx/templates # 微信端模板列表
|
||||
GET /api/wx/template/<id> # 微信端模板详情
|
||||
GET /api/wx/prompts # 微信端用户历史
|
||||
POST /api/wx/update_userinfo # 更新微信用户信息
|
||||
```
|
||||
|
||||
#### 收藏/历史类
|
||||
```
|
||||
GET/POST /api/favorites # 收藏列表/添加
|
||||
DELETE /api/favorites/<id> # 删除收藏
|
||||
POST /api/favorites/quick-add # 快速收藏
|
||||
GET /api/history # 历史记录列表
|
||||
DELETE /api/history/<id> # 删除历史记录
|
||||
PUT /api/history/<id> # 更新历史记录
|
||||
```
|
||||
|
||||
#### 垂直应用类
|
||||
```
|
||||
/meal-planning/* # 饭菜规划
|
||||
/poetry/* # 古诗词解析
|
||||
/api/weekly-report/* # 周报
|
||||
/api/travel-planning/* # 旅行
|
||||
/api/meeting-minutes/* # 会议纪要
|
||||
/api/resume-optimization/* # 简历
|
||||
```
|
||||
|
||||
### 6.3 响应格式混用
|
||||
|
||||
项目中存在两种 API 响应格式,未统一:
|
||||
|
||||
**格式 A** (主路由, 传统 SSR):
|
||||
```json
|
||||
{ "success": true, "data": {...}, "message": "" }
|
||||
```
|
||||
|
||||
**格式 B** (微信路由, app.py):
|
||||
```json
|
||||
{ "code": 200, "message": "success", "data": {...} }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 配置系统
|
||||
|
||||
### 7.1 配置架构
|
||||
|
||||
```
|
||||
┌────────────────────────────────────┐
|
||||
│ .env 文件 (dotenv) │
|
||||
│ SECRET_KEY, DATABASE_URL, │
|
||||
│ LLM_API_KEY, WX_APPID, etc. │
|
||||
└──────────────┬─────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────┐
|
||||
│ config/__init__.py │
|
||||
│ get_config() → 按 FLASK_ENV │
|
||||
│ 返回对应配置类 │
|
||||
├────────────────────────────────────┤
|
||||
│ base.py 基础配置 (所有环境) │
|
||||
│ development 开发环境 (DEBUG=True) │
|
||||
│ production 生产环境 │
|
||||
│ testing 测试环境 │
|
||||
│ local 本地环境 │
|
||||
└────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 7.2 关键配置项
|
||||
|
||||
| 配置项 | 来源 | 生产默认值 |
|
||||
|--------|------|-----------|
|
||||
| SECRET_KEY | 环境变量 | 无默认 (必填) |
|
||||
| DATABASE_URL | 环境变量 | 无默认 (必填) |
|
||||
| TENCENT_DATABASE_URL | 环境变量 | 可选 |
|
||||
| LLM_API_URL | 环境变量 | `https://api.deepseek.com/v1` |
|
||||
| LLM_API_KEY | 环境变量 | 无默认 |
|
||||
| WX_APPID / WX_SECRET | 环境变量 | 无默认 |
|
||||
| CORS_ORIGINS | 环境变量 | `*` (开发) |
|
||||
| SESSION_LIFETIME_HOURS | 环境变量 | 24 |
|
||||
| CACHE_TYPE | 环境变量 | simple |
|
||||
|
||||
### 7.3 遗留配置
|
||||
|
||||
`config.py` (根目录) 已被弃用,通过 `DeprecationWarning` 重定向到 `config/` 系统,但保留向后兼容。
|
||||
|
||||
---
|
||||
|
||||
## 8. 部署架构
|
||||
|
||||
### 8.1 Docker Compose 编排
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ Nginx │ :80/:443
|
||||
└────┬─────┘
|
||||
│
|
||||
┌────────────────┼────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Flask │ │ MySQL │ │ Redis │
|
||||
│ :5000 │ │ :3306 │ │ :6379 │
|
||||
└─────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
- **app**: Python 3.9-slim 镜像, Gunicorn + run_dev:app
|
||||
- **db**: MySQL 8.0, 数据卷持久化, 初始化脚本 `docker/mysql/init.sql`
|
||||
- **redis**: Redis 7-alpine, AOF 持久化
|
||||
- **nginx**: Nginx alpine, 反向代理 + SSL
|
||||
|
||||
### 8.2 两种运行模式
|
||||
|
||||
| 模式 | 入口 | 服务器 | 端口 |
|
||||
|------|------|--------|------|
|
||||
| 开发 | `run_dev.py` | Flask dev server | 5002 |
|
||||
| 生产 (Windows) | `run_production.py` | Waitress (4 threads) | 5000 |
|
||||
| 生产 (Linux) | `Dockerfile` CMD | Gunicorn (gunicorn.conf.py) | 5000 |
|
||||
|
||||
### 8.3 Windows 特殊说明
|
||||
|
||||
根目录有 `windows启动.bat` 和 `windows上唯一启动说明.md`,说明 Windows 环境下需要特殊处理:
|
||||
- `pywin32>=306` 依赖
|
||||
- 使用 Waitress 替代 Gunicorn
|
||||
- 使用 bash shell (Git Bash/WSL) 运行 shell 脚本
|
||||
|
||||
---
|
||||
|
||||
## 9. Vue 前端重构
|
||||
|
||||
### 9.1 当前进度
|
||||
|
||||
Vue 3 SPA 前端位于 `vue-app/`,与 Flask 后端并行开发,已完成:
|
||||
|
||||
| 页面 | 路由 | 状态 |
|
||||
|------|------|------|
|
||||
| 首页 | `/` | 已完成 |
|
||||
| 提示词生成 | `/generate` | 已完成 (14 种场景 Tab + 筛选 + 模板选择 + AI 生成) |
|
||||
| 收藏管理 | `/favorites` | 已完成 |
|
||||
| 登录/注册 | `/login`, `/register` | 已完成 (Session Cookie 认证) |
|
||||
| 个人资料 | `/profile` | 已完成 (需登录) |
|
||||
| 优化历史 | `/history` | 已完成 |
|
||||
| 饭菜规划 | `/meal-planning` | 已完成 |
|
||||
| 古诗词 | `/poetry` | 已完成 |
|
||||
| 提示词优化 | `/optimization` | 已完成 |
|
||||
| Android 工具 | `/android-tools` | 已完成 |
|
||||
| 简历优化 | `/resume-optimization` | 已完成 |
|
||||
| 提示词质量评价 | `/prompt-quality` | 已完成 |
|
||||
|
||||
### 9.2 代理配置
|
||||
|
||||
Vite 开发服务器 (端口 3000) 通过 `vite.config.ts` 配置代理:
|
||||
```typescript
|
||||
proxy: {
|
||||
'/api': { target: 'http://127.0.0.1:5002' },
|
||||
'/static': { target: 'http://127.0.0.1:5002' },
|
||||
'/poetry': { target: 'http://127.0.0.1:5002' }
|
||||
}
|
||||
```
|
||||
|
||||
Axios 配置 `withCredentials: true` 以携带 Session Cookie,实现同域下的认证状态共享。
|
||||
|
||||
### 9.3 构建产物
|
||||
|
||||
生产构建使用 `npm run build`,产物在 `dist/`,由 Nginx 托管静态文件,API 请求反代到 Flask。
|
||||
|
||||
### 9.4 API 模块覆盖
|
||||
|
||||
Vue 前端已有 14 个 API 模块与后端对接:
|
||||
|
||||
- `auth.ts`, `prompt.ts`, `favorite.ts`, `history.ts`
|
||||
- `meal.ts`, `poetry.ts`, `optimization.ts`, `resume.ts`
|
||||
- `android.ts`, `wx.ts`, `profile.ts`, `promptQuality.ts`
|
||||
- `comparison.ts`, `evaluation.ts`
|
||||
|
||||
---
|
||||
|
||||
## 10. 安全问题诊断
|
||||
|
||||
### 10.1 高危问题
|
||||
|
||||
#### (1) API Key 硬编码
|
||||
|
||||
**位置**: 多个文件中硬编码 DeepSeek API Key
|
||||
|
||||
| 文件 | 行 | 风险 |
|
||||
|------|-----|------|
|
||||
| `src/flask_prompt_master/routes/routes.py` | 22 | `api_key='sk-fdf7cc1c73504e628ec0119b7e11b8cc'` |
|
||||
| `src/flask_prompt_master/routes/expert_generate_2.py` | 22 | 同上 |
|
||||
| `src/flask_prompt_master/routes/expert_generate_3.py` | 22 | 同上 |
|
||||
| `config.py` (旧) | 16 | 注释中仍有硬编码值 |
|
||||
|
||||
**风险**: 如果代码泄露,API Key 会被曝光,可能导致额度耗尽或安全事件。
|
||||
|
||||
#### (2) 密码安全策略不足
|
||||
|
||||
- **旧用户密码**: MD5(password + salt) — MD5 已被证明不安全
|
||||
- **微信 Token**: `hashlib.md5(f'{openid}{int(time.time())}'.encode()).hexdigest()` — 可预测
|
||||
- **User.login_salt**: 明文存储
|
||||
|
||||
### 10.2 中危问题
|
||||
|
||||
#### (3) CORS 配置过于宽松
|
||||
|
||||
`config/development.py:51`:
|
||||
```python
|
||||
CORS_ORIGINS = ['http://localhost:3000', 'http://127.0.0.1:3000', '*']
|
||||
```
|
||||
|
||||
开发环境包含 `*`,生产环境 CORS_ORIGINS 默认值来自 `.env`,若未配置也可能放宽。
|
||||
|
||||
#### (4) WeChat API 日志泄露
|
||||
|
||||
`routes.py:643-647`:
|
||||
```python
|
||||
print("\n=== 微信登录配置 ===")
|
||||
print(f"APPID: {WX_APPID}")
|
||||
print(f"SECRET: {WX_SECRET}") # 在控制台打印密钥
|
||||
print("==================\n")
|
||||
```
|
||||
|
||||
以及在 `log_manager.py`、`monitor_manager.py` 中多处 `print` 包含敏感信息。
|
||||
|
||||
### 10.3 低危问题
|
||||
|
||||
#### (5) 用户上下文默认回退
|
||||
|
||||
`user_context.py:23`:
|
||||
```python
|
||||
return 1 # 未登录时回退到 user_id=1
|
||||
```
|
||||
|
||||
未登录用户生成的内容会被关联到 user_id=1 (admin),可能污染数据统计。
|
||||
|
||||
---
|
||||
|
||||
## 11. 技术债务清单
|
||||
|
||||
### 11.1 代码重复
|
||||
|
||||
| 重复项 | 文件 | 说明 |
|
||||
|--------|------|------|
|
||||
| 专家提示词逻辑 | `expert_generate_2.py` + `expert_generate_3.py` + `smart_prompt_optimization.py` | 3 个独立实现,意图分析提示词和领域专家模板几乎完全一致 (80%+ 重复) |
|
||||
| LLM 客户端创建 | `routes.py:22`, `expert_generate_2.py:21`, `expert_generate_3.py:22`, `prompt_quality_service.py:47` | 每个文件独立创建 `OpenAI(...)` 客户端,且硬编码 key |
|
||||
| 意图分析提示词 | `expert_generate_2.py:27-45` vs `expert_generate_3.py:27-45` | 逐字重复,仅变量名不同 |
|
||||
| Axios 实例 | `api/index.ts` vs `api/client.ts` | 两个不同的 Axios 实例配置,功能重叠 |
|
||||
| 提示词模板数据 | `promptsTemplates.py` (4214 行) | 模板数据硬编码在 Python 文件中,应使用数据库 seed 或 JSON 配置 |
|
||||
|
||||
### 11.2 架构问题
|
||||
|
||||
| 问题 | 影响 |
|
||||
|------|------|
|
||||
| `routes.py` 1467 行单文件 | 难以维护,路由职责混杂 (SSR 页面 + API + 微信 + 模板管理) |
|
||||
| 两个 Flask app 实例 (`__init__.py` + `app.py`) | 新手困惑,app.py 是遗留 Mock 服务 |
|
||||
| 旧版 `flask_prompt_master/routes.py` (1176 行) 与新版并存 | 不确定是否仍在使用 |
|
||||
| 服务层覆盖不全 | 只有 4 个 service 文件,大部分业务逻辑在 route 文件中 |
|
||||
| 错误处理依赖 `print` 和 `flash` | 缺乏统一错误处理中间件 |
|
||||
| 无自动化测试 | 根目录 30+ 手动测试脚本,无 pytest/unittest |
|
||||
|
||||
### 11.3 前端问题
|
||||
|
||||
| 问题 | 说明 |
|
||||
|------|------|
|
||||
| 双前端并行 | SSR (Jinja2) + Vue 3 SPA 同时存在,维护成本高 |
|
||||
| 表单使用 SSR | 主生成页面仍走 `POST /` 表单提交 (非 SPA) |
|
||||
| Element Plus 版本 | 2.13.6 (2025 年版本),非最新 |
|
||||
|
||||
### 11.4 安全问题 (汇总)
|
||||
|
||||
| 等级 | 数量 | 详情 |
|
||||
|------|------|------|
|
||||
| 高危 | 2 | API Key 硬编码、MD5 密码 |
|
||||
| 中危 | 2 | CORS `*`、密钥日志泄露 |
|
||||
| 低危 | 1 | user_context 默认回退 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 改进建议
|
||||
|
||||
### 12.1 安全修复 (优先)
|
||||
|
||||
1. **移除所有硬编码 API Key**: 从环境变量读取,删除 `routes.py:22`、`expert_generate_2.py:22`、`expert_generate_3.py:22` 中的硬编码值
|
||||
2. **迁移旧用户密码到 bcrypt**: 在登录时检测 `login_salt == 'bcrypt'`,若非 bcrypt 则透明升级
|
||||
3. **微信 Token 改用 secrets.token_urlsafe()**: 替换 MD5 方式
|
||||
4. **移除敏感日志**: 删除 WX_SECRET、API Key 等敏感信息的 print 日志
|
||||
|
||||
### 12.2 代码重构 (中期)
|
||||
|
||||
1. **合并专家模式**: 将 3 个 expert_generate 实现合并为一个,通过参数配置差异
|
||||
2. **拆分 routes.py**: 将 SSR 页面路由与 API 路由分离
|
||||
3. **统一响应格式**: 全局定义 `{success, data, message}` 或 `{code, message, data}`,二选一
|
||||
4. **删除遗留代码**: 确认并移除 `flask_prompt_master/routes.py` 和 `src/flask_prompt_master/app.py`
|
||||
|
||||
### 12.3 架构改进 (长期)
|
||||
|
||||
1. **完成 Vue 迁移**: 移除 Jinja2 SSR 模板,全面切换到 Vue 3 SPA
|
||||
2. **服务层完善**: 将 routes 中的业务逻辑抽取到 services (如 LLM 调用、模板管理)
|
||||
3. **统一 LLM 客户端**: 创建 `LLMService` 单例或工厂,所有模块共享
|
||||
4. **引入自动化测试**: 至少覆盖核心 API 路由和认证流程
|
||||
5. **API 版本管理**: 统一 API 路径为 `/api/v1/...`
|
||||
|
||||
### 12.4 立即可执行项
|
||||
|
||||
| 优先级 | 操作 | 预估工作量 |
|
||||
|--------|------|-----------|
|
||||
| P0 | 移除 API Key 硬编码 | 1 小时 |
|
||||
| P0 | 删除敏感 print 日志 | 30 分钟 |
|
||||
| P1 | 配置生产环境 CORS_ORIGINS | 15 分钟 |
|
||||
| P1 | 统一 LLM 客户端创建方式 | 2 小时 |
|
||||
| P2 | 清理遗留 app.py | 30 分钟 |
|
||||
| P2 | 合并 .env 文件 (.env, .env.test, .env.backup, env.example 等 6 个) | 30 分钟 |
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
### A. 文件规模统计
|
||||
|
||||
| 文件 | 行数 | 类型 |
|
||||
|------|------|------|
|
||||
| promptsTemplates.py | 4214 | 模板数据 |
|
||||
| routes.py (新) | 1467 | 主路由 |
|
||||
| routes.py (旧) | 1176 | 旧路由 |
|
||||
| expert_generate_3.py | ~300 | 专家模式 |
|
||||
| expert_generate_2.py | ~280 | 专家模式 |
|
||||
| history_routes.py | 484 | 历史路由 |
|
||||
| auth_service.py | 293 | 认证服务 |
|
||||
|
||||
### B. 依赖清单 (requirements.txt)
|
||||
|
||||
```
|
||||
flask>=2.2.0, flask-cors>=3.0.10, python-dotenv>=1.0.0
|
||||
openai>=1.3.0, flask-sqlalchemy>=3.0.2, flask-migrate>=4.0.4
|
||||
pymysql>=1.1.0, waitress>=3.0.0, redis>=5.0.1
|
||||
pywin32>=306, requests>=2.28.0, psutil>=5.9.0, bcrypt>=4.0.0
|
||||
```
|
||||
|
||||
### C. Git 分支与提交
|
||||
|
||||
- 当前分支: `main`
|
||||
- 最新提交: `5dad35d feat(prompt-quality): 质量评价与对比模块 API、脚本与 Vue 页面`
|
||||
- 总提交数: ~30 次
|
||||
- 活跃期: 2026-02 至 2026-04
|
||||
|
||||
### D. 关联文档
|
||||
|
||||
- `AI项目功能需求文档.md` — 完整功能需求规格
|
||||
- `API_INTERFACE_DOCUMENTATION.md` — API 接口文档
|
||||
- `项目现状评估.md` — 第三方项目评估报告
|
||||
- `vue重构方案.md` — Vue 渐进式迁移方案
|
||||
- `Python项目结构分析与优化报告.md` — 结构分析报告
|
||||
- `windows上唯一启动说明.md` — Windows 启动指南
|
||||
|
||||
---
|
||||
|
||||
*文档结束 — 基于 2026-05-02 代码库静态分析*
|
||||
110
.env.example
Normal file
110
.env.example
Normal file
@@ -0,0 +1,110 @@
|
||||
# ========================================
|
||||
# Flask提示词大师应用环境变量配置示例
|
||||
# ========================================
|
||||
# 复制此文件为 .env 并根据实际情况修改配置
|
||||
# cp env.example .env
|
||||
|
||||
# ========================================
|
||||
# Flask基础配置
|
||||
# ========================================
|
||||
# Flask应用密钥(必需)
|
||||
SECRET_KEY=your-secret-key-here
|
||||
|
||||
# 应用环境(development/production/testing)
|
||||
FLASK_ENV=development
|
||||
|
||||
# 调试模式(开发环境开启,生产环境关闭)
|
||||
DEBUG=True
|
||||
|
||||
# HTTP 端口(可选;仅 Waitress 生产 run_production.py 读取;开发 run_dev.py 固定 5002)
|
||||
# PORT=5000
|
||||
|
||||
# ========================================
|
||||
# 数据库配置
|
||||
# ========================================
|
||||
# 主数据库连接URL(必需)
|
||||
# MySQL示例: mysql+pymysql://username:password@host:port/database_name?charset=utf8mb4
|
||||
DATABASE_URL=mysql+pymysql://username:password@localhost:3306/database_name?charset=utf8mb4
|
||||
|
||||
# 腾讯云数据库连接URL(可选)
|
||||
# TENCENT_DATABASE_URL=mysql+pymysql://username:password@tencent-host:port/database_name?charset=utf8mb4
|
||||
|
||||
# ========================================
|
||||
# OpenAI兼容API配置
|
||||
# ========================================
|
||||
# API基础URL(必需)
|
||||
LLM_API_URL=https://api.deepseek.com/v1
|
||||
|
||||
# API密钥(必需)
|
||||
LLM_API_KEY=sk-your-api-key-here
|
||||
|
||||
# ========================================
|
||||
# 微信小程序配置
|
||||
# ========================================
|
||||
# 小程序AppID(必需)
|
||||
WX_APPID=your-wx-appid-here
|
||||
|
||||
# 小程序Secret(必需)
|
||||
WX_SECRET=your-wx-secret-here
|
||||
|
||||
# ========================================
|
||||
# 跨域配置
|
||||
# ========================================
|
||||
# 允许跨域的域名,多个用逗号分隔
|
||||
# 开发环境: http://localhost:3000,http://127.0.0.1:3000
|
||||
# 生产环境: https://yourdomain.com,https://www.yourdomain.com
|
||||
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
|
||||
|
||||
# ========================================
|
||||
# 日志配置
|
||||
# ========================================
|
||||
# 日志级别(DEBUG/INFO/WARNING/ERROR/CRITICAL)
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# 日志文件路径
|
||||
LOG_FILE=logs/app.log
|
||||
|
||||
# ========================================
|
||||
# 缓存配置
|
||||
# ========================================
|
||||
# 缓存类型(simple/redis/memcached)
|
||||
CACHE_TYPE=simple
|
||||
|
||||
# 缓存默认超时时间(秒)
|
||||
CACHE_DEFAULT_TIMEOUT=300
|
||||
|
||||
# Redis缓存URL(当CACHE_TYPE=redis时使用)
|
||||
# REDIS_URL=redis://localhost:6379/0
|
||||
|
||||
# ========================================
|
||||
# 会话配置
|
||||
# ========================================
|
||||
# 会话生命周期(小时)
|
||||
SESSION_LIFETIME_HOURS=24
|
||||
|
||||
# ========================================
|
||||
# 文件上传配置
|
||||
# ========================================
|
||||
# 最大文件上传大小(字节)
|
||||
MAX_CONTENT_LENGTH=16777216
|
||||
|
||||
# 文件上传目录
|
||||
UPLOAD_FOLDER=uploads
|
||||
|
||||
# ========================================
|
||||
# 安全配置
|
||||
# ========================================
|
||||
# 是否启用CSRF保护
|
||||
WTF_CSRF_ENABLED=True
|
||||
|
||||
# CSRF令牌超时时间(秒)
|
||||
WTF_CSRF_TIME_LIMIT=3600
|
||||
|
||||
# ========================================
|
||||
# 性能配置
|
||||
# ========================================
|
||||
# 数据库连接池大小
|
||||
DB_POOL_SIZE=20
|
||||
|
||||
# 数据库连接池最大溢出连接数
|
||||
DB_MAX_OVERFLOW=30
|
||||
10
.env.test
Normal file
10
.env.test
Normal file
@@ -0,0 +1,10 @@
|
||||
# 测试环境变量文件
|
||||
FLASK_ENV=development
|
||||
SECRET_KEY=test-secret-key-for-development
|
||||
DATABASE_URL=sqlite:///test.db
|
||||
LLM_API_URL=https://api.deepseek.com/v1
|
||||
LLM_API_KEY=sk-test-api-key-for-development
|
||||
WX_APPID=test-wx-appid
|
||||
WX_SECRET=test-wx-secret
|
||||
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
|
||||
LOG_LEVEL=DEBUG
|
||||
44
.gitignore
vendored
44
.gitignore
vendored
@@ -5,3 +5,47 @@ env/
|
||||
ENV/
|
||||
myenv/
|
||||
|
||||
# 环境变量文件
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# 日志文件
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# 缓存文件
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# 数据库文件
|
||||
*.db
|
||||
*.sqlite3
|
||||
|
||||
# 上传文件
|
||||
uploads/
|
||||
|
||||
# 备份文件
|
||||
*.bak
|
||||
*.backup
|
||||
|
||||
# IDE配置文件
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# 操作系统文件
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# 测试覆盖率
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# 构建输出
|
||||
dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# aitsc
|
||||
|
||||
**版本**: v1.0.0
|
||||
|
||||
#### 介绍
|
||||
提示词专家
|
||||
|
||||
@@ -15,7 +17,7 @@
|
||||
|
||||
#### 使用说明
|
||||
|
||||
1. xxxx
|
||||
1. Windows:见 [windows上唯一启动说明.md](windows上唯一启动说明.md),根目录双击 `windows启动.bat`。
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
|
||||
72
config.py
72
config.py
@@ -6,50 +6,68 @@
|
||||
|
||||
# class Config:
|
||||
# SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key'
|
||||
|
||||
|
||||
# # MySQL数据库配置
|
||||
# SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'mysql+pymysql://root:123456@localhost:3306/pro_db?charset=utf8mb4'
|
||||
# SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
|
||||
# # OpenAI兼容API配置
|
||||
# LLM_API_URL = os.environ.get('LLM_API_URL') or 'https://api.deepseek.com/v1'
|
||||
# LLM_API_KEY = os.environ.get('LLM_API_KEY') or 'sk-fdf7cc1c73504e628ec0119b7e11b8cc'
|
||||
|
||||
|
||||
# # 微信小程序配置
|
||||
# WX_APPID = os.environ.get('WX_APPID') or 'wx2c65877d37fc29bf' # 替换为你的小程序 appid
|
||||
# WX_SECRET = os.environ.get('WX_SECRET') or '89aa97dda3c1347c6ae3d6ab4627f1f4' # 替换为你的小程序 secret
|
||||
|
||||
|
||||
# # 添加跨域支持
|
||||
# CORS_ORIGINS = ['*'] # 生产环境建议设置具体域名
|
||||
|
||||
# 此文件已弃用,请使用 config/ 目录下的配置系统
|
||||
# 为了向后兼容,此文件重定向到新的配置系统
|
||||
import os
|
||||
import warnings
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 在配置类定义前加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
# 警告:此文件已弃用,请使用 config/ 目录下的配置系统
|
||||
warnings.warn("config.py 已弃用,请使用新的配置系统 (config/ 目录)", DeprecationWarning)
|
||||
|
||||
class Config:
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key'
|
||||
|
||||
# ---------------------- 原有本地MySQL数据库配置 ----------------------
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'mysql+pymysql://root:123456@localhost:3306/pro_db?charset=utf8mb4'
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False # 关闭SQLAlchemy的修改跟踪(减少性能消耗)
|
||||
|
||||
# ---------------------- 新增:腾讯云数据库配置 ----------------------
|
||||
# 腾讯云数据库连接URI(格式:mysql+pymysql://用户名:密码@数据库地址:端口/数据库名?charset=utf8mb4)
|
||||
# 注意:需先在腾讯云控制台创建目标数据库(如命名为 pro_db_tencent,需替换为你的实际库名)
|
||||
TENCENT_SQLALCHEMY_DATABASE_URI = os.environ.get('TENCENT_DATABASE_URL') or \
|
||||
'mysql+pymysql://root:!Rjb12191@gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com:24936/pro_db?charset=utf8mb4'
|
||||
# 腾讯云数据库同样关闭修改跟踪(与本地配置保持一致)
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY')
|
||||
if not SECRET_KEY:
|
||||
raise ValueError("SECRET_KEY 环境变量未设置,请设置环境变量或使用新的配置系统")
|
||||
|
||||
# ---------------------- 数据库配置 ----------------------
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
|
||||
if not SQLALCHEMY_DATABASE_URI:
|
||||
raise ValueError("DATABASE_URL 环境变量未设置,请设置环境变量或使用新的配置系统")
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
# ---------------------- 腾讯云数据库配置 ----------------------
|
||||
TENCENT_SQLALCHEMY_DATABASE_URI = os.environ.get('TENCENT_DATABASE_URL')
|
||||
TENCENT_SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
# ---------------------- 其他原有配置(保持不变) ----------------------
|
||||
# OpenAI兼容API配置
|
||||
|
||||
# ---------------------- OpenAI兼容API配置 ----------------------
|
||||
LLM_API_URL = os.environ.get('LLM_API_URL') or 'https://api.deepseek.com/v1'
|
||||
LLM_API_KEY = os.environ.get('LLM_API_KEY') or 'sk-fdf7cc1c73504e628ec0119b7e11b8cc'
|
||||
|
||||
# 微信小程序配置
|
||||
WX_APPID = os.environ.get('WX_APPID') or 'wx2c65877d37fc29bf' # 替换为你的小程序 appid
|
||||
WX_SECRET = os.environ.get('WX_SECRET') or '89aa97dda3c1347c6ae3d6ab4627f1f4' # 替换为你的小程序 secret
|
||||
|
||||
# 添加跨域支持
|
||||
CORS_ORIGINS = ['*'] # 生产环境建议设置具体域名(如 ['https://your-domain.com'])
|
||||
LLM_API_KEY = os.environ.get('LLM_API_KEY')
|
||||
if not LLM_API_KEY:
|
||||
raise ValueError("LLM_API_KEY 环境变量未设置,请设置环境变量或使用新的配置系统")
|
||||
|
||||
# ---------------------- 微信小程序配置 ----------------------
|
||||
WX_APPID = os.environ.get('WX_APPID')
|
||||
WX_SECRET = os.environ.get('WX_SECRET')
|
||||
if not WX_APPID or not WX_SECRET:
|
||||
raise ValueError("WX_APPID 和 WX_SECRET 环境变量未设置,请设置环境变量或使用新的配置系统")
|
||||
|
||||
# ---------------------- 跨域配置 ----------------------
|
||||
CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '*').split(',')
|
||||
|
||||
@staticmethod
|
||||
def init_app(app):
|
||||
"""初始化应用配置(为兼容性保留)"""
|
||||
# 创建必要的目录
|
||||
os.makedirs('logs', exist_ok=True)
|
||||
os.makedirs('uploads', exist_ok=True)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -23,14 +23,14 @@ class Config:
|
||||
|
||||
# OpenAI兼容API配置
|
||||
LLM_API_URL = os.environ.get('LLM_API_URL') or 'https://api.deepseek.com/v1'
|
||||
LLM_API_KEY = os.environ.get('LLM_API_KEY') or 'sk-your-api-key-here'
|
||||
LLM_API_KEY = os.environ.get('LLM_API_KEY')
|
||||
|
||||
# 微信小程序配置
|
||||
WX_APPID = os.environ.get('WX_APPID') or 'wx-your-appid-here'
|
||||
WX_SECRET = os.environ.get('WX_SECRET') or 'your-wx-secret-here'
|
||||
|
||||
# 跨域配置
|
||||
CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '*').split(',')
|
||||
WX_APPID = os.environ.get('WX_APPID')
|
||||
WX_SECRET = os.environ.get('WX_SECRET')
|
||||
|
||||
# 跨域配置 - 默认值在具体环境配置中设置
|
||||
CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '').split(',')
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')
|
||||
@@ -79,4 +79,12 @@ class Config:
|
||||
app.logger.addHandler(file_handler)
|
||||
|
||||
app.logger.setLevel(logging.INFO)
|
||||
app.logger.info('应用启动')
|
||||
|
||||
# 获取版本信息
|
||||
try:
|
||||
from src.flask_prompt_master import __version__
|
||||
version_info = f"版本: {__version__}"
|
||||
except ImportError:
|
||||
version_info = "版本: 未知"
|
||||
|
||||
app.logger.info(f'应用启动 - {version_info}')
|
||||
|
||||
@@ -12,6 +12,22 @@ class DevelopmentConfig(Config):
|
||||
super().__init__()
|
||||
if not self.SQLALCHEMY_DATABASE_URI:
|
||||
self.SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
|
||||
|
||||
# 开发环境API配置(如果未设置,使用示例值并给出警告)
|
||||
if not self.LLM_API_KEY:
|
||||
self.LLM_API_KEY = 'sk-dev-example-api-key'
|
||||
import warnings
|
||||
warnings.warn("LLM_API_KEY not set, using development example key")
|
||||
|
||||
if not self.WX_APPID:
|
||||
self.WX_APPID = 'wx-dev-example-appid'
|
||||
|
||||
if not self.WX_SECRET:
|
||||
self.WX_SECRET = 'wx-dev-example-secret'
|
||||
|
||||
# 开发环境CORS配置(如果没有设置,使用开发默认值)
|
||||
if not self.CORS_ORIGINS or self.CORS_ORIGINS == ['']:
|
||||
self.CORS_ORIGINS = ['http://localhost:3000', 'http://127.0.0.1:3000', '*']
|
||||
|
||||
# 开发环境日志配置
|
||||
LOG_LEVEL = 'DEBUG'
|
||||
@@ -37,11 +53,11 @@ class DevelopmentConfig(Config):
|
||||
@staticmethod
|
||||
def init_app(app):
|
||||
Config.init_app(app)
|
||||
|
||||
|
||||
# 开发环境特定初始化
|
||||
import logging
|
||||
app.logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
# 开发环境控制台输出
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.DEBUG)
|
||||
@@ -49,5 +65,12 @@ class DevelopmentConfig(Config):
|
||||
'%(asctime)s %(levelname)s: %(message)s'
|
||||
))
|
||||
app.logger.addHandler(console_handler)
|
||||
|
||||
app.logger.info('开发环境启动')
|
||||
|
||||
# 获取版本信息
|
||||
try:
|
||||
from src.flask_prompt_master import __version__
|
||||
version_info = f"版本: {__version__}"
|
||||
except ImportError:
|
||||
version_info = "版本: 未知"
|
||||
|
||||
app.logger.info(f'开发环境启动 - {version_info}')
|
||||
|
||||
@@ -44,7 +44,14 @@ class LocalConfig(DevelopmentConfig):
|
||||
DevelopmentConfig.init_app(app)
|
||||
|
||||
# 本地环境特定初始化
|
||||
app.logger.info('本地环境启动')
|
||||
# 获取版本信息
|
||||
try:
|
||||
from src.flask_prompt_master import __version__
|
||||
version_info = f"版本: {__version__}"
|
||||
except ImportError:
|
||||
version_info = "版本: 未知"
|
||||
|
||||
app.logger.info(f'本地环境启动 - {version_info}')
|
||||
|
||||
# 可以在这里添加本地特定的初始化代码
|
||||
# 例如:加载本地开发工具、设置本地调试器等
|
||||
|
||||
@@ -7,7 +7,27 @@ class ProductionConfig(Config):
|
||||
"""
|
||||
DEBUG = False
|
||||
TESTING = False
|
||||
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# 生产环境必须设置的关键配置检查
|
||||
required_configs = [
|
||||
('SECRET_KEY', self.SECRET_KEY),
|
||||
('LLM_API_KEY', self.LLM_API_KEY),
|
||||
('WX_APPID', self.WX_APPID),
|
||||
('WX_SECRET', self.WX_SECRET),
|
||||
('SQLALCHEMY_DATABASE_URI', self.SQLALCHEMY_DATABASE_URI)
|
||||
]
|
||||
|
||||
for name, value in required_configs:
|
||||
if not value:
|
||||
raise ValueError(f"生产环境必须设置{name}环境变量")
|
||||
|
||||
# 生产环境CORS配置检查(在父类中已经设置)
|
||||
if not self.CORS_ORIGINS or self.CORS_ORIGINS == ['']:
|
||||
raise ValueError("生产环境必须设置CORS_ORIGINS环境变量,指定允许的域名")
|
||||
|
||||
# 生产环境日志配置
|
||||
LOG_LEVEL = 'WARNING'
|
||||
LOG_FILE = 'logs/production.log'
|
||||
@@ -31,8 +51,8 @@ class ProductionConfig(Config):
|
||||
# 生产环境跨域配置(需要设置具体的域名)
|
||||
CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '').split(',')
|
||||
if not CORS_ORIGINS or CORS_ORIGINS == ['']:
|
||||
# 如果没有设置,使用默认值而不是抛出异常
|
||||
CORS_ORIGINS = ['https://yourdomain.com']
|
||||
# 生产环境必须设置CORS域名
|
||||
raise ValueError("生产环境必须设置CORS_ORIGINS环境变量,指定允许的域名")
|
||||
|
||||
# 生产环境性能配置
|
||||
SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
@@ -66,4 +86,11 @@ class ProductionConfig(Config):
|
||||
mail_handler.setLevel(logging.ERROR)
|
||||
app.logger.addHandler(mail_handler)
|
||||
|
||||
app.logger.info('生产环境启动')
|
||||
# 获取版本信息
|
||||
try:
|
||||
from src.flask_prompt_master import __version__
|
||||
version_info = f"版本: {__version__}"
|
||||
except ImportError:
|
||||
version_info = "版本: 未知"
|
||||
|
||||
app.logger.info(f'生产环境启动 - {version_info}')
|
||||
|
||||
@@ -61,4 +61,11 @@ class TestingConfig(Config):
|
||||
))
|
||||
app.logger.addHandler(console_handler)
|
||||
|
||||
app.logger.info('测试环境启动')
|
||||
# 获取版本信息
|
||||
try:
|
||||
from src.flask_prompt_master import __version__
|
||||
version_info = f"版本: {__version__}"
|
||||
except ImportError:
|
||||
version_info = "版本: 未知"
|
||||
|
||||
app.logger.info(f'测试环境启动 - {version_info}')
|
||||
|
||||
@@ -80,7 +80,7 @@ python init_db.py
|
||||
python run_dev.py
|
||||
```
|
||||
|
||||
访问 http://localhost:5000 开始使用
|
||||
访问 http://localhost:5002 开始使用。仅 Windows 本机启停见仓库根目录 `windows上唯一启动说明.md` 与 `windows启动.bat`。
|
||||
|
||||
## 项目结构
|
||||
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
# Windows 服务配置总结
|
||||
|
||||
## ✅ 配置完成状态
|
||||
|
||||
Flask 提示词大师应用已成功配置为 Windows 服务,支持开机自启动和后台运行。
|
||||
|
||||
## 📦 已创建的文件
|
||||
|
||||
### 核心服务文件
|
||||
|
||||
1. **`simple_windows_service.py`** ✅
|
||||
- 简化版 Windows 服务脚本
|
||||
- 使用 Windows 任务计划程序实现开机自启动
|
||||
- 支持服务启动、安装、删除功能
|
||||
|
||||
2. **`install_startup.bat`** ✅
|
||||
- 开机启动安装脚本
|
||||
- 需要管理员权限运行
|
||||
- 自动配置环境变量
|
||||
|
||||
3. **`simple_service_manager.bat`** ✅
|
||||
- 服务管理脚本
|
||||
- 提供图形化菜单操作
|
||||
- 支持启动、安装、删除、状态查询
|
||||
|
||||
### 配置文件
|
||||
|
||||
4. **`requirements.txt`** ✅
|
||||
- 更新了依赖包列表
|
||||
- 添加了 `pywin32` 和 `requests` 依赖
|
||||
|
||||
5. **`docs/development/Windows服务配置指南.md`** ✅
|
||||
- 详细的服务配置指南
|
||||
- 包含安装、管理、故障排除说明
|
||||
|
||||
## 🚀 服务特点
|
||||
|
||||
### ✅ 已实现功能
|
||||
|
||||
1. **开机自启动** ✅
|
||||
- 使用 Windows 任务计划程序
|
||||
- 系统启动时自动运行
|
||||
- 无需用户登录
|
||||
|
||||
2. **后台运行** ✅
|
||||
- 服务在后台持续运行
|
||||
- 支持多线程处理
|
||||
- 自动错误恢复
|
||||
|
||||
3. **日志记录** ✅
|
||||
- 详细的服务运行日志
|
||||
- 日志文件位置:`logs/simple_service.log`
|
||||
- 支持日志轮转
|
||||
|
||||
4. **环境配置** ✅
|
||||
- 自动设置生产环境变量
|
||||
- 配置 Python 路径
|
||||
- 支持虚拟环境
|
||||
|
||||
5. **健康检查** ✅
|
||||
- 服务状态监控
|
||||
- 自动健康检查
|
||||
- 错误报告机制
|
||||
|
||||
## 📋 使用方法
|
||||
|
||||
### 安装开机启动
|
||||
|
||||
1. **以管理员身份运行**:
|
||||
```bash
|
||||
# 右键点击 install_startup.bat
|
||||
# 选择"以管理员身份运行"
|
||||
```
|
||||
|
||||
2. **或使用命令行**:
|
||||
```bash
|
||||
python simple_windows_service.py install
|
||||
```
|
||||
|
||||
### 启动服务
|
||||
|
||||
1. **使用管理脚本**:
|
||||
```bash
|
||||
simple_service_manager.bat
|
||||
# 选择选项 1 启动服务
|
||||
```
|
||||
|
||||
2. **或使用命令行**:
|
||||
```bash
|
||||
python simple_windows_service.py start
|
||||
```
|
||||
|
||||
### 管理服务
|
||||
|
||||
```bash
|
||||
# 查看任务状态
|
||||
schtasks /query /tn "FlaskPromptMasterStartup"
|
||||
|
||||
# 删除开机启动任务
|
||||
python simple_windows_service.py remove
|
||||
|
||||
# 启动服务
|
||||
python simple_windows_service.py start
|
||||
```
|
||||
|
||||
## 🔧 技术实现
|
||||
|
||||
### 服务架构
|
||||
|
||||
1. **任务计划程序**
|
||||
- 使用 `schtasks` 命令创建系统任务
|
||||
- 触发条件:系统启动时
|
||||
- 运行账户:SYSTEM
|
||||
|
||||
2. **Flask 应用**
|
||||
- 生产环境配置
|
||||
- 多线程支持
|
||||
- 健康检查接口
|
||||
|
||||
3. **日志系统**
|
||||
- 文件日志和控制台日志
|
||||
- 自动创建日志目录
|
||||
- 支持中文编码
|
||||
|
||||
### 环境配置
|
||||
|
||||
```bash
|
||||
# 环境变量
|
||||
FLASK_ENV=production
|
||||
PYTHONPATH=项目根目录
|
||||
|
||||
# 虚拟环境
|
||||
.venv\Scripts\Activate.bat
|
||||
|
||||
# 依赖包
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 📊 服务状态
|
||||
|
||||
### 当前运行状态
|
||||
|
||||
- **服务状态**:✅ 运行中
|
||||
- **访问地址**:`http://localhost:5000`
|
||||
- **健康检查**:✅ 正常
|
||||
- **环境**:production
|
||||
- **端口**:5000
|
||||
|
||||
### 任务计划状态
|
||||
|
||||
- **任务名称**:`FlaskPromptMasterStartup`
|
||||
- **触发条件**:系统启动时
|
||||
- **运行账户**:SYSTEM
|
||||
- **状态**:待安装(需要管理员权限)
|
||||
|
||||
## 🎯 下一步建议
|
||||
|
||||
### 立即可执行
|
||||
|
||||
1. **安装开机启动**:
|
||||
- 以管理员身份运行 `install_startup.bat`
|
||||
- 验证任务计划创建成功
|
||||
|
||||
2. **测试服务功能**:
|
||||
- 访问 `http://localhost:5000`
|
||||
- 测试所有功能模块
|
||||
- 检查日志文件
|
||||
|
||||
3. **配置防火墙**:
|
||||
- 允许端口 5000 的入站连接
|
||||
- 配置网络安全规则
|
||||
|
||||
### 可选优化
|
||||
|
||||
1. **性能监控**:
|
||||
- 配置服务监控工具
|
||||
- 设置性能告警
|
||||
|
||||
2. **日志管理**:
|
||||
- 配置日志轮转
|
||||
- 设置日志清理策略
|
||||
|
||||
3. **备份策略**:
|
||||
- 备份配置文件
|
||||
- 设置自动备份
|
||||
|
||||
## 🔒 安全考虑
|
||||
|
||||
1. **权限管理**
|
||||
- 服务以 SYSTEM 账户运行
|
||||
- 最小权限原则
|
||||
|
||||
2. **网络安全**
|
||||
- 配置防火墙规则
|
||||
- 限制访问来源
|
||||
|
||||
3. **日志安全**
|
||||
- 避免记录敏感信息
|
||||
- 定期清理日志
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **权限不足**
|
||||
- 确保以管理员身份运行安装脚本
|
||||
- 检查用户账户权限
|
||||
|
||||
2. **端口冲突**
|
||||
- 检查端口 5000 是否被占用
|
||||
- 修改服务脚本中的端口号
|
||||
|
||||
3. **服务启动失败**
|
||||
- 查看日志文件获取错误信息
|
||||
- 检查环境变量配置
|
||||
|
||||
### 调试方法
|
||||
|
||||
1. **查看服务日志**:
|
||||
```bash
|
||||
type logs\simple_service.log
|
||||
```
|
||||
|
||||
2. **检查任务状态**:
|
||||
```bash
|
||||
schtasks /query /tn "FlaskPromptMasterStartup"
|
||||
```
|
||||
|
||||
3. **手动测试服务**:
|
||||
```bash
|
||||
python simple_windows_service.py start
|
||||
```
|
||||
|
||||
## 📈 性能指标
|
||||
|
||||
### 当前性能
|
||||
|
||||
- **启动时间**:< 5 秒
|
||||
- **内存使用**:约 50MB
|
||||
- **响应时间**:< 100ms
|
||||
- **并发支持**:多线程
|
||||
|
||||
### 优化建议
|
||||
|
||||
1. **内存优化**
|
||||
- 监控内存使用情况
|
||||
- 配置内存限制
|
||||
|
||||
2. **连接优化**
|
||||
- 配置数据库连接池
|
||||
- 优化网络连接
|
||||
|
||||
3. **缓存策略**
|
||||
- 启用 Redis 缓存
|
||||
- 配置静态文件缓存
|
||||
|
||||
---
|
||||
|
||||
**总结**:Windows 服务配置已完成,应用现在可以作为系统服务运行,支持开机自启动和后台运行。所有核心功能都已实现并测试通过。🎉
|
||||
@@ -1,248 +0,0 @@
|
||||
# Windows 服务配置指南
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本指南介绍如何将 Flask 提示词大师应用配置为 Windows 系统服务,实现开机自启动和后台运行。
|
||||
|
||||
## 🎯 服务特点
|
||||
|
||||
- **开机自启动**:系统启动时自动运行
|
||||
- **后台运行**:无需用户登录即可运行
|
||||
- **自动重启**:服务异常时自动重启
|
||||
- **日志记录**:详细的服务运行日志
|
||||
- **权限管理**:支持系统级权限运行
|
||||
|
||||
## 📦 文件说明
|
||||
|
||||
### 核心文件
|
||||
|
||||
1. **`flask_prompt_master_service.py`**
|
||||
- Windows 服务主程序
|
||||
- 继承 `win32serviceutil.ServiceFramework`
|
||||
- 实现服务的启动、停止、重启功能
|
||||
|
||||
2. **`install_service.bat`**
|
||||
- 服务安装脚本
|
||||
- 需要管理员权限运行
|
||||
- 自动配置环境变量
|
||||
|
||||
3. **`service_manager.bat`**
|
||||
- 服务管理脚本
|
||||
- 提供图形化菜单操作
|
||||
- 支持启动、停止、重启、状态查询
|
||||
|
||||
## 🚀 安装步骤
|
||||
|
||||
### 步骤一:准备环境
|
||||
|
||||
1. **确保虚拟环境已激活**
|
||||
```bash
|
||||
.venv\Scripts\Activate.bat
|
||||
```
|
||||
|
||||
2. **安装依赖**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **检查管理员权限**
|
||||
- 右键点击 `install_service.bat`
|
||||
- 选择"以管理员身份运行"
|
||||
|
||||
### 步骤二:安装服务
|
||||
|
||||
1. **运行安装脚本**
|
||||
```bash
|
||||
install_service.bat
|
||||
```
|
||||
|
||||
2. **验证安装**
|
||||
- 打开"服务"管理器(services.msc)
|
||||
- 查找"Flask 提示词大师服务"
|
||||
- 确认服务已安装
|
||||
|
||||
### 步骤三:启动服务
|
||||
|
||||
1. **使用管理脚本**
|
||||
```bash
|
||||
service_manager.bat
|
||||
```
|
||||
选择选项 1 启动服务
|
||||
|
||||
2. **或使用命令行**
|
||||
```bash
|
||||
python flask_prompt_master_service.py start
|
||||
```
|
||||
|
||||
## 🛠️ 服务管理
|
||||
|
||||
### 使用图形化菜单
|
||||
|
||||
运行 `service_manager.bat`,选择相应操作:
|
||||
|
||||
- **1. 启动服务**:启动 Flask 应用服务
|
||||
- **2. 停止服务**:停止运行中的服务
|
||||
- **3. 重启服务**:重启服务(停止后启动)
|
||||
- **4. 查看状态**:查询服务运行状态
|
||||
- **5. 删除服务**:从系统中删除服务
|
||||
- **6. 退出**:退出管理菜单
|
||||
|
||||
### 使用命令行
|
||||
|
||||
```bash
|
||||
# 启动服务
|
||||
python flask_prompt_master_service.py start
|
||||
|
||||
# 停止服务
|
||||
python flask_prompt_master_service.py stop
|
||||
|
||||
# 重启服务
|
||||
python flask_prompt_master_service.py restart
|
||||
|
||||
# 查看状态
|
||||
python flask_prompt_master_service.py status
|
||||
|
||||
# 删除服务
|
||||
python flask_prompt_master_service.py remove
|
||||
```
|
||||
|
||||
## 📊 服务配置
|
||||
|
||||
### 服务信息
|
||||
|
||||
- **服务名称**:`FlaskPromptMaster`
|
||||
- **显示名称**:`Flask 提示词大师服务`
|
||||
- **描述**:`Flask 提示词大师 Web 应用服务,提供提示词生成功能`
|
||||
- **启动类型**:自动(开机启动)
|
||||
- **登录身份**:本地系统账户
|
||||
|
||||
### 端口配置
|
||||
|
||||
- **监听地址**:`0.0.0.0`(所有网络接口)
|
||||
- **端口号**:`5000`
|
||||
- **访问地址**:`http://localhost:5000`
|
||||
|
||||
## 📝 日志管理
|
||||
|
||||
### 日志位置
|
||||
|
||||
- **服务日志**:`logs/service.log`
|
||||
- **应用日志**:`logs/` 目录下的其他日志文件
|
||||
|
||||
### 日志内容
|
||||
|
||||
- 服务启动/停止事件
|
||||
- 应用运行状态
|
||||
- 错误和异常信息
|
||||
- 健康检查结果
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **服务启动失败**
|
||||
- 检查端口 5000 是否被占用
|
||||
- 确认环境变量配置正确
|
||||
- 查看服务日志获取详细错误信息
|
||||
|
||||
2. **权限不足**
|
||||
- 确保以管理员身份运行安装脚本
|
||||
- 检查服务账户权限设置
|
||||
|
||||
3. **依赖缺失**
|
||||
- 确认所有 Python 包已安装
|
||||
- 检查虚拟环境是否正确激活
|
||||
|
||||
4. **端口冲突**
|
||||
- 修改 `flask_prompt_master_service.py` 中的端口号
|
||||
- 或停止占用端口的其他服务
|
||||
|
||||
### 调试方法
|
||||
|
||||
1. **查看服务日志**
|
||||
```bash
|
||||
type logs\service.log
|
||||
```
|
||||
|
||||
2. **手动测试服务**
|
||||
```bash
|
||||
python flask_prompt_master_service.py debug
|
||||
```
|
||||
|
||||
3. **检查服务状态**
|
||||
```bash
|
||||
sc query FlaskPromptMaster
|
||||
```
|
||||
|
||||
## 🔒 安全考虑
|
||||
|
||||
1. **权限最小化**
|
||||
- 服务以本地系统账户运行
|
||||
- 避免使用管理员权限
|
||||
|
||||
2. **网络安全**
|
||||
- 配置防火墙规则
|
||||
- 限制访问来源
|
||||
|
||||
3. **日志安全**
|
||||
- 定期清理日志文件
|
||||
- 避免记录敏感信息
|
||||
|
||||
## 📈 性能优化
|
||||
|
||||
1. **内存管理**
|
||||
- 监控服务内存使用
|
||||
- 配置适当的内存限制
|
||||
|
||||
2. **连接池**
|
||||
- 配置数据库连接池
|
||||
- 优化网络连接
|
||||
|
||||
3. **缓存策略**
|
||||
- 启用 Redis 缓存
|
||||
- 配置静态文件缓存
|
||||
|
||||
## 🔄 更新维护
|
||||
|
||||
### 服务更新
|
||||
|
||||
1. **停止服务**
|
||||
```bash
|
||||
python flask_prompt_master_service.py stop
|
||||
```
|
||||
|
||||
2. **更新代码**
|
||||
- 替换相关文件
|
||||
- 更新依赖包
|
||||
|
||||
3. **重启服务**
|
||||
```bash
|
||||
python flask_prompt_master_service.py start
|
||||
```
|
||||
|
||||
### 定期维护
|
||||
|
||||
1. **日志清理**
|
||||
- 定期清理旧日志文件
|
||||
- 监控日志文件大小
|
||||
|
||||
2. **性能监控**
|
||||
- 监控服务响应时间
|
||||
- 检查资源使用情况
|
||||
|
||||
3. **备份配置**
|
||||
- 备份服务配置文件
|
||||
- 保存环境变量设置
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如遇到问题,请:
|
||||
|
||||
1. 查看服务日志文件
|
||||
2. 检查系统事件日志
|
||||
3. 确认环境配置正确
|
||||
4. 联系技术支持团队
|
||||
|
||||
---
|
||||
|
||||
**注意**:本服务配置仅适用于 Windows 系统,其他操作系统请参考相应的服务配置方法。
|
||||
@@ -1,205 +0,0 @@
|
||||
# Windows 生产环境部署指南
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本指南介绍如何在 Windows 环境下部署 Flask 提示词大师应用的生产环境。
|
||||
|
||||
## 🎯 部署方案
|
||||
|
||||
### 方案一:Waitress 服务器(推荐)
|
||||
|
||||
Waitress 是一个纯 Python 的 WSGI 服务器,完全兼容 Windows 环境。
|
||||
|
||||
#### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
#### 2. 启动生产环境
|
||||
|
||||
**方法一:使用 Python 脚本**
|
||||
```bash
|
||||
python run_production.py
|
||||
```
|
||||
|
||||
**方法二:使用批处理文件**
|
||||
```bash
|
||||
start_production.bat
|
||||
```
|
||||
|
||||
#### 3. 停止服务器
|
||||
|
||||
**方法一:Ctrl+C**
|
||||
在运行窗口按 `Ctrl+C` 停止服务器
|
||||
|
||||
**方法二:使用批处理文件**
|
||||
```bash
|
||||
stop_production.bat
|
||||
```
|
||||
|
||||
## ⚙️ 配置说明
|
||||
|
||||
### 环境变量配置
|
||||
|
||||
生产环境需要设置以下环境变量:
|
||||
|
||||
```bash
|
||||
# 设置生产环境
|
||||
set FLASK_ENV=production
|
||||
|
||||
# 数据库配置
|
||||
set DATABASE_URL=mysql+pymysql://root:123456@localhost:3306/pro_db?charset=utf8mb4
|
||||
|
||||
# API 配置
|
||||
set LLM_API_URL=https://api.deepseek.com/v1
|
||||
set LLM_API_KEY=your-actual-api-key
|
||||
|
||||
# 微信小程序配置
|
||||
set WX_APPID=your-wx-appid
|
||||
set WX_SECRET=your-wx-secret
|
||||
|
||||
# 安全配置
|
||||
set SECRET_KEY=your-secret-key
|
||||
```
|
||||
|
||||
### 服务器配置
|
||||
|
||||
Waitress 服务器配置(在 `run_production.py` 中):
|
||||
|
||||
```python
|
||||
serve(
|
||||
app,
|
||||
host='0.0.0.0', # 监听所有网络接口
|
||||
port=5000, # 端口号
|
||||
threads=4, # 线程数
|
||||
connection_limit=1000, # 连接限制
|
||||
cleanup_interval=30, # 清理间隔
|
||||
channel_timeout=120, # 通道超时
|
||||
max_request_body_size=1073741824, # 最大请求体大小(1GB)
|
||||
)
|
||||
```
|
||||
|
||||
## 📊 性能优化
|
||||
|
||||
### 1. 数据库优化
|
||||
|
||||
- 使用连接池
|
||||
- 配置适当的索引
|
||||
- 定期清理日志表
|
||||
|
||||
### 2. 缓存策略
|
||||
|
||||
- 启用 Redis 缓存
|
||||
- 配置模板缓存
|
||||
- 实现 API 响应缓存
|
||||
|
||||
### 3. 日志管理
|
||||
|
||||
- 配置日志轮转
|
||||
- 设置日志级别
|
||||
- 监控错误日志
|
||||
|
||||
## 🔒 安全配置
|
||||
|
||||
### 1. 网络安全
|
||||
|
||||
- 配置防火墙规则
|
||||
- 使用 HTTPS
|
||||
- 限制访问 IP
|
||||
|
||||
### 2. 应用安全
|
||||
|
||||
- 启用 CSRF 保护
|
||||
- 配置 CORS 策略
|
||||
- 输入验证和过滤
|
||||
|
||||
### 3. 数据安全
|
||||
|
||||
- 数据库访问控制
|
||||
- 敏感信息加密
|
||||
- 定期备份
|
||||
|
||||
## 📈 监控和维护
|
||||
|
||||
### 1. 健康检查
|
||||
|
||||
访问健康检查接口:
|
||||
```
|
||||
GET http://localhost:5000/health
|
||||
```
|
||||
|
||||
### 2. 日志监控
|
||||
|
||||
- 应用日志:`logs/app.log`
|
||||
- 访问日志:`logs/access.log`
|
||||
- 错误日志:`logs/error.log`
|
||||
|
||||
### 3. 性能监控
|
||||
|
||||
- CPU 使用率
|
||||
- 内存使用情况
|
||||
- 响应时间
|
||||
- 并发连接数
|
||||
|
||||
## 🚀 部署脚本
|
||||
|
||||
### 启动脚本
|
||||
|
||||
`start_production.bat` - 生产环境启动脚本
|
||||
|
||||
### 停止脚本
|
||||
|
||||
`stop_production.bat` - 生产环境停止脚本
|
||||
|
||||
### 部署脚本
|
||||
|
||||
`deploy.sh` - 自动化部署脚本(Linux 环境)
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **端口被占用**
|
||||
```bash
|
||||
netstat -ano | findstr :5000
|
||||
taskkill /PID <进程ID> /F
|
||||
```
|
||||
|
||||
2. **数据库连接失败**
|
||||
- 检查数据库服务状态
|
||||
- 验证连接字符串
|
||||
- 确认网络连接
|
||||
|
||||
3. **API 调用失败**
|
||||
- 检查 API 密钥配置
|
||||
- 验证网络连接
|
||||
- 查看错误日志
|
||||
|
||||
### 日志分析
|
||||
|
||||
```bash
|
||||
# 查看应用日志
|
||||
type logs\app.log
|
||||
|
||||
# 查看错误日志
|
||||
type logs\error.log
|
||||
|
||||
# 实时监控日志
|
||||
Get-Content logs\app.log -Wait
|
||||
```
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如遇到问题,请:
|
||||
|
||||
1. 查看日志文件
|
||||
2. 检查配置参数
|
||||
3. 验证环境变量
|
||||
4. 联系技术支持
|
||||
|
||||
---
|
||||
|
||||
**版本**: 1.0
|
||||
**更新日期**: 2025-08-17
|
||||
**维护者**: 开发团队
|
||||
@@ -4,21 +4,15 @@
|
||||
|
||||
### 1. 启动监控系统
|
||||
|
||||
#### 方法一:使用批处理脚本(推荐)
|
||||
```bash
|
||||
# 双击运行或在命令行执行
|
||||
start_monitor.bat
|
||||
```
|
||||
在项目根目录激活虚拟环境后执行:
|
||||
|
||||
#### 方法二:直接使用Python
|
||||
```bash
|
||||
# 激活虚拟环境
|
||||
.venv\Scripts\Activate.ps1
|
||||
|
||||
# 启动监控管理器
|
||||
python monitor_manager.py
|
||||
```
|
||||
|
||||
(应用本机启停见仓库根目录 `windows上唯一启动说明.md` 与 `windows启动.bat`。)
|
||||
|
||||
### 2. 基本操作
|
||||
|
||||
启动后,您将看到交互式界面,可以输入以下命令:
|
||||
@@ -199,7 +193,7 @@ schtasks /delete /tn "FlaskMonitor" /f
|
||||
### 2. 服务集成
|
||||
|
||||
#### 与Windows服务集成
|
||||
可以将监控脚本集成到现有的Windows服务中:
|
||||
应用安装为系统服务见仓库根目录 `windows上唯一启动说明.md`(`flask_prompt_master_service.py` / `simple_windows_service.py`)。监控可与之配合:
|
||||
|
||||
1. 修改 `simple_windows_service.py`
|
||||
2. 在服务启动时自动启动监控
|
||||
|
||||
@@ -5,8 +5,8 @@ Ran tool
|
||||
|
||||
Read file: gunicorn.conf.py
|
||||
Read file: uwsgi.ini
|
||||
Read file: run_production_simple.py
|
||||
Read file: run_production_public.py
|
||||
Read file: run_production.py
|
||||
Read file: run_dev.py
|
||||
Read file: docker-compose.prod.yml
|
||||
基于对您项目启动配置的分析,我来回答关于高并发访问支持的问题:
|
||||
|
||||
@@ -14,28 +14,30 @@ Read file: docker-compose.prod.yml
|
||||
|
||||
### 🔴 **不支持高并发** 的启动方式:
|
||||
|
||||
1. **Flask内置服务器启动** (`run_production_simple.py`, `run_production_public.py`)
|
||||
- 使用 `app.run()` 启动
|
||||
- 虽然启用了 `threaded=True`,但仍然是单进程
|
||||
- **并发能力限制**:通常只能处理几十到几百个并发连接
|
||||
- **适用场景**:开发测试、小规模部署
|
||||
1. **开发内置服务器** (`run_dev.py`)
|
||||
- 使用 Flask 开发服务器,调试与重载
|
||||
- **适用场景**:本地开发
|
||||
|
||||
2. **Windows 生产 Waitress** (`run_production.py`)
|
||||
- WSGI 多线程,适合 Windows 单机生产
|
||||
- 说明见仓库根目录 `windows上唯一启动说明.md`
|
||||
|
||||
### 🟡 **部分支持高并发** 的启动方式:
|
||||
|
||||
2. **uWSGI启动** (`uwsgi.ini`)
|
||||
3. **uWSGI启动** (`uwsgi.ini`)
|
||||
- 配置了4个进程,每个进程2个线程
|
||||
- **并发能力**:理论上可处理 4×2×1000 = 8000 个并发连接
|
||||
- **限制**:进程数固定,无法动态扩展
|
||||
|
||||
### <EFBFBD><EFBFBD> **支持高并发** 的启动方式:
|
||||
### 🟢 **支持高并发** 的启动方式:
|
||||
|
||||
3. **Gunicorn启动** (`gunicorn.conf.py`)
|
||||
4. **Gunicorn启动** (`gunicorn.conf.py`)
|
||||
- 工作进程数:`multiprocessing.cpu_count() * 2 + 1`
|
||||
- 每个工作进程连接数:1000
|
||||
- **并发能力**:在8核服务器上可处理约 17×1000 = 17000 个并发连接
|
||||
- **优势**:进程数根据CPU核心数自动调整
|
||||
|
||||
4. **Docker容器化部署** (`docker-compose.prod.yml`)
|
||||
5. **Docker容器化部署** (`docker-compose.prod.yml`)
|
||||
- 包含Nginx反向代理
|
||||
- 支持水平扩展(可启动多个app容器)
|
||||
- **并发能力**:理论上无上限,取决于服务器资源
|
||||
|
||||
117
docs/prompt_quality_module_plan.md
Normal file
117
docs/prompt_quality_module_plan.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# 提示词质量对比与评价模块 — 开发计划
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 目标 | 多份优化后提示词 → 模型结构化评价;评价历史检索与简单可视化对比 |
|
||||
| 存储 | 与现有 Flask-SQLAlchemy 一致;开发默认 SQLite(`DATABASE_URL` 未设时);生产可用 MySQL,表结构通用 |
|
||||
|
||||
## 1. 接口定义
|
||||
|
||||
### POST `/api/prompt-quality/analyze`
|
||||
|
||||
**输入** `application/json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"prompts": ["提示词文本1", "提示词文本2"],
|
||||
"task_context": "可选:任务背景/领域,便于模型对齐预期输出"
|
||||
}
|
||||
```
|
||||
|
||||
**输出** `application/json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"batch_id": "uuid",
|
||||
"results": [
|
||||
{
|
||||
"optimized_prompt": "与输入对应的完整文本",
|
||||
"evaluation": {
|
||||
"score": 82,
|
||||
"strengths": ["..."],
|
||||
"weaknesses": ["..."],
|
||||
"suggestions": ["..."],
|
||||
"applicability": ["..."],
|
||||
"mandatory_fixes": ["必须修正项"],
|
||||
"optimization_suggestions": ["可优化建议"]
|
||||
},
|
||||
"history_id": "记录主键字符串",
|
||||
"timestamp": "2026-04-03T12:00:00+00:00"
|
||||
}
|
||||
],
|
||||
"batch_comparison": "批量横向对比摘要(可选)",
|
||||
"incremental_learning_hints": ["从本批提炼的可迁移经验"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/api/prompt-quality/history`
|
||||
|
||||
**Query**:`page`, `per_page`, `date_from`, `date_to`(ISO 日期), `q`(关键词,匹配提示词片段或评价 JSON 文本), `batch_id`(可选)
|
||||
|
||||
**输出**:分页列表,每条含 `batch_id`、`created_at`、`preview`、`item_count`、`results` 摘要或详情开关。
|
||||
|
||||
### GET `/api/prompt-quality/history/<int:record_id>`
|
||||
|
||||
单条记录详情(权限:同 `user_id`)。
|
||||
|
||||
## 2. 评价逻辑(算法描述)
|
||||
|
||||
1. **预处理**:去空、单条最大长度截断(如 8000 字符)、条数上限(如 ≤8)。
|
||||
2. **模型调用**:系统角色 = 提示词评估专家;用户内容 = 编号列表 + 可选 `task_context`;要求 **仅输出合法 JSON**(与约定 schema 一致)。
|
||||
3. **维度约束**(写入 system 说明):清晰度、指令完整性、可操作性、预期输出匹配度;区分 **mandatory_fixes**(必须修正)与 **optimization_suggestions**(建议优化)。
|
||||
4. **校验**:`json.loads`;缺字段时用安全默认值补全;`score` 钳制 0–100。
|
||||
5. **持久化**:每条输入写一行 `prompt_quality_record`;同批次共用 `batch_id`;批次级 `batch_comparison` / `incremental_learning_hints` 挂在 `prompt_index=0` 行或独立字段。
|
||||
|
||||
## 3. 表结构要点
|
||||
|
||||
**`prompt_quality_record`**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | PK | `history_id` 来源 |
|
||||
| user_id | int, index | 与 Session 对齐 |
|
||||
| batch_id | string(36), index | 同次分析 UUID |
|
||||
| prompt_index | int | 批次内序号 |
|
||||
| optimized_prompt | text | 被评文本 |
|
||||
| evaluation_json | JSON | 结构化评价(含两类优先级字段) |
|
||||
| batch_summary | text, nullable | 批次对比摘要(仅首条或冗余存储) |
|
||||
| incremental_hints_json | JSON, nullable | 增量学习建议列表 |
|
||||
| created_at | datetime | ISO 时间来源 |
|
||||
|
||||
SQLite:`JSON` 由 SQLAlchemy 映射为 TEXT;MySQL 可用原生 JSON。
|
||||
|
||||
## 4. 查询 API 示例
|
||||
|
||||
```http
|
||||
GET /api/prompt-quality/history?page=1&per_page=10&date_from=2026-04-01&date_to=2026-04-03&q=openlaw
|
||||
```
|
||||
|
||||
## 5. 前端(Vue)
|
||||
|
||||
- 路由:`/prompt-quality`
|
||||
- 多行文本输入,约定 **空行分隔** 多条提示词;按钮「模型分析评价」
|
||||
- 展示:卡片列表(score、优劣、两类建议、适用场景);批次对比与增量建议
|
||||
- 历史区:表格 + 筛选 + 查看详情抽屉
|
||||
|
||||
---
|
||||
|
||||
**实施顺序**:模型与建表 SQL → service + 蓝图 → 注册应用 → Vue API + 页面 + 路由 + 导航。
|
||||
|
||||
---
|
||||
|
||||
## 实施记录(已完成)
|
||||
|
||||
| 组件 | 路径 |
|
||||
|------|------|
|
||||
| 计划文档 | `docs/prompt_quality_module_plan.md` |
|
||||
| ORM | `src/flask_prompt_master/models/prompt_quality_models.py` |
|
||||
| LLM 服务 | `src/flask_prompt_master/services/prompt_quality_service.py` |
|
||||
| API 蓝图 | `src/flask_prompt_master/routes/prompt_quality_routes.py`,前缀 `/api/prompt-quality` |
|
||||
| 应用注册 | `src/flask_prompt_master/__init__.py` 注册蓝图并加载模型 |
|
||||
| 手工建表 SQL(SQLite 友好) | `scripts/prompt_quality_schema.sql` |
|
||||
| 建表 | 首次可执行 `db.create_all()`(已在当前环境对 MySQL 验证) |
|
||||
| Vue 页面 | `vue-app/src/views/PromptQualityView.vue`,路由 `/prompt-quality`,顶栏「质量评价」 |
|
||||
| Vue API | `vue-app/src/api/modules/promptQuality.ts`、`api/types/promptQuality.ts` |
|
||||
@@ -1,52 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ============================================================
|
||||
echo 🚀 Flask 提示词大师 - Windows 服务安装脚本
|
||||
echo ============================================================
|
||||
echo.
|
||||
|
||||
REM 检查管理员权限
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% == 0 (
|
||||
echo ✅ 管理员权限检查通过
|
||||
) else (
|
||||
echo ❌ 需要管理员权限才能安装服务
|
||||
echo 💡 请右键点击此脚本,选择"以管理员身份运行"
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 激活虚拟环境
|
||||
call .venv\Scripts\Activate.bat
|
||||
|
||||
REM 设置环境变量
|
||||
set FLASK_ENV=production
|
||||
set PYTHONPATH=%cd%
|
||||
|
||||
echo 📊 环境变量设置完成
|
||||
echo 🌐 正在安装 Windows 服务...
|
||||
|
||||
REM 安装服务
|
||||
python flask_prompt_master_service.py install
|
||||
|
||||
if %errorLevel% == 0 (
|
||||
echo ✅ 服务安装成功!
|
||||
echo.
|
||||
echo 📋 服务信息:
|
||||
echo 服务名称: FlaskPromptMaster
|
||||
echo 显示名称: Flask 提示词大师服务
|
||||
echo 描述: Flask 提示词大师 Web 应用服务
|
||||
echo.
|
||||
echo 🎯 下一步操作:
|
||||
echo 1. 启动服务: python flask_prompt_master_service.py start
|
||||
echo 2. 停止服务: python flask_prompt_master_service.py stop
|
||||
echo 3. 重启服务: python flask_prompt_master_service.py restart
|
||||
echo 4. 删除服务: python flask_prompt_master_service.py remove
|
||||
echo.
|
||||
echo 💡 服务安装后会自动开机启动
|
||||
) else (
|
||||
echo ❌ 服务安装失败!
|
||||
echo 💡 请检查错误信息并重试
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -1,51 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ============================================================
|
||||
echo 🚀 Flask 提示词大师 - 开机启动安装脚本
|
||||
echo ============================================================
|
||||
echo.
|
||||
|
||||
REM 检查管理员权限
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% == 0 (
|
||||
echo ✅ 管理员权限检查通过
|
||||
) else (
|
||||
echo ❌ 需要管理员权限才能安装开机启动任务
|
||||
echo 💡 请右键点击此脚本,选择"以管理员身份运行"
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 激活虚拟环境
|
||||
call .venv\Scripts\Activate.bat
|
||||
|
||||
REM 设置环境变量
|
||||
set FLASK_ENV=production
|
||||
set PYTHONPATH=%cd%
|
||||
|
||||
echo 📊 环境变量设置完成
|
||||
echo 🌐 正在安装开机启动任务...
|
||||
|
||||
REM 安装开机启动任务
|
||||
python simple_windows_service.py install
|
||||
|
||||
if %errorLevel% == 0 (
|
||||
echo ✅ 开机启动任务安装成功!
|
||||
echo.
|
||||
echo 📋 任务信息:
|
||||
echo 任务名称: FlaskPromptMasterStartup
|
||||
echo 触发条件: 系统启动时
|
||||
echo 运行账户: SYSTEM
|
||||
echo.
|
||||
echo 🎯 下一步操作:
|
||||
echo 1. 启动服务: python simple_windows_service.py start
|
||||
echo 2. 删除任务: python simple_windows_service.py remove
|
||||
echo 3. 查看状态: schtasks /query /tn "FlaskPromptMasterStartup"
|
||||
echo.
|
||||
echo 💡 系统重启后会自动启动服务
|
||||
) else (
|
||||
echo ❌ 开机启动任务安装失败!
|
||||
echo 💡 请检查错误信息并重试
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -9,4 +9,5 @@ waitress>=3.0.0
|
||||
redis>=5.0.1
|
||||
pywin32>=306
|
||||
requests>=2.28.0
|
||||
psutil>=5.9.0
|
||||
psutil>=5.9.0
|
||||
bcrypt>=4.0.0
|
||||
36
run_production.py
Normal file
36
run_production.py
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""生产环境启动:Waitress WSGI(Windows 友好)。说明见仓库根目录 windows上唯一启动说明.md。"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
from waitress import serve
|
||||
|
||||
from src.flask_prompt_master import create_app
|
||||
|
||||
|
||||
def main() -> None:
|
||||
os.environ.setdefault("FLASK_ENV", "production")
|
||||
app = create_app()
|
||||
port = int(os.environ.get("PORT", "5000"))
|
||||
print("=" * 60)
|
||||
print("Flask 提示词大师 - 生产环境 (Waitress)")
|
||||
print("=" * 60)
|
||||
print(f"监听: 0.0.0.0:{port} 按 Ctrl+C 停止")
|
||||
print("=" * 60)
|
||||
serve(
|
||||
app,
|
||||
host="0.0.0.0",
|
||||
port=port,
|
||||
threads=4,
|
||||
connection_limit=1000,
|
||||
cleanup_interval=30,
|
||||
channel_timeout=120,
|
||||
max_request_body_size=1073741824,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(0)
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
生产环境启动脚本 - 支持外网访问
|
||||
使用 Flask 内置服务器,配置为生产模式
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from src.flask_prompt_master import create_app
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
# 设置生产环境变量
|
||||
os.environ['FLASK_ENV'] = 'production'
|
||||
|
||||
# 创建应用实例
|
||||
app = create_app()
|
||||
|
||||
# 获取端口号(支持环境变量配置)
|
||||
port = int(os.environ.get('PORT', 5002))
|
||||
|
||||
print("=" * 60)
|
||||
print("🚀 Flask 提示词大师 - 生产环境启动(外网访问)")
|
||||
print("=" * 60)
|
||||
print(f"📊 环境: {os.environ.get('FLASK_ENV', 'unknown')}")
|
||||
print(f"🌐 服务器: Flask 内置服务器")
|
||||
print(f"🔗 内网地址: http://10.0.4.13:5002")
|
||||
print(f"🌍 外网地址: http://101.43.95.130:5002")
|
||||
print(f"📝 日志: 控制台输出")
|
||||
print("=" * 60)
|
||||
print("✅ 服务器启动中...")
|
||||
print("💡 按 Ctrl+C 停止服务器")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 启动 Flask 内置服务器(生产模式配置)
|
||||
app.run(
|
||||
host='0.0.0.0', # 监听所有网络接口
|
||||
port=port,
|
||||
debug=False, # 生产环境关闭调试
|
||||
threaded=True, # 启用多线程
|
||||
use_reloader=False # 关闭自动重载
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
print("\n" + "=" * 60)
|
||||
print("🛑 服务器已停止")
|
||||
print("=" * 60)
|
||||
except Exception as e:
|
||||
print(f"\n❌ 启动失败: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
简单的生产环境启动脚本
|
||||
使用 Flask 内置服务器,但配置为生产模式
|
||||
适合 Windows 环境快速部署
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from src.flask_prompt_master import create_app
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
# 设置生产环境变量
|
||||
os.environ['FLASK_ENV'] = 'production'
|
||||
|
||||
# 创建应用实例
|
||||
app = create_app()
|
||||
|
||||
print("=" * 60)
|
||||
print("🚀 Flask 提示词大师 - 生产环境启动")
|
||||
print("=" * 60)
|
||||
print(f"📊 环境: {os.environ.get('FLASK_ENV', 'unknown')}")
|
||||
print(f"🌐 服务器: Flask 内置服务器")
|
||||
print(f"🔗 地址: http://0.0.0.0:5002")
|
||||
print(f"📝 日志: 控制台输出")
|
||||
print("=" * 60)
|
||||
print("✅ 服务器启动中...")
|
||||
print("💡 按 Ctrl+C 停止服务器")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 启动 Flask 内置服务器(生产模式配置)
|
||||
app.run(
|
||||
host='0.0.0.0',
|
||||
port=5002, # 修改端口为5002
|
||||
debug=False, # 生产环境关闭调试
|
||||
threaded=True, # 启用多线程
|
||||
use_reloader=False # 关闭自动重载
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
print("\n" + "=" * 60)
|
||||
print("🛑 服务器已停止")
|
||||
print("=" * 60)
|
||||
except Exception as e:
|
||||
print(f"\n❌ 启动失败: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
48
scripts/ensure_evaluation_schema_mysql.sql
Normal file
48
scripts/ensure_evaluation_schema_mysql.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
-- 评价 / 对比功能所需结构(在已有库上增量执行;若列或表已存在会报错,可忽略对应语句)
|
||||
-- MySQL 5.7+ / 8.0
|
||||
|
||||
-- 1. prompt_history 扩展字段(模型 history_models.PromptHistory 依赖)
|
||||
-- 分两条执行:避免 AFTER 依赖列名/顺序;若某列已存在会报 Duplicate column,可忽略该条
|
||||
ALTER TABLE prompt_history
|
||||
ADD COLUMN comparison_group_id VARCHAR(64) NULL COMMENT '对比组ID';
|
||||
ALTER TABLE prompt_history
|
||||
ADD COLUMN is_comparison_enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否参与对比';
|
||||
ALTER TABLE prompt_history ADD INDEX idx_comparison_group (comparison_group_id);
|
||||
|
||||
-- 若上面因「列已存在」失败,说明已执行过,可继续执行下面建表。
|
||||
|
||||
-- 2. 评价表(与 evaluation_models.PromptEvaluation 一致)
|
||||
CREATE TABLE IF NOT EXISTS prompt_evaluation (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
history_id INT NOT NULL COMMENT '历史记录ID',
|
||||
user_id INT NOT NULL COMMENT '评价者用户ID',
|
||||
clarity_rating TINYINT NULL COMMENT '清晰度评分',
|
||||
specificity_rating TINYINT NULL COMMENT '具体性评分',
|
||||
effectiveness_rating TINYINT NULL COMMENT '有效性评分',
|
||||
professionalism_rating TINYINT NULL COMMENT '专业性评分',
|
||||
completeness_rating TINYINT NULL COMMENT '完整性评分',
|
||||
is_best_version TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否为最佳版本',
|
||||
comparison_group_id VARCHAR(64) NULL COMMENT '对比组ID',
|
||||
overall_rating TINYINT NULL COMMENT '综合评分',
|
||||
comments TEXT NULL COMMENT '评价意见',
|
||||
created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
INDEX idx_history_id (history_id),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_comparison_group (comparison_group_id),
|
||||
CONSTRAINT fk_pe_history FOREIGN KEY (history_id) REFERENCES prompt_history(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='提示词评价表';
|
||||
|
||||
-- 3. 对比组表
|
||||
CREATE TABLE IF NOT EXISTS comparison_group (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||
group_id VARCHAR(64) NOT NULL COMMENT '对比组ID',
|
||||
user_id INT NOT NULL COMMENT '创建者用户ID',
|
||||
name VARCHAR(100) NULL COMMENT '对比组名称',
|
||||
description TEXT NULL COMMENT '对比组描述',
|
||||
is_public TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否公开',
|
||||
created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
UNIQUE KEY uk_group_id (group_id),
|
||||
INDEX idx_user_id (user_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='对比组表';
|
||||
39
scripts/ensure_evaluation_schema_sqlite.sql
Normal file
39
scripts/ensure_evaluation_schema_sqlite.sql
Normal file
@@ -0,0 +1,39 @@
|
||||
-- 开发环境默认 SQLite(config/development.py → sqlite:///dev.db)增量修复
|
||||
-- 在仓库根目录执行: sqlite3 dev.db < scripts/ensure_evaluation_schema_sqlite.sql
|
||||
-- 若列已存在会报错,可忽略该句或先 .schema prompt_history 查看
|
||||
|
||||
ALTER TABLE prompt_history ADD COLUMN comparison_group_id VARCHAR(64);
|
||||
ALTER TABLE prompt_history ADD COLUMN is_comparison_enabled INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- 评价表(与 ORM 一致;若已存在可跳过整段)
|
||||
CREATE TABLE IF NOT EXISTS prompt_evaluation (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
history_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
clarity_rating INTEGER,
|
||||
specificity_rating INTEGER,
|
||||
effectiveness_rating INTEGER,
|
||||
professionalism_rating INTEGER,
|
||||
completeness_rating INTEGER,
|
||||
is_best_version INTEGER NOT NULL DEFAULT 0,
|
||||
comparison_group_id VARCHAR(64),
|
||||
overall_rating INTEGER,
|
||||
comments TEXT,
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP,
|
||||
FOREIGN KEY (history_id) REFERENCES prompt_history(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS comparison_group (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
user_id INTEGER NOT NULL,
|
||||
name VARCHAR(100),
|
||||
description TEXT,
|
||||
is_public INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_pe_history ON prompt_evaluation(history_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_pe_user ON prompt_evaluation(user_id);
|
||||
15
scripts/prompt_quality_schema.sql
Normal file
15
scripts/prompt_quality_schema.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- 提示词结构化质量评价表(SQLite 与 MySQL 通用写法:MySQL 可将 evaluation_json 改为 JSON 类型)
|
||||
CREATE TABLE IF NOT EXISTS prompt_quality_record (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
batch_id VARCHAR(36) NOT NULL,
|
||||
prompt_index INTEGER NOT NULL DEFAULT 0,
|
||||
optimized_prompt TEXT NOT NULL,
|
||||
evaluation_json TEXT NOT NULL,
|
||||
batch_summary TEXT,
|
||||
incremental_hints_json TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_pqr_user_id ON prompt_quality_record (user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_pqr_batch_id ON prompt_quality_record (batch_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_pqr_created_at ON prompt_quality_record (created_at);
|
||||
61
scripts/run_prompt_history_mysql_migration.py
Normal file
61
scripts/run_prompt_history_mysql_migration.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""为 prompt_history 增加对比相关列(可重复执行,已存在则跳过)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
load_dotenv(ROOT / ".env")
|
||||
|
||||
DATABASE_URL = os.environ.get("DATABASE_URL")
|
||||
if not DATABASE_URL:
|
||||
print("ERROR: 未设置 DATABASE_URL", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
STATEMENTS = [
|
||||
(
|
||||
"comparison_group_id",
|
||||
"ALTER TABLE prompt_history ADD COLUMN comparison_group_id VARCHAR(64) NULL COMMENT '对比组ID'",
|
||||
),
|
||||
(
|
||||
"is_comparison_enabled",
|
||||
"ALTER TABLE prompt_history ADD COLUMN is_comparison_enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否参与对比'",
|
||||
),
|
||||
(
|
||||
"idx_comparison_group",
|
||||
"ALTER TABLE prompt_history ADD INDEX idx_comparison_group (comparison_group_id)",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
engine = create_engine(DATABASE_URL)
|
||||
ok = 0
|
||||
with engine.connect() as conn:
|
||||
for name, sql in STATEMENTS:
|
||||
try:
|
||||
conn.execute(text(sql))
|
||||
conn.commit()
|
||||
print(f"OK: {name}")
|
||||
ok += 1
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
err = str(e).lower()
|
||||
if "duplicate" in err or "1060" in str(e) or "1061" in str(e) or "1826" in str(e):
|
||||
print(f"SKIP (已存在): {name}")
|
||||
else:
|
||||
print(f"FAIL: {name} -> {e}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"完成,成功执行 {ok} 条新变更。")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,108 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ============================================================
|
||||
echo 🚀 Flask 提示词大师 - Windows 服务管理脚本
|
||||
echo ============================================================
|
||||
echo.
|
||||
|
||||
REM 激活虚拟环境
|
||||
call .venv\Scripts\Activate.bat
|
||||
|
||||
REM 设置环境变量
|
||||
set FLASK_ENV=production
|
||||
set PYTHONPATH=%cd%
|
||||
|
||||
echo 📊 环境变量设置完成
|
||||
echo.
|
||||
|
||||
:menu
|
||||
echo 请选择操作:
|
||||
echo 1. 启动服务
|
||||
echo 2. 停止服务
|
||||
echo 3. 重启服务
|
||||
echo 4. 查看服务状态
|
||||
echo 5. 删除服务
|
||||
echo 6. 退出
|
||||
echo.
|
||||
set /p choice=请输入选项 (1-6):
|
||||
|
||||
if "%choice%"=="1" goto start_service
|
||||
if "%choice%"=="2" goto stop_service
|
||||
if "%choice%"=="3" goto restart_service
|
||||
if "%choice%"=="4" goto status_service
|
||||
if "%choice%"=="5" goto remove_service
|
||||
if "%choice%"=="6" goto exit
|
||||
echo ❌ 无效选项,请重新选择
|
||||
goto menu
|
||||
|
||||
:start_service
|
||||
echo.
|
||||
echo 🚀 正在启动服务...
|
||||
python flask_prompt_master_service.py start
|
||||
if %errorLevel% == 0 (
|
||||
echo ✅ 服务启动成功!
|
||||
echo 💡 访问地址: http://localhost:5000
|
||||
) else (
|
||||
echo ❌ 服务启动失败!
|
||||
)
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:stop_service
|
||||
echo.
|
||||
echo 🛑 正在停止服务...
|
||||
python flask_prompt_master_service.py stop
|
||||
if %errorLevel% == 0 (
|
||||
echo ✅ 服务停止成功!
|
||||
) else (
|
||||
echo ❌ 服务停止失败!
|
||||
)
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:restart_service
|
||||
echo.
|
||||
echo 🔄 正在重启服务...
|
||||
python flask_prompt_master_service.py restart
|
||||
if %errorLevel% == 0 (
|
||||
echo ✅ 服务重启成功!
|
||||
echo 💡 访问地址: http://localhost:5000
|
||||
) else (
|
||||
echo ❌ 服务重启失败!
|
||||
)
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:status_service
|
||||
echo.
|
||||
echo 📊 正在查询服务状态...
|
||||
python flask_prompt_master_service.py status
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:remove_service
|
||||
echo.
|
||||
echo ⚠️ 警告:此操作将删除服务!
|
||||
set /p confirm=确认删除服务?(y/N):
|
||||
if /i "%confirm%"=="y" (
|
||||
echo 🗑️ 正在删除服务...
|
||||
python flask_prompt_master_service.py remove
|
||||
if %errorLevel% == 0 (
|
||||
echo ✅ 服务删除成功!
|
||||
) else (
|
||||
echo ❌ 服务删除失败!
|
||||
)
|
||||
) else (
|
||||
echo ❌ 操作已取消
|
||||
)
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:exit
|
||||
echo 👋 再见!
|
||||
exit /b 0
|
||||
@@ -1,70 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ============================================================
|
||||
echo 🚀 Flask 提示词大师 - 简化服务管理脚本
|
||||
echo ============================================================
|
||||
echo.
|
||||
|
||||
REM 激活虚拟环境
|
||||
call .venv\Scripts\Activate.bat
|
||||
|
||||
REM 设置环境变量
|
||||
set FLASK_ENV=production
|
||||
set PYTHONPATH=%cd%
|
||||
|
||||
echo 📊 环境变量设置完成
|
||||
echo.
|
||||
|
||||
:menu
|
||||
echo 请选择操作:
|
||||
echo 1. 启动服务
|
||||
echo 2. 安装开机启动
|
||||
echo 3. 删除开机启动
|
||||
echo 4. 查看任务状态
|
||||
echo 5. 退出
|
||||
echo.
|
||||
set /p choice=请输入选项 (1-5):
|
||||
|
||||
if "%choice%"=="1" goto start_service
|
||||
if "%choice%"=="2" goto install_startup
|
||||
if "%choice%"=="3" goto remove_startup
|
||||
if "%choice%"=="4" goto check_status
|
||||
if "%choice%"=="5" goto exit
|
||||
echo ❌ 无效选项,请重新选择
|
||||
goto menu
|
||||
|
||||
:start_service
|
||||
echo.
|
||||
echo 🚀 正在启动服务...
|
||||
python simple_windows_service.py start
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:install_startup
|
||||
echo.
|
||||
echo 📋 正在安装开机启动任务...
|
||||
python simple_windows_service.py install
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:remove_startup
|
||||
echo.
|
||||
echo 🗑️ 正在删除开机启动任务...
|
||||
python simple_windows_service.py remove
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:check_status
|
||||
echo.
|
||||
echo 📊 正在查询任务状态...
|
||||
schtasks /query /tn "FlaskPromptMasterStartup" /fo table
|
||||
echo.
|
||||
pause
|
||||
goto menu
|
||||
|
||||
:exit
|
||||
echo 👋 再见!
|
||||
exit /b 0
|
||||
@@ -7,6 +7,9 @@ import traceback
|
||||
import logging
|
||||
from dotenv import load_dotenv
|
||||
|
||||
__version__ = '1.0.0'
|
||||
__version_info__ = (1, 0, 0)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 加载环境变量
|
||||
@@ -67,6 +70,20 @@ def create_app(config_class=None):
|
||||
# 注册历史记录蓝图
|
||||
from src.flask_prompt_master.routes.history_routes import history_bp
|
||||
app.register_blueprint(history_bp)
|
||||
|
||||
# 迭代对话上下文(多轮优化)
|
||||
from src.flask_prompt_master.models import conversation # noqa: F401
|
||||
from src.flask_prompt_master.routes.conversation_routes import conversation_bp
|
||||
app.register_blueprint(conversation_bp)
|
||||
|
||||
# 内容导出(Markdown 下载)
|
||||
from src.flask_prompt_master.routes.export_routes import export_bp
|
||||
app.register_blueprint(export_bp)
|
||||
|
||||
# 提示词结构化质量评价(多段文本 + 模型 JSON 评价 + 历史)
|
||||
from src.flask_prompt_master.models import prompt_quality_models # noqa: F401
|
||||
from src.flask_prompt_master.routes.prompt_quality_routes import prompt_quality_bp
|
||||
app.register_blueprint(prompt_quality_bp)
|
||||
|
||||
# 注册智能周报生成蓝图
|
||||
from src.flask_prompt_master.routes.weekly_report import weekly_report_bp
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -37,7 +37,7 @@ def init_admin(app):
|
||||
login_manager.login_message = '请先登录'
|
||||
|
||||
# 创建Admin实例
|
||||
admin = Admin(app, name='提示词大师后台管理', template_mode='bootstrap4')
|
||||
admin = Admin(app, name='提示词大师后台管理')
|
||||
|
||||
# 注册视图
|
||||
admin.add_view(UserAdminView(User, db.session, name='用户管理', endpoint='admin_user'))
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,23 +1,36 @@
|
||||
import os
|
||||
import warnings
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 在配置类定义前加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
# 警告:此文件已弃用,请使用 config/ 目录下的配置系统
|
||||
warnings.warn("src/flask_prompt_master/config.py 已弃用,请使用新的配置系统 (config/ 目录)", DeprecationWarning)
|
||||
|
||||
class Config:
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key'
|
||||
|
||||
# MySQL数据库配置 - 腾讯云数据库
|
||||
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:!Rjb12191@gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com:24936/pro_db?charset=utf8mb4'
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY')
|
||||
if not SECRET_KEY:
|
||||
raise ValueError("SECRET_KEY 环境变量未设置")
|
||||
|
||||
# MySQL数据库配置 - 从环境变量读取
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
|
||||
if not SQLALCHEMY_DATABASE_URI:
|
||||
raise ValueError("DATABASE_URL 环境变量未设置")
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
|
||||
# OpenAI兼容API配置
|
||||
LLM_API_URL = os.environ.get('LLM_API_URL') or 'https://api.deepseek.com/v1'
|
||||
LLM_API_KEY = os.environ.get('LLM_API_KEY') or 'sk-fdf7cc1c73504e628ec0119b7e11b8cc'
|
||||
|
||||
LLM_API_KEY = os.environ.get('LLM_API_KEY')
|
||||
if not LLM_API_KEY:
|
||||
raise ValueError("LLM_API_KEY 环境变量未设置")
|
||||
|
||||
# 微信小程序配置
|
||||
WX_APPID = os.environ.get('WX_APPID') or 'wx2c65877d37fc29bf' # 替换为你的小程序 appid
|
||||
WX_SECRET = os.environ.get('WX_SECRET') or '89aa97dda3c1347c6ae3d6ab4627f1f4' # 替换为你的小程序 secret
|
||||
|
||||
# 添加跨域支持
|
||||
CORS_ORIGINS = ['*'] # 生产环境建议设置具体域名
|
||||
WX_APPID = os.environ.get('WX_APPID')
|
||||
WX_SECRET = os.environ.get('WX_SECRET')
|
||||
if not WX_APPID or not WX_SECRET:
|
||||
raise ValueError("WX_APPID 和 WX_SECRET 环境变量未设置")
|
||||
|
||||
# 跨域配置
|
||||
CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '*').split(',')
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
62
src/flask_prompt_master/models/conversation.py
Normal file
62
src/flask_prompt_master/models/conversation.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
对话上下文模型:支持多轮迭代优化提示词
|
||||
"""
|
||||
from datetime import datetime
|
||||
from src.flask_prompt_master import db
|
||||
|
||||
|
||||
class Conversation(db.Model):
|
||||
__tablename__ = 'conversation'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
user_id = db.Column(db.Integer, nullable=False, index=True, comment='用户ID')
|
||||
scene_type = db.Column(db.String(30), default='prompt', comment='场景类型: prompt/meal/poetry/report')
|
||||
context_messages = db.Column(db.JSON, nullable=False, default=list, comment='对话上下文 [{role, content}]')
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
MAX_ROUNDS = 10 # 最多保留最近 N 轮对话
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'user_id': self.user_id,
|
||||
'scene_type': self.scene_type,
|
||||
'context_messages': self.context_messages or [],
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def append_round(cls, conversation_id, user_id, scene_type, role, content):
|
||||
"""追加一轮对话,若不存在则新建"""
|
||||
conv = None
|
||||
if conversation_id:
|
||||
conv = cls.query.filter_by(id=conversation_id, user_id=user_id).first()
|
||||
if conv is None:
|
||||
conv = cls(user_id=user_id, scene_type=scene_type, context_messages=[])
|
||||
db.session.add(conv)
|
||||
|
||||
messages = list(conv.context_messages or [])
|
||||
messages.append({'role': role, 'content': content})
|
||||
|
||||
# 保留最近 MAX_ROUNDS 轮
|
||||
if len(messages) > cls.MAX_ROUNDS:
|
||||
messages = messages[-cls.MAX_ROUNDS:]
|
||||
|
||||
conv.context_messages = messages
|
||||
conv.updated_at = datetime.utcnow()
|
||||
db.session.flush()
|
||||
return conv
|
||||
|
||||
@classmethod
|
||||
def build_llm_context(cls, conversation_id, user_id, system_prompt, new_user_msg):
|
||||
"""构建 LLM 调用的 messages: system + history + new_user"""
|
||||
messages = [{'role': 'system', 'content': system_prompt}]
|
||||
if conversation_id:
|
||||
conv = cls.query.filter_by(id=conversation_id, user_id=user_id).first()
|
||||
if conv and conv.context_messages:
|
||||
messages.extend(conv.context_messages)
|
||||
messages.append({'role': 'user', 'content': new_user_msg})
|
||||
return messages
|
||||
292
src/flask_prompt_master/models/evaluation_models.py
Normal file
292
src/flask_prompt_master/models/evaluation_models.py
Normal file
@@ -0,0 +1,292 @@
|
||||
"""
|
||||
提示词评价数据模型
|
||||
支持多维度评价和对比功能
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from src.flask_prompt_master import db
|
||||
from sqlalchemy import func, desc, asc, and_, or_
|
||||
|
||||
|
||||
class PromptEvaluation(db.Model):
|
||||
"""提示词评价模型"""
|
||||
__tablename__ = 'prompt_evaluation'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='主键ID')
|
||||
history_id = db.Column(db.Integer, db.ForeignKey('prompt_history.id'), nullable=False, comment='历史记录ID')
|
||||
user_id = db.Column(db.Integer, nullable=False, comment='评价者用户ID')
|
||||
|
||||
# 多维度评分 (1-5分)
|
||||
clarity_rating = db.Column(db.SmallInteger, comment='清晰度评分') # 是否表达清晰
|
||||
specificity_rating = db.Column(db.SmallInteger, comment='具体性评分') # 是否具体明确
|
||||
effectiveness_rating = db.Column(db.SmallInteger, comment='有效性评分') # 是否能获得良好响应
|
||||
professionalism_rating = db.Column(db.SmallInteger, comment='专业性评分') # 是否专业规范
|
||||
completeness_rating = db.Column(db.SmallInteger, comment='完整性评分') # 是否包含必要要素
|
||||
|
||||
# 对比评价
|
||||
is_best_version = db.Column(db.Boolean, default=False, comment='是否为最佳版本')
|
||||
comparison_group_id = db.Column(db.String(64), comment='对比组ID') # 同一组对比的标识
|
||||
|
||||
# 综合评价
|
||||
overall_rating = db.Column(db.SmallInteger, comment='综合评分')
|
||||
comments = db.Column(db.Text, comment='评价意见')
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow, comment='创建时间')
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment='更新时间')
|
||||
|
||||
# 关联关系
|
||||
history = db.relationship('PromptHistory', backref='evaluations', lazy='select')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<PromptEvaluation {self.id}>'
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典格式"""
|
||||
return {
|
||||
'id': self.id,
|
||||
'history_id': self.history_id,
|
||||
'user_id': self.user_id,
|
||||
'clarity_rating': self.clarity_rating,
|
||||
'specificity_rating': self.specificity_rating,
|
||||
'effectiveness_rating': self.effectiveness_rating,
|
||||
'professionalism_rating': self.professionalism_rating,
|
||||
'completeness_rating': self.completeness_rating,
|
||||
'is_best_version': self.is_best_version,
|
||||
'comparison_group_id': self.comparison_group_id,
|
||||
'overall_rating': self.overall_rating,
|
||||
'comments': self.comments,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
||||
# 计算平均维度评分
|
||||
'average_dimension_rating': self.calculate_average_dimension_rating()
|
||||
}
|
||||
|
||||
def calculate_average_dimension_rating(self):
|
||||
"""计算维度平均评分"""
|
||||
ratings = [
|
||||
self.clarity_rating,
|
||||
self.specificity_rating,
|
||||
self.effectiveness_rating,
|
||||
self.professionalism_rating,
|
||||
self.completeness_rating
|
||||
]
|
||||
valid_ratings = [r for r in ratings if r is not None]
|
||||
if not valid_ratings:
|
||||
return None
|
||||
return sum(valid_ratings) / len(valid_ratings)
|
||||
|
||||
@classmethod
|
||||
def submit_evaluation(cls, history_id, user_id, evaluation_data):
|
||||
"""提交评价"""
|
||||
# 检查是否已存在评价
|
||||
existing_evaluation = cls.query.filter_by(
|
||||
history_id=history_id,
|
||||
user_id=user_id
|
||||
).first()
|
||||
|
||||
if existing_evaluation:
|
||||
# 更新现有评价
|
||||
for key, value in evaluation_data.items():
|
||||
if hasattr(existing_evaluation, key):
|
||||
setattr(existing_evaluation, key, value)
|
||||
existing_evaluation.updated_at = datetime.utcnow()
|
||||
db.session.commit()
|
||||
return existing_evaluation
|
||||
else:
|
||||
# 创建新评价
|
||||
evaluation = cls(
|
||||
history_id=history_id,
|
||||
user_id=user_id,
|
||||
**evaluation_data
|
||||
)
|
||||
db.session.add(evaluation)
|
||||
db.session.commit()
|
||||
return evaluation
|
||||
|
||||
@classmethod
|
||||
def get_history_evaluations(cls, history_id):
|
||||
"""获取历史记录的所有评价"""
|
||||
evaluations = cls.query.filter_by(history_id=history_id).all()
|
||||
return [e.to_dict() for e in evaluations]
|
||||
|
||||
@classmethod
|
||||
def get_comparison_group_evaluations(cls, comparison_group_id):
|
||||
"""获取对比组的所有评价"""
|
||||
evaluations = cls.query.filter_by(comparison_group_id=comparison_group_id).all()
|
||||
return [e.to_dict() for e in evaluations]
|
||||
|
||||
@classmethod
|
||||
def get_best_version_in_group(cls, comparison_group_id):
|
||||
"""获取对比组中的最佳版本"""
|
||||
best_evaluation = cls.query.filter_by(
|
||||
comparison_group_id=comparison_group_id,
|
||||
is_best_version=True
|
||||
).first()
|
||||
return best_evaluation.to_dict() if best_evaluation else None
|
||||
|
||||
@classmethod
|
||||
def get_evaluation_statistics(cls, history_id=None, user_id=None, date_from=None, date_to=None):
|
||||
"""获取评价统计信息"""
|
||||
query = cls.query
|
||||
|
||||
if history_id:
|
||||
query = query.filter_by(history_id=history_id)
|
||||
if user_id:
|
||||
query = query.filter_by(user_id=user_id)
|
||||
if date_from:
|
||||
query = query.filter(cls.created_at >= date_from)
|
||||
if date_to:
|
||||
query = query.filter(cls.created_at <= date_to)
|
||||
|
||||
# 获取所有评价
|
||||
evaluations = query.all()
|
||||
|
||||
if not evaluations:
|
||||
return {
|
||||
'total_evaluations': 0,
|
||||
'average_overall_rating': 0,
|
||||
'dimension_averages': {
|
||||
'clarity': 0,
|
||||
'specificity': 0,
|
||||
'effectiveness': 0,
|
||||
'professionalism': 0,
|
||||
'completeness': 0
|
||||
},
|
||||
'best_version_count': 0
|
||||
}
|
||||
|
||||
# 计算统计信息
|
||||
total_evaluations = len(evaluations)
|
||||
|
||||
# 计算综合评分平均值
|
||||
overall_ratings = [e.overall_rating for e in evaluations if e.overall_rating is not None]
|
||||
average_overall_rating = sum(overall_ratings) / len(overall_ratings) if overall_ratings else 0
|
||||
|
||||
# 计算各维度平均值
|
||||
dimension_ratings = {
|
||||
'clarity': [],
|
||||
'specificity': [],
|
||||
'effectiveness': [],
|
||||
'professionalism': [],
|
||||
'completeness': []
|
||||
}
|
||||
|
||||
for row in evaluations:
|
||||
if row.clarity_rating is not None:
|
||||
dimension_ratings['clarity'].append(row.clarity_rating)
|
||||
if row.specificity_rating is not None:
|
||||
dimension_ratings['specificity'].append(row.specificity_rating)
|
||||
if row.effectiveness_rating is not None:
|
||||
dimension_ratings['effectiveness'].append(row.effectiveness_rating)
|
||||
if row.professionalism_rating is not None:
|
||||
dimension_ratings['professionalism'].append(row.professionalism_rating)
|
||||
if row.completeness_rating is not None:
|
||||
dimension_ratings['completeness'].append(row.completeness_rating)
|
||||
|
||||
dimension_averages = {}
|
||||
for dimension, ratings in dimension_ratings.items():
|
||||
dimension_averages[dimension] = sum(ratings) / len(ratings) if ratings else 0
|
||||
|
||||
# 计算最佳版本数量
|
||||
best_version_count = sum(1 for e in evaluations if e.is_best_version)
|
||||
|
||||
return {
|
||||
'total_evaluations': total_evaluations,
|
||||
'average_overall_rating': average_overall_rating,
|
||||
'dimension_averages': dimension_averages,
|
||||
'best_version_count': best_version_count
|
||||
}
|
||||
|
||||
|
||||
class ComparisonGroup(db.Model):
|
||||
"""对比组模型"""
|
||||
__tablename__ = 'comparison_group'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='主键ID')
|
||||
group_id = db.Column(db.String(64), unique=True, nullable=False, comment='对比组ID')
|
||||
user_id = db.Column(db.Integer, nullable=False, comment='创建者用户ID')
|
||||
name = db.Column(db.String(100), comment='对比组名称')
|
||||
description = db.Column(db.Text, comment='对比组描述')
|
||||
is_public = db.Column(db.Boolean, default=False, comment='是否公开')
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow, comment='创建时间')
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment='更新时间')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<ComparisonGroup {self.group_id}>'
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典格式"""
|
||||
return {
|
||||
'id': self.id,
|
||||
'group_id': self.group_id,
|
||||
'user_id': self.user_id,
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'is_public': self.is_public,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
||||
'history_count': self.get_history_count()
|
||||
}
|
||||
|
||||
def get_history_count(self):
|
||||
"""获取对比组中的历史记录数量"""
|
||||
from .history_models import PromptHistory
|
||||
return PromptHistory.query.filter_by(comparison_group_id=self.group_id).count()
|
||||
|
||||
@classmethod
|
||||
def create_group(cls, user_id, group_id=None, name=None, description=None, is_public=False):
|
||||
"""创建对比组"""
|
||||
import uuid
|
||||
|
||||
if not group_id:
|
||||
group_id = str(uuid.uuid4())
|
||||
|
||||
group = cls(
|
||||
group_id=group_id,
|
||||
user_id=user_id,
|
||||
name=name,
|
||||
description=description,
|
||||
is_public=is_public
|
||||
)
|
||||
db.session.add(group)
|
||||
db.session.commit()
|
||||
return group
|
||||
|
||||
@classmethod
|
||||
def get_user_groups(cls, user_id):
|
||||
"""获取用户的对比组列表"""
|
||||
groups = cls.query.filter_by(user_id=user_id).all()
|
||||
return [g.to_dict() for g in groups]
|
||||
|
||||
@classmethod
|
||||
def get_group_by_id(cls, group_id):
|
||||
"""根据组ID获取对比组"""
|
||||
group = cls.query.filter_by(group_id=group_id).first()
|
||||
return group.to_dict() if group else None
|
||||
|
||||
@classmethod
|
||||
def add_history_to_group(cls, group_id, history_id):
|
||||
"""向对比组添加历史记录"""
|
||||
from .history_models import PromptHistory
|
||||
|
||||
history = PromptHistory.query.get(history_id)
|
||||
if not history:
|
||||
return False
|
||||
|
||||
history.comparison_group_id = group_id
|
||||
db.session.commit()
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def remove_history_from_group(cls, group_id, history_id):
|
||||
"""从对比组移除历史记录"""
|
||||
from .history_models import PromptHistory
|
||||
|
||||
history = PromptHistory.query.get(history_id)
|
||||
if not history:
|
||||
return False
|
||||
|
||||
if history.comparison_group_id == group_id:
|
||||
history.comparison_group_id = None
|
||||
db.session.commit()
|
||||
return True
|
||||
return False
|
||||
@@ -81,6 +81,8 @@ class PromptTemplate(db.Model):
|
||||
sub_category = db.Column(db.String(50))
|
||||
system_prompt = db.Column(db.Text, nullable=False)
|
||||
is_default = db.Column(db.Boolean, default=False)
|
||||
max_tokens = db.Column(db.Integer, default=500)
|
||||
example_input = db.Column(db.Text)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
class MealPlan(db.Model):
|
||||
|
||||
45
src/flask_prompt_master/models/prompt_quality_models.py
Normal file
45
src/flask_prompt_master/models/prompt_quality_models.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""提示词结构化质量评价记录(与优化历史 prompt_history 独立)"""
|
||||
|
||||
from datetime import datetime
|
||||
from src.flask_prompt_master import db
|
||||
|
||||
|
||||
class PromptQualityRecord(db.Model):
|
||||
__tablename__ = 'prompt_quality_record'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
user_id = db.Column(db.Integer, nullable=False, index=True)
|
||||
batch_id = db.Column(db.String(36), nullable=False, index=True)
|
||||
prompt_index = db.Column(db.Integer, nullable=False, default=0)
|
||||
optimized_prompt = db.Column(db.Text, nullable=False)
|
||||
evaluation_json = db.Column(db.JSON, nullable=False)
|
||||
batch_summary = db.Column(db.Text, nullable=True)
|
||||
incremental_hints_json = db.Column(db.JSON, nullable=True)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow, index=True)
|
||||
|
||||
def evaluation_dict(self):
|
||||
data = self.evaluation_json if isinstance(self.evaluation_json, dict) else {}
|
||||
return data
|
||||
|
||||
def to_api_item(self):
|
||||
"""单条符合对外 JSON 契约的条目。"""
|
||||
ev = self.evaluation_dict()
|
||||
return {
|
||||
'optimized_prompt': self.optimized_prompt,
|
||||
'evaluation': ev,
|
||||
'history_id': str(self.id),
|
||||
'timestamp': (self.created_at.isoformat() + 'Z') if self.created_at else None,
|
||||
}
|
||||
|
||||
def to_history_row(self):
|
||||
ev = self.evaluation_dict()
|
||||
score = ev.get('score')
|
||||
return {
|
||||
'id': self.id,
|
||||
'batch_id': self.batch_id,
|
||||
'prompt_index': self.prompt_index,
|
||||
'preview': (self.optimized_prompt or '')[:120],
|
||||
'score': score,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,9 +6,8 @@ from flask import Blueprint, render_template, request, jsonify
|
||||
from openai import OpenAI
|
||||
import logging
|
||||
import os
|
||||
from src.flask_prompt_master import db
|
||||
from src.flask_prompt_master.models.models import User
|
||||
from src.flask_prompt_master.models.history_models import PromptHistory, UserStatistics
|
||||
from src.flask_prompt_master.user_context import get_current_user_id
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
android_tools_bp = Blueprint('android_tools', __name__)
|
||||
@@ -73,25 +72,6 @@ CODE_REVIEW_SYSTEM = """你是一位资深的 Android 开发专家,擅长代
|
||||
输出格式清晰,使用小标题和分点,便于 Reviewer 逐项打勾或评论。"""
|
||||
|
||||
|
||||
def _get_user_id():
|
||||
try:
|
||||
from flask_login import current_user
|
||||
if current_user.is_authenticated:
|
||||
return getattr(current_user, 'id', None) or getattr(current_user, 'uid', None)
|
||||
except Exception:
|
||||
pass
|
||||
from flask import session
|
||||
uid = session.get('user_id')
|
||||
if uid is not None:
|
||||
return uid
|
||||
try:
|
||||
u = User.query.filter_by(login_name='admin').first()
|
||||
return u.uid if u else 1
|
||||
except Exception as e:
|
||||
logger.warning("获取默认用户失败: %s", e)
|
||||
return 1
|
||||
|
||||
|
||||
def _call_llm(system_prompt: str, user_content: str, max_tokens: int = 1200) -> str:
|
||||
try:
|
||||
resp = client.chat.completions.create(
|
||||
@@ -139,7 +119,7 @@ def crash_log_analyze_api():
|
||||
result = _call_llm(CRASH_ANALYST_SYSTEM, raw_log)
|
||||
if not result:
|
||||
return jsonify({'success': False, 'message': '分析失败,请重试'})
|
||||
user_id = _get_user_id()
|
||||
user_id = get_current_user_id()
|
||||
_save_to_history(user_id, raw_log, result, 'Crash/异常日志解读')
|
||||
return jsonify({'success': True, 'data': {'result': result}})
|
||||
except Exception as e:
|
||||
@@ -163,7 +143,7 @@ def gradle_conflict_analyze_api():
|
||||
result = _call_llm(GRADLE_CONFLICT_SYSTEM, raw_error)
|
||||
if not result:
|
||||
return jsonify({'success': False, 'message': '分析失败,请重试'})
|
||||
user_id = _get_user_id()
|
||||
user_id = get_current_user_id()
|
||||
_save_to_history(user_id, raw_error, result, '依赖冲突分析')
|
||||
return jsonify({'success': True, 'data': {'result': result}})
|
||||
except Exception as e:
|
||||
@@ -187,7 +167,7 @@ def perf_optimize_api():
|
||||
result = _call_llm(PERF_OPTIMIZE_SYSTEM, user_input, max_tokens=1400)
|
||||
if not result:
|
||||
return jsonify({'success': False, 'message': '生成失败,请重试'})
|
||||
user_id = _get_user_id()
|
||||
user_id = get_current_user_id()
|
||||
_save_to_history(user_id, user_input, result, '性能优化建议')
|
||||
return jsonify({'success': True, 'data': {'result': result}})
|
||||
except Exception as e:
|
||||
@@ -211,7 +191,7 @@ def tech_review_api():
|
||||
result = _call_llm(TECH_REVIEW_SYSTEM, user_input, max_tokens=1400)
|
||||
if not result:
|
||||
return jsonify({'success': False, 'message': '生成失败,请重试'})
|
||||
user_id = _get_user_id()
|
||||
user_id = get_current_user_id()
|
||||
_save_to_history(user_id, user_input, result, '技术方案评审要点')
|
||||
return jsonify({'success': True, 'data': {'result': result}})
|
||||
except Exception as e:
|
||||
@@ -235,7 +215,7 @@ def code_review_api():
|
||||
result = _call_llm(CODE_REVIEW_SYSTEM, user_input, max_tokens=1400)
|
||||
if not result:
|
||||
return jsonify({'success': False, 'message': '生成失败,请重试'})
|
||||
user_id = _get_user_id()
|
||||
user_id = get_current_user_id()
|
||||
_save_to_history(user_id, user_input, result, 'Code Review 清单')
|
||||
return jsonify({'success': True, 'data': {'result': result}})
|
||||
except Exception as e:
|
||||
|
||||
@@ -138,11 +138,20 @@ def change_password():
|
||||
def check_login():
|
||||
"""检查登录状态API"""
|
||||
if 'user_id' in session:
|
||||
uid = session['user_id']
|
||||
# 判断是否新用户(无任何生成记录)
|
||||
is_new = False
|
||||
try:
|
||||
from src.flask_prompt_master.models.models import Prompt
|
||||
is_new = Prompt.query.filter_by(user_id=uid).count() == 0
|
||||
except Exception:
|
||||
pass
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logged_in': True,
|
||||
'is_new_user': is_new,
|
||||
'user': {
|
||||
'user_id': session['user_id'],
|
||||
'user_id': uid,
|
||||
'nickname': session.get('nickname'),
|
||||
'login_name': session.get('login_name')
|
||||
}
|
||||
@@ -150,7 +159,8 @@ def check_login():
|
||||
else:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logged_in': False
|
||||
'logged_in': False,
|
||||
'is_new_user': True, # 未登录也算新用户, 引导注册
|
||||
})
|
||||
|
||||
@auth_bp.route('/api/profile/stats', methods=['GET'])
|
||||
|
||||
502
src/flask_prompt_master/routes/comparison_routes.py
Normal file
502
src/flask_prompt_master/routes/comparison_routes.py
Normal file
@@ -0,0 +1,502 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
对比组管理路由
|
||||
提供对比组创建、管理功能API
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
from src.flask_prompt_master import db
|
||||
from src.flask_prompt_master.models import ComparisonGroup, PromptHistory, PromptEvaluation
|
||||
from src.flask_prompt_master.user_context import get_current_user_id
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
comparison_bp = Blueprint('comparison', __name__, url_prefix='/api/comparison')
|
||||
|
||||
|
||||
def get_user_id_from_request():
|
||||
"""从请求中获取用户ID(显式传入时优先)"""
|
||||
user_id = request.headers.get('X-User-Id') or request.args.get('user_id')
|
||||
|
||||
if not user_id and request.is_json:
|
||||
data = request.get_json(silent=True) or {}
|
||||
user_id = data.get('user_id')
|
||||
|
||||
try:
|
||||
return int(user_id) if user_id is not None and str(user_id).strip() != '' else None
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def resolve_comparison_user_id():
|
||||
return get_user_id_from_request() or get_current_user_id()
|
||||
|
||||
|
||||
@comparison_bp.route('/create', methods=['POST'])
|
||||
def create_comparison_group():
|
||||
"""创建对比组"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请求数据为空'
|
||||
}), 400
|
||||
|
||||
user_id = resolve_comparison_user_id() or data.get('user_id')
|
||||
if not user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '缺少必要参数:user_id'
|
||||
}), 400
|
||||
|
||||
# 生成组ID
|
||||
group_id = str(uuid.uuid4())
|
||||
|
||||
# 创建对比组(与 Vue 侧 group_name / group_description 字段对齐)
|
||||
group = ComparisonGroup.create_group(
|
||||
user_id=user_id,
|
||||
group_id=group_id,
|
||||
name=data.get('name') or data.get('group_name'),
|
||||
description=data.get('description') or data.get('group_description'),
|
||||
is_public=data.get('is_public', False)
|
||||
)
|
||||
|
||||
# 如果有历史记录ID,添加到对比组
|
||||
history_ids = data.get('history_ids', [])
|
||||
added_histories = []
|
||||
|
||||
for history_id in history_ids:
|
||||
history = PromptHistory.query.get(history_id)
|
||||
if history:
|
||||
history.comparison_group_id = group_id
|
||||
history.is_comparison_enabled = True
|
||||
added_histories.append(history.to_dict())
|
||||
|
||||
if added_histories:
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '对比组创建成功',
|
||||
'data': {
|
||||
'group_id': group_id,
|
||||
'group': group.to_dict(),
|
||||
'added_histories': added_histories,
|
||||
'total_histories': len(added_histories)
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'创建对比组失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'创建对比组失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@comparison_bp.route('/groups', methods=['GET'])
|
||||
def get_user_groups():
|
||||
"""获取用户的对比组列表"""
|
||||
try:
|
||||
user_id = resolve_comparison_user_id()
|
||||
if not user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '缺少必要参数:user_id'
|
||||
}), 400
|
||||
|
||||
# 获取用户的对比组
|
||||
groups = ComparisonGroup.get_user_groups(user_id)
|
||||
|
||||
# 为每个组添加统计信息
|
||||
for group in groups:
|
||||
group_id = group['group_id']
|
||||
|
||||
# 获取组中的历史记录数量
|
||||
history_count = PromptHistory.query.filter_by(comparison_group_id=group_id).count()
|
||||
group['history_count'] = history_count
|
||||
|
||||
# 获取评价数量
|
||||
evaluation_count = PromptEvaluation.query.filter_by(comparison_group_id=group_id).count()
|
||||
group['evaluation_count'] = evaluation_count
|
||||
|
||||
# 获取最佳版本
|
||||
best_version = PromptEvaluation.query.filter_by(
|
||||
comparison_group_id=group_id,
|
||||
is_best_version=True
|
||||
).first()
|
||||
group['has_best_version'] = best_version is not None
|
||||
|
||||
# 获取最后活动时间
|
||||
last_history = PromptHistory.query.filter_by(
|
||||
comparison_group_id=group_id
|
||||
).order_by(PromptHistory.created_at.desc()).first()
|
||||
|
||||
group['last_activity'] = last_history.created_at.isoformat() if last_history else None
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '获取对比组列表成功',
|
||||
'data': {
|
||||
'groups': groups,
|
||||
'total': len(groups)
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'获取对比组列表失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'获取对比组列表失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@comparison_bp.route('/<string:group_id>', methods=['GET'])
|
||||
def get_group_detail(group_id):
|
||||
"""获取对比组详情"""
|
||||
try:
|
||||
# 获取对比组
|
||||
group = ComparisonGroup.query.filter_by(group_id=group_id).first()
|
||||
if not group:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '对比组不存在'
|
||||
}), 404
|
||||
|
||||
# 检查权限(如果是私有组)
|
||||
user_id = resolve_comparison_user_id()
|
||||
if not group.is_public and group.user_id != user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '无权访问此对比组'
|
||||
}), 403
|
||||
|
||||
# 获取组中的历史记录
|
||||
histories = PromptHistory.query.filter_by(comparison_group_id=group_id).all()
|
||||
history_list = [h.to_dict() for h in histories]
|
||||
|
||||
# 为每个历史记录添加评价信息
|
||||
for history in history_list:
|
||||
history_id = history['id']
|
||||
evaluations = PromptEvaluation.query.filter_by(history_id=history_id).all()
|
||||
history['evaluations'] = [e.to_dict() for e in evaluations]
|
||||
|
||||
# 计算平均评分
|
||||
if evaluations:
|
||||
overall_ratings = [e.overall_rating for e in evaluations if e.overall_rating]
|
||||
history['average_rating'] = sum(overall_ratings) / len(overall_ratings) if overall_ratings else 0
|
||||
else:
|
||||
history['average_rating'] = 0
|
||||
|
||||
# 获取所有评价
|
||||
evaluations = PromptEvaluation.query.filter_by(comparison_group_id=group_id).all()
|
||||
evaluation_list = [e.to_dict() for e in evaluations]
|
||||
|
||||
# 获取最佳版本
|
||||
best_version = PromptEvaluation.query.filter_by(
|
||||
comparison_group_id=group_id,
|
||||
is_best_version=True
|
||||
).first()
|
||||
best_version_data = best_version.to_dict() if best_version else None
|
||||
|
||||
# 获取维度分析
|
||||
dimension_stats = {
|
||||
'clarity': {'ratings': [], 'average': 0, 'count': 0},
|
||||
'specificity': {'ratings': [], 'average': 0, 'count': 0},
|
||||
'effectiveness': {'ratings': [], 'average': 0, 'count': 0},
|
||||
'professionalism': {'ratings': [], 'average': 0, 'count': 0},
|
||||
'completeness': {'ratings': [], 'average': 0, 'count': 0}
|
||||
}
|
||||
|
||||
for eval in evaluations:
|
||||
if eval.clarity_rating:
|
||||
dimension_stats['clarity']['ratings'].append(eval.clarity_rating)
|
||||
if eval.specificity_rating:
|
||||
dimension_stats['specificity']['ratings'].append(eval.specificity_rating)
|
||||
if eval.effectiveness_rating:
|
||||
dimension_stats['effectiveness']['ratings'].append(eval.effectiveness_rating)
|
||||
if eval.professionalism_rating:
|
||||
dimension_stats['professionalism']['ratings'].append(eval.professionalism_rating)
|
||||
if eval.completeness_rating:
|
||||
dimension_stats['completeness']['ratings'].append(eval.completeness_rating)
|
||||
|
||||
for dimension, data in dimension_stats.items():
|
||||
ratings = data['ratings']
|
||||
if ratings:
|
||||
data['average'] = sum(ratings) / len(ratings)
|
||||
data['count'] = len(ratings)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '获取对比组详情成功',
|
||||
'data': {
|
||||
'group': group.to_dict(),
|
||||
'histories': history_list,
|
||||
'evaluations': evaluation_list,
|
||||
'best_version': best_version_data,
|
||||
'statistics': {
|
||||
'total_histories': len(history_list),
|
||||
'total_evaluations': len(evaluation_list),
|
||||
'dimension_stats': dimension_stats
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'获取对比组详情失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'获取对比组详情失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@comparison_bp.route('/<string:group_id>/add', methods=['POST'])
|
||||
def add_to_comparison_group(group_id):
|
||||
"""向对比组添加提示词"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请求数据为空'
|
||||
}), 400
|
||||
|
||||
# 验证对比组是否存在
|
||||
group = ComparisonGroup.query.filter_by(group_id=group_id).first()
|
||||
if not group:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '对比组不存在'
|
||||
}), 404
|
||||
|
||||
# 检查权限
|
||||
user_id = resolve_comparison_user_id()
|
||||
if not group.is_public and group.user_id != user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '无权修改此对比组'
|
||||
}), 403
|
||||
|
||||
history_ids = data.get('history_ids', [])
|
||||
added_histories = []
|
||||
failed_histories = []
|
||||
|
||||
for history_id in history_ids:
|
||||
history = PromptHistory.query.get(history_id)
|
||||
if not history:
|
||||
failed_histories.append({
|
||||
'history_id': history_id,
|
||||
'reason': '历史记录不存在'
|
||||
})
|
||||
continue
|
||||
|
||||
# 检查是否已在其他对比组中
|
||||
if history.comparison_group_id and history.comparison_group_id != group_id:
|
||||
failed_histories.append({
|
||||
'history_id': history_id,
|
||||
'reason': '历史记录已属于其他对比组'
|
||||
})
|
||||
continue
|
||||
|
||||
# 添加到对比组
|
||||
history.comparison_group_id = group_id
|
||||
history.is_comparison_enabled = True
|
||||
added_histories.append(history.to_dict())
|
||||
|
||||
if added_histories:
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '添加提示词到对比组成功',
|
||||
'data': {
|
||||
'group_id': group_id,
|
||||
'added_histories': added_histories,
|
||||
'failed_histories': failed_histories,
|
||||
'total_added': len(added_histories),
|
||||
'total_failed': len(failed_histories)
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'添加提示词到对比组失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'添加提示词到对比组失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@comparison_bp.route('/<string:group_id>/remove', methods=['POST'])
|
||||
def remove_from_comparison_group(group_id):
|
||||
"""从对比组移除提示词"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请求数据为空'
|
||||
}), 400
|
||||
|
||||
# 验证对比组是否存在
|
||||
group = ComparisonGroup.query.filter_by(group_id=group_id).first()
|
||||
if not group:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '对比组不存在'
|
||||
}), 404
|
||||
|
||||
# 检查权限
|
||||
user_id = resolve_comparison_user_id()
|
||||
if not group.is_public and group.user_id != user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '无权修改此对比组'
|
||||
}), 403
|
||||
|
||||
history_ids = data.get('history_ids', [])
|
||||
removed_histories = []
|
||||
failed_histories = []
|
||||
|
||||
for history_id in history_ids:
|
||||
history = PromptHistory.query.get(history_id)
|
||||
if not history:
|
||||
failed_histories.append({
|
||||
'history_id': history_id,
|
||||
'reason': '历史记录不存在'
|
||||
})
|
||||
continue
|
||||
|
||||
# 检查是否属于此对比组
|
||||
if history.comparison_group_id != group_id:
|
||||
failed_histories.append({
|
||||
'history_id': history_id,
|
||||
'reason': '历史记录不属于此对比组'
|
||||
})
|
||||
continue
|
||||
|
||||
# 从对比组移除
|
||||
history.comparison_group_id = None
|
||||
history.is_comparison_enabled = False
|
||||
removed_histories.append(history.to_dict())
|
||||
|
||||
if removed_histories:
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '从对比组移除提示词成功',
|
||||
'data': {
|
||||
'group_id': group_id,
|
||||
'removed_histories': removed_histories,
|
||||
'failed_histories': failed_histories,
|
||||
'total_removed': len(removed_histories),
|
||||
'total_failed': len(failed_histories)
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'从对比组移除提示词失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'从对比组移除提示词失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@comparison_bp.route('/<string:group_id>', methods=['DELETE'])
|
||||
def delete_comparison_group(group_id):
|
||||
"""删除对比组"""
|
||||
try:
|
||||
# 验证对比组是否存在
|
||||
group = ComparisonGroup.query.filter_by(group_id=group_id).first()
|
||||
if not group:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '对比组不存在'
|
||||
}), 404
|
||||
|
||||
# 检查权限
|
||||
user_id = resolve_comparison_user_id()
|
||||
if group.user_id != user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '无权删除此对比组'
|
||||
}), 403
|
||||
|
||||
# 移除对比组关联的历史记录
|
||||
histories = PromptHistory.query.filter_by(comparison_group_id=group_id).all()
|
||||
for history in histories:
|
||||
history.comparison_group_id = None
|
||||
history.is_comparison_enabled = False
|
||||
|
||||
# 删除对比组
|
||||
db.session.delete(group)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '删除对比组成功',
|
||||
'data': {
|
||||
'group_id': group_id,
|
||||
'removed_histories_count': len(histories)
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'删除对比组失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'删除对比组失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@comparison_bp.route('/<string:group_id>/update', methods=['PUT'])
|
||||
def update_comparison_group(group_id):
|
||||
"""更新对比组信息"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请求数据为空'
|
||||
}), 400
|
||||
|
||||
# 验证对比组是否存在
|
||||
group = ComparisonGroup.query.filter_by(group_id=group_id).first()
|
||||
if not group:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '对比组不存在'
|
||||
}), 404
|
||||
|
||||
# 检查权限
|
||||
user_id = resolve_comparison_user_id()
|
||||
if group.user_id != user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '无权更新此对比组'
|
||||
}), 403
|
||||
|
||||
# 更新字段
|
||||
if 'name' in data:
|
||||
group.name = data['name']
|
||||
if 'description' in data:
|
||||
group.description = data['description']
|
||||
if 'is_public' in data:
|
||||
group.is_public = data['is_public']
|
||||
|
||||
group.updated_at = datetime.utcnow()
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '更新对比组成功',
|
||||
'data': group.to_dict()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'更新对比组失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'更新对比组失败: {str(e)}'
|
||||
}), 500
|
||||
154
src/flask_prompt_master/routes/conversation_routes.py
Normal file
154
src/flask_prompt_master/routes/conversation_routes.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
迭代对话路由:支持基于历史上下文的多轮提示词优化
|
||||
"""
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
from openai import OpenAI
|
||||
import os
|
||||
import logging
|
||||
from src.flask_prompt_master import db
|
||||
from src.flask_prompt_master.user_context import get_current_user_id
|
||||
from src.flask_prompt_master.models.models import Prompt, PromptTemplate
|
||||
from src.flask_prompt_master.models.conversation import Conversation
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
conversation_bp = Blueprint('conversation', __name__)
|
||||
|
||||
_client = OpenAI(
|
||||
api_key=os.environ.get('LLM_API_KEY') or '',
|
||||
base_url=os.environ.get('LLM_API_URL') or 'https://api.deepseek.com/v1',
|
||||
)
|
||||
|
||||
|
||||
@conversation_bp.route('/api/prompt/continue', methods=['POST'])
|
||||
def continue_prompt():
|
||||
"""基于已有结果继续优化提示词(多轮对话)"""
|
||||
data = request.get_json(silent=True) or {}
|
||||
conversation_id = data.get('conversation_id')
|
||||
previous_result = (data.get('previous_result') or '').strip()
|
||||
refine_instruction = (data.get('refine_instruction') or '').strip()
|
||||
template_id = data.get('template_id')
|
||||
|
||||
if not previous_result:
|
||||
return jsonify({'success': False, 'message': '缺少上一轮结果'}), 400
|
||||
if not refine_instruction:
|
||||
return jsonify({'success': False, 'message': '请输入优化指令'}), 400
|
||||
|
||||
if template_id is not None and template_id != '':
|
||||
try:
|
||||
template_id = int(template_id)
|
||||
except (TypeError, ValueError):
|
||||
template_id = None
|
||||
|
||||
user_id = get_current_user_id()
|
||||
|
||||
# 获取 system_prompt
|
||||
system_prompt = _get_system_prompt(template_id)
|
||||
|
||||
try:
|
||||
# 记录用户的新一轮指令到对话历史
|
||||
conv = Conversation.append_round(
|
||||
conversation_id=conversation_id,
|
||||
user_id=user_id,
|
||||
scene_type='prompt',
|
||||
role='user',
|
||||
content=f"上一轮结果:\n{previous_result}\n\n优化指令:{refine_instruction}",
|
||||
)
|
||||
|
||||
# 构建完整上下文
|
||||
current_app.logger.info(
|
||||
f"迭代对话 conversation_id={conv.id} user_id={user_id} "
|
||||
f"rounds={len(conv.context_messages)} instruction={refine_instruction[:80]}"
|
||||
)
|
||||
|
||||
# 调用 LLM
|
||||
response = _client.chat.completions.create(
|
||||
model='deepseek-chat',
|
||||
messages=Conversation.build_llm_context(
|
||||
conv.id, user_id, system_prompt,
|
||||
refine_instruction,
|
||||
),
|
||||
temperature=0.7,
|
||||
max_tokens=_get_max_tokens(template_id),
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
generated_text = response.choices[0].message.content.strip()
|
||||
|
||||
# 记录助手回复到对话历史
|
||||
Conversation.append_round(
|
||||
conversation_id=conv.id,
|
||||
user_id=user_id,
|
||||
scene_type='prompt',
|
||||
role='assistant',
|
||||
content=generated_text,
|
||||
)
|
||||
|
||||
# 保存到 Prompt 表
|
||||
prompt = Prompt(
|
||||
input_text=refine_instruction,
|
||||
generated_text=generated_text,
|
||||
user_id=user_id,
|
||||
)
|
||||
db.session.add(prompt)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'conversation_id': conv.id,
|
||||
'generated_text': generated_text,
|
||||
'rounds': len(conv.context_messages) // 2,
|
||||
'prompt': {
|
||||
'id': prompt.id,
|
||||
'input_text': refine_instruction,
|
||||
'generated_text': generated_text,
|
||||
},
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'continue_prompt error: {e}')
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'message': f'优化失败: {str(e)}'}), 500
|
||||
|
||||
|
||||
@conversation_bp.route('/api/conversation/<int:conversation_id>', methods=['GET'])
|
||||
def get_conversation(conversation_id):
|
||||
"""获取对话历史"""
|
||||
user_id = get_current_user_id()
|
||||
conv = Conversation.query.filter_by(id=conversation_id, user_id=user_id).first()
|
||||
if not conv:
|
||||
return jsonify({'success': False, 'message': '对话不存在'}), 404
|
||||
return jsonify({'success': True, 'conversation': conv.to_dict()})
|
||||
|
||||
|
||||
@conversation_bp.route('/api/conversation/<int:conversation_id>', methods=['DELETE'])
|
||||
def clear_conversation(conversation_id):
|
||||
"""清除对话上下文(开始新对话)"""
|
||||
user_id = get_current_user_id()
|
||||
conv = Conversation.query.filter_by(id=conversation_id, user_id=user_id).first()
|
||||
if not conv:
|
||||
return jsonify({'success': False, 'message': '对话不存在'}), 404
|
||||
db.session.delete(conv)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '对话已清除'})
|
||||
|
||||
|
||||
def _get_system_prompt(template_id):
|
||||
"""获取系统提示词"""
|
||||
if template_id:
|
||||
template = PromptTemplate.query.get(template_id)
|
||||
if template:
|
||||
return template.system_prompt
|
||||
default = PromptTemplate.query.filter_by(is_default=True).first()
|
||||
if default:
|
||||
return default.system_prompt
|
||||
return "你是一个专业的提示词工程师,请直接返回优化后的提示词,不要添加任何解释。"
|
||||
|
||||
|
||||
def _get_max_tokens(template_id):
|
||||
"""获取 max_tokens"""
|
||||
if template_id:
|
||||
template = PromptTemplate.query.get(template_id)
|
||||
if template and template.max_tokens:
|
||||
return template.max_tokens
|
||||
return 500
|
||||
519
src/flask_prompt_master/routes/evaluation_routes.py
Normal file
519
src/flask_prompt_master/routes/evaluation_routes.py
Normal file
@@ -0,0 +1,519 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
提示词评价路由
|
||||
提供多维度评价和对比功能API
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
from src.flask_prompt_master import db
|
||||
from src.flask_prompt_master.models import PromptEvaluation, ComparisonGroup, PromptHistory
|
||||
from src.flask_prompt_master.user_context import get_current_user_id
|
||||
from datetime import datetime, timedelta
|
||||
import uuid
|
||||
|
||||
evaluation_bp = Blueprint('evaluation', __name__, url_prefix='/api/evaluation')
|
||||
|
||||
|
||||
def get_user_id_from_request():
|
||||
"""从请求中获取用户ID(显式传入时优先)"""
|
||||
user_id = request.headers.get('X-User-Id') or request.args.get('user_id')
|
||||
|
||||
if not user_id and request.is_json:
|
||||
data = request.get_json(silent=True) or {}
|
||||
user_id = data.get('user_id')
|
||||
|
||||
try:
|
||||
return int(user_id) if user_id is not None and str(user_id).strip() != '' else None
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def resolve_evaluation_user_id():
|
||||
"""与历史/生成 API 一致:显式 user_id 优先,否则用 Session 当前用户。"""
|
||||
return get_user_id_from_request() or get_current_user_id()
|
||||
|
||||
|
||||
@evaluation_bp.route('/submit', methods=['POST'])
|
||||
def submit_evaluation():
|
||||
"""提交评价"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请求数据为空'
|
||||
}), 400
|
||||
|
||||
# 验证必要字段
|
||||
history_id = data.get('history_id')
|
||||
user_id = resolve_evaluation_user_id() or data.get('user_id')
|
||||
|
||||
if not history_id or not user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '缺少必要参数:history_id 或 user_id'
|
||||
}), 400
|
||||
|
||||
# 验证历史记录是否存在
|
||||
history = PromptHistory.query.get(history_id)
|
||||
if not history:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '历史记录不存在'
|
||||
}), 404
|
||||
|
||||
# 准备评价数据
|
||||
evaluation_data = {
|
||||
'clarity_rating': data.get('clarity_rating'),
|
||||
'specificity_rating': data.get('specificity_rating'),
|
||||
'effectiveness_rating': data.get('effectiveness_rating'),
|
||||
'professionalism_rating': data.get('professionalism_rating'),
|
||||
'completeness_rating': data.get('completeness_rating'),
|
||||
'is_best_version': data.get('is_best_version', False),
|
||||
'comparison_group_id': data.get('comparison_group_id'),
|
||||
'overall_rating': data.get('overall_rating'),
|
||||
'comments': data.get('comments')
|
||||
}
|
||||
|
||||
# 如果标记为最佳版本,需要确保同组中只有一个最佳版本
|
||||
if evaluation_data.get('is_best_version') and evaluation_data.get('comparison_group_id'):
|
||||
# 清除同组中其他记录的最佳版本标记
|
||||
evaluations = PromptEvaluation.query.filter_by(
|
||||
comparison_group_id=evaluation_data['comparison_group_id']
|
||||
).all()
|
||||
|
||||
for row in evaluations:
|
||||
if row.is_best_version:
|
||||
row.is_best_version = False
|
||||
|
||||
# 提交更改
|
||||
db.session.commit()
|
||||
|
||||
# 提交评价
|
||||
evaluation = PromptEvaluation.submit_evaluation(history_id, user_id, evaluation_data)
|
||||
|
||||
# 更新历史记录的平均评分(如果需要)
|
||||
if evaluation_data.get('overall_rating'):
|
||||
history.satisfaction_rating = evaluation_data['overall_rating']
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '评价提交成功',
|
||||
'data': evaluation.to_dict()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'提交评价失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'提交评价失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@evaluation_bp.route('/history/<int:history_id>', methods=['GET'])
|
||||
def get_history_evaluations(history_id):
|
||||
"""获取指定历史记录的评价"""
|
||||
try:
|
||||
evaluations = PromptEvaluation.get_history_evaluations(history_id)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '获取评价成功',
|
||||
'data': {
|
||||
'history_id': history_id,
|
||||
'evaluations': evaluations,
|
||||
'total': len(evaluations)
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'获取历史记录评价失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'获取历史记录评价失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@evaluation_bp.route('/list', methods=['GET'])
|
||||
def list_user_evaluations():
|
||||
"""当前用户的评价列表(含关联历史摘要,供评价历史页)"""
|
||||
try:
|
||||
user_id = resolve_evaluation_user_id()
|
||||
page = request.args.get('page', 1, type=int) or 1
|
||||
per_page = min(request.args.get('per_page', 20, type=int) or 20, 100)
|
||||
history_id = request.args.get('history_id', type=int)
|
||||
days = request.args.get('days', type=int)
|
||||
|
||||
query = PromptEvaluation.query.filter_by(user_id=user_id)
|
||||
if history_id:
|
||||
query = query.filter_by(history_id=history_id)
|
||||
if days is not None and days > 0:
|
||||
date_from = datetime.utcnow() - timedelta(days=days)
|
||||
query = query.filter(PromptEvaluation.created_at >= date_from)
|
||||
|
||||
query = query.order_by(PromptEvaluation.created_at.desc())
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
items = []
|
||||
for ev in pagination.items:
|
||||
item = ev.to_dict()
|
||||
hist = PromptHistory.query.get(ev.history_id)
|
||||
if hist:
|
||||
item['history'] = {
|
||||
'id': hist.id,
|
||||
'generated_prompt': hist.generated_prompt,
|
||||
'original_input': hist.original_input,
|
||||
}
|
||||
else:
|
||||
item['history'] = None
|
||||
items.append(item)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '获取评价列表成功',
|
||||
'data': {
|
||||
'evaluations': items,
|
||||
'total': pagination.total,
|
||||
'page': pagination.page,
|
||||
'per_page': pagination.per_page,
|
||||
'pages': pagination.pages,
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'获取评价列表失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'获取评价列表失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@evaluation_bp.route('/comparison/<string:group_id>', methods=['GET'])
|
||||
def get_comparison_evaluations(group_id):
|
||||
"""获取对比组评价"""
|
||||
try:
|
||||
evaluations = PromptEvaluation.get_comparison_group_evaluations(group_id)
|
||||
best_version = PromptEvaluation.get_best_version_in_group(group_id)
|
||||
|
||||
# 获取对比组信息
|
||||
group = ComparisonGroup.query.filter_by(group_id=group_id).first()
|
||||
group_info = group.to_dict() if group else None
|
||||
|
||||
# 获取对比组中的历史记录
|
||||
histories = PromptHistory.query.filter_by(comparison_group_id=group_id).all()
|
||||
history_list = [h.to_dict() for h in histories]
|
||||
|
||||
# 为每个历史记录添加评价信息
|
||||
for history in history_list:
|
||||
history_id = history['id']
|
||||
history_evaluations = PromptEvaluation.query.filter_by(history_id=history_id).all()
|
||||
history['evaluations'] = [e.to_dict() for e in history_evaluations]
|
||||
history['average_rating'] = sum([e.overall_rating for e in history_evaluations if e.overall_rating]) / len([e for e in history_evaluations if e.overall_rating]) if history_evaluations else 0
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '获取对比组评价成功',
|
||||
'data': {
|
||||
'group_id': group_id,
|
||||
'group_info': group_info,
|
||||
'evaluations': evaluations,
|
||||
'best_version': best_version,
|
||||
'histories': history_list,
|
||||
'total_histories': len(history_list),
|
||||
'total_evaluations': len(evaluations)
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'获取对比组评价失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'获取对比组评价失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@evaluation_bp.route('/compare', methods=['POST'])
|
||||
def batch_compare_evaluations():
|
||||
"""批量对比评价"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请求数据为空'
|
||||
}), 400
|
||||
|
||||
history_ids = data.get('history_ids', [])
|
||||
user_id = resolve_evaluation_user_id() or data.get('user_id')
|
||||
|
||||
if not history_ids or not user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '缺少必要参数:history_ids 或 user_id'
|
||||
}), 400
|
||||
|
||||
# 验证所有历史记录是否存在
|
||||
histories = []
|
||||
for history_id in history_ids:
|
||||
history = PromptHistory.query.get(history_id)
|
||||
if not history:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'历史记录 {history_id} 不存在'
|
||||
}), 404
|
||||
histories.append(history)
|
||||
|
||||
# 创建对比组
|
||||
group_id = str(uuid.uuid4())
|
||||
group_name = data.get('group_name', f'对比组 {datetime.now().strftime("%Y-%m-%d %H:%M")}')
|
||||
group_description = data.get('group_description', '')
|
||||
|
||||
group = ComparisonGroup.create_group(
|
||||
user_id=user_id,
|
||||
group_id=group_id,
|
||||
name=group_name,
|
||||
description=group_description,
|
||||
is_public=data.get('is_public', False)
|
||||
)
|
||||
|
||||
# 将历史记录添加到对比组
|
||||
for history in histories:
|
||||
history.comparison_group_id = group_id
|
||||
history.is_comparison_enabled = True
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# 返回对比组信息
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '批量对比评价创建成功',
|
||||
'data': {
|
||||
'group_id': group_id,
|
||||
'group_info': group.to_dict(),
|
||||
'histories': [h.to_dict() for h in histories],
|
||||
'total_histories': len(histories)
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'批量对比评价失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'批量对比评价失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@evaluation_bp.route('/stats', methods=['GET'])
|
||||
def get_evaluation_statistics():
|
||||
"""获取评价统计信息"""
|
||||
try:
|
||||
user_id = get_user_id_from_request() or request.args.get('user_id') or get_current_user_id()
|
||||
history_id = request.args.get('history_id')
|
||||
days = request.args.get('days', 30, type=int)
|
||||
|
||||
# 计算日期范围
|
||||
date_to = datetime.utcnow()
|
||||
date_from = date_to - timedelta(days=days)
|
||||
|
||||
# 获取统计信息
|
||||
stats = PromptEvaluation.get_evaluation_statistics(
|
||||
history_id=history_id,
|
||||
user_id=user_id,
|
||||
date_from=date_from,
|
||||
date_to=date_to
|
||||
)
|
||||
|
||||
# 获取趋势数据(最近7天)
|
||||
trend_data = []
|
||||
for i in range(7):
|
||||
day_start = date_to - timedelta(days=i+1)
|
||||
day_end = date_to - timedelta(days=i)
|
||||
|
||||
day_stats = PromptEvaluation.get_evaluation_statistics(
|
||||
history_id=history_id,
|
||||
user_id=user_id,
|
||||
date_from=day_start,
|
||||
date_to=day_end
|
||||
)
|
||||
|
||||
trend_data.append({
|
||||
'date': day_start.strftime('%Y-%m-%d'),
|
||||
'total_evaluations': day_stats['total_evaluations'],
|
||||
'average_overall_rating': day_stats['average_overall_rating']
|
||||
})
|
||||
|
||||
trend_data.reverse() # 按时间顺序排列
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '获取评价统计成功',
|
||||
'data': {
|
||||
'statistics': stats,
|
||||
'trend_data': trend_data,
|
||||
'time_range': {
|
||||
'date_from': date_from.strftime('%Y-%m-%d'),
|
||||
'date_to': date_to.strftime('%Y-%m-%d'),
|
||||
'days': days
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'获取评价统计失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'获取评价统计失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@evaluation_bp.route('/dimension-analysis', methods=['GET'])
|
||||
def get_dimension_analysis():
|
||||
"""获取维度分析数据"""
|
||||
try:
|
||||
user_id = get_user_id_from_request() or request.args.get('user_id') or get_current_user_id()
|
||||
days = request.args.get('days', 30, type=int)
|
||||
|
||||
# 计算日期范围
|
||||
date_to = datetime.utcnow()
|
||||
date_from = date_to - timedelta(days=days)
|
||||
|
||||
# 获取评价数据
|
||||
query = PromptEvaluation.query
|
||||
|
||||
if user_id:
|
||||
query = query.filter_by(user_id=user_id)
|
||||
|
||||
query = query.filter(PromptEvaluation.created_at >= date_from)
|
||||
query = query.filter(PromptEvaluation.created_at <= date_to)
|
||||
|
||||
evaluations = query.all()
|
||||
|
||||
# 初始化维度数据
|
||||
dimension_data = {
|
||||
'clarity': {'ratings': [], 'average': 0, 'count': 0},
|
||||
'specificity': {'ratings': [], 'average': 0, 'count': 0},
|
||||
'effectiveness': {'ratings': [], 'average': 0, 'count': 0},
|
||||
'professionalism': {'ratings': [], 'average': 0, 'count': 0},
|
||||
'completeness': {'ratings': [], 'average': 0, 'count': 0}
|
||||
}
|
||||
|
||||
# 收集维度评分
|
||||
for row in evaluations:
|
||||
if row.clarity_rating:
|
||||
dimension_data['clarity']['ratings'].append(row.clarity_rating)
|
||||
if row.specificity_rating:
|
||||
dimension_data['specificity']['ratings'].append(row.specificity_rating)
|
||||
if row.effectiveness_rating:
|
||||
dimension_data['effectiveness']['ratings'].append(row.effectiveness_rating)
|
||||
if row.professionalism_rating:
|
||||
dimension_data['professionalism']['ratings'].append(row.professionalism_rating)
|
||||
if row.completeness_rating:
|
||||
dimension_data['completeness']['ratings'].append(row.completeness_rating)
|
||||
|
||||
# 计算平均值
|
||||
for dimension, data in dimension_data.items():
|
||||
ratings = data['ratings']
|
||||
if ratings:
|
||||
data['average'] = sum(ratings) / len(ratings)
|
||||
data['count'] = len(ratings)
|
||||
|
||||
# 准备图表数据
|
||||
chart_labels = list(dimension_data.keys())
|
||||
chart_averages = [dimension_data[d]['average'] for d in chart_labels]
|
||||
chart_counts = [dimension_data[d]['count'] for d in chart_labels]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '获取维度分析成功',
|
||||
'data': {
|
||||
'dimension_data': dimension_data,
|
||||
'chart_data': {
|
||||
'labels': chart_labels,
|
||||
'averages': chart_averages,
|
||||
'counts': chart_counts
|
||||
},
|
||||
'time_range': {
|
||||
'date_from': date_from.strftime('%Y-%m-%d'),
|
||||
'date_to': date_to.strftime('%Y-%m-%d'),
|
||||
'days': days
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'获取维度分析失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'获取维度分析失败: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
@evaluation_bp.route('/best-version', methods=['POST'])
|
||||
def mark_best_version():
|
||||
"""标记最佳版本"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请求数据为空'
|
||||
}), 400
|
||||
|
||||
evaluation_id = data.get('evaluation_id')
|
||||
comparison_group_id = data.get('comparison_group_id')
|
||||
user_id = resolve_evaluation_user_id() or data.get('user_id')
|
||||
|
||||
if not evaluation_id or not comparison_group_id or not user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '缺少必要参数:evaluation_id, comparison_group_id 或 user_id'
|
||||
}), 400
|
||||
|
||||
# 获取评价
|
||||
evaluation = PromptEvaluation.query.get(evaluation_id)
|
||||
if not evaluation:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '评价不存在'
|
||||
}), 404
|
||||
|
||||
# 验证权限
|
||||
if evaluation.user_id != user_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '无权修改此评价'
|
||||
}), 403
|
||||
|
||||
# 验证对比组
|
||||
if evaluation.comparison_group_id != comparison_group_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '评价不属于此对比组'
|
||||
}), 400
|
||||
|
||||
# 清除同组中其他记录的最佳版本标记
|
||||
evaluations = PromptEvaluation.query.filter_by(
|
||||
comparison_group_id=comparison_group_id
|
||||
).all()
|
||||
|
||||
for eval_item in evaluations:
|
||||
if eval_item.is_best_version:
|
||||
eval_item.is_best_version = False
|
||||
|
||||
# 设置当前评价为最佳版本
|
||||
evaluation.is_best_version = True
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '标记最佳版本成功',
|
||||
'data': evaluation.to_dict()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'标记最佳版本失败: {str(e)}')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'标记最佳版本失败: {str(e)}'
|
||||
}), 500
|
||||
97
src/flask_prompt_master/routes/export_routes.py
Normal file
97
src/flask_prompt_master/routes/export_routes.py
Normal file
@@ -0,0 +1,97 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
内容导出路由:Markdown 文件下载
|
||||
"""
|
||||
from flask import Blueprint, request, jsonify, Response
|
||||
from src.flask_prompt_master.models.models import Prompt, MealPlan, WeeklyReport, TravelPlan, MeetingMinutes, ResumeOptimization
|
||||
from src.flask_prompt_master.models.history_models import PromptHistory
|
||||
from src.flask_prompt_master.user_context import get_current_user_id
|
||||
from datetime import datetime
|
||||
|
||||
export_bp = Blueprint('export', __name__)
|
||||
|
||||
CONTENT_MODELS = {
|
||||
'prompt': Prompt,
|
||||
'meal': MealPlan,
|
||||
'report': WeeklyReport,
|
||||
'travel': TravelPlan,
|
||||
'meeting': MeetingMinutes,
|
||||
'resume': ResumeOptimization,
|
||||
'history': PromptHistory,
|
||||
}
|
||||
|
||||
FIELD_MAP = {
|
||||
'prompt': ('input_text', 'generated_text'),
|
||||
'meal': ('meal_plan_content',),
|
||||
'report': ('report_content',),
|
||||
'travel': ('plan_content',),
|
||||
'meeting': ('summary_content',),
|
||||
'resume': ('optimized_content',),
|
||||
'history': ('generated_prompt',),
|
||||
}
|
||||
|
||||
TITLE_MAP = {
|
||||
'prompt': '提示词生成结果',
|
||||
'meal': '饭菜规划',
|
||||
'report': '周报/日报',
|
||||
'travel': '旅行攻略',
|
||||
'meeting': '会议纪要',
|
||||
'resume': '简历优化',
|
||||
'history': '历史记录',
|
||||
}
|
||||
|
||||
|
||||
@export_bp.route('/api/export/<content_type>/<int:content_id>', methods=['GET'])
|
||||
def export_content(content_type, content_id):
|
||||
"""导出内容为 Markdown 文件下载"""
|
||||
if content_type not in CONTENT_MODELS:
|
||||
return jsonify({'success': False, 'message': f'不支持的内容类型: {content_type}'}), 400
|
||||
|
||||
model = CONTENT_MODELS[content_type]
|
||||
record = model.query.get(content_id)
|
||||
if not record:
|
||||
return jsonify({'success': False, 'message': '内容不存在'}), 404
|
||||
|
||||
fields = FIELD_MAP.get(content_type, [])
|
||||
title = TITLE_MAP.get(content_type, '导出内容')
|
||||
|
||||
# 组装 Markdown 内容
|
||||
lines = [f'# {title}', '', f'> 导出时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}', '']
|
||||
for field in fields:
|
||||
val = getattr(record, field, None) or ''
|
||||
lines.append(val)
|
||||
lines.append('')
|
||||
|
||||
markdown = '\n'.join(lines)
|
||||
|
||||
filename = f'{content_type}_{content_id}_{datetime.now().strftime("%Y%m%d%H%M%S")}.md'
|
||||
return Response(
|
||||
markdown,
|
||||
mimetype='text/markdown; charset=utf-8',
|
||||
headers={
|
||||
'Content-Disposition': f'attachment; filename="{filename}"',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@export_bp.route('/api/export/text', methods=['POST'])
|
||||
def export_text():
|
||||
"""直接导出文本内容为 Markdown(无需存储记录)"""
|
||||
data = request.get_json(silent=True) or {}
|
||||
content = (data.get('content') or '').strip()
|
||||
title = (data.get('title') or '导出内容').strip()
|
||||
|
||||
if not content:
|
||||
return jsonify({'success': False, 'message': '内容为空'}), 400
|
||||
|
||||
markdown = f'# {title}\n\n> 导出时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}\n\n{content}\n'
|
||||
filename = f'export_{datetime.now().strftime("%Y%m%d%H%M%S")}.md'
|
||||
return Response(
|
||||
markdown,
|
||||
mimetype='text/markdown; charset=utf-8',
|
||||
headers={
|
||||
'Content-Disposition': f'attachment; filename="{filename}"',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
},
|
||||
)
|
||||
@@ -4,7 +4,6 @@
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify, current_app, render_template
|
||||
from flask_login import login_required, current_user
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import desc, asc, and_, or_, func
|
||||
import json
|
||||
@@ -14,17 +13,11 @@ import io
|
||||
# 导入数据模型
|
||||
from ..models.history_models import PromptHistory, HistoryTag, UserStatistics
|
||||
from ..models.models import PromptTemplate, db
|
||||
from ..user_context import get_current_user_id
|
||||
|
||||
# 创建蓝图
|
||||
history_bp = Blueprint('history', __name__)
|
||||
|
||||
def get_current_user_id():
|
||||
"""获取当前用户ID"""
|
||||
try:
|
||||
return current_user.id if current_user.is_authenticated else 1
|
||||
except:
|
||||
return 1
|
||||
|
||||
@history_bp.route('/history')
|
||||
def history_page():
|
||||
"""历史记录页面"""
|
||||
|
||||
189
src/flask_prompt_master/routes/prompt_quality_routes.py
Normal file
189
src/flask_prompt_master/routes/prompt_quality_routes.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""提示词结构化质量评价 API"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from flask import Blueprint, jsonify, request, current_app
|
||||
from sqlalchemy import desc
|
||||
|
||||
from src.flask_prompt_master import db
|
||||
from src.flask_prompt_master.models.prompt_quality_models import PromptQualityRecord
|
||||
from src.flask_prompt_master.services.prompt_quality_service import analyze_prompts
|
||||
from src.flask_prompt_master.user_context import get_current_user_id
|
||||
|
||||
prompt_quality_bp = Blueprint('prompt_quality', __name__, url_prefix='/api/prompt-quality')
|
||||
|
||||
|
||||
@prompt_quality_bp.route('/analyze', methods=['POST'])
|
||||
def analyze():
|
||||
"""多提示词 → 模型评价 → 落库 → 返回契约 JSON。"""
|
||||
data = request.get_json(silent=True) or {}
|
||||
prompts = data.get('prompts')
|
||||
task_context = (data.get('task_context') or '').strip() or None
|
||||
|
||||
if not isinstance(prompts, list):
|
||||
return jsonify({'success': False, 'message': 'prompts 须为非空数组'}), 400
|
||||
|
||||
try:
|
||||
parsed = analyze_prompts(prompts, task_context)
|
||||
except ValueError as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 400
|
||||
except Exception as e:
|
||||
current_app.logger.exception('prompt-quality analyze')
|
||||
return jsonify({'success': False, 'message': f'分析失败: {e}'}), 500
|
||||
|
||||
user_id = get_current_user_id()
|
||||
batch_id = str(uuid.uuid4())
|
||||
results_out = []
|
||||
summary = parsed.get('batch_comparison') or ''
|
||||
hints = parsed.get('incremental_learning_hints') or []
|
||||
|
||||
try:
|
||||
for idx, item in enumerate(parsed['results']):
|
||||
rec = PromptQualityRecord(
|
||||
user_id=user_id,
|
||||
batch_id=batch_id,
|
||||
prompt_index=idx,
|
||||
optimized_prompt=item['optimized_prompt'],
|
||||
evaluation_json=item['evaluation'],
|
||||
batch_summary=summary if idx == 0 else None,
|
||||
incremental_hints_json=hints if idx == 0 else None,
|
||||
)
|
||||
db.session.add(rec)
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.exception('prompt-quality persist')
|
||||
return jsonify({'success': False, 'message': f'保存评价失败: {e}'}), 500
|
||||
|
||||
rows = (
|
||||
PromptQualityRecord.query.filter_by(batch_id=batch_id)
|
||||
.order_by(PromptQualityRecord.prompt_index.asc())
|
||||
.all()
|
||||
)
|
||||
for rec in rows:
|
||||
results_out.append(rec.to_api_item())
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
'success': True,
|
||||
'data': {
|
||||
'batch_id': batch_id,
|
||||
'results': results_out,
|
||||
'batch_comparison': summary,
|
||||
'incremental_learning_hints': hints,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@prompt_quality_bp.route('/history', methods=['GET'])
|
||||
def list_history():
|
||||
"""按用户、时间、关键词、批次筛选;按记录分页(含 batch_id 便于前端聚合)。"""
|
||||
user_id = get_current_user_id()
|
||||
page = request.args.get('page', 1, type=int) or 1
|
||||
per_page = min(request.args.get('per_page', 15, type=int) or 15, 50)
|
||||
q = (request.args.get('q') or '').strip()
|
||||
batch_id = (request.args.get('batch_id') or '').strip() or None
|
||||
date_from = request.args.get('date_from')
|
||||
date_to = request.args.get('date_to')
|
||||
|
||||
base = PromptQualityRecord.query.filter(PromptQualityRecord.user_id == user_id)
|
||||
if batch_id:
|
||||
base = base.filter(PromptQualityRecord.batch_id == batch_id)
|
||||
if q:
|
||||
base = base.filter(PromptQualityRecord.optimized_prompt.contains(q))
|
||||
if date_from:
|
||||
try:
|
||||
d0 = datetime.fromisoformat(date_from.replace('Z', '+00:00'))
|
||||
if d0.tzinfo:
|
||||
d0 = d0.replace(tzinfo=None)
|
||||
base = base.filter(PromptQualityRecord.created_at >= d0)
|
||||
except ValueError:
|
||||
return jsonify({'success': False, 'message': 'date_from 格式无效'}), 400
|
||||
if date_to:
|
||||
try:
|
||||
d1 = datetime.fromisoformat(date_to.replace('Z', '+00:00'))
|
||||
if d1.tzinfo:
|
||||
d1 = d1.replace(tzinfo=None)
|
||||
base = base.filter(PromptQualityRecord.created_at < d1 + timedelta(days=1))
|
||||
except ValueError:
|
||||
return jsonify({'success': False, 'message': 'date_to 格式无效'}), 400
|
||||
|
||||
base = base.order_by(desc(PromptQualityRecord.created_at))
|
||||
pagination = base.paginate(page=page, per_page=per_page, error_out=False)
|
||||
items = [r.to_history_row() for r in pagination.items]
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
'success': True,
|
||||
'data': {
|
||||
'items': items,
|
||||
'pagination': {
|
||||
'page': pagination.page,
|
||||
'per_page': pagination.per_page,
|
||||
'total': pagination.total,
|
||||
'pages': pagination.pages,
|
||||
'has_next': pagination.has_next,
|
||||
'has_prev': pagination.has_prev,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@prompt_quality_bp.route('/history/<int:record_id>', methods=['GET'])
|
||||
def get_record(record_id: int):
|
||||
user_id = get_current_user_id()
|
||||
rec = PromptQualityRecord.query.filter_by(id=record_id, user_id=user_id).first()
|
||||
if not rec:
|
||||
return jsonify({'success': False, 'message': '记录不存在'}), 404
|
||||
|
||||
batch = (
|
||||
PromptQualityRecord.query.filter_by(batch_id=rec.batch_id, user_id=user_id)
|
||||
.order_by(PromptQualityRecord.prompt_index.asc())
|
||||
.all()
|
||||
)
|
||||
results = [r.to_api_item() for r in batch]
|
||||
summary = next((r.batch_summary for r in batch if r.batch_summary), '') or ''
|
||||
hints = next((r.incremental_hints_json for r in batch if r.incremental_hints_json), []) or []
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
'success': True,
|
||||
'data': {
|
||||
'batch_id': rec.batch_id,
|
||||
'results': results,
|
||||
'batch_comparison': summary,
|
||||
'incremental_learning_hints': hints if isinstance(hints, list) else [],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@prompt_quality_bp.route('/history/batch/<string:batch_uuid>', methods=['GET'])
|
||||
def get_batch(batch_uuid: str):
|
||||
"""按 batch_id 取整批详情(可视化对比)。"""
|
||||
user_id = get_current_user_id()
|
||||
batch = (
|
||||
PromptQualityRecord.query.filter_by(batch_id=batch_uuid, user_id=user_id)
|
||||
.order_by(PromptQualityRecord.prompt_index.asc())
|
||||
.all()
|
||||
)
|
||||
if not batch:
|
||||
return jsonify({'success': False, 'message': '批次不存在'}), 404
|
||||
results = [r.to_api_item() for r in batch]
|
||||
summary = next((r.batch_summary for r in batch if r.batch_summary), '') or ''
|
||||
hints = next((r.incremental_hints_json for r in batch if r.incremental_hints_json), []) or []
|
||||
return jsonify(
|
||||
{
|
||||
'success': True,
|
||||
'data': {
|
||||
'batch_id': batch_uuid,
|
||||
'results': results,
|
||||
'batch_comparison': summary,
|
||||
'incremental_learning_hints': hints if isinstance(hints, list) else [],
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -2,12 +2,13 @@
|
||||
"""
|
||||
简历/求职信优化功能路由
|
||||
"""
|
||||
from flask import Blueprint, render_template, request, jsonify, session
|
||||
from flask import Blueprint, render_template, request, jsonify
|
||||
from openai import OpenAI
|
||||
import logging
|
||||
import os
|
||||
from src.flask_prompt_master import db
|
||||
from src.flask_prompt_master.models.models import User, ResumeOptimization
|
||||
from src.flask_prompt_master.models.models import ResumeOptimization
|
||||
from src.flask_prompt_master.user_context import get_current_user_id
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
resume_optimization_bp = Blueprint('resume_optimization', __name__)
|
||||
@@ -51,18 +52,6 @@ def _generate_optimization(opt_type: str, original: str, job_description: str) -
|
||||
return response.choices[0].message.content
|
||||
|
||||
|
||||
def _get_resume_user_id():
|
||||
user_id = session.get('user_id')
|
||||
if user_id is not None:
|
||||
return user_id
|
||||
try:
|
||||
u = User.query.filter_by(login_name='admin').first()
|
||||
return u.uid if u else 1
|
||||
except Exception as e:
|
||||
logger.warning(f"获取默认用户失败: {e}")
|
||||
return 1
|
||||
|
||||
|
||||
@resume_optimization_bp.route('/resume-optimization', methods=['GET'])
|
||||
def optimization_page():
|
||||
"""简历/求职信优化页面"""
|
||||
@@ -100,7 +89,7 @@ def save_optimization_api():
|
||||
optimized_content = (data.get('optimized_content') or '').strip()
|
||||
if not optimized_content:
|
||||
return jsonify({'success': False, 'message': '优化内容不能为空'})
|
||||
user_id = _get_resume_user_id()
|
||||
user_id = get_current_user_id()
|
||||
r = ResumeOptimization(user_id=user_id, opt_type=opt_type, original_content=original_content, job_description=job_description or None, optimized_content=optimized_content)
|
||||
db.session.add(r)
|
||||
db.session.commit()
|
||||
@@ -114,7 +103,7 @@ def save_optimization_api():
|
||||
@resume_optimization_bp.route('/api/resume-optimization/list', methods=['GET'])
|
||||
def list_optimization_api():
|
||||
try:
|
||||
user_id = _get_resume_user_id()
|
||||
user_id = get_current_user_id()
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
pag = ResumeOptimization.query.filter_by(user_id=user_id).order_by(ResumeOptimization.created_at.desc()).paginate(page=page, per_page=per_page, error_out=False)
|
||||
@@ -128,7 +117,7 @@ def list_optimization_api():
|
||||
@resume_optimization_bp.route('/api/resume-optimization/<int:rid>', methods=['GET'])
|
||||
def get_optimization_api(rid):
|
||||
try:
|
||||
user_id = _get_resume_user_id()
|
||||
user_id = get_current_user_id()
|
||||
r = ResumeOptimization.query.filter_by(id=rid, user_id=user_id).first()
|
||||
if not r:
|
||||
return jsonify({'success': False, 'message': '记录不存在'})
|
||||
@@ -140,7 +129,7 @@ def get_optimization_api(rid):
|
||||
@resume_optimization_bp.route('/api/resume-optimization/<int:rid>', methods=['DELETE'])
|
||||
def delete_optimization_api(rid):
|
||||
try:
|
||||
user_id = _get_resume_user_id()
|
||||
user_id = get_current_user_id()
|
||||
r = ResumeOptimization.query.filter_by(id=rid, user_id=user_id).first()
|
||||
if not r:
|
||||
return jsonify({'success': False, 'message': '记录不存在'})
|
||||
|
||||
@@ -3,6 +3,7 @@ from openai import OpenAI
|
||||
from src.flask_prompt_master import db
|
||||
from src.flask_prompt_master.models import User, Prompt, Feedback, PromptTemplate, WxUser
|
||||
from src.flask_prompt_master.models.history_models import PromptHistory, UserStatistics
|
||||
from src.flask_prompt_master.user_context import get_current_user_id
|
||||
from src.flask_prompt_master.forms import PromptForm, FeedbackForm
|
||||
from src.flask_prompt_master.config import Config
|
||||
import pymysql
|
||||
@@ -48,20 +49,28 @@ def get_system_prompt(template_id=None):
|
||||
|
||||
请直接返回优化后的提示词,不要添加任何解释或其他内容。"""
|
||||
|
||||
def generate_with_llm(input_text, template_id=None, max_retries=3):
|
||||
def generate_with_llm(input_text, template_id=None, max_retries=3, max_tokens=None):
|
||||
"""调用大模型API生成提示词,带重试机制"""
|
||||
import time
|
||||
|
||||
|
||||
system_prompt = get_system_prompt(template_id)
|
||||
start_time = time.time() # 记录开始时间
|
||||
|
||||
|
||||
# 确定 max_tokens: 参数传入 > 模板配置 > 默认 500
|
||||
_max_tokens = max_tokens or 500
|
||||
if not max_tokens and template_id:
|
||||
template = PromptTemplate.query.get(template_id)
|
||||
if template and template.max_tokens:
|
||||
_max_tokens = template.max_tokens
|
||||
|
||||
# 记录参数到日志
|
||||
current_app.logger.info("=== API 调用参数 ===")
|
||||
current_app.logger.info(f"模板ID: {template_id}")
|
||||
current_app.logger.info(f"输入文本: {input_text}")
|
||||
current_app.logger.info(f"系统提示: {system_prompt}")
|
||||
current_app.logger.info(f"max_tokens: {_max_tokens}")
|
||||
current_app.logger.info("==================")
|
||||
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
@@ -71,7 +80,7 @@ def generate_with_llm(input_text, template_id=None, max_retries=3):
|
||||
{"role": "user", "content": input_text}
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=500,
|
||||
max_tokens=_max_tokens,
|
||||
timeout=60 # 设置60秒超时
|
||||
)
|
||||
|
||||
@@ -149,15 +158,6 @@ def save_to_history(input_text, generated_text, template_id=None, generation_tim
|
||||
current_app.logger.error(f"保存历史记录失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_current_user_id():
|
||||
"""获取当前用户ID"""
|
||||
try:
|
||||
# 这里应该根据实际的用户认证系统来获取用户ID
|
||||
# 暂时返回默认用户ID
|
||||
return 1
|
||||
except:
|
||||
return 1
|
||||
|
||||
def get_template_icon(category):
|
||||
"""根据分类返回对应的Font Awesome图标类名"""
|
||||
icons = {
|
||||
@@ -174,6 +174,59 @@ def get_template_icon(category):
|
||||
}
|
||||
return icons.get(category, 'fa-star') # 默认返回星星图标
|
||||
|
||||
|
||||
def build_sample_input_text(template):
|
||||
"""选择模板时填入输入框的示例需求文案(不自动调用 LLM)"""
|
||||
name = (template.name or '模板').strip()
|
||||
desc = (template.description or '').strip()
|
||||
if desc:
|
||||
return (
|
||||
f"我的需求背景:\n{desc}\n\n"
|
||||
f"请结合「{name}」的能力,帮我生成专业提示词。可补充:受众、语气、篇幅与输出格式。"
|
||||
)
|
||||
return (
|
||||
f"我想使用「{name}」完成以下任务:\n\n"
|
||||
f"【请描述具体目标、约束条件、参考材料与期望的输出形式】"
|
||||
)
|
||||
|
||||
|
||||
def select_quick_start_templates(template_list, limit=5):
|
||||
"""从当前列表中选取若干模板用于「快速开始」"""
|
||||
pool = list(template_list or [])
|
||||
if not pool:
|
||||
return []
|
||||
|
||||
def score(t):
|
||||
s = 0
|
||||
if getattr(t, 'is_default', False):
|
||||
s += 100
|
||||
if getattr(t, 'is_hot', False):
|
||||
s += 50
|
||||
if getattr(t, 'is_recommended', False):
|
||||
s += 40
|
||||
if getattr(t, 'is_featured', False):
|
||||
s += 30
|
||||
return s
|
||||
|
||||
pool.sort(key=lambda t: (-score(t), (t.name or '')))
|
||||
out = []
|
||||
seen = set()
|
||||
for t in pool:
|
||||
if t.id in seen:
|
||||
continue
|
||||
seen.add(t.id)
|
||||
out.append(t)
|
||||
if len(out) >= limit:
|
||||
break
|
||||
return out
|
||||
|
||||
|
||||
@main_bp.route('/featured')
|
||||
def featured_hub():
|
||||
"""特色功能:更多 AI 应用入口(二级页面)"""
|
||||
return render_template('featured_hub.html')
|
||||
|
||||
|
||||
@main_bp.route('/', methods=['GET', 'POST'])
|
||||
def index():
|
||||
form = PromptForm()
|
||||
@@ -183,6 +236,8 @@ def index():
|
||||
|
||||
# 默认只显示"通用"分类的模板,提升加载性能
|
||||
templates = PromptTemplate.query.filter_by(category="通用").all()
|
||||
sample_inputs = {t.id: build_sample_input_text(t) for t in templates}
|
||||
quick_start_templates = select_quick_start_templates(templates, 5)
|
||||
|
||||
# 获取所有可用的分类选项(基于所有模板)
|
||||
industries = sorted(set(t.industry for t in all_templates if t.industry))
|
||||
@@ -207,7 +262,9 @@ def index():
|
||||
return render_template('generate.html', form=form, prompt=None, templates=templates,
|
||||
get_template_icon=get_template_icon, industries=industries,
|
||||
professions=professions, categories=categories,
|
||||
sub_categories=sub_categories)
|
||||
sub_categories=sub_categories,
|
||||
sample_inputs=sample_inputs,
|
||||
quick_start_templates=quick_start_templates)
|
||||
|
||||
# 调用LLM生成提示词
|
||||
generated_text = generate_with_llm(input_text, template_id)
|
||||
@@ -218,7 +275,9 @@ def index():
|
||||
return render_template('generate.html', form=form, prompt=None, templates=templates,
|
||||
get_template_icon=get_template_icon, industries=industries,
|
||||
professions=professions, categories=categories,
|
||||
sub_categories=sub_categories)
|
||||
sub_categories=sub_categories,
|
||||
sample_inputs=sample_inputs,
|
||||
quick_start_templates=quick_start_templates)
|
||||
|
||||
# 获取搜索状态
|
||||
search_state = request.form.get('search_state', '')
|
||||
@@ -250,7 +309,9 @@ def index():
|
||||
get_template_icon=get_template_icon, industries=industries,
|
||||
professions=professions, categories=categories,
|
||||
sub_categories=sub_categories, selected_template_id=template_id,
|
||||
search_state=search_state)
|
||||
search_state=search_state,
|
||||
sample_inputs=sample_inputs,
|
||||
quick_start_templates=quick_start_templates)
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"生成提示词时发生错误: {str(e)}")
|
||||
@@ -259,11 +320,15 @@ def index():
|
||||
return render_template('generate.html', form=form, prompt=None, templates=templates,
|
||||
get_template_icon=get_template_icon, industries=industries,
|
||||
professions=professions, categories=categories,
|
||||
sub_categories=sub_categories)
|
||||
sub_categories=sub_categories,
|
||||
sample_inputs=sample_inputs,
|
||||
quick_start_templates=quick_start_templates)
|
||||
return render_template('generate.html', form=form, prompt=None, templates=templates,
|
||||
get_template_icon=get_template_icon, industries=industries,
|
||||
professions=professions, categories=categories,
|
||||
sub_categories=sub_categories)
|
||||
sub_categories=sub_categories,
|
||||
sample_inputs=sample_inputs,
|
||||
quick_start_templates=quick_start_templates)
|
||||
|
||||
@main_bp.route('/api/templates/<category>')
|
||||
def get_templates_by_category(category):
|
||||
@@ -284,16 +349,112 @@ def get_templates_by_category(category):
|
||||
'industry': template.industry,
|
||||
'profession': template.profession,
|
||||
'sub_category': template.sub_category,
|
||||
'system_prompt': template.system_prompt
|
||||
'system_prompt': template.system_prompt,
|
||||
'max_tokens': template.max_tokens or 500,
|
||||
'sample_input': build_sample_input_text(template),
|
||||
}
|
||||
template_list.append(template_dict)
|
||||
|
||||
|
||||
qs = select_quick_start_templates(templates, 5)
|
||||
quick_start = [
|
||||
{'id': t.id, 'name': t.name, 'sample_input': build_sample_input_text(t)}
|
||||
for t in qs
|
||||
]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'templates': template_list,
|
||||
'count': len(template_list)
|
||||
'count': len(template_list),
|
||||
'quick_start': quick_start,
|
||||
})
|
||||
|
||||
|
||||
@main_bp.route('/api/generate/meta', methods=['GET'])
|
||||
def api_generate_meta():
|
||||
"""Vue / SPA:筛选维度与场景分类(与首页 generate 页数据源一致)"""
|
||||
all_templates = PromptTemplate.query.all()
|
||||
industries = sorted(set(t.industry for t in all_templates if t.industry))
|
||||
professions = sorted(set(t.profession for t in all_templates if t.profession))
|
||||
all_categories = sorted(set(t.category for t in all_templates if t.category))
|
||||
categories = []
|
||||
if '通用' in all_categories:
|
||||
categories.append('通用')
|
||||
all_categories.remove('通用')
|
||||
categories.extend(all_categories)
|
||||
sub_categories = sorted(set(t.sub_category for t in all_templates if t.sub_category))
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'categories': categories,
|
||||
'industries': industries,
|
||||
'professions': professions,
|
||||
'sub_categories': sub_categories,
|
||||
})
|
||||
|
||||
|
||||
@main_bp.route('/api/prompt/generate', methods=['POST'])
|
||||
def api_prompt_generate():
|
||||
"""Vue / SPA:JSON 提交生成提示词(逻辑与首页 POST / 对齐)"""
|
||||
data = request.get_json(silent=True) or {}
|
||||
input_text = (data.get('input_text') or '').strip()
|
||||
template_id = data.get('template_id')
|
||||
if template_id is not None and template_id != '':
|
||||
try:
|
||||
template_id = int(template_id)
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({'success': False, 'message': '模板 ID 无效'}), 400
|
||||
else:
|
||||
template_id = None
|
||||
|
||||
if not input_text:
|
||||
return jsonify({'success': False, 'message': '请输入您的需求描述'}), 400
|
||||
if len(input_text) > 1000:
|
||||
return jsonify({'success': False, 'message': '需求描述请勿超过 1000 字'}), 400
|
||||
|
||||
# 可选 max_tokens 覆盖
|
||||
_mt = data.get('max_tokens')
|
||||
_max_tokens = None
|
||||
if _mt is not None and _mt != '':
|
||||
try:
|
||||
_max_tokens = int(_mt)
|
||||
_max_tokens = max(100, min(4096, _max_tokens))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
try:
|
||||
generated_text = generate_with_llm(input_text, template_id, max_tokens=_max_tokens)
|
||||
if not generated_text or (
|
||||
isinstance(generated_text, str) and generated_text.startswith('提示词生成失败')
|
||||
):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': generated_text or '生成失败',
|
||||
}), 500
|
||||
|
||||
user_id = get_current_user_id()
|
||||
|
||||
prompt = Prompt(
|
||||
input_text=input_text,
|
||||
generated_text=generated_text,
|
||||
user_id=user_id,
|
||||
)
|
||||
db.session.add(prompt)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'generated_text': generated_text,
|
||||
'prompt': {
|
||||
'id': prompt.id,
|
||||
'input_text': input_text,
|
||||
'generated_text': generated_text,
|
||||
},
|
||||
})
|
||||
except Exception as e:
|
||||
current_app.logger.error('api_prompt_generate: %s', str(e))
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'message': f'生成时发生错误: {str(e)}'}), 500
|
||||
|
||||
|
||||
@main_bp.route('/prompt/<int:prompt_id>')
|
||||
def show_prompt(prompt_id):
|
||||
prompt = Prompt.query.get_or_404(prompt_id)
|
||||
@@ -368,6 +529,35 @@ def delete_template(template_id):
|
||||
}), 500
|
||||
|
||||
# 添加微信小程序API路由
|
||||
@main_bp.route('/api/prompt/test', methods=['POST'])
|
||||
def api_prompt_test():
|
||||
"""用生成的提示词 + 测试输入实际调用 LLM,查看效果"""
|
||||
data = request.get_json(silent=True) or {}
|
||||
prompt_text = (data.get('prompt_text') or '').strip()
|
||||
test_input = (data.get('test_input') or '').strip()
|
||||
if not prompt_text:
|
||||
return jsonify({'success': False, 'message': '缺少提示词内容'}), 400
|
||||
if not test_input:
|
||||
return jsonify({'success': False, 'message': '请输入测试内容'}), 400
|
||||
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model='deepseek-chat',
|
||||
messages=[
|
||||
{'role': 'system', 'content': prompt_text},
|
||||
{'role': 'user', 'content': test_input},
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=1000,
|
||||
timeout=60,
|
||||
)
|
||||
test_output = response.choices[0].message.content.strip()
|
||||
return jsonify({'success': True, 'test_output': test_output})
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'prompt_test error: {e}')
|
||||
return jsonify({'success': False, 'message': f'测试调用失败: {str(e)}'}), 500
|
||||
|
||||
|
||||
@main_bp.route('/api/wx/generate', methods=['POST'])
|
||||
def wx_generate_prompt():
|
||||
"""微信小程序生成提示词接口"""
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
智能提示词优化:根据用户需求描述先生成对应的优化模板,再根据模板优化用户提示词。
|
||||
复用项目默认的专家提示词生成器逻辑(意图分析 + 领域)来生成「优化师模板」。
|
||||
"""
|
||||
from flask import Blueprint, render_template, request, jsonify, session
|
||||
from flask import Blueprint, render_template, request, jsonify
|
||||
from openai import OpenAI
|
||||
import json
|
||||
import logging
|
||||
@@ -11,6 +11,7 @@ import os
|
||||
from src.flask_prompt_master import db
|
||||
from src.flask_prompt_master.models.models import User, Prompt
|
||||
from src.flask_prompt_master.models.history_models import PromptHistory, UserStatistics
|
||||
from src.flask_prompt_master.user_context import get_current_user_id
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
smart_prompt_optimization_bp = Blueprint('smart_prompt_optimization', __name__)
|
||||
@@ -107,17 +108,6 @@ def _optimize_with_template(optimizer_system: str, prompt_to_optimize: str) -> s
|
||||
return resp.choices[0].message.content.strip()
|
||||
|
||||
|
||||
def _get_user_id():
|
||||
"""与历史页 get_current_user_id 完全一致,未登录统一用 1,保证历史列表能查到"""
|
||||
try:
|
||||
from flask_login import current_user
|
||||
if current_user.is_authenticated:
|
||||
return getattr(current_user, 'id', None) or getattr(current_user, 'uid', None)
|
||||
except Exception:
|
||||
pass
|
||||
return 1
|
||||
|
||||
|
||||
@smart_prompt_optimization_bp.route('/smart-prompt-optimization', methods=['GET'])
|
||||
def optimization_page():
|
||||
"""智能提示词优化应用页"""
|
||||
@@ -153,7 +143,7 @@ def generate_api():
|
||||
if not generated_prompt:
|
||||
return jsonify({'code': 500, 'message': '优化失败,请重试', 'data': None})
|
||||
|
||||
user_id = _get_user_id()
|
||||
user_id = get_current_user_id()
|
||||
# 保存到 Prompt 表
|
||||
try:
|
||||
p = Prompt(input_text=user_input, generated_text=generated_prompt, user_id=user_id)
|
||||
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user