套用wujie修改

This commit is contained in:
董宏凯 committed 2026-06-11 17:36:20 +08:00
1 parent c48317672c
commit 31525e79a8
5 files changed
+171 -110

No files matched your search

+85 -75
View File
@@ -4,28 +4,38 @@ import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";
import { isWujieEnv } from "@/core/embed/wujie";
import { cn } from "@/lib/utils";
function Dialog({
modal,
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
// 无界嵌入时 Radix modal 会给 body 注入异常 margin-right,导致右侧空白。
// 默认降为 non-modal;显式传 modal={true} 仍可强制 modal。
return (
<DialogPrimitive.Root
data-slot="dialog"
modal={modal ?? !isWujieEnv()}
{...props}
/>
);
}
const DialogTrigger = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>
>(({ ...props }, ref) => {
return (
<DialogPrimitive.Trigger
ref={ref}
data-slot="dialog-trigger"
{...props}
/>
);
});
DialogTrigger.displayName = DialogPrimitive.Trigger.displayName;
const DialogTrigger = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>
>(({ ...props }, ref) => {
return (
<DialogPrimitive.Trigger
ref={ref}
data-slot="dialog-trigger"
{...props}
/>
);
});
DialogTrigger.displayName = DialogPrimitive.Trigger.displayName;
function DialogPortal({
...props
@@ -33,68 +43,68 @@ function DialogPortal({
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
const DialogClose = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Close>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>
>(({ ...props }, ref) => {
return <DialogPrimitive.Close ref={ref} data-slot="dialog-close" {...props} />;
});
DialogClose.displayName = DialogPrimitive.Close.displayName;
const DialogClose = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Close>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>
>(({ ...props }, ref) => {
return <DialogPrimitive.Close ref={ref} data-slot="dialog-close" {...props} />;
});
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => {
return (
<DialogPrimitive.Overlay
ref={ref}
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className,
)}
{...props}
/>
);
});
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
DialogClose.displayName = DialogPrimitive.Close.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
}
>(({ className, children, showCloseButton = true, ...props }, ref) => {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
);
});
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => {
return (
<DialogPrimitive.Overlay
ref={ref}
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className,
)}
{...props}
/>
);
});
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
}
>(({ className, children, showCloseButton = true, ...props }, ref) => {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
);
});
DialogContent.displayName = DialogPrimitive.Content.displayName;
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
@@ -6,7 +6,7 @@ import { useSearchParams } from "next/navigation";
import { useTheme } from "@/contexts/ThemeContext";
import { loginByUsername } from "@/core/auth/api";
import { setStoredAuth } from "@/core/auth";
import { readWujieProps } from "@/core/embed/wujie";
import { readWujieProps, isWujieEnv } from "@/core/embed/wujie";
import RoundtablePlanningPage from "@/roundtable-planning/pages/RoundtablePlanningPage";
import { ChatRuntime } from "./WorkspaceRoutes";
import "@/roundtable-planning/styles/roundtable-planning.css";
@@ -66,6 +66,12 @@ export default function EmbedRoundtablePage() {
setTheme(resolveEmbedRoundtableTheme(themeParam));
}, [themeParam, setTheme]);
useEffect(() => {
if (!isWujieEnv()) return;
document.documentElement.classList.add("wujie-embed");
return () => document.documentElement.classList.remove("wujie-embed");
}, []);
useEffect(() => {
let cancelled = false;
async function runLogin() {
@@ -27,7 +27,6 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { isWujieEnv } from "@/core/embed/wujie";
import { cn } from "@/lib/utils";
import { listChains, setChainPublic, type ChainScope } from "../api/chains";
@@ -188,15 +187,8 @@ export function BusinessChainListDialog({
onOpenChange,
onConfigure,
}: BusinessChainListDialogProps) {
const inWujie = isWujieEnv();
return (
<Dialog
open={open}
onOpenChange={onOpenChange}
// Wujie 内部容器场景下,Radix modal 的 body-scroll-lock 可能计算出异常
// margin-right,导致右侧出现大片空白。这里降为 non-modal 规避布局抖动。
modal={!inWujie}
>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="flex max-h-[85vh] flex-col gap-0 sm:max-w-2xl">
<DialogHeader className="shrink-0">
<DialogTitle className="flex items-center gap-2">
@@ -157,6 +157,8 @@ export default function RoundtablePlanningPage() {
const isDark = theme === "dark";
const navigate = useNavigate();
const location = useLocation();
/** 无界(wujie)嵌入抽屉:隐藏业务链条入口,设置按钮改作手动回传交付。 */
const inWujieEmbed = isWujieEnv();
// ─── 会商自动化串联(模块 C):任务页带进来的预填 ─────────────────
// 只在首次渲染读一次 navigation state;随后清掉 history state,避免刷新/后退重放。
@@ -167,6 +169,12 @@ export default function RoundtablePlanningPage() {
);
// 由 prefill.businessCode 解析出的「自动选用业务链」结果;有值时「进入研讨」跳过选链弹窗直接进 Step 2。
const [autoChainResult, setAutoChainResult] = useState<SeatSelectionResult | null>(null);
/** 无界直传 chainId 时,链条解析完成前禁止弹选链窗(避免 modal 布局抖动)。 */
const [autoChainLoading, setAutoChainLoading] = useState(
() =>
inWujieEmbed &&
!!(prefillRef.current?.chainId || prefillRef.current?.businessCode),
);
const autoStartedRef = useRef(false);
const prefillClearedRef = useRef(false);
// 共识达成后只向父页面(无界)回传一次交付结果。
@@ -774,6 +782,7 @@ export default function RoundtablePlanningPage() {
useEffect(() => {
const prefill = prefillRef.current;
if (!prefill?.chainId && !prefill?.businessCode) return;
setAutoChainLoading(true);
let cancelled = false;
void (async () => {
try {
@@ -794,6 +803,8 @@ export default function RoundtablePlanningPage() {
if (!cancelled && result) setAutoChainResult(result);
} catch {
/* 解析失败 → 保持 null,回退弹窗手选 */
} finally {
if (!cancelled) setAutoChainLoading(false);
}
})();
return () => {
@@ -801,13 +812,9 @@ export default function RoundtablePlanningPage() {
};
}, []);
// C4(无界回传):第二步达成共识后,把总控结论 + 各智能体交付一次性广播给父页面。
// 用 ref 防重(共识态会多次触发渲染);非无界环境静默跳过。
useEffect(() => {
if (!step2.hasConsensus || consensusEmittedRef.current) return;
if (!isWujieEnv()) return;
consensusEmittedRef.current = true;
emitWujieEvent("roundtable:consensus", {
/** 组装无界回传载荷:总控共识 + 各智能体最终交付(剔除总控席位)。 */
const buildWujieConsensusPayload = useCallback(
() => ({
taskTitle: prefillRef.current?.taskTitle ?? null,
chainId: prefillRef.current?.chainId ?? null,
chainTitle: chainMeta?.title ?? null,
@@ -817,14 +824,43 @@ export default function RoundtablePlanningPage() {
step2.step2RoundtableDialogues,
step2.coordinatorName,
),
});
}, [
step2.hasConsensus,
step2.coordinatorName,
step2.lastLeaderContent,
step2.step2RoundtableDialogues,
chainMeta,
]);
}),
[
chainMeta,
step2.coordinatorName,
step2.lastLeaderContent,
step2.step2RoundtableDialogues,
],
);
const emitConsensusToParent = useCallback(() => {
if (!inWujieEmbed) return false;
return emitWujieEvent("roundtable:consensus", buildWujieConsensusPayload());
}, [buildWujieConsensusPayload, inWujieEmbed]);
const handleSettingsClick = useCallback(() => {
if (inWujieEmbed) {
const deliverables = buildWujieConsensusPayload().deliverables;
if (deliverables.length === 0) {
triggerToast("暂无可回传的智能体交付结果");
return;
}
if (emitConsensusToParent()) {
triggerToast(`已回传 ${deliverables.length} 个智能体交付结果`);
}
return;
}
triggerToast("⚙️ 系统管理面板拉取中(暂不开放外部审计)");
}, [buildWujieConsensusPayload, emitConsensusToParent, inWujieEmbed, triggerToast]);
// C4(无界回传):第二步达成共识后,把总控结论 + 各智能体交付一次性广播给父页面。
// 用 ref 防重(共识态会多次触发渲染);非无界环境静默跳过。
useEffect(() => {
if (!step2.hasConsensus || consensusEmittedRef.current) return;
if (!inWujieEmbed) return;
consensusEmittedRef.current = true;
emitConsensusToParent();
}, [step2.hasConsensus, inWujieEmbed, emitConsensusToParent]);
// 把页面重置回「第一步全新任务」——新建任务 + 后台挂起后都复用。
// 注意:只清前端状态,不删后端已建的草稿 / 已挂起的后台作业(它们继续在后台跑,
@@ -1016,15 +1052,17 @@ export default function RoundtablePlanningPage() {
/>
<div className="flex items-center gap-2">
<button
id="business-chain-btn"
onClick={() => setIsChainListOpen(true)}
title="查看 / 配置可复用的业务链条(有序智能体编排)"
className="bg-white hover:bg-slate-50 border border-[#e2e8f0] text-[#334155] text-xs font-bold px-3 py-2 rounded-xl flex items-center gap-1.5 transition dark:border-border dark:bg-secondary dark:text-foreground dark:hover:bg-accent"
>
<ListOrdered className="w-3.5 h-3.5 text-primary" />
业务链条
</button>
{!inWujieEmbed && (
<button
id="business-chain-btn"
onClick={() => setIsChainListOpen(true)}
title="查看 / 配置可复用的业务链条(有序智能体编排)"
className="bg-white hover:bg-slate-50 border border-[#e2e8f0] text-[#334155] text-xs font-bold px-3 py-2 rounded-xl flex items-center gap-1.5 transition dark:border-border dark:bg-secondary dark:text-foreground dark:hover:bg-accent"
>
<ListOrdered className="w-3.5 h-3.5 text-primary" />
业务链条
</button>
)}
<button
id="new-task-btn"
@@ -1083,7 +1121,8 @@ export default function RoundtablePlanningPage() {
<button
id="settings-btn"
onClick={() => triggerToast("⚙️ 系统管理面板拉取中(暂不开放外部审计)")}
onClick={handleSettingsClick}
title={inWujieEmbed ? "回传各智能体交付结果给宿主" : "系统设置"}
className="p-1.5 text-slate-400 hover:text-slate-600 rounded-lg hover:bg-slate-100 transition-colors dark:text-muted-foreground dark:hover:bg-accent dark:hover:text-foreground"
>
<Settings className="w-4 h-4" />
@@ -1142,6 +1181,14 @@ export default function RoundtablePlanningPage() {
void drafts.ensureDraft(1).finally(() => handleConfirmAgents(result));
return;
}
if (
inWujieEmbed &&
autoChainLoading &&
(prefillRef.current?.chainId || prefillRef.current?.businessCode)
) {
triggerToast("正在加载业务链条,请稍候…");
return;
}
// 先把草稿落地拿到 draft_id,推荐历史才有归属;失败也照常打开弹窗
// (弹窗在无 draftId 时退化为「直接推荐、不存历史」)。
void drafts.ensureDraft(1).finally(() => setIsRecommendDialogOpen(true));
+6
View File
@@ -337,6 +337,12 @@
height: 100%;
}
/* 无界嵌入:Radix scroll-lock 可能误算 margin-right,留下右侧空白条 */
html.wujie-embed body {
margin-right: 0 !important;
padding-right: 0 !important;
}
[data-sidebar="menu-button"][data-active="true"] {
box-shadow: inset 3px 0 0 var(--primary);
font-weight: 600;