agent-Specialization/server/chat/approval.py
JOJO c9c3eedd82 feat: 移除实验性间隔思考模式,推理强度前置到前端
- 彻底移除「每 N 次请求强制思考」的间隔调度机制(思考/非思考交替导致缓存命中率极低)
- 运行模式收敛为 fast/thinking:原 deep 改名 thinking,历史数据读取时映射
- 新增推理强度五档滑块(low/medium/high/xhigh/max + 默认不传参),置于模型/模式弹窗
- 模型配置新增 reasoning_effort 开关;档位会话级持久化到对话 meta,随对话保存/恢复
- 个人空间新增「默认推理强度」;新用户默认运行模式改为 thinking
- 滑块交互:JS 插值驱动(点击/拖动/吸附全程平滑),首帧档位语义定位无闪烁
- 保存链路:前端防抖 600ms + conversation_id 兜底,修复 /new 发消息丢档位、切对话恢复错误
2026-07-21 13:47:53 +08:00

128 lines
5.2 KiB
Python

from __future__ import annotations
from server.chat import chat_bp
import json, time
from datetime import datetime
from typing import Dict, Any, Optional
from pathlib import Path
from io import BytesIO
import zipfile
import os
from flask import Blueprint, jsonify, request, session, send_file
from werkzeug.utils import secure_filename
from werkzeug.exceptions import RequestEntityTooLarge
import secrets
from config import MAX_UPLOAD_SIZE, OUTPUT_FORMATS
from modules.personalization_manager import (
load_personalization_config,
resolve_context_compression_settings,
save_personalization_config,
RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
)
from modules.skills_manager import (
get_skills_catalog,
infer_private_skills_dir,
merge_enabled_skills,
sync_workspace_skills,
)
from modules.upload_security import UploadSecurityError
from modules.host_sandbox_policy import load_policy, save_policy
from modules.user_manager import UserWorkspace
from core.web_terminal import WebTerminal
from config.model_profiles import get_model_context_window
from server.auth_helpers import api_login_required, resolve_admin_policy, get_current_user_record, get_current_username
from server.context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, get_or_create_usage_tracker
from server.security import rate_limited, prune_socket_tokens
from server.utils_common import debug_log
from server.state import PROJECT_MAX_STORAGE_MB, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
from server.state import tool_approval_manager, user_question_manager
from server.extensions import socketio
from server.monitor import get_cached_monitor_snapshot
from server.files import sanitize_filename_preserve_unicode
UPLOAD_FOLDER_NAME = ".astrion/user_upload"
@chat_bp.route('/api/user-questions/pending', methods=['GET'])
@api_login_required
@with_terminal
def list_pending_user_questions(terminal: WebTerminal, workspace: UserWorkspace, username: str):
"""获取当前用户待回答的问题列表。"""
requested_conv_id = (request.args.get("conversation_id") or "").strip() or None
if requested_conv_id is None:
requested_conv_id = getattr(terminal.context_manager, "current_conversation_id", None)
items = user_question_manager.list_pending(username=username, conversation_id=requested_conv_id)
return jsonify({
"success": True,
"items": items,
"conversation_id": requested_conv_id,
})
@chat_bp.route('/api/user-questions/<question_id>/answer', methods=['POST'])
@api_login_required
@with_terminal
@rate_limited("user_question_answer", 120, 60, scope="user")
def answer_user_question(terminal: WebTerminal, workspace: UserWorkspace, username: str, question_id: str):
"""提交 ask_user 工具问题的回答。"""
data = request.get_json() or {}
try:
item = user_question_manager.answer(
question_id=question_id,
username=username,
selected_option_id=data.get("selected_option_id"),
text=data.get("text"),
)
except ValueError as exc:
return jsonify({"success": False, "error": str(exc)}), 400
except KeyError:
return jsonify({"success": False, "error": "问题不存在"}), 404
except PermissionError as exc:
return jsonify({"success": False, "error": str(exc)}), 403
except Exception as exc:
return jsonify({"success": False, "error": str(exc)}), 500
return jsonify({
"success": True,
"item": item,
})
@chat_bp.route('/api/tool-approvals/pending', methods=['GET'])
@api_login_required
@with_terminal
def list_pending_tool_approvals(terminal: WebTerminal, workspace: UserWorkspace, username: str):
"""获取当前用户待审批工具列表。"""
requested_conv_id = (request.args.get("conversation_id") or "").strip() or None
if requested_conv_id is None:
requested_conv_id = getattr(terminal.context_manager, "current_conversation_id", None)
items = tool_approval_manager.list_pending(username=username, conversation_id=requested_conv_id)
return jsonify({
"success": True,
"items": items,
"conversation_id": requested_conv_id,
})
@chat_bp.route('/api/tool-approvals/<approval_id>/decision', methods=['POST'])
@api_login_required
@with_terminal
@rate_limited("tool_approval_decision", 60, 60, scope="user")
def decide_tool_approval(terminal: WebTerminal, workspace: UserWorkspace, username: str, approval_id: str):
"""提交工具审批决策。"""
data = request.get_json() or {}
decision = str(data.get("decision") or "").strip().lower()
try:
item = tool_approval_manager.decide(approval_id=approval_id, username=username, decision=decision)
except ValueError as exc:
return jsonify({"success": False, "error": str(exc)}), 400
except KeyError:
return jsonify({"success": False, "error": "审批请求不存在"}), 404
except PermissionError as exc:
return jsonify({"success": False, "error": str(exc)}), 403
except Exception as exc:
return jsonify({"success": False, "error": str(exc)}), 500
return jsonify({
"success": True,
"item": item,
})