feat: 任务工作区 UI 细化(底部按钮主题色/左上业务名/右侧智能体信息默认收起/xdfx目的弹框按行动id解析)

- 底部「快捷跳转」按钮改用主题色 var(--color-main)(随红/蓝主题自适配),hover 用 --brand-950
- 三个工作区左上角标明当前分析页面(任务分析/三情分析/行动分析)
- 进入任务工作区(taskGate/taskBind)时右侧「智能体信息」面板默认收起(AgentDetailPanel defaultCollapsed)
- xdfx「弹框查看」目的树:先用行动id换回任务id 再查(TaskPurposeDialog resolveFromActionId);
  仅深链原始行动id(≠任务id)时触发,切换业务进入不误解析

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
AdministratorandClaude Opus 4.8 committed 2026-06-18 16:19:02 +08:00
1 parent 095469b8a9
commit f762a76ecf
4 files changed
+50 -9

No files matched your search

@@ -131,14 +131,17 @@ export function AgentDetailPanel({
agentName, agentName,
onAskQuestion, onAskQuestion,
disabled, disabled,
defaultCollapsed,
}: { }: {
agent: Agent | null | undefined; agent: Agent | null | undefined;
agentName: string; agentName: string;
onAskQuestion: (text: string) => void; onAskQuestion: (text: string) => void;
disabled?: boolean; disabled?: boolean;
/** 初始收起(任务工作区进入时默认收起智能体信息面板)。 */
defaultCollapsed?: boolean;
}) { }) {
const router = useRouter(); const router = useRouter();
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(defaultCollapsed ?? false);
const [mobileOpen, setMobileOpen] = useState(false); const [mobileOpen, setMobileOpen] = useState(false);
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
const [draft, setDraft] = useState<AgentDraft | null>(null); const [draft, setDraft] = useState<AgentDraft | null>(null);
+1
View File
@@ -435,6 +435,7 @@ export default function AgentChatPage({
agentName={agentDisplayName} agentName={agentDisplayName}
disabled={thread.isLoading || isDemoMode} disabled={thread.isLoading || isDemoMode}
onAskQuestion={(text) => handleSubmit({ text, files: [] })} onAskQuestion={(text) => handleSubmit({ text, files: [] })}
defaultCollapsed={taskCtxOn}
/> />
</div> </div>
</ThreadContext.Provider> </ThreadContext.Provider>
@@ -36,6 +36,7 @@ import {
type TaskDeeplinkConfig, type TaskDeeplinkConfig,
} from "@/core/auth/task-deeplink-config"; } from "@/core/auth/task-deeplink-config";
import { import {
TASK_BUSINESSES,
buttonsForBusiness, buttonsForBusiness,
resolveTaskButtons, resolveTaskButtons,
runTaskButton, runTaskButton,
@@ -120,6 +121,10 @@ function TaskJumpBar({ business, taskId }: { business: TaskBusiness; taskId: str
// 目的树弹框(purpose 类型按钮)由组件层打开。 // 目的树弹框(purpose 类型按钮)由组件层打开。
const [purposeOpen, setPurposeOpen] = useState(false); const [purposeOpen, setPurposeOpen] = useState(false);
// xdfx:深链原始「行动id」(与解析后的任务id 不同才算真行动id),弹框据此换回任务id 再查目的。
const ctxActionId = readTaskContext()?.actionId;
const purposeActionId =
business === "xdfx" && ctxActionId && ctxActionId !== taskId ? ctxActionId : undefined;
if (jumpButtons.length === 0) return null; if (jumpButtons.length === 0) return null;
@@ -141,7 +146,7 @@ function TaskJumpBar({ business, taskId }: { business: TaskBusiness; taskId: str
{jumpButtons.map((btn) => ( {jumpButtons.map((btn) => (
<Button <Button
key={btn.id} key={btn.id}
className="h-11 gap-2 border-transparent bg-[#cf1322] px-6 text-base text-white shadow-sm hover:bg-[#a30f1b]" className="h-11 gap-2 border-transparent bg-[var(--color-main)] px-6 text-base text-white shadow-sm hover:bg-[var(--brand-950)]"
onClick={() => handleRun(btn)} onClick={() => handleRun(btn)}
title={btn.label} title={btn.label}
> >
@@ -155,7 +160,12 @@ function TaskJumpBar({ business, taskId }: { business: TaskBusiness; taskId: str
)} )}
</Button> </Button>
))} ))}
<TaskPurposeDialog taskId={taskId} open={purposeOpen} onOpenChange={setPurposeOpen} /> <TaskPurposeDialog
taskId={taskId}
open={purposeOpen}
onOpenChange={setPurposeOpen}
resolveFromActionId={purposeActionId}
/>
</div> </div>
); );
} }
@@ -168,21 +178,29 @@ function TaskConversationsList({
taskId, taskId,
agentId, agentId,
chatsBase, chatsBase,
business,
lite, lite,
}: { }: {
taskId: string; taskId: string;
agentId: string; agentId: string;
chatsBase: string; chatsBase: string;
business: TaskBusiness;
lite?: boolean; lite?: boolean;
}) { }) {
const { pathname } = useLocation(); const { pathname } = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const { threads, isLoading } = useTaskThreads(taskId, agentId); const { threads, isLoading } = useTaskThreads(taskId, agentId);
const currentThreadId = useMemo(() => parseThreadId(pathname, chatsBase), [pathname, chatsBase]); const currentThreadId = useMemo(() => parseThreadId(pathname, chatsBase), [pathname, chatsBase]);
const businessName = TASK_BUSINESSES.find((b) => b.code === business)?.name ?? "";
return ( return (
<div className="bg-background/85 border-border/70 flex w-64 shrink-0 flex-col border-r shadow-[12px_0_30px_-30px_rgba(15,23,42,0.45)] backdrop-blur-xl dark:border-[#1b477e]/70 dark:bg-[#061b35]/82"> <div className="bg-background/85 border-border/70 flex w-64 shrink-0 flex-col border-r shadow-[12px_0_30px_-30px_rgba(15,23,42,0.45)] backdrop-blur-xl dark:border-[#1b477e]/70 dark:bg-[#061b35]/82">
<div className="border-border/70 border-b p-3 dark:border-[#1b477e]/70"> <div className="border-border/70 border-b p-3 dark:border-[#1b477e]/70">
{/* 左上角标明当前是哪个分析页面 */}
<div className="mb-3 flex items-center gap-2">
<span className="h-4 w-1 rounded-full bg-[var(--color-main)]" />
<span className="text-foreground text-sm font-semibold tracking-wide">{businessName}</span>
</div>
<TaskDetailHeader /> <TaskDetailHeader />
<div className="mb-2 flex items-center justify-between gap-2"> <div className="mb-2 flex items-center justify-between gap-2">
<div className="min-w-0"> <div className="min-w-0">
@@ -318,6 +336,7 @@ export function TaskFxWorkspace({
taskId={taskId} taskId={taskId}
agentId={agentId} agentId={agentId}
chatsBase={chatsBase} chatsBase={chatsBase}
business={business}
lite={lite} lite={lite}
/> />
<div className="relative min-h-0 min-w-0 flex-1"> <div className="relative min-h-0 min-w-0 flex-1">
@@ -30,6 +30,7 @@ import {
type PurposeTreeNode, type PurposeTreeNode,
type TaskPurposeResult, type TaskPurposeResult,
} from "@/core/auth/task-purpose"; } from "@/core/auth/task-purpose";
import { fetchTaskIdFromAction } from "@/core/auth/task-deeplink";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
/** 目的树表格:拍平成行,列数随数据自适应;层级缺失补空、字段变动不崩。 */ /** 目的树表格:拍平成行,列数随数据自适应;层级缺失补空、字段变动不崩。 */
@@ -98,32 +99,49 @@ export function TaskPurposeDialog({
taskId, taskId,
open, open,
onOpenChange, onOpenChange,
resolveFromActionId,
}: { }: {
taskId: string; taskId: string;
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
/** xdfx:深链原始「行动id」。提供时弹框先用它换出真正「任务id」再查目的树。 */
resolveFromActionId?: string;
}) { }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [result, setResult] = useState<TaskPurposeResult | null>(null); const [result, setResult] = useState<TaskPurposeResult | null>(null);
const [resolvedTaskId, setResolvedTaskId] = useState(taskId);
useEffect(() => { useEffect(() => {
if (!open) return; if (!open) return;
let cancelled = false; let cancelled = false;
setLoading(true); setLoading(true);
fetchTaskPurposeDetail(taskId) (async () => {
.then((r) => !cancelled && setResult(r)) try {
.catch(() => !cancelled && setResult({ tree: [], hasData: false })) // xdfx:行动id 换任务id 再查目的树(与登录时的解析一致);无行动id 时直接用任务id。
.finally(() => !cancelled && setLoading(false)); let tid = taskId;
if (resolveFromActionId) {
tid = (await fetchTaskIdFromAction(resolveFromActionId)) || taskId;
}
if (cancelled) return;
setResolvedTaskId(tid);
const r = await fetchTaskPurposeDetail(tid);
if (!cancelled) setResult(r);
} catch {
if (!cancelled) setResult({ tree: [], hasData: false });
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [open, taskId]); }, [open, taskId, resolveFromActionId]);
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[90vw] !max-w-[90vw] sm:!max-w-[90vw]"> <DialogContent className="w-[90vw] !max-w-[90vw] sm:!max-w-[90vw]">
<DialogHeader> <DialogHeader>
<DialogTitle>任务目的(任务 {taskId})</DialogTitle> <DialogTitle>任务目的(任务 {resolvedTaskId})</DialogTitle>
</DialogHeader> </DialogHeader>
{loading ? ( {loading ? (
<div className="text-muted-foreground flex items-center gap-2 py-6 text-sm"> <div className="text-muted-foreground flex items-center gap-2 py-6 text-sm">