feat: 智能体控制台前端(console)

- React+antd+zustand,注册表驱动路由/菜单/权限
- Tauri 桌面壳、后端托管 SPA、跨标签页消息队列
This commit is contained in:
2026-08-23 22:43:18 +08:00
parent 96659759b0
commit 9b30f681c6
975 changed files with 198229 additions and 0 deletions
@@ -0,0 +1,49 @@
import { describe, it, expect, vi } from "vitest";
import { screen } from "@testing-library/react";
import ThemeToggleButton from "./index";
import { ThemeProvider } from "@/contexts/ThemeContext";
import { render } from "@testing-library/react";
vi.mock("react-i18next", () => ({
useTranslation: () => ({ t: (k: string) => k }),
}));
// Wrap with real ThemeProvider, control initial theme via localStorage
function renderWithTheme(mode: "light" | "dark" | "system" = "light") {
localStorage.setItem("qwenpaw-theme", mode);
return render(
<ThemeProvider>
<ThemeToggleButton />
</ThemeProvider>,
);
}
describe("ThemeToggleButton", () => {
it("renders the theme toggle button", () => {
renderWithTheme("light");
expect(screen.getByRole("button")).toBeInTheDocument();
});
it("shows sun icon when light mode is active", () => {
renderWithTheme("light");
expect(
document.querySelector('[data-icon="SparkSunLine"]'),
).toBeInTheDocument();
});
it("shows moon icon when dark mode is active", () => {
renderWithTheme("dark");
expect(
document.querySelector('[data-icon="SparkMoonLine"]'),
).toBeInTheDocument();
});
it("shows sun-moon icon when system mode is active", () => {
renderWithTheme("system");
expect(document.querySelector(".lucide-sun-moon")).toBeInTheDocument();
});
it("renders without crashing", () => {
expect(() => renderWithTheme("light")).not.toThrow();
});
});
@@ -0,0 +1,86 @@
/* ---- Theme toggle button ---- */
.toggleBtn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 32px;
border: none;
border-radius: 8px;
background: transparent;
cursor: pointer;
transition:
background 0.15s ease,
color 0.15s ease;
color: rgba(0, 0, 0, 0.65);
padding: 0;
flex-shrink: 0;
font-size: 24px;
&:hover {
background: rgba(97, 92, 237, 0.08);
color: #ff7f16;
}
}
/* ---- Theme Dropdown ---- */
.themeDropdown {
:global {
.qwenpaw-dropdown-menu-item,
.ant-dropdown-menu-item {
color: rgba(26, 23, 22, 0.65);
&:hover {
background: rgba(43, 18, 0, 0.06) !important;
}
}
.qwenpaw-dropdown-menu-item-selected,
.ant-dropdown-menu-item-selected {
background: rgba(43, 18, 0, 0.03) !important;
color: rgba(26, 23, 22, 0.88) !important;
}
}
}
/* Dark mode overrides */
:global(.dark-mode) {
.toggleBtn {
color: rgba(255, 255, 255, 0.65);
&:hover {
background: rgba(255, 255, 255, 0.1);
color: #fff;
}
}
.themeDropdown {
border: 1px solid rgba(255, 255, 255, 0.1) !important;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5) !important;
border-radius: 8px;
overflow: hidden;
:global {
.qwenpaw-dropdown-menu,
.ant-dropdown-menu {
background: #2a2a2a !important;
border: none !important;
box-shadow: none !important;
}
.qwenpaw-dropdown-menu-item,
.ant-dropdown-menu-item {
color: rgba(255, 255, 255, 0.65);
&:hover {
background: rgba(255, 255, 255, 0.08) !important;
}
}
.qwenpaw-dropdown-menu-item-selected,
.ant-dropdown-menu-item-selected {
background: rgba(255, 255, 255, 0.06) !important;
color: rgba(255, 255, 255, 0.88) !important;
}
}
}
}
@@ -0,0 +1,49 @@
import { Dropdown, Button, type MenuProps } from "antd";
import { SparkMoonLine, SparkSunLine } from "@agentscope-ai/icons";
import { SunMoon } from "lucide-react";
import { useTheme, type ThemeMode } from "../../contexts/ThemeContext";
import { useTranslation } from "react-i18next";
import type { ReactNode } from "react";
import styles from "./index.module.less";
const ICONS: Record<ThemeMode, ReactNode> = {
light: <SparkSunLine />,
dark: <SparkMoonLine />,
system: <SunMoon size="1em" />,
};
export default function ThemeToggleButton() {
const { themeMode, isDark, setThemeMode } = useTheme();
const { t } = useTranslation();
const items: MenuProps["items"] = [
{
key: "light",
label: t("theme.light"),
onClick: () => setThemeMode("light"),
},
{
key: "dark",
label: t("theme.dark"),
onClick: () => setThemeMode("dark"),
},
{
key: "system",
label: t("theme.system"),
onClick: () => setThemeMode("system"),
},
];
const icon =
themeMode === "system" ? ICONS.system : ICONS[isDark ? "dark" : "light"];
return (
<Dropdown
menu={{ items, selectedKeys: [themeMode] }}
placement="bottomRight"
overlayClassName={styles.themeDropdown}
>
<Button className={styles.toggleBtn} type="text" icon={icon} />
</Dropdown>
);
}