CLI(bun 跑源码即生效): - boot:loadBootData 一次性加载模型清单(含 context_window)/个性化默认 (含压缩设置)/path 授权/会话列表;新会话 unshift 标 current - /session:Enter 真实加载(timeline.reset + getSessionHistory 渲染历史文本) - /new:对齐 web /new 页面语义——空对话草稿不立即建会话,首条消息 createTask 不带 cid 由服务端惰性创建,响应回 id 后插入列表标 current - /context + 状态栏:token_update 事件(运行期事件流)+ 打开面板时查询 新端点 GET /api/runtime/sessions/<cid>/token-stats;百分比算法对齐 web InputComposer(自动压缩阈值优先于模型窗口) - /model:Enter = 本地生效 + createTask 带 model_key/run_mode/thinking_mode 覆盖(会话级即时)+ savePersonalization patch 存默认(effort default 存 null) - /path:增删后 savePathAuths 全量提交两组(服务端 deny 校验保留) - /agents /tasks /workflow /rewind:打开时拉真实列表(sub_agents/ background_commands/workflows/versioning-checkpoints) - 发送链路统一 sendWithSettings 携带 /model 生效值(含排队队列) 后端: - 8 端点装饰器 api_login_required → api_login_or_host_token_required (personalization×2 / path-authorization×2 / sub_agents / background_commands / workflows / versioning-checkpoints),web session 行为不变 - headless_app 用 add_url_rule 共享上述视图函数(URL 与 full 一致); sub_agents/background_commands 支持 query 显式传 conversation_id (Bearer 装配的 terminal 无当前对话概念) - RuntimeService 新增 get_session_token_stats;gateway_api 挂 token-stats 路由 - headless 404 兜底:非 /api/ 的 GET 路径回落告知页(对齐 SPA fallback) 验证:tsc ✅ py_compile ✅ 冒烟 6/6 ✅ headless 装配 54 路由 ✅ localconfig 无头实测读到真实配置 ✅;真实服务端到端待用户重启 8091 实机验收 Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
99 lines
3.7 KiB
Python
99 lines
3.7 KiB
Python
"""工作流页面路由 + REST API。
|
||
|
||
- `/workflows` 工作流库列表页,返回主 SPA 入口,由前端 bootstrapRoute 识别路径
|
||
- `/workflow/<name>` 工作流编辑器页,同上
|
||
- `/api/workflows` 系列:工作流 CRUD(WORKFLOW.md 落盘,见 modules/workflow_manager.py)
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from flask import Blueprint, current_app, jsonify, request
|
||
|
||
from modules.workflow_manager import (
|
||
delete_workflow,
|
||
list_workflows,
|
||
load_workflow,
|
||
save_workflow,
|
||
)
|
||
from server.auth_helpers import api_login_required, login_required
|
||
from server.gateway_auth import api_login_or_host_token_required
|
||
from server.context import with_terminal
|
||
from modules.i18n import tr
|
||
|
||
workflow_page_bp = Blueprint("workflow_page", __name__)
|
||
|
||
|
||
@workflow_page_bp.route("/workflows")
|
||
@login_required
|
||
def workflow_library_page():
|
||
"""工作流库入口,返回与 /new 相同的 SPA index.html。"""
|
||
return current_app.send_static_file("index.html")
|
||
|
||
|
||
@workflow_page_bp.route("/workflow/<path:name>")
|
||
@login_required
|
||
def workflow_editor_page(name: str):
|
||
"""工作流编辑器入口,返回 SPA index.html 让前端路由处理。"""
|
||
return current_app.send_static_file("index.html")
|
||
|
||
|
||
# ---------------------------------------------------------------- REST API
|
||
|
||
|
||
@workflow_page_bp.route("/api/workflows", methods=["GET"])
|
||
@api_login_or_host_token_required
|
||
@with_terminal
|
||
def api_list_workflows(terminal, workspace, username):
|
||
"""工作流列表(内置 + 用户库双源合并,仅元信息)。"""
|
||
try:
|
||
return jsonify({"workflows": list_workflows(workspace.data_dir)})
|
||
except Exception as exc:
|
||
return jsonify({"error": tr("workflow_page.list_failed", error=exc)}), 500
|
||
|
||
|
||
@workflow_page_bp.route("/api/workflows/<path:name>", methods=["GET"])
|
||
@api_login_required
|
||
@with_terminal
|
||
def api_load_workflow(name: str, terminal, workspace, username):
|
||
"""加载完整工作流定义(用户库优先,其次内置)。"""
|
||
try:
|
||
return jsonify({"workflow": load_workflow(name, workspace.data_dir)})
|
||
except FileNotFoundError:
|
||
return jsonify({"error": tr("workflow_page.workflow_not_found", name=name)}), 404
|
||
except ValueError as exc:
|
||
return jsonify({"error": str(exc)}), 400
|
||
|
||
|
||
@workflow_page_bp.route("/api/workflows/<path:name>", methods=["PUT"])
|
||
@api_login_required
|
||
@with_terminal
|
||
def api_save_workflow(name: str, terminal, workspace, username):
|
||
"""保存工作流到用户库(原子写;结构 error 级校验不过则 400)。"""
|
||
data = request.get_json(silent=True)
|
||
if not isinstance(data, dict) or not isinstance(data.get("workflow"), dict):
|
||
return jsonify({"error": tr("workflow_page.missing_workflow_object")}), 400
|
||
wf = dict(data["workflow"])
|
||
wf["name"] = name # 名称以 URL 为准
|
||
try:
|
||
save_workflow(wf, workspace.data_dir)
|
||
return jsonify({"ok": True})
|
||
except ValueError as exc:
|
||
return jsonify({"error": str(exc)}), 400
|
||
except OSError as exc:
|
||
return jsonify({"error": tr("workflow_page.write_file_failed", error=exc)}), 500
|
||
|
||
|
||
@workflow_page_bp.route("/api/workflows/<path:name>", methods=["DELETE"])
|
||
@api_login_required
|
||
@with_terminal
|
||
def api_delete_workflow(name: str, terminal, workspace, username):
|
||
"""删除用户库中的工作流(内置示例不可删)。"""
|
||
try:
|
||
delete_workflow(name, workspace.data_dir)
|
||
return jsonify({"ok": True})
|
||
except FileNotFoundError:
|
||
return jsonify({"error": tr("workflow_page.workflow_not_found", name=name)}), 404
|
||
except ValueError as exc:
|
||
return jsonify({"error": str(exc)}), 400
|
||
except OSError as exc:
|
||
return jsonify({"error": tr("workflow_page.delete_failed", error=exc)}), 500
|