样式修改
This commit is contained in:
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.",
|
||||
|
||||
Reference in new issue
Block a user