- 新增 modules/i18n.py:tr() + 进程级语言缓存 + modules/i18n_messages/ 域文案包自动聚合 - 新增 29 个域文案包,共 1153 条双语 key;90+ 源文件 1146 处用户可见消息 tr 化 - ui_locale 存入 personalization.json(用户级共享),前后端双向同步 - 前端匹配点双语兼容(history/shared/ChatArea/taskPolling/upload 等正则) - 修复语言判等陷阱:审批等待加稳定 code 字段;conversation.py 不存在判等改双语 helper - 边界:日志/prompt 注入/子智能体工具回填/容器内嵌脚本不迁移
93 lines
3.1 KiB
Python
93 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import threading
|
|
import time
|
|
import uuid
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from modules.i18n import tr
|
|
|
|
|
|
class ToolApprovalManager:
|
|
def __init__(self):
|
|
self._items: Dict[str, Dict[str, Any]] = {}
|
|
self._lock = threading.Lock()
|
|
|
|
def create_request(
|
|
self,
|
|
*,
|
|
username: str,
|
|
conversation_id: Optional[str],
|
|
task_id: Optional[str],
|
|
tool_call_id: Optional[str],
|
|
tool_name: str,
|
|
arguments: Dict[str, Any],
|
|
preview: Dict[str, Any],
|
|
) -> Dict[str, Any]:
|
|
approval_id = f"approval_{uuid.uuid4().hex}"
|
|
item = {
|
|
"approval_id": approval_id,
|
|
"username": username,
|
|
"conversation_id": conversation_id,
|
|
"task_id": task_id,
|
|
"tool_call_id": tool_call_id,
|
|
"tool_name": tool_name,
|
|
"arguments": arguments or {},
|
|
"preview": preview or {},
|
|
"status": "pending",
|
|
"created_at": time.time(),
|
|
"decided_at": None,
|
|
"decision": None,
|
|
}
|
|
with self._lock:
|
|
self._items[approval_id] = item
|
|
return dict(item)
|
|
|
|
def get(self, approval_id: str) -> Optional[Dict[str, Any]]:
|
|
with self._lock:
|
|
item = self._items.get(approval_id)
|
|
return dict(item) if item else None
|
|
|
|
def list_pending(self, username: str, conversation_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
with self._lock:
|
|
rows = []
|
|
for item in self._items.values():
|
|
if item.get("username") != username:
|
|
continue
|
|
if item.get("status") != "pending":
|
|
continue
|
|
if conversation_id and item.get("conversation_id") != conversation_id:
|
|
continue
|
|
rows.append(dict(item))
|
|
rows.sort(key=lambda x: x.get("created_at", 0.0))
|
|
return rows
|
|
|
|
def decide(
|
|
self,
|
|
approval_id: str,
|
|
username: str,
|
|
decision: str,
|
|
*,
|
|
reason: Optional[str] = None,
|
|
decider: Optional[str] = None,
|
|
) -> Dict[str, Any]:
|
|
normalized = str(decision or "").strip().lower()
|
|
if normalized not in {"approved", "rejected"}:
|
|
raise ValueError(tr("tool_approval.decision_invalid"))
|
|
with self._lock:
|
|
item = self._items.get(approval_id)
|
|
if not item:
|
|
raise KeyError(tr("tool_approval.request_not_found"))
|
|
if item.get("username") != username:
|
|
raise PermissionError(tr("tool_approval.no_permission"))
|
|
if item.get("status") != "pending":
|
|
return dict(item)
|
|
item["status"] = normalized
|
|
item["decision"] = normalized
|
|
item["decided_at"] = time.time()
|
|
if isinstance(reason, str) and reason.strip():
|
|
item["reason"] = reason.strip()
|
|
if isinstance(decider, str) and decider.strip():
|
|
item["decider"] = decider.strip()
|
|
return dict(item)
|