122 lines
4.6 KiB
TypeScript
122 lines
4.6 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import { useSearchParams } from "next/navigation";
|
||
|
||
import { useTheme } from "@/contexts/ThemeContext";
|
||
import { loginByUsername } from "@/core/auth/api";
|
||
import { setStoredAuth } from "@/core/auth";
|
||
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";
|
||
|
||
const DEFAULT_USERNAME = "guest";
|
||
|
||
/**
|
||
* 把宿主传入的主题标识映射成 light/dark。**默认深蓝(dark)**:
|
||
* 只有明确传 light/红白 才走浅色,其余(dark / dark-blue / 深蓝 / 空)一律深蓝。
|
||
* 不复用 `resolveEmbedTheme`(它只认严格的 "dark-blue",传 "dark" 会被判成浅色)。
|
||
*/
|
||
function resolveEmbedRoundtableTheme(raw: string): "dark" | "light" {
|
||
const v = raw.trim().toLowerCase();
|
||
if (v === "light" || v === "red" || v === "红白") return "light";
|
||
return "dark";
|
||
}
|
||
|
||
/**
|
||
* 圆桌规划嵌入页(独立入口,**与现有 wujie ticket 登录无关**)。
|
||
*
|
||
* 用于宿主(如 parallel-agents-vue 的 RoundtableDrawer)用无界把圆桌**主体页面**塞进抽屉:
|
||
* - **登录**:走「用户名免密登录」(`loginByUsername`),username 来自 `window.$wujie.props.username`
|
||
* (或 URL `?username=`),不经 LoginPage 的 MaxKey/consumer ticket 流程。
|
||
* - **无侧边栏**:本路由挂在顶层(`/embed/roundtable`),在 `AppProviders` 之下、`PageRoutes` 之外,
|
||
* 天然不含页面侧边栏 / workspace 导航,只渲染圆桌主体。
|
||
* - **任务预填**:taskTitle / taskGoal / chainId 由 `RoundtablePlanningPage` 自身的
|
||
* `readWujiePrefill()` 直接从 `window.$wujie.props` 读取(抽屉打开即自动跑第一步、按链条进第二步)。
|
||
*/
|
||
export default function EmbedRoundtablePage() {
|
||
const searchParams = useSearchParams();
|
||
const { setTheme } = useTheme();
|
||
|
||
// username / password / theme:wujie props 优先,URL query 兜底。
|
||
const wp = readWujieProps();
|
||
const username =
|
||
(typeof wp.username === "string" && wp.username.trim()) ||
|
||
(typeof wp.userName === "string" && wp.userName.trim()) ||
|
||
searchParams.get("username")?.trim() ||
|
||
searchParams.get("userName")?.trim() ||
|
||
DEFAULT_USERNAME;
|
||
// 共享口令(用户名+密码登录模式):仅当后端开启 username_login.require_password 时校验。
|
||
// 不 trim —— 口令可能含首尾空格;未传则按「免密」走(后端未开门禁时照常)。
|
||
const password =
|
||
(typeof wp.password === "string" && wp.password) ||
|
||
searchParams.get("password") ||
|
||
undefined;
|
||
// 主题色:宿主传入优先;默认「深蓝」= dark 主题(其主色为蓝,oklch hue 259)。
|
||
const themeParam =
|
||
(typeof wp.theme === "string" && wp.theme.trim()) ||
|
||
searchParams.get("theme")?.trim() ||
|
||
"dark";
|
||
|
||
const [authReady, setAuthReady] = useState(false);
|
||
const [authError, setAuthError] = useState<string | null>(null);
|
||
|
||
useEffect(() => {
|
||
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() {
|
||
setAuthError(null);
|
||
try {
|
||
const auth = await loginByUsername(username, password);
|
||
if (cancelled) return;
|
||
setStoredAuth(auth);
|
||
setAuthReady(true);
|
||
} catch (error) {
|
||
if (cancelled) return;
|
||
setAuthError(error instanceof Error ? error.message : "登录失败");
|
||
}
|
||
}
|
||
void runLogin();
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, [username, password]);
|
||
|
||
if (authError) {
|
||
return (
|
||
<div className="flex size-full items-center justify-center bg-background p-6 text-sm text-red-600">
|
||
登录失败:{authError}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!authReady) {
|
||
return (
|
||
<div className="flex size-full items-center justify-center bg-background p-6 text-sm text-muted-foreground">
|
||
正在进入圆桌…
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// RoundtablePlanningPage 依赖 SubtasksProvider / ArtifactsProvider / PromptInputProvider
|
||
// (Step 2 沙箱用 useArtifacts)。ChatRuntime 正是这三者的封装;不套 WorkspaceLayout,
|
||
// 故无侧边栏,只渲染圆桌主体。
|
||
return (
|
||
<div className="h-full w-full overflow-hidden">
|
||
<ChatRuntime>
|
||
<RoundtablePlanningPage />
|
||
</ChatRuntime>
|
||
</div>
|
||
);
|
||
}
|