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,
onAskQuestion,
disabled,
defaultCollapsed,
}: {
agent: Agent | null | undefined;
agentName: string;
onAskQuestion: (text: string) => void;
disabled?: boolean;
/** 初始收起(任务工作区进入时默认收起智能体信息面板)。 */
defaultCollapsed?: boolean;
}) {
const router = useRouter();
const [collapsed, setCollapsed] = useState(false);
const [collapsed, setCollapsed] = useState(defaultCollapsed ?? false);
const [mobileOpen, setMobileOpen] = useState(false);
const [editing, setEditing] = useState(false);
const [draft, setDraft] = useState<AgentDraft | null>(null);
+1
View File
@@ -435,6 +435,7 @@ export default function AgentChatPage({
agentName={agentDisplayName}
disabled={thread.isLoading || isDemoMode}
onAskQuestion={(text) => handleSubmit({ text, files: [] })}
defaultCollapsed={taskCtxOn}
/>
</div>
</ThreadContext.Provider>
@@ -36,6 +36,7 @@ import {
type TaskDeeplinkConfig,
} from "@/core/auth/task-deeplink-config";
import {
TASK_BUSINESSES,
buttonsForBusiness,
resolveTaskButtons,
runTaskButton,
@@ -120,6 +121,10 @@ function TaskJumpBar({ business, taskId }: { business: TaskBusiness; taskId: str
// 目的树弹框(purpose 类型按钮)由组件层打开。
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;
@@ -141,7 +146,7 @@ function TaskJumpBar({ business, taskId }: { business: TaskBusiness; taskId: str
{jumpButtons.map((btn) => (
<Button
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)}
title={btn.label}
>
@@ -155,7 +160,12 @@ function TaskJumpBar({ business, taskId }: { business: TaskBusiness; taskId: str
)}
</Button>
))}
<TaskPurposeDialog taskId={taskId} open={purposeOpen} onOpenChange={setPurposeOpen} />
<TaskPurposeDialog
taskId={taskId}
open={purposeOpen}
onOpenChange={setPurposeOpen}
resolveFromActionId={purposeActionId}
/>
</div>
);
}
@@ -168,21 +178,29 @@ function TaskConversationsList({
taskId,
agentId,
chatsBase,
business,
lite,
}: {
taskId: string;
agentId: string;
chatsBase: string;
business: TaskBusiness;
lite?: boolean;
}) {
const { pathname } = useLocation();
const navigate = useNavigate();
const { threads, isLoading } = useTaskThreads(taskId, agentId);
const currentThreadId = useMemo(() => parseThreadId(pathname, chatsBase), [pathname, chatsBase]);
const businessName = TASK_BUSINESSES.find((b) => b.code === business)?.name ?? "";
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="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 />
<div className="mb-2 flex items-center justify-between gap-2">
<div className="min-w-0">
@@ -318,6 +336,7 @@ export function TaskFxWorkspace({
taskId={taskId}
agentId={agentId}
chatsBase={chatsBase}
business={business}
lite={lite}
/>
<div className="relative min-h-0 min-w-0 flex-1">
@@ -30,6 +30,7 @@ import {
type PurposeTreeNode,
type TaskPurposeResult,
} from "@/core/auth/task-purpose";
import { fetchTaskIdFromAction } from "@/core/auth/task-deeplink";
import { cn } from "@/lib/utils";
/** 目的树表格:拍平成行,列数随数据自适应;层级缺失补空、字段变动不崩。 */
@@ -98,32 +99,49 @@ export function TaskPurposeDialog({
taskId,
open,
onOpenChange,
resolveFromActionId,
}: {
taskId: string;
open: boolean;
onOpenChange: (open: boolean) => void;
/** xdfx:深链原始「行动id」。提供时弹框先用它换出真正「任务id」再查目的树。 */
resolveFromActionId?: string;
}) {
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<TaskPurposeResult | null>(null);
const [resolvedTaskId, setResolvedTaskId] = useState(taskId);
useEffect(() => {
if (!open) return;
let cancelled = false;
setLoading(true);
fetchTaskPurposeDetail(taskId)
.then((r) => !cancelled && setResult(r))
.catch(() => !cancelled && setResult({ tree: [], hasData: false }))
.finally(() => !cancelled && setLoading(false));
(async () => {
try {
// xdfx:行动id 换任务id 再查目的树(与登录时的解析一致);无行动id 时直接用任务id。
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 () => {
cancelled = true;
};
}, [open, taskId]);
}, [open, taskId, resolveFromActionId]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[90vw] !max-w-[90vw] sm:!max-w-[90vw]">
<DialogHeader>
<DialogTitle>任务目的(任务 {taskId})</DialogTitle>
<DialogTitle>任务目的(任务 {resolvedTaskId})</DialogTitle>
</DialogHeader>
{loading ? (
<div className="text-muted-foreground flex items-center gap-2 py-6 text-sm">