样式修改

This commit is contained in:
董宏凯 committed 2026-07-23 10:10:47 +08:00
1 parent 13bfa719ee
commit c8f69781e4
6 files changed
+218 -62

No files matched your search

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
<defs>
<style>
.hit { fill: rgba(255, 255, 255, 0.8); opacity: 0; }
.mark { fill: #fff; }
</style>
</defs>
<g transform="translate(-2434.135 -67.941)">
<rect class="hit" width="12" height="12" transform="translate(2434.135 67.941)"/>
<path class="mark" d="M.885,0,0,.885,3.692,4.577,4.135,5l.442-.423L8.269.885,7.385,0,4.135,3.25Z" transform="translate(2436 71.942)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 501 B

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<defs>
<style>
.background { fill: none; }
.mark { stroke: rgba(0, 0, 0, 0); stroke-miterlimit: 10; fill: url(#gradient); }
</style>
<linearGradient id="gradient" x1="0.277" y1="0.03" x2="0.725" y2="1.083" gradientUnits="objectBoundingBox">
<stop offset="0" stop-color="#a2e1f9"/>
<stop offset="1" stop-color="#45c3f3"/>
</linearGradient>
</defs>
<rect class="background" width="16" height="16"/>
<path class="mark" d="M14554.5,10202.5a1,1,0,1,1,0-2h5a1,1,0,0,1,0,2Zm9.8-.482-2.95-2.157a.445.445,0,0,1-.187-.361.46.46,0,0,1,.187-.366l2.95-2.152a.45.45,0,0,1,.716.365v4.307a.44.44,0,0,1-.131.317.445.445,0,0,1-.322.136A.454.454,0,0,1,14564.3,10202.019Zm-9.8-3.521a1,1,0,1,1,0-2h5a1,1,0,1,1,0,2Zm0-4a1,1,0,1,1,0-2h10a1,1,0,1,1,0,2Z" transform="translate(-14551.5 -10189.5)"/>
</svg>

After

Width:  |  Height:  |  Size: 913 B

@@ -62,6 +62,7 @@ import type { AgentThreadState } from "@/core/threads/types";
import { textOfMessage } from "@/core/threads/utils";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
import expandLogo from "@/assets/position-roundtable/expand-logo.svg";
import { listChains } from "@/roundtable-planning/api/chains";
import { Step1Panel } from "@/roundtable-planning/components/Step1Panel";
import { PositionNodeChat } from "@/position-roundtable/components/PositionNodeChat";
@@ -462,6 +463,8 @@ export default function PositionRoundtablePage({
const [startingChain, setStartingChain] = useState(false);
const [rightPanelOpen, setRightPanelOpen] = useState(true);
const [rightTab, setRightTab] = useState<WorkspaceTab>("agents");
const [taskPanelCollapsed, setTaskPanelCollapsed] = useState(false);
const [intentPanelCollapsed, setIntentPanelCollapsed] = useState(false);
const artifactsCtx = useArtifactsOptional();
const artifactsCtxRef = useRef(artifactsCtx);
const [sandboxArtifact, setSandboxArtifact] = useState<PositionArtifactPreview | null>(null);
@@ -748,6 +751,9 @@ export default function PositionRoundtablePage({
}, [positionSession?.chainSnapshot, positionSession?.status, selectedChain]);
const isIntelligenceDeliveryOverview =
activePosition.isIntentPosition && positionSession !== null && positionSession.status !== "intent_pending";
const showTaskAndIntentPanels = !activePosition.isIntentPosition || isIntelligenceDeliveryOverview;
const leftSidebarHidden =
showTaskAndIntentPanels && taskPanelCollapsed && intentPanelCollapsed;
const allPositionDeliveriesDone = useMemo(
() => allExecutableNodesDone(positionSession?.nodes ?? []),
[positionSession?.nodes],
@@ -1347,6 +1353,8 @@ export default function PositionRoundtablePage({
setSelectedAgentId(null);
setActiveNodeKey(null);
setRightPanelOpen(nextPositionId === DEFAULT_POSITION_ID);
setTaskPanelCollapsed(false);
setIntentPanelCollapsed(false);
},
[closeSandbox],
);
@@ -1512,12 +1520,27 @@ export default function PositionRoundtablePage({
</div>
</header>
<div className="flex min-h-0 flex-1 gap-3 overflow-hidden p-3">
<aside className="custom-scrollbar flex w-[360px] shrink-0 flex-col gap-3 overflow-y-auto rounded-2xl border border-slate-200 bg-white p-3 shadow-sm dark:border-[#2b8ffe]/45 dark:bg-[#032650]/80 dark:text-[#c5cfe0] dark:shadow-none dark:backdrop-blur-md">
<div
className={cn(
"relative flex min-h-0 flex-1 overflow-hidden p-3 transition-[gap] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none",
leftSidebarHidden ? "gap-0" : "gap-3",
)}
>
<aside
aria-hidden={leftSidebarHidden || undefined}
className={cn(
"custom-scrollbar relative flex min-h-0 shrink-0 flex-col gap-3 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm transition-[width,opacity,transform,padding,border-width] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none dark:rounded-none dark:border-0 dark:bg-transparent dark:text-[#c5cfe0] dark:shadow-none",
leftSidebarHidden
? "w-0 -translate-x-4 !border-0 !p-0 opacity-0 pointer-events-none"
: "w-[360px] translate-x-0 p-3 opacity-100 dark:p-0",
)}
>
<TaskInformationCard
task={task}
editable={!isArchivedSession}
onEdit={openTaskEditor}
collapsed={taskPanelCollapsed}
onCollapsedChange={setTaskPanelCollapsed}
/>
{activePosition.isIntentPosition ? (
@@ -1526,6 +1549,8 @@ export default function PositionRoundtablePage({
intent={frozenIntent ?? intent.intentReady}
editable={!isArchivedSession}
onEdit={openIntentEditor}
collapsed={intentPanelCollapsed}
onCollapsedChange={setIntentPanelCollapsed}
/>
) : (
<BusinessChainSelector
@@ -1542,6 +1567,8 @@ export default function PositionRoundtablePage({
intent={frozenIntent ?? intent.intentReady}
editable={!isArchivedSession}
onEdit={openIntentEditor}
collapsed={intentPanelCollapsed}
onCollapsedChange={setIntentPanelCollapsed}
/>
<BusinessChainFlow
chain={activeChain}
@@ -1555,6 +1582,21 @@ export default function PositionRoundtablePage({
)}
</aside>
{leftSidebarHidden && (
<button
type="button"
title="展开左侧任务与意图信息"
aria-label="展开左侧任务与意图信息"
onClick={() => {
setTaskPanelCollapsed(false);
setIntentPanelCollapsed(false);
}}
className="absolute left-3 top-3 z-20 flex h-10 w-[30px] items-center justify-center rounded-r-[4px] border border-l-0 border-[#2b8ffe]/60 bg-[#032650]/95 shadow-[4px_0_12px_rgba(0,0,0,0.18)] transition-colors hover:bg-[#0b386d] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#45c3f3]/70"
>
<img src={expandLogo} alt="" className="size-4" />
</button>
)}
<section className="relative flex min-w-[420px] flex-1 flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-[#2b8ffe]/45 dark:bg-[#032650]/80 dark:shadow-none dark:backdrop-blur-md">
{!isIntelligenceDeliveryOverview && !rightPanelOpen && !sandboxOpen && (
<TButton
@@ -2603,7 +2645,7 @@ function HoverEditButton({
title={title}
aria-label={title}
onClick={onClick}
className="absolute right-3 top-3 z-10 flex size-7 items-center justify-center rounded-full border border-slate-200 bg-white/95 text-slate-500 opacity-0 shadow-sm transition-all hover:border-primary/40 hover:bg-primary/5 hover:text-primary focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 group-hover:opacity-100 dark:border-[#2b8ffe]/35 dark:bg-[#063363]/95 dark:text-[#9ebfe2] dark:hover:border-[#2b8ffe]/80 dark:hover:bg-[#0b4fae]/45 dark:hover:text-[#83c1ff]"
className="pointer-events-none flex size-7 shrink-0 items-center justify-center rounded-[4px] text-[#45c3f3] opacity-0 transition-[opacity,color,background-color] duration-150 group-hover:pointer-events-auto group-hover:opacity-100 hover:bg-[#45c3f3]/10 hover:text-[#a2e1f9] focus-visible:pointer-events-auto focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#45c3f3]/65 motion-reduce:transition-none"
>
<PencilLine className="size-3.5" strokeWidth={2.4} />
</button>
@@ -2614,46 +2656,64 @@ function TaskInformationCard({
task,
editable,
onEdit,
collapsed,
onCollapsedChange,
}: {
task: PositionTask;
editable?: boolean;
onEdit?: () => void;
collapsed: boolean;
onCollapsedChange: (collapsed: boolean) => void;
}) {
return (
<section className="group relative rounded-2xl border border-[#ECE7E4] bg-white p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)] dark:border-[#2b8ffe]/30 dark:bg-[#042b59]/55 dark:shadow-none">
{editable && onEdit && <HoverEditButton title="编辑任务信息" onClick={onEdit} />}
<div className="mb-3 flex items-center gap-2">
<span className="flex size-5 items-center justify-center rounded-md bg-primary text-primary-foreground">
<Target className="size-3" strokeWidth={2.5} />
</span>
<h2 className="text-sm font-semibold tracking-wide text-slate-900 dark:text-[#e8edf5]">
任务信息
</h2>
<section className="group relative shrink-0 overflow-hidden rounded-[4px] border border-[#ECE7E4] bg-white shadow-[0_4px_20px_rgba(0,0,0,0.02)] dark:border-[rgba(43,143,254,0.6)] dark:bg-[#032650] dark:shadow-none">
<div className="relative flex h-10 items-center border-b border-slate-200 bg-primary/10 px-4 dark:border-b-[#2b8ffe]/60 dark:bg-[linear-gradient(89.65deg,#0062d9_0.03%,rgba(0,98,217,0.78)_52%,rgba(0,98,217,0)_100%)] dark:after:absolute dark:after:inset-x-0 dark:after:bottom-[-1px] dark:after:h-px dark:after:bg-[#45c3f3]/85 dark:after:content-['']">
<span
aria-hidden="true"
className="mr-3 h-5 w-1 shrink-0 rounded-l-[2px] rounded-br-[2px] bg-[linear-gradient(167.47deg,#a2e1f9_0%,#45c3f3_100%)]"
/>
<h2 className="text-base font-semibold tracking-wide text-slate-900 dark:text-white">任务信息</h2>
<div className="relative z-10 ml-auto flex items-center gap-2">
{editable && onEdit && <HoverEditButton title="编辑任务信息" onClick={onEdit} />}
<button
type="button"
title={collapsed ? "展开任务信息" : "收起任务信息"}
aria-label={collapsed ? "展开任务信息" : "收起任务信息"}
aria-expanded={!collapsed}
onClick={() => onCollapsedChange(!collapsed)}
className="flex size-7 shrink-0 items-center justify-center rounded-[4px] transition-colors hover:bg-[#45c3f3]/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#45c3f3]/65"
>
<img src={expandLogo} alt="" className="size-4" />
</button>
</div>
</div>
<div className="space-y-3">
<div>
<p className="text-[10px] font-semibold uppercase tracking-wide text-slate-400 dark:text-[#8aa0c4]">
任务名称
</p>
<p className="mt-0.5 line-clamp-2 text-xs font-semibold leading-relaxed text-slate-800 dark:text-[#e8edf5]">
{desensitize(task.title)}
</p>
</div>
<div>
<p className="text-[10px] font-semibold uppercase tracking-wide text-slate-400 dark:text-[#8aa0c4]">
任务描述
</p>
<p className="mt-0.5 line-clamp-5 whitespace-pre-wrap text-[11px] leading-relaxed text-slate-600 dark:text-[#a9c2e4]">
{desensitize(task.description)}
</p>
</div>
<div className="flex items-center justify-between gap-2 border-t border-slate-100 pt-2.5 dark:border-[#2b8ffe]/20">
<span className="text-[10px] font-semibold uppercase tracking-wide text-slate-400 dark:text-[#8aa0c4]">
任务方向
</span>
<span className="truncate rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-semibold text-primary dark:bg-[#2b8ffe]/15 dark:text-[#83c1ff]">
{desensitize(task.direction)}
</span>
<div
className={cn(
"grid transition-[grid-template-rows,opacity] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none",
collapsed ? "grid-rows-[0fr] opacity-0" : "grid-rows-[1fr] opacity-100",
)}
>
<div className="min-h-0 overflow-hidden">
<div className="space-y-3 p-4">
<div>
<p className="text-xs font-semibold text-slate-500 dark:text-[#9ebfe2]">任务名称</p>
<p
title={desensitize(task.title)}
className="mt-1 line-clamp-1 text-sm font-semibold leading-relaxed text-slate-800 dark:text-[#f2f7ff]"
>
{desensitize(task.title)}
</p>
</div>
<div>
<p className="text-xs font-semibold text-slate-500 dark:text-[#9ebfe2]">任务描述</p>
<p
title={desensitize(task.description)}
className="mt-1 line-clamp-3 whitespace-pre-wrap text-sm leading-6 text-slate-600 dark:text-[#d8e6f8]"
>
{desensitize(task.description)}
</p>
</div>
</div>
</div>
</div>
</section>
@@ -2930,40 +2990,83 @@ function IntentUnderstandingCard({
intent,
editable,
onEdit,
collapsed,
onCollapsedChange,
}: {
intent: ReturnType<typeof useStep1Intent>["intentReady"];
editable?: boolean;
onEdit?: () => void;
collapsed: boolean;
onCollapsedChange: (collapsed: boolean) => void;
}) {
return (
<section className="group relative rounded-2xl border border-[#ECE7E4] bg-white p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)] dark:border-[#2b8ffe]/30 dark:bg-[#042b59]/55 dark:shadow-none">
{editable && onEdit && <HoverEditButton title="编辑意图理解" onClick={onEdit} />}
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold tracking-wide text-slate-900 dark:text-[#e8edf5]">
<span className="flex size-5 items-center justify-center rounded-md bg-primary text-primary-foreground">
<Sparkles className="size-3" strokeWidth={2.5} />
</span>
意图理解
</h2>
<section
className={cn(
"group relative flex min-h-0 flex-col overflow-hidden rounded-[4px] border border-[#ECE7E4] bg-white shadow-[0_4px_20px_rgba(0,0,0,0.02)] dark:border-[rgba(43,143,254,0.6)] dark:bg-[#032650] dark:shadow-none",
collapsed ? "flex-none" : "flex-1",
)}
>
<div className="relative flex h-10 shrink-0 items-center border-b border-slate-200 bg-primary/10 px-4 dark:border-b-[#2b8ffe]/60 dark:bg-[linear-gradient(89.65deg,#0062d9_0.03%,rgba(0,98,217,0.78)_52%,rgba(0,98,217,0)_100%)] dark:after:absolute dark:after:inset-x-0 dark:after:bottom-[-1px] dark:after:h-px dark:after:bg-[#45c3f3]/85 dark:after:content-['']">
<span
aria-hidden="true"
className="mr-3 h-5 w-1 shrink-0 rounded-l-[2px] rounded-br-[2px] bg-[linear-gradient(167.47deg,#a2e1f9_0%,#45c3f3_100%)]"
/>
<h2 className="text-base font-semibold tracking-wide text-slate-900 dark:text-white">意图理解</h2>
<div className="relative z-10 ml-auto flex items-center gap-2">
{editable && onEdit && <HoverEditButton title="编辑意图理解" onClick={onEdit} />}
<button
type="button"
title={collapsed ? "展开意图理解" : "收起意图理解"}
aria-label={collapsed ? "展开意图理解" : "收起意图理解"}
aria-expanded={!collapsed}
onClick={() => onCollapsedChange(!collapsed)}
className="flex size-7 shrink-0 items-center justify-center rounded-[4px] transition-colors hover:bg-[#45c3f3]/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#45c3f3]/65"
>
<img src={expandLogo} alt="" className="size-4" />
</button>
</div>
</div>
<div
className={cn(
"grid min-h-0 transition-[grid-template-rows,opacity] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none",
collapsed ? "grid-rows-[0fr] opacity-0" : "grid-rows-[1fr] opacity-100",
)}
>
<div className="min-h-0 overflow-hidden">
{intent ? (
<div className="space-y-3">
<p className="text-xs font-medium leading-5 text-slate-800 dark:text-[#e8edf5]">{desensitize(intent.objective)}</p>
<div className="space-y-1.5">
{intent.constraints.map((constraint, index) => (
<div
key={`${constraint}-${index}`}
className="flex items-start gap-2 text-[11px] text-muted-foreground dark:text-[#a9c2e4]"
>
<CheckCircle2 className="mt-0.5 size-3 shrink-0 text-emerald-500" />
<span className="leading-4">{desensitize(constraint)}</span>
</div>
))}
<div className="space-y-3 p-4">
<div>
<p className="text-xs font-semibold text-slate-500 dark:text-[#9ebfe2]">任务意图</p>
<p className="mt-1 text-sm leading-6 text-slate-700 dark:text-[#f2f7ff]">
{desensitize(intent.objective)}
</p>
</div>
<div>
<p className="text-xs font-semibold text-slate-500 dark:text-[#9ebfe2]">关键约束</p>
<ul className="mt-1 list-disc space-y-1 pl-4 text-sm leading-6 text-slate-600 marker:text-slate-400 dark:text-[#d8e6f8] dark:marker:text-[#83b6ed]">
{intent.constraints.map((constraint, index) => (
<li key={`${constraint}-${index}`}>{desensitize(constraint)}</li>
))}
</ul>
</div>
{intent.assumptions && intent.assumptions.length > 0 && (
<div>
<p className="text-xs font-semibold text-slate-500 dark:text-[#9ebfe2]">关键假设</p>
<ul className="mt-1 list-disc space-y-1 pl-4 text-sm leading-6 text-slate-600 marker:text-slate-400 dark:text-[#d8e6f8] dark:marker:text-[#83b6ed]">
{intent.assumptions.map((assumption, index) => (
<li key={`${assumption}-${index}`}>{desensitize(assumption)}</li>
))}
</ul>
</div>
)}
</div>
) : (
<div className="rounded-xl border border-dashed border-[#E8D0CC] p-3 text-xs leading-5 text-muted-foreground dark:border-[#2b8ffe]/30 dark:text-[#8aa0c4]">
<div className="p-4 text-sm leading-6 text-muted-foreground dark:text-[#d8e6f8]">
情报分析岗完成意图识别后,其他岗位会在这里看到统一的任务意图与约束。
</div>
)}
</div>
</div>
</section>
);
}
@@ -942,6 +942,14 @@ make gateway
Direct access (without nginx):
- Gateway: `http://localhost:8001`
### Browser CORS
The Gateway always permits the local TaskCOP/Vite origins
`http://localhost:8080` and `http://127.0.0.1:8080` with credentials. A
deployed frontend on another origin must be listed exactly in
`GATEWAY_CORS_ORIGINS` (for example `http://47.88.25.99:7010`); those explicit
origins are merged with the local TaskCOP origins.
### Frontend Configuration
The frontend uses environment variables to connect to backend services:
@@ -285,6 +285,14 @@ Provider note:
- `models[*].use` references provider classes by module path (for example `langchain_openai:ChatOpenAI`).
- If a provider module is missing, DeerFlow now returns an actionable error with install guidance (for example `uv add langchain-google-genai`).
### Browser CORS
The Gateway permits the local TaskCOP/Vite development origins
`http://localhost:8080` and `http://127.0.0.1:8080` with credentials. For a
separate deployed frontend, set `GATEWAY_CORS_ORIGINS` to its exact origin
(including scheme and port), for example `http://47.88.25.99:7010`. Explicit
origins are used in addition to the local TaskCOP development origins.
### Extensions Configuration (`extensions_config.json`)
MCP servers and skill states in a single file:
@@ -1328,8 +1328,18 @@ This gateway provides custom endpoints for models, MCP configuration, skills, an
v = os.environ.get("GATEWAY_CORS_ALLOW_ALL", "").strip().lower()
return v in ("1", "true", "yes", "on") or is_auth_disabled()
cors_origins_env = os.environ.get("GATEWAY_CORS_ORIGINS", "").strip()
cors_origins: list[str] = [o.strip() for o in cors_origins_env.split(",") if o.strip()] if cors_origins_env else []
# The legacy TaskCOP Vue application uses Vite's default development port.
# Keep these origins available even when deployments configure additional
# origins through GATEWAY_CORS_ORIGINS.
taskcop_vite_origins = [
"http://localhost:8080",
"http://127.0.0.1:8080",
]
cors_origins_env = os.environ.get("GATEWAY_CORS_ORIGINS", "").strip()
cors_origins: list[str] = [o.strip() for o in cors_origins_env.split(",") if o.strip()] if cors_origins_env else []
if cors_origins:
cors_origins = list(dict.fromkeys([*cors_origins, *taskcop_vite_origins]))
# Headers the LangGraph SDK / browser clients need to read from cross-origin
# responses. Without exposing them, the SDK's `response.headers.get(...)` returns
@@ -1382,9 +1392,10 @@ This gateway provides custom endpoints for models, MCP configuration, skills, an
"http://127.0.0.1:5173",
"http://localhost:5174",
"http://127.0.0.1:5174",
"http://localhost:3000",
"http://127.0.0.1:3000",
]
"http://localhost:3000",
"http://127.0.0.1:3000",
*taskcop_vite_origins,
]
logger.warning(
"No CORS configuration found; enabling local dev origins only: %s. "
"Set GATEWAY_CORS_ORIGINS or GATEWAY_CORS_ALLOW_ALL for deployment.",