fix: improve agent handoff and leaderboard scheduling

This commit is contained in:
吉鑫升 committed 2026-06-05 21:46:04 +08:00
1 parent cbbde887d4
commit a41b5a6d4c
4 files changed
+75 -26

No files matched your search

@@ -133,8 +133,7 @@ export function SkillDetailDialog({
return stripYamlFrontmatter(contentData?.content ?? "");
}, [contentData]);
const defaultTab: "detail" | "source" =
skill && !skill.detail ? "source" : "detail";
const defaultTab: "detail" = "detail";
const selectedTab = activeTab ?? defaultTab;
function startEditing() {
@@ -234,10 +233,6 @@ export function SkillDetailDialog({
{tx.filesSectionTitle}
</TabsTrigger>
)}
<TabsTrigger value="source" className="text-xs">
<FileCode2Icon className="mr-1 size-3.5" />
{tx.contentSectionTitle}
</TabsTrigger>
</TabsList>
<div className="flex shrink-0 items-center gap-1">
@@ -337,13 +332,9 @@ export function SkillDetailDialog({
<div className="text-muted-foreground rounded-lg border border-dashed px-4 py-8 text-center">
<FileTextIcon className="mx-auto mb-2 size-6 opacity-40" />
<p className="text-sm">{tx.detailEmpty}</p>
{isOwner ? (
<p className="mt-1 text-xs">
Open the SKILL.md tab to view the skill source.
</p>
) : (
{isOwner && canManage ? (
<p className="mt-1 text-xs">{tx.detailAdminOnlyHint}</p>
)}
) : null}
</div>
)}
</TabsContent>
+8 -3
View File
@@ -131,8 +131,8 @@ export default function AgentChatPage() {
const pendingMessageHandled = useRef(false);
useEffect(() => {
if (pendingMessageHandled.current) return;
if (!agent_id || !isNewThread) return;
let pending: { agentId?: string; text?: string } | null = null;
if (!agent_id) return;
let pending: { agentId?: string; threadId?: string; text?: string } | null = null;
try {
const raw = sessionStorage.getItem("pendingAgentMessage");
if (raw) pending = JSON.parse(raw);
@@ -140,10 +140,15 @@ export default function AgentChatPage() {
pending = null;
}
if (!pending || pending.agentId !== agent_id || !pending.text) return;
if (pending.threadId) {
if (pending.threadId !== threadId) return;
} else if (!isNewThread) {
return;
}
pendingMessageHandled.current = true;
sessionStorage.removeItem("pendingAgentMessage");
handleSubmit({ text: pending.text, files: [] });
}, [agent_id, isNewThread, handleSubmit]);
}, [agent_id, threadId, isNewThread, handleSubmit]);
const handleClarificationSubmit = useCallback(
(answer: string) => {
+40 -11
View File
@@ -1,9 +1,11 @@
"use client";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { type PromptInputMessage } from "@/components/ai-elements/prompt-input";
import { getAPIClient } from "@/core/api";
import { usePromptPrefixSettings } from "@/core/system-settings/hooks";
import { useUserPreferencesQuery } from "@/core/user-preferences";
import { ArtifactTrigger, useArtifacts } from "@/components/workspace/artifacts";
@@ -58,6 +60,8 @@ export default function ChatPage() {
const hideAdminPrompt = userPreferences?.hide_admin_prompt_prefix === true;
const [promptPrefixOn, setPromptPrefixOn] = useState(false);
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null);
const [agentHandoffPending, setAgentHandoffPending] = useState(false);
const agentHandoffPendingRef = useRef(false);
const { agents: selectorAgents, isLoading: selectorLoading } =
useAgentSelector();
const { selectedArtifact } = useArtifacts();
@@ -148,15 +152,37 @@ export default function ChatPage() {
message.text &&
(!message.files || message.files.length === 0)
) {
try {
sessionStorage.setItem(
"pendingAgentMessage",
JSON.stringify({ agentId: selectedAgentId, text: message.text }),
);
} catch {
// sessionStorage may be unavailable in private mode; fall through.
}
router.push(`/page/workspace/agents/${selectedAgentId}/chats/new`);
if (agentHandoffPendingRef.current) return;
const text = message.text.trim();
agentHandoffPendingRef.current = true;
setAgentHandoffPending(true);
setHasStartedConversation(true);
void (async () => {
try {
const created = await getAPIClient(isMock).threads.create({
metadata: { agent_id: selectedAgentId },
});
sessionStorage.setItem(
"pendingAgentMessage",
JSON.stringify({
agentId: selectedAgentId,
threadId: created.thread_id,
text,
}),
);
saveLastAgentId(selectedAgentId);
router.push(
`/page/workspace/agents/${selectedAgentId}/chats/${created.thread_id}`,
);
} catch (err) {
agentHandoffPendingRef.current = false;
setAgentHandoffPending(false);
setHasStartedConversation(false);
toast.error(
err instanceof Error ? err.message : "创建智能体对话失败",
);
}
})();
return;
}
@@ -200,6 +226,8 @@ export default function ChatPage() {
hideAdminPrompt,
preferencesLoading,
selectedAgentId,
agentHandoffPending,
isMock,
router,
settings.context.writing_mode,
writingArtifactPath,
@@ -345,7 +373,8 @@ export default function ChatPage() {
context={settings.context}
disabled={
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" ||
isUploading
isUploading ||
agentHandoffPending
}
onContextChange={(context) =>
setSettings("context", context)
@@ -72,6 +72,14 @@ _LEADERBOARD_CACHE_TTL_SECONDS = max(
5.0,
float(os.getenv("ADMIN_LEADERBOARD_CACHE_TTL_SECONDS", "300")),
)
_LEADERBOARD_QUERY_PAUSE_SECONDS = max(
0.0,
float(os.getenv("ADMIN_LEADERBOARD_QUERY_PAUSE_SECONDS", "0.05")),
)
_LEADERBOARD_START_DELAY_SECONDS = max(
0.0,
float(os.getenv("ADMIN_LEADERBOARD_START_DELAY_SECONDS", "0.2")),
)
_leaderboard_semaphore = asyncio.Semaphore(_LEADERBOARD_MAX_CONCURRENT)
_leaderboard_jobs: dict[str, asyncio.Task] = {}
_leaderboard_cache: dict[str, dict] = {}
@@ -1035,6 +1043,11 @@ def _leaderboard_cache_key(
return sha256(json.dumps(payload, sort_keys=True, ensure_ascii=False).encode("utf-8")).hexdigest()
async def _leaderboard_pause() -> None:
if _LEADERBOARD_QUERY_PAUSE_SECONDS > 0:
await asyncio.sleep(_LEADERBOARD_QUERY_PAUSE_SECONDS)
async def _build_user_leaderboard_response(
session_factory,
settings,
@@ -1064,16 +1077,25 @@ async def _build_user_leaderboard_response(
"cleanup_system_questions": settings.cleanup_system_questions,
}
overview = await _query_overview(session, since_dt, until_dt, **common)
await _leaderboard_pause()
users = await _query_user_leaderboard(session, since_dt, until_dt, **common)
await _leaderboard_pause()
skills = await _query_call_rankings(session, since_dt, until_dt, user_id=None, field="skill", limit=limit, **tool_common)
await _leaderboard_pause()
tools = await _query_call_rankings(session, since_dt, until_dt, user_id=None, field="tool", limit=limit, **tool_common)
await _leaderboard_pause()
models = await _query_model_rankings(session, since_dt, until_dt, user_id=None, limit=limit, **common)
if not models:
models = await _query_run_dimensions(session, since_dt, until_dt, user_id=None, field="model", limit=limit, **common)
await _leaderboard_pause()
agents = await _query_run_dimensions(session, since_dt, until_dt, user_id=None, field="agent", limit=limit, **common)
await _leaderboard_pause()
run_statuses = await _query_run_dimensions(session, since_dt, until_dt, user_id=None, field="status", limit=limit, **common)
await _leaderboard_pause()
recent_questions = await _query_recent_questions(session, since_dt, until_dt, user_id=None, limit=limit, **question_common)
await _leaderboard_pause()
frequent_questions = await _query_frequent_questions(session, since_dt, until_dt, limit=limit, **question_common)
await _leaderboard_pause()
trends = await _query_trends(session, since_dt, until_dt, **common)
return AdminLeaderboardResponse(
@@ -1113,6 +1135,8 @@ async def _refresh_leaderboard_cache(
) -> None:
acquired = False
try:
if _LEADERBOARD_START_DELAY_SECONDS > 0:
await asyncio.sleep(_LEADERBOARD_START_DELAY_SECONDS)
await _leaderboard_semaphore.acquire()
acquired = True
response = await asyncio.wait_for(