Compare commits

11 Commits

Author SHA1 Message Date
renjianbo
daf7c1867a feat(S5): prompt effect verification - test generated prompt against LLM
- Backend: POST /api/prompt/test uses generated prompt as system_prompt
- Frontend: test input card, run test button, output display
- testPrompt API module with typed response

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 09:56:02 +08:00
renjianbo
e2c0b6b87a feat(S4): new user onboarding wizard with 3-step guided flow
- Backend: check-login API returns is_new_user flag (no prompt history)
- Vue: OnboardingWizard component (scene select → tips → ready)
- Vue: HomeView conditionally shows wizard for new users
- Auth store: expose isNewUser state, auto-detect on refresh

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 09:54:32 +08:00
renjianbo
5cd7e1eb30 feat(S3): content export - Markdown download and PDF print
- Backend: GET /api/export/:type/:id returns .md file download
- Backend: POST /api/export/text for direct text-to-md export
- Frontend: downloadMarkdown() utility for client-side .md download
- Frontend: printAsPdf() utility via browser print dialog
- GenerateView: export dropdown button (MD / PDF) on result card

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 09:51:48 +08:00
renjianbo
53d179dd00 feat(S1): iterative conversation for multi-round prompt refinement
- Conversation model: store dialog context (max 10 rounds), JSON messages
- POST /api/prompt/continue: append round, build LLM context from history
- GET/DELETE /api/conversation/🆔 retrieve or clear conversation
- Vue: refine input card below result, round counter, reset button
- Vue: continuePrompt API with conversation_id tracking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 09:49:22 +08:00
renjianbo
eb5056b5f2 feat(S2): max_tokens dynamic from template, remove hardcoded 500
- PromptTemplate model: add max_tokens (default 500) and example_input fields
- generate_with_llm: read max_tokens from template, accept override param
- api_prompt_generate: accept optional max_tokens with 100-4096 clamp
- Vue: add advanced options toggle with max_tokens input
- Vue: auto-populate max_tokens from selected template

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 09:46:12 +08:00
renjianbo
ba73f8395a chore: add project analysis doc, clean up legacy scripts, update docs
- Add comprehensive project analysis document (项目分析文档.md)
- Add Windows startup guide and batch script
- Replace legacy startup scripts with run_production.py (Waitress)
- Remove deprecated bat scripts and duplicate run files
- Update .env.example, README.md, and docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 08:55:43 +08:00
renjianbo
5dad35de82 feat(prompt-quality): 质量评价与对比模块 API、脚本与 Vue 页面
Made-with: Cursor
2026-04-06 19:02:21 +08:00
renjianbo
daa34582e9 feat(vue-app,flask): Vue 试验田全量对接与 Session 用户上下文统一
新增 vue-app(生成/收藏/历史/登录/优化/Android/饭菜/诗词/简历等),Flask 增加 user_context 并调整历史、生成、简历等路由;模板 base/generate 可访问性改进;补充部署说明与文档。

Made-with: Cursor
2026-04-05 21:10:41 +08:00
renjianbo
9a3f15f3e2 feat(ui): 特色功能独立为/featured页面并加入导航;生成页筛选状态、移动端抽屉、快速开始与示例需求等体验优化
Made-with: Cursor
2026-04-03 12:15:30 +08:00
renjianbo
94cbc7f7c0 feat: 添加版本号 v1.0.0 和版本管理功能
- 在 src/flask_prompt_master/__init__.py 添加 __version__ 和 __version_info__
- 创建 VERSION 文件存储版本号
- 更新所有环境配置(development/production/testing/local/base)在启动时显示版本信息
- 更新 README.md 文档添加版本号显示

版本号: v1.0.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 18:02:02 +08:00
renjianbo
f19668ca3a first commit 2026-04-02 17:39:09 +08:00
201 changed files with 19336 additions and 2634 deletions

View 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
View 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

View 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
View 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
View 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
View File

@@ -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/

View File

@@ -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

1
VERSION Normal file
View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -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)

View File

@@ -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}')

View File

@@ -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}')

View File

@@ -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}')
# 可以在这里添加本地特定的初始化代码
# 例如:加载本地开发工具、设置本地调试器等

View File

@@ -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}')

View File

@@ -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}')

View File

@@ -80,7 +80,7 @@ python init_db.py
python run_dev.py
```
访问 http://localhost:5000 开始使用
访问 http://localhost:5002 开始使用。仅 Windows 本机启停见仓库根目录 `windows上唯一启动说明.md``windows启动.bat`
## 项目结构

View File

@@ -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 服务配置已完成,应用现在可以作为系统服务运行,支持开机自启动和后台运行。所有核心功能都已实现并测试通过。🎉

View File

@@ -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 系统,其他操作系统请参考相应的服务配置方法。

View File

@@ -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
**维护者**: 开发团队

View File

@@ -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. 在服务启动时自动启动监控

View File

@@ -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容器
- **并发能力**:理论上无上限,取决于服务器资源

View 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` 钳制 0100。
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 映射为 TEXTMySQL 可用原生 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` 注册蓝图并加载模型 |
| 手工建表 SQLSQLite 友好) | `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` |

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3
"""生产环境启动Waitress WSGIWindows 友好)。说明见仓库根目录 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)

View File

@@ -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()

View File

@@ -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()

View 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='对比组表';

View File

@@ -0,0 +1,39 @@
-- 开发环境默认 SQLiteconfig/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);

View 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);

View 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())

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'))

View File

@@ -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(',')

View 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

View 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

View File

@@ -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):

View 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,
}

View File

@@ -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:

View File

@@ -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'])

View 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

View 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

View 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

View 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',
},
)

View File

@@ -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():
"""历史记录页面"""

View 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 [],
},
}
)

View File

@@ -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': '记录不存在'})

View File

@@ -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 / SPAJSON 提交生成提示词(逻辑与首页 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():
"""微信小程序生成提示词接口"""

View File

@@ -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)

Some files were not shown because too many files have changed in this diff Show More