diff --git a/frontend-web/src/components/workspace/custom-prompt-menu.tsx b/frontend-web/src/components/workspace/custom-prompt-menu.tsx index 1f9b0a3b..d6cd72b7 100644 --- a/frontend-web/src/components/workspace/custom-prompt-menu.tsx +++ b/frontend-web/src/components/workspace/custom-prompt-menu.tsx @@ -7,7 +7,7 @@ import { Settings2Icon, Trash2Icon, } from "lucide-react"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; @@ -28,8 +28,7 @@ import { } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; -import { useAgents } from "@/core/agents/hooks"; -import { getStoredAuth } from "@/core/auth"; +import { useAgentSelector } from "@/core/agents/hooks"; import { useCreateCustomPrompt, useCustomPrompts, @@ -165,10 +164,9 @@ function ManagePromptsDialog({ const createPrompt = useCreateCustomPrompt(); const updatePrompt = useUpdateCustomPrompt(); const deletePrompt = useDeleteCustomPrompt(); - const { agents } = useAgents(); + const { agents } = useAgentSelector(); const preferences = useUserPreferences(); const updatePreferences = useUpdateUserPreferences(); - const currentUserId = getStoredAuth()?.user_id ?? null; const [editingId, setEditingId] = useState(null); const [title, setTitle] = useState(""); const [content, setContent] = useState(""); @@ -176,16 +174,7 @@ function ManagePromptsDialog({ const saving = createPrompt.isPending || updatePrompt.isPending; - const selectableAgents = useMemo(() => { - const owned = currentUserId - ? agents.filter((agent) => agent.user_id === currentUserId) - : []; - if (agentId && !owned.some((agent) => agent.id === agentId)) { - const bound = agents.find((agent) => agent.id === agentId); - if (bound) return [bound, ...owned]; - } - return owned; - }, [agents, agentId, currentUserId]); + const selectableAgents = agents; const agentNameById = (id?: string | null) => id ? agents.find((agent) => agent.id === id)?.name ?? null : null; @@ -235,7 +224,11 @@ function ManagePromptsDialog({ setEditingId(id); setTitle(target.title); setContent(target.content); - setAgentId(target.agent_id || undefined); + setAgentId( + target.agent_id && selectableAgents.some((agent) => agent.id === target.agent_id) + ? target.agent_id + : undefined, + ); }; const handleToggleHideAdmin = async (next: boolean) => { diff --git a/offline-backend-20260512/backend/app/gateway/routers/admin_users.py b/offline-backend-20260512/backend/app/gateway/routers/admin_users.py index ef5a2ece..64265701 100644 --- a/offline-backend-20260512/backend/app/gateway/routers/admin_users.py +++ b/offline-backend-20260512/backend/app/gateway/routers/admin_users.py @@ -25,7 +25,7 @@ from datetime import datetime, timedelta from fastapi import APIRouter, Depends, HTTPException, Query, Request from fastapi.responses import StreamingResponse from pydantic import BaseModel, Field -from sqlalchemy import and_, case, desc, func, or_, select +from sqlalchemy import and_, case, desc, func, not_, or_, select from langchain_core.messages import HumanMessage, SystemMessage from app.gateway.deps import ( @@ -355,6 +355,22 @@ def _run_time_filter(since: datetime, until: datetime): return and_(RunRow.created_at >= since, RunRow.created_at <= until) +def _non_scheduled_run_filter(): + """Exclude scheduler-generated runs that predate scheduled_task_runs links.""" + return or_( + RunRow.first_human_message.is_(None), + not_(RunRow.first_human_message.like("【定时任务触发】%")), + ) + + +def _non_scheduled_tool_filter(): + return or_( + ToolCallMetricRow.run_id.is_(None), + RunRow.run_id.is_(None), + _non_scheduled_run_filter(), + ) + + def _tool_time_filter(since: datetime, until: datetime): return and_(ToolCallMetricRow.created_at >= since, ToolCallMetricRow.created_at <= until) @@ -404,6 +420,7 @@ def _apply_run_scope( stmt = stmt.where(RunRow.status != "error") if not include_scheduled: stmt = stmt.where(or_(run_id_column.is_(None), run_id_column.not_in(_scheduled_run_ids_stmt()))) + stmt = stmt.where(_non_scheduled_run_filter()) return stmt @@ -417,6 +434,7 @@ def _apply_tool_scope( stmt = _apply_user_scope(stmt, ToolCallMetricRow.user_id, exclude_user_ids=exclude_user_ids, include_admins=include_admins) if not include_scheduled: stmt = stmt.where(or_(ToolCallMetricRow.run_id.is_(None), ToolCallMetricRow.run_id.not_in(_scheduled_run_ids_stmt()))) + stmt = stmt.outerjoin(RunRow, RunRow.run_id == ToolCallMetricRow.run_id).where(_non_scheduled_tool_filter()) return stmt @@ -910,6 +928,7 @@ async def _query_overview( if not include_scheduled: run_where.append(or_(RunRow.run_id.is_(None), RunRow.run_id.not_in(_scheduled_run_ids_stmt()))) tool_where.append(or_(ToolCallMetricRow.run_id.is_(None), ToolCallMetricRow.run_id.not_in(_scheduled_run_ids_stmt()))) + tool_where.append(_non_scheduled_tool_filter()) total_users_stmt = select(func.count(UserRow.id)) total_users_stmt = _apply_user_scope(total_users_stmt, UserRow.id, exclude_user_ids=exclude_user_ids, include_admins=include_admins) @@ -928,7 +947,9 @@ async def _query_overview( select( func.count(ToolCallMetricRow.id), func.sum(case((func.nullif(ToolCallMetricRow.skill_name, "").is_not(None), 1), else_=0)), - ).where(*tool_where) + ) + .outerjoin(RunRow, RunRow.run_id == ToolCallMetricRow.run_id) + .where(*tool_where) )).one() return AdminLeaderboardOverview( diff --git a/offline-backend-20260512/config.yaml b/offline-backend-20260512/config.yaml index d616bc35..126f7489 100644 --- a/offline-backend-20260512/config.yaml +++ b/offline-backend-20260512/config.yaml @@ -147,18 +147,7 @@ title: # - keep:压缩后保留多少最近的上下文(同样支持 tokens / messages / fraction)。 # - trim_tokens_to_summarize:喂给摘要模型的消息最多裁到多少 token。 # - model_name:生成摘要用的模型(留空则用主对话模型)。 -summarization: - enabled: true - # model_name: deepseek-chat - trigger: - - type: tokens - value: 60000 - - type: messages - value: 80 - keep: - type: messages - value: 20 - trim_tokens_to_summarize: 4000 + skills: path: /app/skills security_scan: @@ -187,10 +176,7 @@ auth_login: ca_cert_path: "" # 可选:PEM 格式 CA 包路径,设置后优先生效并启用校验 # 日志系统入口(仅管理员可见):右下角「设置」菜单中会多出一个「日志系统」入口, # 点击后在新标签页打开下面的 url。enabled 关掉则不显示该入口。 -log_system: - enabled: true - url: "https://your-log-system.example.com" - label: "日志系统" + database: backend: mysql mysql_url: $MYSQL_DATABASE_URL @@ -269,4 +255,22 @@ memory: scan_content: true # 注入/外渗模式扫描 block_invisible_unicode: true # streaming_scrubber:预留字段,代码尚未实现 —— 待版本支持后取消注释。 - # streaming_scrubber: true # SSE 流前清洗 \ No newline at end of file + # streaming_scrubber: true # SSE 流前清洗 + +log_system: + enabled: true + url: "https://your-log-system.example.com" + label: "日志系统" + +summarization: + enabled: true + # model_name: deepseek-chat + trigger: + - type: tokens + value: 60000 + - type: messages + value: 80 + keep: + type: messages + value: 20 + trim_tokens_to_summarize: 4000