feat: add prompt/personalization selection and conversation APIs
This commit is contained in:
parent
82e7d0680a
commit
9f7b443268
@ -24,6 +24,10 @@
|
|||||||
4. 停止任务:`POST /api/v1/tasks/<task_id>/cancel`
|
4. 停止任务:`POST /api/v1/tasks/<task_id>/cancel`
|
||||||
5. 文件上传(仅 user_upload):`POST /api/v1/files/upload`
|
5. 文件上传(仅 user_upload):`POST /api/v1/files/upload`
|
||||||
6. 文件浏览/下载:`GET /api/v1/files`、`GET /api/v1/files/download`
|
6. 文件浏览/下载:`GET /api/v1/files`、`GET /api/v1/files/download`
|
||||||
|
7. 对话查询/删除:`GET /api/v1/conversations`、`GET/DELETE /api/v1/conversations/{id}`
|
||||||
|
8. Prompt 管理:`GET/POST /api/v1/prompts`、`GET /api/v1/prompts/{name}`
|
||||||
|
9. 个性化管理:`GET/POST /api/v1/personalizations`、`GET /api/v1/personalizations/{name}`
|
||||||
|
10. 模型列表与健康检查:`GET /api/v1/models`、`GET /api/v1/health`
|
||||||
|
|
||||||
详细参数与返回请看:
|
详细参数与返回请看:
|
||||||
|
|
||||||
@ -31,6 +35,7 @@
|
|||||||
- `messages_tasks.md`:发送消息/轮询/停止
|
- `messages_tasks.md`:发送消息/轮询/停止
|
||||||
- `events.md`:事件流格式与事件类型说明(与 WebSocket 同源)
|
- `events.md`:事件流格式与事件类型说明(与 WebSocket 同源)
|
||||||
- `files.md`:上传/列目录/下载
|
- `files.md`:上传/列目录/下载
|
||||||
|
- `prompts_personalization.md`:Prompt 与个性化管理
|
||||||
- `errors.md`:HTTP 错误码与常见排查
|
- `errors.md`:HTTP 错误码与常见排查
|
||||||
- `examples.md`:curl/Python/JS/Flutter 示例
|
- `examples.md`:curl/Python/JS/Flutter 示例
|
||||||
- `openapi.yaml`:OpenAPI 3.0 规范(可导入 Postman/Swagger)
|
- `openapi.yaml`:OpenAPI 3.0 规范(可导入 Postman/Swagger)
|
||||||
|
|||||||
@ -56,6 +56,8 @@ Body(JSON):
|
|||||||
| `run_mode` | string/null | 否 | 运行模式:`"fast"` \| `"thinking"` \| `"deep"`;若传入则优先使用 |
|
| `run_mode` | string/null | 否 | 运行模式:`"fast"` \| `"thinking"` \| `"deep"`;若传入则优先使用 |
|
||||||
| `thinking_mode` | boolean/null | 否 | 兼容字段:true=thinking,false=fast;当 `run_mode` 为空时才使用 |
|
| `thinking_mode` | boolean/null | 否 | 兼容字段:true=thinking,false=fast;当 `run_mode` 为空时才使用 |
|
||||||
| `max_iterations` | integer/null | 否 | 最大迭代次数,默认服务端配置为 **100**(`config.MAX_ITERATIONS_PER_TASK`);传入可覆盖 |
|
| `max_iterations` | integer/null | 否 | 最大迭代次数,默认服务端配置为 **100**(`config.MAX_ITERATIONS_PER_TASK`);传入可覆盖 |
|
||||||
|
| `prompt_name` | string/null | 否 | 选择自定义主 prompt(存放于 `data/prompts/<name>.txt`);若不存在返回 404 |
|
||||||
|
| `personalization_name` | string/null | 否 | 选择个性化配置(`data/personalization/<name>.json`);若不存在返回 404 |
|
||||||
| `images` | string[] | 否 | 图片路径列表(服务端可访问的路径);一般配合特定模型使用 |
|
| `images` | string[] | 否 | 图片路径列表(服务端可访问的路径);一般配合特定模型使用 |
|
||||||
|
|
||||||
优先级:`run_mode` > `thinking_mode` > 终端当前配置。`run_mode="deep"` 将启用深度思考模式(若模型与配置允许)。
|
优先级:`run_mode` > `thinking_mode` > 终端当前配置。`run_mode="deep"` 将启用深度思考模式(若模型与配置允许)。
|
||||||
|
|||||||
58
api_doc/prompts_personalization.md
Normal file
58
api_doc/prompts_personalization.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Prompt 与个性化(Personalization)管理 API
|
||||||
|
|
||||||
|
## Prompt(主提示词)存储位置
|
||||||
|
- 路径:`api/users/<user>/data/prompts/<name>.txt`
|
||||||
|
- 内容格式:纯文本
|
||||||
|
|
||||||
|
## 个性化存储位置
|
||||||
|
- 路径:`api/users/<user>/data/personalization/<name>.json`
|
||||||
|
- 内容格式:JSON,对应原有 personalization 配置结构
|
||||||
|
|
||||||
|
## 接口
|
||||||
|
|
||||||
|
### 列出 Prompt
|
||||||
|
`GET /api/v1/prompts`
|
||||||
|
|
||||||
|
响应:
|
||||||
|
```json
|
||||||
|
{ "success": true, "items": [ { "name": "default", "size": 123, "updated_at": 1769182550.59 } ] }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取 Prompt 内容
|
||||||
|
`GET /api/v1/prompts/{name}`
|
||||||
|
|
||||||
|
响应:
|
||||||
|
```json
|
||||||
|
{ "success": true, "name": "custom_a", "content": "你的主系统提示..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建/覆盖 Prompt
|
||||||
|
`POST /api/v1/prompts`
|
||||||
|
```json
|
||||||
|
{ "name": "custom_a", "content": "你的主系统提示内容" }
|
||||||
|
```
|
||||||
|
成功返回 `{ "success": true, "name": "custom_a" }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 列出个性化
|
||||||
|
`GET /api/v1/personalizations`
|
||||||
|
|
||||||
|
### 获取个性化内容
|
||||||
|
`GET /api/v1/personalizations/{name}`
|
||||||
|
|
||||||
|
### 创建/覆盖个性化
|
||||||
|
`POST /api/v1/personalizations`
|
||||||
|
```json
|
||||||
|
{ "name": "biz_mobile", "content": { ... personalization json ... } }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 在对话/消息中使用
|
||||||
|
|
||||||
|
- `POST /api/v1/conversations` 可选参数:`prompt_name`、`personalization_name`,会写入对话元数据并在后续消息中应用。
|
||||||
|
- `POST /api/v1/messages` 可选参数:`prompt_name`、`personalization_name`,立即应用并写入元数据。
|
||||||
|
- 元数据字段:`custom_prompt_name`、`personalization_name`;对话加载时会自动套用对应文件(若不存在则忽略)。
|
||||||
|
|
||||||
|
优先级:调用时传入 > 对话元数据 > 默认主 prompt / 默认个性化配置。
|
||||||
@ -2542,7 +2542,8 @@ class MainTerminal:
|
|||||||
)
|
)
|
||||||
messages.append({"role": "system", "content": thinking_prompt})
|
messages.append({"role": "system", "content": thinking_prompt})
|
||||||
|
|
||||||
personalization_config = load_personalization_config(self.data_dir)
|
# 支持按对话覆盖的个性化配置
|
||||||
|
personalization_config = getattr(self.context_manager, "custom_personalization_config", None) or load_personalization_config(self.data_dir)
|
||||||
personalization_block = build_personalization_prompt(personalization_config, include_header=False)
|
personalization_block = build_personalization_prompt(personalization_config, include_header=False)
|
||||||
if personalization_block:
|
if personalization_block:
|
||||||
personalization_template = self.load_prompt("personalization").strip()
|
personalization_template = self.load_prompt("personalization").strip()
|
||||||
|
|||||||
267
server/api_v1.py
267
server/api_v1.py
@ -1,18 +1,20 @@
|
|||||||
"""API v1:Bearer Token 版轻量接口(后台任务 + 轮询 + 文件)。"""
|
"""API v1:Bearer Token 版轻量接口(后台任务 + 轮询 + 文件)。"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
import zipfile
|
import zipfile
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
from flask import Blueprint, request, jsonify, send_file, session
|
from flask import Blueprint, request, jsonify, send_file, session
|
||||||
|
|
||||||
from .api_auth import api_token_required
|
from .api_auth import api_token_required
|
||||||
from .tasks import task_manager
|
from .tasks import task_manager
|
||||||
from .context import get_user_resources, ensure_conversation_loaded, get_upload_guard
|
from .context import get_user_resources, ensure_conversation_loaded, get_upload_guard, apply_conversation_overrides
|
||||||
from .files import sanitize_filename_preserve_unicode
|
from .files import sanitize_filename_preserve_unicode
|
||||||
from .utils_common import debug_log
|
from .utils_common import debug_log
|
||||||
|
from config.model_profiles import MODEL_PROFILES
|
||||||
|
|
||||||
api_v1_bp = Blueprint("api_v1", __name__, url_prefix="/api/v1")
|
api_v1_bp = Blueprint("api_v1", __name__, url_prefix="/api/v1")
|
||||||
|
|
||||||
@ -30,6 +32,40 @@ def _within_uploads(workspace, rel_path: str) -> Path:
|
|||||||
return target
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
def _conversation_path(workspace, conv_id: str) -> Path:
|
||||||
|
return Path(workspace.data_dir) / "conversations" / f"{conv_id}.json"
|
||||||
|
|
||||||
|
|
||||||
|
def _update_conversation_metadata(workspace, conv_id: str, updates: Dict[str, Any]):
|
||||||
|
path = _conversation_path(workspace, conv_id)
|
||||||
|
if not path.exists():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
data = {}
|
||||||
|
try:
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
except Exception:
|
||||||
|
data = {}
|
||||||
|
meta = data.get("metadata") or {}
|
||||||
|
meta.update({k: v for k, v in updates.items() if v is not None})
|
||||||
|
data["metadata"] = meta
|
||||||
|
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
except Exception as exc:
|
||||||
|
debug_log(f"[meta] 更新对话元数据失败: {exc}")
|
||||||
|
|
||||||
|
|
||||||
|
def _prompt_dir(workspace):
|
||||||
|
p = Path(workspace.data_dir) / "prompts"
|
||||||
|
p.mkdir(parents=True, exist_ok=True)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def _personalization_dir(workspace):
|
||||||
|
p = Path(workspace.data_dir) / "personalization"
|
||||||
|
p.mkdir(parents=True, exist_ok=True)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
@api_v1_bp.route("/conversations", methods=["POST"])
|
@api_v1_bp.route("/conversations", methods=["POST"])
|
||||||
@api_token_required
|
@api_token_required
|
||||||
def create_conversation_api():
|
def create_conversation_api():
|
||||||
@ -37,10 +73,21 @@ def create_conversation_api():
|
|||||||
terminal, workspace = get_user_resources(username)
|
terminal, workspace = get_user_resources(username)
|
||||||
if not terminal:
|
if not terminal:
|
||||||
return jsonify({"success": False, "error": "系统未初始化"}), 503
|
return jsonify({"success": False, "error": "系统未初始化"}), 503
|
||||||
|
payload = request.get_json(silent=True) or {}
|
||||||
|
prompt_name = payload.get("prompt_name")
|
||||||
|
personalization_name = payload.get("personalization_name")
|
||||||
result = terminal.create_new_conversation()
|
result = terminal.create_new_conversation()
|
||||||
if not result.get("success"):
|
if not result.get("success"):
|
||||||
return jsonify({"success": False, "error": result.get("error") or "创建对话失败"}), 500
|
return jsonify({"success": False, "error": result.get("error") or "创建对话失败"}), 500
|
||||||
return jsonify({"success": True, "conversation_id": result.get("conversation_id")})
|
conv_id = result.get("conversation_id")
|
||||||
|
# 记录元数据,稍后消息时可自动应用
|
||||||
|
_update_conversation_metadata(workspace, conv_id, {
|
||||||
|
"custom_prompt_name": prompt_name,
|
||||||
|
"personalization_name": personalization_name,
|
||||||
|
})
|
||||||
|
# 立即应用覆盖
|
||||||
|
apply_conversation_overrides(terminal, workspace, conv_id)
|
||||||
|
return jsonify({"success": True, "conversation_id": conv_id})
|
||||||
|
|
||||||
|
|
||||||
@api_v1_bp.route("/messages", methods=["POST"])
|
@api_v1_bp.route("/messages", methods=["POST"])
|
||||||
@ -55,6 +102,8 @@ def send_message_api():
|
|||||||
thinking_mode = payload.get("thinking_mode")
|
thinking_mode = payload.get("thinking_mode")
|
||||||
run_mode = payload.get("run_mode")
|
run_mode = payload.get("run_mode")
|
||||||
max_iterations = payload.get("max_iterations")
|
max_iterations = payload.get("max_iterations")
|
||||||
|
prompt_name = payload.get("prompt_name")
|
||||||
|
personalization_name = payload.get("personalization_name")
|
||||||
if not message and not images:
|
if not message and not images:
|
||||||
return jsonify({"success": False, "error": "消息不能为空"}), 400
|
return jsonify({"success": False, "error": "消息不能为空"}), 400
|
||||||
|
|
||||||
@ -66,6 +115,29 @@ def send_message_api():
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return jsonify({"success": False, "error": f"对话加载失败: {exc}"}), 400
|
return jsonify({"success": False, "error": f"对话加载失败: {exc}"}), 400
|
||||||
|
|
||||||
|
# 应用自定义 prompt/personalization(如果提供)
|
||||||
|
try:
|
||||||
|
if prompt_name:
|
||||||
|
prompt_path = _prompt_dir(workspace) / f"{prompt_name}.txt"
|
||||||
|
if not prompt_path.exists():
|
||||||
|
return jsonify({"success": False, "error": "prompt 不存在"}), 404
|
||||||
|
terminal.context_manager.custom_system_prompt = prompt_path.read_text(encoding="utf-8")
|
||||||
|
if personalization_name:
|
||||||
|
pers_path = _personalization_dir(workspace) / f"{personalization_name}.json"
|
||||||
|
if not pers_path.exists():
|
||||||
|
return jsonify({"success": False, "error": "personalization 不存在"}), 404
|
||||||
|
try:
|
||||||
|
terminal.context_manager.custom_personalization_config = json.loads(pers_path.read_text(encoding="utf-8"))
|
||||||
|
except Exception:
|
||||||
|
return jsonify({"success": False, "error": "personalization 解析失败"}), 400
|
||||||
|
# 持久化到对话元数据
|
||||||
|
_update_conversation_metadata(workspace, conversation_id, {
|
||||||
|
"custom_prompt_name": prompt_name,
|
||||||
|
"personalization_name": personalization_name,
|
||||||
|
})
|
||||||
|
except Exception as exc:
|
||||||
|
return jsonify({"success": False, "error": f"自定义参数错误: {exc}"}), 400
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rec = task_manager.create_chat_task(
|
rec = task_manager.create_chat_task(
|
||||||
username=username,
|
username=username,
|
||||||
@ -91,6 +163,70 @@ def send_message_api():
|
|||||||
}), 202
|
}), 202
|
||||||
|
|
||||||
|
|
||||||
|
@api_v1_bp.route("/conversations", methods=["GET"])
|
||||||
|
@api_token_required
|
||||||
|
def list_conversations_api():
|
||||||
|
username = session.get("username")
|
||||||
|
terminal, workspace = get_user_resources(username)
|
||||||
|
if not terminal or not workspace:
|
||||||
|
return jsonify({"success": False, "error": "系统未初始化"}), 503
|
||||||
|
limit = max(1, min(int(request.args.get("limit", 20)), 100))
|
||||||
|
offset = max(0, int(request.args.get("offset", 0)))
|
||||||
|
conv_dir = Path(workspace.data_dir) / "conversations"
|
||||||
|
items = []
|
||||||
|
for p in sorted(conv_dir.glob("conv_*.json"), key=lambda x: x.stat().st_mtime, reverse=True):
|
||||||
|
try:
|
||||||
|
data = json.loads(p.read_text(encoding="utf-8"))
|
||||||
|
meta = data.get("metadata") or {}
|
||||||
|
items.append({
|
||||||
|
"id": data.get("id"),
|
||||||
|
"title": data.get("title"),
|
||||||
|
"created_at": data.get("created_at"),
|
||||||
|
"updated_at": data.get("updated_at"),
|
||||||
|
"run_mode": meta.get("run_mode"),
|
||||||
|
"model_key": meta.get("model_key"),
|
||||||
|
"custom_prompt_name": meta.get("custom_prompt_name"),
|
||||||
|
"personalization_name": meta.get("personalization_name"),
|
||||||
|
"messages_count": len(data.get("messages", [])),
|
||||||
|
})
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
sliced = items[offset:offset + limit]
|
||||||
|
return jsonify({"success": True, "data": sliced, "total": len(items)})
|
||||||
|
|
||||||
|
|
||||||
|
@api_v1_bp.route("/conversations/<conv_id>", methods=["GET"])
|
||||||
|
@api_token_required
|
||||||
|
def get_conversation_api(conv_id: str):
|
||||||
|
username = session.get("username")
|
||||||
|
_, workspace = get_user_resources(username)
|
||||||
|
if not workspace:
|
||||||
|
return jsonify({"success": False, "error": "系统未初始化"}), 503
|
||||||
|
path = _conversation_path(workspace, conv_id)
|
||||||
|
if not path.exists():
|
||||||
|
return jsonify({"success": False, "error": "对话不存在"}), 404
|
||||||
|
try:
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
include_messages = request.args.get("full", "0") == "1"
|
||||||
|
if not include_messages:
|
||||||
|
data["messages"] = None
|
||||||
|
return jsonify({"success": True, "data": data})
|
||||||
|
except Exception as exc:
|
||||||
|
return jsonify({"success": False, "error": str(exc)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@api_v1_bp.route("/conversations/<conv_id>", methods=["DELETE"])
|
||||||
|
@api_token_required
|
||||||
|
def delete_conversation_api(conv_id: str):
|
||||||
|
username = session.get("username")
|
||||||
|
terminal, workspace = get_user_resources(username)
|
||||||
|
if not terminal or not workspace:
|
||||||
|
return jsonify({"success": False, "error": "系统未初始化"}), 503
|
||||||
|
result = terminal.delete_conversation(conv_id)
|
||||||
|
status = 200 if result.get("success") else 404
|
||||||
|
return jsonify(result), status
|
||||||
|
|
||||||
|
|
||||||
@api_v1_bp.route("/tasks/<task_id>", methods=["GET"])
|
@api_v1_bp.route("/tasks/<task_id>", methods=["GET"])
|
||||||
@api_token_required
|
@api_token_required
|
||||||
def get_task_events(task_id: str):
|
def get_task_events(task_id: str):
|
||||||
@ -235,4 +371,129 @@ def download_file_api():
|
|||||||
return send_file(target, as_attachment=True, download_name=target.name)
|
return send_file(target, as_attachment=True, download_name=target.name)
|
||||||
|
|
||||||
|
|
||||||
|
@api_v1_bp.route("/prompts", methods=["GET"])
|
||||||
|
@api_token_required
|
||||||
|
def list_prompts_api():
|
||||||
|
username = session.get("username")
|
||||||
|
_, workspace = get_user_resources(username)
|
||||||
|
if not workspace:
|
||||||
|
return jsonify({"success": False, "error": "系统未初始化"}), 503
|
||||||
|
base = _prompt_dir(workspace)
|
||||||
|
items = []
|
||||||
|
for p in sorted(base.glob("*.txt")):
|
||||||
|
stat = p.stat()
|
||||||
|
items.append({
|
||||||
|
"name": p.stem,
|
||||||
|
"size": stat.st_size,
|
||||||
|
"updated_at": stat.st_mtime,
|
||||||
|
})
|
||||||
|
return jsonify({"success": True, "items": items})
|
||||||
|
|
||||||
|
|
||||||
|
@api_v1_bp.route("/prompts/<name>", methods=["GET"])
|
||||||
|
@api_token_required
|
||||||
|
def get_prompt_api(name: str):
|
||||||
|
username = session.get("username")
|
||||||
|
_, workspace = get_user_resources(username)
|
||||||
|
if not workspace:
|
||||||
|
return jsonify({"success": False, "error": "系统未初始化"}), 503
|
||||||
|
p = _prompt_dir(workspace) / f"{name}.txt"
|
||||||
|
if not p.exists():
|
||||||
|
return jsonify({"success": False, "error": "prompt 不存在"}), 404
|
||||||
|
return jsonify({"success": True, "name": name, "content": p.read_text(encoding="utf-8")})
|
||||||
|
|
||||||
|
|
||||||
|
@api_v1_bp.route("/prompts", methods=["POST"])
|
||||||
|
@api_token_required
|
||||||
|
def create_prompt_api():
|
||||||
|
username = session.get("username")
|
||||||
|
_, workspace = get_user_resources(username)
|
||||||
|
if not workspace:
|
||||||
|
return jsonify({"success": False, "error": "系统未初始化"}), 503
|
||||||
|
payload = request.get_json() or {}
|
||||||
|
name = (payload.get("name") or "").strip()
|
||||||
|
content = payload.get("content") or ""
|
||||||
|
if not name:
|
||||||
|
return jsonify({"success": False, "error": "name 不能为空"}), 400
|
||||||
|
p = _prompt_dir(workspace) / f"{name}.txt"
|
||||||
|
p.write_text(content, encoding="utf-8")
|
||||||
|
return jsonify({"success": True, "name": name})
|
||||||
|
|
||||||
|
|
||||||
|
@api_v1_bp.route("/personalizations", methods=["GET"])
|
||||||
|
@api_token_required
|
||||||
|
def list_personalizations_api():
|
||||||
|
username = session.get("username")
|
||||||
|
_, workspace = get_user_resources(username)
|
||||||
|
if not workspace:
|
||||||
|
return jsonify({"success": False, "error": "系统未初始化"}), 503
|
||||||
|
base = _personalization_dir(workspace)
|
||||||
|
items = []
|
||||||
|
for p in sorted(base.glob("*.json")):
|
||||||
|
stat = p.stat()
|
||||||
|
items.append({
|
||||||
|
"name": p.stem,
|
||||||
|
"size": stat.st_size,
|
||||||
|
"updated_at": stat.st_mtime,
|
||||||
|
})
|
||||||
|
return jsonify({"success": True, "items": items})
|
||||||
|
|
||||||
|
|
||||||
|
@api_v1_bp.route("/personalizations/<name>", methods=["GET"])
|
||||||
|
@api_token_required
|
||||||
|
def get_personalization_api(name: str):
|
||||||
|
username = session.get("username")
|
||||||
|
_, workspace = get_user_resources(username)
|
||||||
|
if not workspace:
|
||||||
|
return jsonify({"success": False, "error": "系统未初始化"}), 503
|
||||||
|
p = _personalization_dir(workspace) / f"{name}.json"
|
||||||
|
if not p.exists():
|
||||||
|
return jsonify({"success": False, "error": "personalization 不存在"}), 404
|
||||||
|
try:
|
||||||
|
content = json.loads(p.read_text(encoding="utf-8"))
|
||||||
|
except Exception as exc:
|
||||||
|
return jsonify({"success": False, "error": f"解析失败: {exc}"}), 500
|
||||||
|
return jsonify({"success": True, "name": name, "content": content})
|
||||||
|
|
||||||
|
|
||||||
|
@api_v1_bp.route("/personalizations", methods=["POST"])
|
||||||
|
@api_token_required
|
||||||
|
def create_personalization_api():
|
||||||
|
username = session.get("username")
|
||||||
|
_, workspace = get_user_resources(username)
|
||||||
|
if not workspace:
|
||||||
|
return jsonify({"success": False, "error": "系统未初始化"}), 503
|
||||||
|
payload = request.get_json() or {}
|
||||||
|
name = (payload.get("name") or "").strip()
|
||||||
|
content = payload.get("content")
|
||||||
|
if not name:
|
||||||
|
return jsonify({"success": False, "error": "name 不能为空"}), 400
|
||||||
|
if content is None:
|
||||||
|
return jsonify({"success": False, "error": "content 不能为空"}), 400
|
||||||
|
p = _personalization_dir(workspace) / f"{name}.json"
|
||||||
|
try:
|
||||||
|
p.write_text(json.dumps(content, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
except Exception as exc:
|
||||||
|
return jsonify({"success": False, "error": f"保存失败: {exc}"}), 500
|
||||||
|
return jsonify({"success": True, "name": name})
|
||||||
|
|
||||||
|
|
||||||
|
@api_v1_bp.route("/models", methods=["GET"])
|
||||||
|
def list_models_api():
|
||||||
|
items = []
|
||||||
|
for key, profile in MODEL_PROFILES.items():
|
||||||
|
items.append({
|
||||||
|
"model_key": key,
|
||||||
|
"name": profile.get("name", key),
|
||||||
|
"supports_thinking": profile.get("supports_thinking", False),
|
||||||
|
"fast_only": profile.get("fast_only", False),
|
||||||
|
})
|
||||||
|
return jsonify({"success": True, "items": items})
|
||||||
|
|
||||||
|
|
||||||
|
@api_v1_bp.route("/health", methods=["GET"])
|
||||||
|
def health_api():
|
||||||
|
return jsonify({"success": True, "status": "ok"})
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["api_v1_bp"]
|
__all__ = ["api_v1_bp"]
|
||||||
|
|||||||
@ -8,6 +8,8 @@ from core.web_terminal import WebTerminal
|
|||||||
from modules.gui_file_manager import GuiFileManager
|
from modules.gui_file_manager import GuiFileManager
|
||||||
from modules.upload_security import UploadQuarantineManager, UploadSecurityError
|
from modules.upload_security import UploadQuarantineManager, UploadSecurityError
|
||||||
from modules.personalization_manager import load_personalization_config
|
from modules.personalization_manager import load_personalization_config
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
from modules.usage_tracker import UsageTracker
|
from modules.usage_tracker import UsageTracker
|
||||||
|
|
||||||
from . import state
|
from . import state
|
||||||
@ -164,6 +166,43 @@ def emit_user_quota_update(username: Optional[str]):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def apply_conversation_overrides(terminal: WebTerminal, workspace, conversation_id: Optional[str]):
|
||||||
|
"""根据对话元数据应用自定义 prompt / personalization(仅 API 用途)。"""
|
||||||
|
if not conversation_id:
|
||||||
|
return
|
||||||
|
conv_path = Path(workspace.data_dir) / "conversations" / f"{conversation_id}.json"
|
||||||
|
if not conv_path.exists():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
data = json.loads(conv_path.read_text(encoding="utf-8"))
|
||||||
|
meta = data.get("metadata") or {}
|
||||||
|
prompt_name = meta.get("custom_prompt_name")
|
||||||
|
personalization_name = meta.get("personalization_name")
|
||||||
|
# prompt override
|
||||||
|
if prompt_name:
|
||||||
|
prompt_path = Path(workspace.data_dir) / "prompts" / f"{prompt_name}.txt"
|
||||||
|
if prompt_path.exists():
|
||||||
|
terminal.context_manager.custom_system_prompt = prompt_path.read_text(encoding="utf-8")
|
||||||
|
else:
|
||||||
|
terminal.context_manager.custom_system_prompt = None
|
||||||
|
else:
|
||||||
|
terminal.context_manager.custom_system_prompt = None
|
||||||
|
# personalization override
|
||||||
|
if personalization_name:
|
||||||
|
pers_path = Path(workspace.data_dir) / "personalization" / f"{personalization_name}.json"
|
||||||
|
if pers_path.exists():
|
||||||
|
try:
|
||||||
|
terminal.context_manager.custom_personalization_config = json.loads(pers_path.read_text(encoding="utf-8"))
|
||||||
|
except Exception:
|
||||||
|
terminal.context_manager.custom_personalization_config = None
|
||||||
|
else:
|
||||||
|
terminal.context_manager.custom_personalization_config = None
|
||||||
|
else:
|
||||||
|
terminal.context_manager.custom_personalization_config = None
|
||||||
|
except Exception as exc:
|
||||||
|
debug_log(f"[apply_overrides] 读取对话元数据失败: {exc}")
|
||||||
|
|
||||||
|
|
||||||
def with_terminal(func):
|
def with_terminal(func):
|
||||||
"""注入用户专属终端和工作区"""
|
"""注入用户专属终端和工作区"""
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
@ -251,6 +290,11 @@ def ensure_conversation_loaded(terminal: WebTerminal, conversation_id: Optional[
|
|||||||
session['thinking_mode'] = terminal.thinking_mode
|
session['thinking_mode'] = terminal.thinking_mode
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
# 应用对话级自定义 prompt / personalization(仅 API)
|
||||||
|
try:
|
||||||
|
apply_conversation_overrides(terminal, workspace, conversation_id)
|
||||||
|
except Exception as exc:
|
||||||
|
debug_log(f"[apply_overrides] 失败: {exc}")
|
||||||
return conversation_id, created_new
|
return conversation_id, created_new
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -943,6 +943,11 @@ class ContextManager:
|
|||||||
|
|
||||||
def load_prompt(self, prompt_name: str) -> str:
|
def load_prompt(self, prompt_name: str) -> str:
|
||||||
"""加载prompt模板"""
|
"""加载prompt模板"""
|
||||||
|
# 允许覆盖主系统提示(仅对 main_system 系列生效)
|
||||||
|
if prompt_name.startswith("main_system"):
|
||||||
|
override = getattr(self, "custom_system_prompt", None)
|
||||||
|
if override:
|
||||||
|
return override
|
||||||
prompt_file = Path(PROMPTS_DIR) / f"{prompt_name}.txt"
|
prompt_file = Path(PROMPTS_DIR) / f"{prompt_name}.txt"
|
||||||
if prompt_file.exists():
|
if prompt_file.exists():
|
||||||
with open(prompt_file, 'r', encoding='utf-8') as f:
|
with open(prompt_file, 'r', encoding='utf-8') as f:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user