套用wujie修改
This commit is contained in:
1 parent
c48317672c
commit
31525e79a8
5 files changed
+171
-110
No files matched your search
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user