fix: improve agent handoff and leaderboard scheduling
This commit is contained in:
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>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user