-
模板内容
{dirty ? : null}
+
{selectedDefinition?.variables.map((variable) => (
} onClick={() => editorRef.current?.insertText(variable.placeholder)}>
@@ -253,7 +251,7 @@ export default function StoryboardPromptsPage() {
-
+
);
}
diff --git a/web/src/pages/admin/theme/admin-ant-theme.ts b/web/src/pages/admin/theme/admin-ant-theme.ts
new file mode 100644
index 000000000..1978712c0
--- /dev/null
+++ b/web/src/pages/admin/theme/admin-ant-theme.ts
@@ -0,0 +1,156 @@
+import type { ThemeConfig } from "antd";
+import { theme as antdTheme } from "antd";
+
+/** Isolated Ant Design theme for the admin console. Does not read product skins. */
+export function getIsolatedAdminAntTheme(dark: boolean): ThemeConfig {
+ const text = dark ? "#f5f5f5" : "#111111";
+ const textSecondary = dark ? "#c4c4c4" : "#525252";
+ const canvas = dark ? "#0f0f0f" : "#f5f5f5";
+ const surface = dark ? "#181818" : "#ffffff";
+ const surfaceMuted = dark ? "#222222" : "#f6f6f6";
+ const border = dark ? "rgba(245, 245, 245, 0.12)" : "rgba(17, 17, 17, 0.12)";
+ const primary = dark ? "#f5f5f5" : "#111111";
+ const primaryFg = dark ? "#0f0f0f" : "#ffffff";
+ const hover = dark ? "#ffffff" : "#2a2a2a";
+ const active = dark ? "#d4d4d4" : "#404040";
+ const danger = dark ? "#f87171" : "#dc2626";
+ const success = dark ? "#4ade80" : "#15803d";
+ const warning = dark ? "#fbbf24" : "#b45309";
+ const info = dark ? "#60a5fa" : "#2563eb";
+ const switchOn = dark ? "#22c55e" : "#16a34a";
+ const switchOff = dark ? "#3f4b5a" : "#cbd5e1";
+
+ return {
+ algorithm: dark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
+ cssVar: { key: `admin-console-${dark ? "dark" : "light"}` },
+ token: {
+ fontFamily: 'ui-sans-serif, "SF Pro Text", "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", system-ui, sans-serif',
+ fontSize: 13,
+ fontSizeSM: 12,
+ borderRadius: 6,
+ borderRadiusLG: 8,
+ borderRadiusSM: 4,
+ controlHeight: 32,
+ controlHeightSM: 28,
+ controlHeightLG: 36,
+ lineWidth: 1,
+ colorPrimary: primary,
+ colorPrimaryHover: hover,
+ colorPrimaryActive: active,
+ colorText: text,
+ colorTextSecondary: textSecondary,
+ colorTextTertiary: textSecondary,
+ colorBgBase: canvas,
+ colorBgContainer: surface,
+ colorBgElevated: surface,
+ colorBgLayout: canvas,
+ colorBorder: border,
+ colorBorderSecondary: border,
+ colorSuccess: success,
+ colorWarning: warning,
+ colorError: danger,
+ colorInfo: info,
+ colorLink: text,
+ colorLinkHover: hover,
+ boxShadow: "none",
+ boxShadowSecondary: dark ? "0 22px 56px rgba(0, 0, 0, 0.48)" : "0 16px 40px rgba(15, 23, 42, 0.16)",
+ motionDurationFast: "0.12s",
+ motionDurationMid: "0.12s",
+ motionDurationSlow: "0.18s",
+ },
+ components: {
+ Button: {
+ borderRadius: 6,
+ fontWeight: 550,
+ paddingInline: 12,
+ paddingInlineSM: 8,
+ primaryShadow: "none",
+ dangerShadow: "none",
+ defaultBg: surface,
+ defaultColor: text,
+ defaultBorderColor: border,
+ defaultHoverBg: surfaceMuted,
+ defaultHoverColor: text,
+ defaultHoverBorderColor: border,
+ primaryColor: primaryFg,
+ },
+ Input: {
+ borderRadius: 6,
+ activeShadow: "none",
+ paddingInline: 10,
+ activeBg: surface,
+ hoverBg: surface,
+ },
+ InputNumber: {
+ borderRadius: 6,
+ activeShadow: "none",
+ activeBg: surface,
+ hoverBg: surface,
+ },
+ Select: {
+ borderRadius: 6,
+ optionPadding: "7px 10px",
+ optionSelectedBg: surfaceMuted,
+ optionActiveBg: surfaceMuted,
+ },
+ Switch: {
+ colorPrimary: switchOn,
+ colorPrimaryHover: switchOn,
+ colorTextQuaternary: switchOff,
+ colorTextTertiary: switchOff,
+ },
+ Table: {
+ headerBg: surfaceMuted,
+ headerColor: textSecondary,
+ headerSplitColor: "transparent",
+ borderColor: border,
+ rowHoverBg: surfaceMuted,
+ cellPaddingBlock: 6,
+ cellPaddingBlockMD: 6,
+ cellPaddingBlockSM: 4,
+ cellPaddingInline: 12,
+ cellPaddingInlineSM: 8,
+ },
+ Tabs: {
+ horizontalItemPadding: "8px 0",
+ itemColor: textSecondary,
+ itemSelectedColor: text,
+ inkBarColor: text,
+ },
+ Drawer: {
+ colorBgElevated: surface,
+ paddingLG: 16,
+ },
+ Modal: {
+ borderRadiusLG: 8,
+ contentBg: surface,
+ headerBg: surface,
+ },
+ Tooltip: {
+ borderRadius: 6,
+ colorBgSpotlight: dark ? "#f5f5f5" : "#111111",
+ colorTextLightSolid: dark ? "#0f0f0f" : "#ffffff",
+ },
+ Dropdown: {
+ borderRadiusLG: 8,
+ controlItemBgHover: surfaceMuted,
+ paddingBlock: 4,
+ },
+ Form: {
+ itemMarginBottom: 14,
+ },
+ Segmented: {
+ itemSelectedBg: surface,
+ trackBg: surfaceMuted,
+ },
+ Card: {
+ boxShadow: "none",
+ boxShadowTertiary: "none",
+ },
+ Message: {
+ borderRadiusLG: 8,
+ contentPadding: "8px 14px",
+ },
+ },
+ };
+}
diff --git a/web/src/pages/admin/theme/admin-chrome.css b/web/src/pages/admin/theme/admin-chrome.css
new file mode 100644
index 000000000..69fa56c83
--- /dev/null
+++ b/web/src/pages/admin/theme/admin-chrome.css
@@ -0,0 +1,978 @@
+/* Admin chrome: one layout language for every admin page.
+ * Loaded after admin-ui.css so these rules win over per-page leftovers. */
+
+[data-admin-root] {
+ background: var(--admin-canvas);
+ color: var(--admin-text);
+}
+
+[data-admin-root] *,
+[data-admin-root] *::before,
+[data-admin-root] *::after {
+ box-sizing: border-box;
+}
+
+[data-admin-root] .admin-shell {
+ position: relative;
+ background: var(--admin-canvas);
+}
+
+[data-admin-root] .admin-sidebar {
+ width: var(--admin-sidebar-width);
+ flex-basis: var(--admin-sidebar-width);
+ background: var(--admin-sidebar);
+ border-right: 1px solid var(--admin-border);
+ box-shadow: none;
+ border-radius: 0 !important;
+}
+
+[data-admin-root] .admin-sidebar.is-collapsed {
+ width: var(--admin-sidebar-collapsed);
+ flex-basis: var(--admin-sidebar-collapsed);
+}
+
+[data-admin-root] .admin-sidebar-identity {
+ display: flex;
+ height: 56px;
+ align-items: center;
+ padding: 0 12px;
+ border-bottom: 1px solid var(--admin-border);
+}
+
+[data-admin-root] .admin-sidebar-brand-button {
+ display: flex;
+ min-width: 0;
+ width: 100%;
+ align-items: center;
+ gap: 10px;
+ padding: 4px;
+ border: 0;
+ background: transparent;
+ color: inherit;
+ text-align: left;
+}
+
+[data-admin-root] .admin-sidebar-brand-button.is-collapsed {
+ justify-content: center;
+}
+
+[data-admin-root] .admin-sidebar-brand-mark {
+ width: 28px;
+ height: 28px;
+ border-radius: var(--admin-radius);
+ box-shadow: none !important;
+}
+
+[data-admin-root] .admin-sidebar-brand-title {
+ color: var(--admin-text);
+ font-size: 13px;
+ font-weight: 650;
+ letter-spacing: -0.01em;
+ line-height: 16px;
+}
+
+[data-admin-root] .admin-sidebar-brand-version {
+ color: var(--admin-text-muted);
+ font-size: 11px;
+ font-weight: 500;
+ line-height: 14px;
+}
+
+[data-admin-root] .admin-sidebar-nav {
+ padding: 10px 8px 16px;
+}
+
+[data-admin-root] .admin-nav-group + .admin-nav-group {
+ margin-top: 14px;
+}
+
+[data-admin-root] .admin-nav-group-label,
+[data-admin-root] .admin-nav-group-label span {
+ display: block;
+ margin: 0 0 4px;
+ padding: 0 8px;
+ color: var(--admin-text-muted) !important;
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ line-height: 16px;
+ text-transform: none;
+}
+
+[data-admin-root] .admin-nav-group-label::before,
+[data-admin-root] .admin-nav-group-label::after {
+ content: none !important;
+ display: none !important;
+ width: 0 !important;
+ height: 0 !important;
+ min-width: 0 !important;
+ flex: 0 !important;
+ border: 0 !important;
+ background: none !important;
+}
+
+[data-admin-root] .admin-nav-group-items,
+[data-admin-root] .admin-nav-group-items:not(.is-collapsed) {
+ margin-left: 0 !important;
+ padding-left: 0 !important;
+ border: 0 !important;
+ border-left: 0 !important;
+}
+
+[data-admin-root] .admin-nav-collapsed-separator {
+ height: 1px;
+ margin: 8px 10px;
+ background: var(--admin-divider);
+}
+
+[data-admin-root] .admin-nav-link {
+ position: relative;
+ display: flex;
+ height: 32px;
+ align-items: center;
+ gap: 10px;
+ padding: 0 8px;
+ border: 0;
+ border-radius: var(--admin-radius);
+ color: var(--admin-text-secondary);
+ font-size: 13px;
+ font-weight: 500;
+ line-height: 16px;
+ text-decoration: none;
+ transition: background-color var(--admin-motion), color var(--admin-motion);
+}
+
+[data-admin-root] .admin-nav-link.is-collapsed {
+ justify-content: center;
+ padding: 0;
+}
+
+[data-admin-root] .admin-nav-link:hover {
+ background: var(--admin-nav-hover);
+ color: var(--admin-text);
+}
+
+[data-admin-root] .admin-nav-link.is-active {
+ background: var(--admin-nav-active);
+ border: 0;
+ box-shadow: none;
+ color: var(--admin-text);
+ font-weight: 650;
+}
+
+[data-admin-root] .admin-nav-link.is-active::before,
+[data-admin-root] .admin-sidebar.is-collapsed .admin-nav-link.is-active::before {
+ content: none !important;
+ display: none !important;
+}
+
+[data-admin-root] .admin-nav-link svg {
+ width: 15px;
+ height: 15px;
+ flex: 0 0 auto;
+ color: currentColor;
+ stroke-width: 1.75;
+}
+
+[data-admin-root] .admin-nav-link:focus-visible,
+[data-admin-root] .admin-icon-button:focus-visible,
+[data-admin-root] .admin-sidebar-edge-toggle:focus-visible {
+ outline: 2px solid var(--admin-focus);
+ outline-offset: 1px;
+}
+
+[data-admin-root] .admin-sidebar-footer {
+ padding: 8px;
+ border-top: 1px solid var(--admin-border);
+ background: var(--admin-sidebar);
+}
+
+[data-admin-root] .admin-sidebar-edge-toggle {
+ position: absolute;
+ top: 50%;
+ left: calc(var(--admin-sidebar-width) - 11px);
+ z-index: 4;
+ display: grid;
+ width: 22px;
+ height: 36px;
+ place-items: center;
+ border: 1px solid var(--admin-border);
+ border-radius: 999px;
+ background: var(--admin-surface);
+ color: var(--admin-text-muted);
+ transform: translateY(-50%);
+}
+
+[data-admin-root] .admin-sidebar-edge-toggle.is-collapsed {
+ left: calc(var(--admin-sidebar-collapsed) - 11px);
+}
+
+[data-admin-root] .admin-sidebar-edge-toggle:hover {
+ color: var(--admin-text);
+}
+
+[data-admin-root] .admin-page-root,
+[data-admin-root] .admin-page-root > div {
+ height: 100%;
+ min-height: 0;
+ background: var(--admin-canvas);
+}
+
+[data-admin-root] .admin-page-frame {
+ display: flex;
+ height: 100%;
+ min-height: 0;
+ flex-direction: column;
+ padding: 16px 20px 16px;
+}
+
+[data-admin-root] .admin-page-frame-scrollable {
+ overflow: auto;
+}
+
+[data-admin-root] .admin-page-header {
+ gap: 12px !important;
+ margin-bottom: 12px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid var(--admin-border);
+}
+
+[data-admin-root] .admin-page-location {
+ display: flex;
+ min-width: 0;
+ align-items: baseline;
+ gap: 6px;
+}
+
+[data-admin-root] .admin-page-location-section {
+ color: var(--admin-text-muted);
+ font-size: 12px;
+ font-weight: 500;
+ text-decoration: none;
+}
+
+[data-admin-root] .admin-page-location-section:hover {
+ color: var(--admin-text);
+}
+
+[data-admin-root] .admin-page-location-separator {
+ color: var(--admin-text-placeholder);
+}
+
+[data-admin-root] .admin-page-title {
+ color: var(--admin-text);
+ font-size: 18px;
+ font-weight: 650;
+ letter-spacing: -0.02em;
+ line-height: 24px;
+}
+
+[data-admin-root] .admin-page-description {
+ margin: 4px 0 0;
+ color: var(--admin-text-muted);
+ font-size: 12px;
+ line-height: 18px;
+}
+
+[data-admin-root] .admin-page-actions {
+ gap: 8px !important;
+}
+
+[data-admin-root] .admin-icon-button,
+[data-admin-root] .admin-page-theme-toggle {
+ display: grid;
+ width: 32px;
+ height: 32px;
+ place-items: center;
+ border: 1px solid var(--admin-border);
+ border-radius: var(--admin-radius);
+ background: var(--admin-surface);
+ color: var(--admin-text-secondary);
+}
+
+[data-admin-root] .admin-icon-button:hover,
+[data-admin-root] .admin-page-theme-toggle:hover {
+ background: var(--admin-surface-2);
+ color: var(--admin-text);
+}
+
+[data-admin-root] .admin-mobile-navigation {
+ height: 48px;
+ padding: 0 12px;
+ border-bottom: 1px solid var(--admin-border);
+ background: var(--admin-sidebar);
+}
+
+[data-admin-root] .admin-mobile-navigation-trigger,
+[data-admin-root] .admin-mobile-navigation-action {
+ display: flex;
+ height: 36px;
+ align-items: center;
+ gap: 8px;
+ padding: 0 8px;
+ border: 1px solid var(--admin-border);
+ border-radius: var(--admin-radius);
+ background: var(--admin-surface);
+ color: var(--admin-text);
+}
+
+[data-admin-root] .admin-mobile-navigation-eyebrow {
+ display: block;
+ color: var(--admin-text-muted);
+ font-size: 10px;
+ font-weight: 650;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+}
+
+[data-admin-root] .admin-mobile-navigation-current-label {
+ display: block;
+ font-size: 13px;
+ font-weight: 650;
+}
+
+[data-admin-root] .admin-data-table {
+ display: flex;
+ min-height: 0;
+ flex: 1;
+ flex-direction: column;
+}
+
+[data-admin-root] .admin-list-toolbar {
+ display: flex;
+ min-height: 32px;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ margin: 0 0 10px;
+ padding: 0;
+}
+
+[data-admin-root] .admin-list-toolbar-main,
+[data-admin-root] .admin-list-toolbar-actions,
+[data-admin-root] .admin-list-toolbar-filters {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+}
+
+[data-admin-root] .admin-list-toolbar .ant-input-affix-wrapper,
+[data-admin-root] .admin-list-toolbar .ant-select-selector,
+[data-admin-root] .admin-list-toolbar .ant-btn,
+[data-admin-root] .admin-page-actions .ant-btn {
+ height: var(--admin-control-h) !important;
+ border-radius: var(--admin-radius) !important;
+}
+
+[data-admin-root] .admin-filter-chip {
+ display: inline-flex;
+ height: 28px;
+ align-items: center;
+ gap: 6px;
+ padding: 0 8px;
+ border: 1px solid var(--admin-border);
+ border-radius: 999px;
+ background: var(--admin-surface);
+ color: var(--admin-text-secondary);
+ font-size: 12px;
+}
+
+[data-admin-root] .admin-table-frame {
+ display: flex;
+ min-height: 0;
+ flex: 1;
+ flex-direction: column;
+ overflow: hidden;
+ border: 1px solid var(--admin-border);
+ border-radius: var(--admin-radius-lg);
+ background: var(--admin-surface);
+ box-shadow: none;
+}
+
+[data-admin-root] .admin-table-surface,
+[data-admin-root] .admin-table-surface .ant-table-wrapper,
+[data-admin-root] .admin-table-surface .ant-table,
+[data-admin-root] .admin-table-surface .ant-table-container {
+ background: var(--admin-surface) !important;
+}
+
+[data-admin-root] .admin-table-scroll {
+ min-height: 0;
+ flex: 1;
+ overflow: auto;
+}
+
+[data-admin-root] .admin-table-surface .ant-table-thead > tr > th {
+ height: var(--admin-head-h);
+ padding: 0 12px !important;
+ border-bottom: 1px solid var(--admin-border) !important;
+ background: var(--admin-surface-2) !important;
+ color: var(--admin-text-muted) !important;
+ font-size: 11px !important;
+ font-weight: 650 !important;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+}
+
+[data-admin-root] .admin-table-surface .ant-table-tbody > tr > td {
+ height: var(--admin-row-h);
+ padding: 6px 12px !important;
+ border-bottom: 1px solid var(--admin-row-border) !important;
+ background: transparent !important;
+ color: var(--admin-text) !important;
+ font-size: 13px;
+ font-variant-numeric: tabular-nums;
+}
+
+[data-admin-root] .admin-table-surface .ant-table-tbody > tr:hover > td {
+ background: var(--admin-row-hover) !important;
+}
+
+[data-admin-root] .admin-table-pagination {
+ display: flex;
+ min-height: 44px;
+ align-items: center;
+ justify-content: flex-end;
+ padding: 0 10px;
+ border-top: 1px solid var(--admin-border);
+ background: var(--admin-surface);
+}
+
+[data-admin-root] .admin-pagination-bar {
+ display: flex;
+ min-height: 36px;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+ margin: 0;
+ padding: 0;
+}
+
+[data-admin-root] .admin-pagination-total {
+ color: var(--admin-text-muted);
+ font-size: 12px;
+ font-variant-numeric: tabular-nums;
+}
+
+[data-admin-root] .admin-pagination-pages {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+}
+
+[data-admin-root] .admin-pagination-btn {
+ display: grid;
+ min-width: 28px;
+ height: 28px;
+ place-items: center;
+ padding: 0 6px;
+ border: 0;
+ border-radius: var(--admin-radius-sm);
+ background: transparent;
+ color: var(--admin-text-secondary);
+ font-size: 12px;
+}
+
+[data-admin-root] .admin-pagination-btn:hover:not(:disabled) {
+ background: var(--admin-nav-hover);
+ color: var(--admin-text);
+}
+
+[data-admin-root] .admin-pagination-btn.is-active {
+ background: var(--admin-accent);
+ color: var(--admin-accent-fg);
+ font-weight: 650;
+}
+
+[data-admin-root] .admin-pagination-btn:disabled {
+ opacity: 0.38;
+}
+
+[data-admin-root] .admin-batch-bar {
+ margin: 0 0 8px;
+ border: 1px solid var(--admin-border);
+ border-radius: var(--admin-radius);
+ background: var(--admin-surface);
+ box-shadow: none;
+ backdrop-filter: none;
+}
+
+[data-admin-root] .admin-status-badge {
+ display: inline-flex;
+ height: 22px;
+ align-items: center;
+ gap: 6px;
+ padding: 0 8px;
+ border: 1px solid var(--admin-border);
+ border-radius: 999px;
+ background: var(--admin-surface-2);
+ color: var(--admin-text-secondary);
+ font-size: 11px;
+ font-weight: 650;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+[data-admin-root] .admin-status-badge::before {
+ width: 6px;
+ height: 6px;
+ border-radius: 999px;
+ background: currentColor;
+ content: "";
+}
+
+[data-admin-root] .admin-status-badge.is-success {
+ border-color: color-mix(in srgb, var(--admin-status-success) 28%, var(--admin-border));
+ color: var(--admin-status-success);
+}
+
+[data-admin-root] .admin-status-badge.is-warning {
+ border-color: color-mix(in srgb, var(--admin-status-warning) 28%, var(--admin-border));
+ color: var(--admin-status-warning);
+}
+
+[data-admin-root] .admin-status-badge.is-error {
+ border-color: color-mix(in srgb, var(--admin-status-error) 28%, var(--admin-border));
+ color: var(--admin-status-error);
+}
+
+[data-admin-root] .admin-status-badge.is-info {
+ border-color: color-mix(in srgb, var(--admin-status-info) 28%, var(--admin-border));
+ color: var(--admin-status-info);
+}
+
+[data-admin-root] .admin-empty {
+ display: flex;
+ min-height: 160px;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ padding: 24px;
+ color: var(--admin-text-muted);
+ text-align: center;
+}
+
+[data-admin-root] .admin-empty h3 {
+ margin: 0;
+ color: var(--admin-text);
+ font-size: 14px;
+ font-weight: 650;
+}
+
+[data-admin-root] .admin-empty p {
+ margin: 0;
+ font-size: 12px;
+ line-height: 18px;
+}
+
+[data-admin-root] .admin-stat-tile {
+ min-height: 84px;
+ padding: 12px 14px;
+ border: 1px solid var(--admin-border);
+ border-radius: var(--admin-radius-lg);
+ background: var(--admin-surface);
+ box-shadow: none;
+}
+
+[data-admin-root] .admin-stat-tile-label {
+ color: var(--admin-text-muted);
+ font-size: 11px;
+ font-weight: 650;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+}
+
+[data-admin-root] .admin-stat-tile-value {
+ margin-top: 6px;
+ color: var(--admin-text);
+ font-size: 22px;
+ font-weight: 650;
+ font-variant-numeric: tabular-nums;
+ letter-spacing: -0.03em;
+ line-height: 28px;
+}
+
+[data-admin-root] .admin-stat-tile-detail {
+ margin-top: 6px;
+ color: var(--admin-text-muted);
+ font-size: 12px;
+}
+
+[data-admin-root] .admin-settings-stack {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+[data-admin-root] .admin-settings-section {
+ overflow: hidden;
+ border: 1px solid var(--admin-border);
+ border-radius: var(--admin-radius-lg);
+ background: var(--admin-surface);
+ box-shadow: none;
+}
+
+[data-admin-root] .admin-settings-section-summary,
+[data-admin-root] .admin-settings-section-content,
+[data-admin-root] .admin-settings-section-footer {
+ background: var(--admin-surface);
+}
+
+[data-admin-root] .admin-settings-section-summary {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 12px 14px;
+ border-bottom: 1px solid var(--admin-border);
+}
+
+[data-admin-root] .admin-settings-section-summary h2 {
+ margin: 0;
+ color: var(--admin-text);
+ font-size: 13px;
+ font-weight: 650;
+ letter-spacing: -0.01em;
+}
+
+[data-admin-root] .admin-settings-section-summary p {
+ margin: 3px 0 0;
+ color: var(--admin-text-muted);
+ font-size: 12px;
+ line-height: 18px;
+}
+
+[data-admin-root] .admin-settings-section-content {
+ padding: 4px 0;
+}
+
+[data-admin-root] :is(
+ .admin-feature-command-bar,
+ .admin-appearance-command-bar,
+ .admin-access-command-bar,
+ .admin-email-command-bar,
+ .admin-storage-command-bar,
+ .admin-ark-command-bar,
+ .admin-intercept-command-bar,
+ .admin-drawing-command-bar,
+ .admin-runtime-policy-command-bar,
+ .admin-third-party-command-bar
+) {
+ position: sticky;
+ top: 0;
+ z-index: 8;
+ display: flex;
+ min-height: 44px !important;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ margin: 0;
+ border: 0 !important;
+ border-bottom: 1px solid var(--admin-border) !important;
+ border-radius: 0 !important;
+ background: var(--admin-canvas) !important;
+ padding: 8px 0 10px !important;
+ box-shadow: none !important;
+ backdrop-filter: none !important;
+}
+
+[data-admin-root] :is(
+ .admin-feature-command-bar,
+ .admin-appearance-command-bar,
+ .admin-access-command-bar,
+ .admin-email-command-bar,
+ .admin-storage-command-bar,
+ .admin-ark-command-bar,
+ .admin-intercept-command-bar,
+ .admin-drawing-command-bar,
+ .admin-runtime-policy-command-bar,
+ .admin-third-party-command-bar
+).is-dirty {
+ border-bottom-color: color-mix(in srgb, var(--admin-status-warning) 55%, var(--admin-border)) !important;
+ background: color-mix(in srgb, var(--admin-status-warning) 8%, var(--admin-canvas)) !important;
+}
+
+[data-admin-root] :is(
+ .admin-feature-command-copy strong,
+ .admin-appearance-command-copy strong
+) {
+ color: var(--admin-text);
+ font-size: 12px;
+ font-weight: 650;
+}
+
+[data-admin-root] .admin-feature-board {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+[data-admin-root] .admin-feature-domain {
+ overflow: hidden;
+ border: 1px solid var(--admin-border);
+ border-radius: var(--admin-radius-lg);
+ background: var(--admin-surface);
+ box-shadow: none;
+}
+
+[data-admin-root] .admin-feature-domain-header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 12px 14px;
+ border-bottom: 1px solid var(--admin-border);
+ background: var(--admin-surface);
+}
+
+[data-admin-root] .admin-feature-domain-header h2 {
+ margin: 0;
+ color: var(--admin-text);
+ font-size: 13px;
+ font-weight: 650;
+}
+
+[data-admin-root] .admin-feature-domain-header p {
+ margin: 2px 0 0;
+ color: var(--admin-text-muted);
+ font-size: 12px;
+}
+
+[data-admin-root] .admin-feature-domain-icon,
+[data-admin-root] .admin-feature-board-row-icon {
+ display: grid;
+ width: 28px;
+ height: 28px;
+ place-items: center;
+ border: 1px solid var(--admin-border);
+ border-radius: var(--admin-radius);
+ background: var(--admin-surface-2);
+ color: var(--admin-text-secondary);
+}
+
+[data-admin-root] .admin-feature-board-row {
+ display: flex;
+ min-height: 52px;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 10px 14px;
+ border-bottom: 1px solid var(--admin-row-border);
+ background: transparent;
+}
+
+[data-admin-root] .admin-feature-board-row:last-child {
+ border-bottom: 0;
+}
+
+[data-admin-root] .admin-feature-board-row-copy {
+ display: flex;
+ min-width: 0;
+ align-items: center;
+ gap: 10px;
+}
+
+[data-admin-root] .admin-feature-board-row-name {
+ display: flex;
+ min-width: 0;
+ flex-direction: column;
+ gap: 2px;
+}
+
+[data-admin-root] .admin-feature-board-row-name small {
+ color: var(--admin-text-muted);
+ font-size: 10px;
+ font-weight: 650;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+}
+
+[data-admin-root] .admin-feature-board-row-name strong {
+ color: var(--admin-text);
+ font-size: 13px;
+ font-weight: 650;
+}
+
+[data-admin-root] .admin-feature-board-row-name span {
+ color: var(--admin-text-muted);
+ font-size: 12px;
+ line-height: 18px;
+}
+
+[data-admin-root] .admin-feature-board-row-control {
+ display: flex;
+ flex: 0 0 auto;
+ align-items: center;
+ gap: 10px;
+ color: var(--admin-text-muted);
+ font-size: 12px;
+}
+
+[data-admin-root] .admin-feature-board-source-selector {
+ display: inline-flex;
+ padding: 2px;
+ border: 1px solid var(--admin-border);
+ border-radius: var(--admin-radius);
+ background: var(--admin-surface-2);
+}
+
+[data-admin-root] .admin-feature-board-source-selector button {
+ height: 28px;
+ padding: 0 10px;
+ border: 0;
+ border-radius: var(--admin-radius-sm);
+ background: transparent;
+ color: var(--admin-text-secondary);
+ font-size: 12px;
+ font-weight: 550;
+}
+
+[data-admin-root] .admin-feature-board-source-selector button.is-selected {
+ background: var(--admin-surface);
+ color: var(--admin-text);
+ box-shadow: 0 0 0 1px var(--admin-border);
+}
+
+[data-admin-root] .admin-callout {
+ display: flex;
+ gap: 10px;
+ padding: 10px 12px;
+ border: 1px solid var(--admin-border);
+ border-radius: var(--admin-radius);
+ background: var(--admin-surface-2);
+ color: var(--admin-text-secondary);
+}
+
+[data-admin-root] .admin-callout.is-warning {
+ border-color: color-mix(in srgb, var(--admin-status-warning) 32%, var(--admin-border));
+ background: color-mix(in srgb, var(--admin-status-warning) 8%, var(--admin-surface));
+}
+
+[data-admin-root] .admin-callout.is-error {
+ border-color: color-mix(in srgb, var(--admin-status-error) 32%, var(--admin-border));
+ background: color-mix(in srgb, var(--admin-status-error) 8%, var(--admin-surface));
+}
+
+[data-admin-root] .admin-callout-title {
+ color: var(--admin-text);
+ font-size: 13px;
+ font-weight: 650;
+}
+
+[data-admin-root] .admin-callout-body {
+ margin-top: 2px;
+ font-size: 12px;
+ line-height: 18px;
+}
+
+[data-admin-root] .ant-btn {
+ font-weight: 550;
+}
+
+[data-admin-root] .ant-switch.ant-switch-checked,
+.admin-overlays .ant-switch.ant-switch-checked,
+.admin-drawer .ant-switch.ant-switch-checked,
+.admin-modal-root .ant-switch.ant-switch-checked {
+ background: var(--admin-switch-on) !important;
+}
+
+[data-admin-root] .ant-form-item {
+ margin-bottom: 14px;
+}
+
+[data-admin-root] .ant-form-item-label > label {
+ color: var(--admin-text-secondary);
+ font-size: 12px;
+ font-weight: 650;
+}
+
+[data-admin-root] .admin-row-actions {
+ display: inline-flex;
+ justify-content: center;
+ gap: 4px;
+}
+
+[data-admin-root] .admin-unauthorized {
+ display: grid;
+ min-height: 100%;
+ place-items: center;
+ padding: 48px 20px;
+ background: var(--admin-canvas);
+}
+
+[data-admin-root] .admin-unauthorized-card {
+ width: min(440px, 100%);
+ padding: 20px;
+ border: 1px solid var(--admin-border);
+ border-radius: var(--admin-radius-lg);
+ background: var(--admin-surface);
+}
+
+[data-admin-root] .admin-unauthorized-card h1 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 650;
+}
+
+[data-admin-root] .admin-unauthorized-card p {
+ margin: 8px 0 0;
+ color: var(--admin-text-muted);
+ font-size: 13px;
+}
+
+.admin-drawer .ant-drawer-content,
+.admin-secondary-drawer .ant-drawer-content,
+.admin-modal-root .ant-modal-content,
+.admin-channel-modal-root .ant-modal-content {
+ background: var(--admin-surface) !important;
+ color: var(--admin-text) !important;
+ box-shadow: var(--admin-shadow-overlay);
+}
+
+.admin-drawer .ant-drawer-header,
+.admin-drawer .ant-drawer-footer,
+.admin-drawer .ant-drawer-body,
+.admin-secondary-drawer .ant-drawer-header,
+.admin-secondary-drawer .ant-drawer-footer,
+.admin-secondary-drawer .ant-drawer-body,
+.admin-modal-root .ant-modal-header,
+.admin-modal-root .ant-modal-body,
+.admin-modal-root .ant-modal-footer,
+.admin-channel-modal-root .ant-modal-header,
+.admin-channel-modal-root .ant-modal-body,
+.admin-channel-modal-root .ant-modal-footer {
+ background: var(--admin-surface) !important;
+ color: var(--admin-text);
+}
+
+.admin-drawer .ant-drawer-header,
+.admin-secondary-drawer .ant-drawer-header,
+.admin-modal-root .ant-modal-header,
+.admin-channel-modal-root .ant-modal-header {
+ border-bottom: 1px solid var(--admin-border);
+}
+
+.admin-drawer .ant-drawer-footer,
+.admin-secondary-drawer .ant-drawer-footer,
+.admin-modal-root .ant-modal-footer,
+.admin-channel-modal-root .ant-modal-footer {
+ border-top: 1px solid var(--admin-border);
+}
+
+.admin-drawer .ant-drawer-title,
+.admin-modal-root .ant-modal-title,
+.admin-channel-modal-root .ant-modal-title {
+ color: var(--admin-text);
+ font-size: 14px;
+ font-weight: 650;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ [data-admin-root] *,
+ body.admin-overlays * {
+ transition: none !important;
+ animation: none !important;
+ }
+}
diff --git a/web/src/pages/admin/theme/admin-tokens.css b/web/src/pages/admin/theme/admin-tokens.css
new file mode 100644
index 000000000..26279f76e
--- /dev/null
+++ b/web/src/pages/admin/theme/admin-tokens.css
@@ -0,0 +1,144 @@
+/* Admin console tokens. Scoped only to admin roots.
+ * Never alias --workspace-*, --user-*, or --skin-* from the product UI.
+ * Light/dark follow the page theme class; product skins are ignored. */
+
+:is(
+ [data-admin-root],
+ body.admin-overlays,
+ .admin-drawer,
+ .admin-secondary-drawer,
+ .admin-modal-root,
+ .admin-channel-modal-root,
+ .admin-mobile-navigation-menu
+) {
+ --admin-font: ui-sans-serif, "SF Pro Text", "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", system-ui, sans-serif;
+ --admin-font-mono: ui-monospace, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", monospace;
+ --admin-space: 8px;
+ --admin-radius-sm: 4px;
+ --admin-radius: 6px;
+ --admin-radius-lg: 8px;
+ --admin-control-h: 32px;
+ --admin-control-h-sm: 28px;
+ --admin-row-h: 40px;
+ --admin-head-h: 36px;
+ --admin-sidebar-width: 224px;
+ --admin-sidebar-collapsed: 64px;
+ --admin-motion: 120ms ease;
+ --admin-shadow-overlay: 0 16px 40px rgba(15, 23, 42, 0.16);
+
+ --admin-canvas: #f5f5f5;
+ --admin-sidebar: #f5f5f5;
+ --admin-surface: #ffffff;
+ --admin-surface-2: #f6f6f6;
+ --admin-surface-3: #ececec;
+ --admin-text: #111111;
+ --admin-text-secondary: #525252;
+ --admin-text-muted: #737373;
+ --admin-text-placeholder: #a3a3a3;
+ --admin-border: rgba(17, 17, 17, 0.1);
+ --admin-border-strong: rgba(17, 17, 17, 0.16);
+ --admin-divider: rgba(17, 17, 17, 0.08);
+ --admin-row-border: rgba(17, 17, 17, 0.06);
+ --admin-row-hover: #f0f0f0;
+ --admin-nav-hover: rgba(17, 17, 17, 0.05);
+ --admin-nav-active: #ececec;
+ --admin-nav-active-border: transparent;
+ --admin-accent: #111111;
+ --admin-accent-fg: #ffffff;
+ --admin-focus: rgba(17, 17, 17, 0.28);
+ --admin-status-success: #15803d;
+ --admin-status-warning: #b45309;
+ --admin-status-error: #dc2626;
+ --admin-status-info: #2563eb;
+ --admin-switch-on: #16a34a;
+ --admin-chart-primary: #0f172a;
+ --admin-chart-secondary: #0f766e;
+ --admin-chart-warning: #b45309;
+ --admin-card-shadow: none;
+
+ /* Compatibility aliases used by existing admin-ui.css. Independent values, not workspace skins. */
+ --admin-layer-0: var(--admin-canvas);
+ --admin-layer-1: var(--admin-surface);
+ --admin-layer-2: var(--admin-surface-2);
+ --admin-layer-3: var(--admin-surface-3);
+ --admin-text-primary: var(--admin-text);
+ --admin-control-border: var(--admin-border-strong);
+ --admin-card-border: var(--admin-border);
+
+ /* Firewall: leftover product/skin variables must resolve to admin values inside this tree. */
+ --background: var(--admin-canvas);
+ --foreground: var(--admin-text);
+ --color-surface: var(--admin-surface);
+ --color-border: var(--admin-border);
+ --border: var(--admin-border);
+ --muted: var(--admin-surface-2);
+ --popover: var(--admin-surface);
+ --ring: var(--admin-focus);
+ --font-sans: var(--admin-font);
+ --workspace-content: var(--admin-canvas);
+ --workspace-surface: var(--admin-surface);
+ --workspace-navigation: var(--admin-sidebar);
+ --workspace-page: var(--admin-canvas);
+ --workspace-accent: var(--admin-accent);
+ --workspace-nav-active-bg: var(--admin-nav-active);
+ --workspace-sidebar-width: var(--admin-sidebar-width);
+ --skin-admin-layer-0: var(--admin-canvas);
+ --skin-admin-layer-1: var(--admin-surface);
+ --skin-admin-layer-2: var(--admin-surface-2);
+ --skin-admin-layer-3: var(--admin-surface-3);
+ --theme-admin-card-shadow: none;
+ --r-sm: var(--admin-radius-sm);
+ --r-md: var(--admin-radius);
+ --r-lg: var(--admin-radius-lg);
+ --r-xl: var(--admin-radius-lg);
+ --r-2xl: var(--admin-radius-lg);
+ --motion-instant: var(--admin-motion);
+ --motion-state: var(--admin-motion);
+ --motion-ease-out: ease;
+
+ color: var(--admin-text);
+ font-family: var(--admin-font);
+ font-size: 13px;
+ line-height: 1.45;
+ font-feature-settings: "ss01" 0, "cv11" 0;
+}
+
+.dark :is(
+ [data-admin-root],
+ .admin-drawer,
+ .admin-secondary-drawer,
+ .admin-modal-root,
+ .admin-channel-modal-root,
+ .admin-mobile-navigation-menu
+),
+.dark body.admin-overlays {
+ --admin-canvas: #0f0f0f;
+ --admin-sidebar: #0f0f0f;
+ --admin-surface: #181818;
+ --admin-surface-2: #222222;
+ --admin-surface-3: #2a2a2a;
+ --admin-text: #f5f5f5;
+ --admin-text-secondary: #c4c4c4;
+ --admin-text-muted: #8a8a8a;
+ --admin-text-placeholder: #737373;
+ --admin-border: rgba(245, 245, 245, 0.1);
+ --admin-border-strong: rgba(245, 245, 245, 0.16);
+ --admin-divider: rgba(245, 245, 245, 0.08);
+ --admin-row-border: rgba(245, 245, 245, 0.06);
+ --admin-row-hover: #1c1c1c;
+ --admin-nav-hover: rgba(245, 245, 245, 0.05);
+ --admin-nav-active: #222222;
+ --admin-nav-active-border: transparent;
+ --admin-accent: #f5f5f5;
+ --admin-accent-fg: #0f0f0f;
+ --admin-focus: rgba(245, 245, 245, 0.28);
+ --admin-status-success: #4ade80;
+ --admin-status-warning: #fbbf24;
+ --admin-status-error: #f87171;
+ --admin-status-info: #60a5fa;
+ --admin-switch-on: #22c55e;
+ --admin-chart-primary: #e8eef6;
+ --admin-chart-secondary: #2dd4bf;
+ --admin-chart-warning: #fbbf24;
+ --admin-shadow-overlay: 0 22px 56px rgba(0, 0, 0, 0.48);
+}
diff --git a/web/src/pages/admin/ui/controls.tsx b/web/src/pages/admin/ui/controls.tsx
new file mode 100644
index 000000000..bd56291b3
--- /dev/null
+++ b/web/src/pages/admin/ui/controls.tsx
@@ -0,0 +1,190 @@
+import { Button, Checkbox as AntCheckbox, Segmented, Select as AntSelect, Switch as AntSwitch, Tooltip as AntTooltip } from "antd";
+import type { CheckboxChangeEvent } from "antd/es/checkbox";
+import type { ButtonProps } from "antd";
+import type { LucideIcon } from "lucide-react";
+import type { CSSProperties, InputHTMLAttributes, ReactNode } from "react";
+
+import { cn } from "@/lib/utils";
+
+export function AdminSwitch({
+ size = "md",
+ className,
+ ...props
+}: {
+ checked?: boolean;
+ defaultChecked?: boolean;
+ disabled?: boolean;
+ loading?: boolean;
+ size?: "sm" | "md";
+ checkedChildren?: ReactNode;
+ unCheckedChildren?: ReactNode;
+ onChange?: (checked: boolean) => void;
+ className?: string;
+ "aria-label"?: string;
+}) {
+ return (
+
props.onChange?.(checked)}
+ />
+ );
+}
+
+export function Switch(props: Parameters[0]) {
+ return ;
+}
+
+export function AdminSelect({
+ size = "md",
+ ariaLabel,
+ className,
+ onChange,
+ ...props
+}: {
+ value?: V;
+ onChange?: (value: V) => void;
+ options?: Array<{ value: V; label: ReactNode; disabled?: boolean; title?: string }>;
+ placeholder?: string;
+ disabled?: boolean;
+ allowClear?: boolean;
+ size?: "sm" | "md";
+ ariaLabel?: string;
+ className?: string;
+}) {
+ return (
+ onChange?.(value as V)}
+ />
+ );
+}
+
+export function Select(props: Parameters>[0]) {
+ return {...props} />;
+}
+
+export function AdminCheckbox({
+ bare,
+ size,
+ children,
+ className,
+ onChange,
+ ...props
+}: Omit, "size" | "onChange"> & {
+ size?: "sm" | "md";
+ indeterminate?: boolean;
+ bare?: boolean;
+ children?: ReactNode;
+ onChange?: (event: CheckboxChangeEvent) => void;
+}) {
+ return (
+
+ {bare ? null : children}
+
+ );
+}
+
+export function Checkbox(props: Parameters[0]) {
+ return ;
+}
+
+export function AdminTooltip({ title, placement = "top", delay = 80, children }: { title?: ReactNode; placement?: "top" | "right" | "bottom" | "left"; delay?: number; children: ReactNode }) {
+ if (!title) return <>{children}>;
+ return (
+
+ {children}
+
+ );
+}
+
+export function Tooltip(props: Parameters[0]) {
+ return ;
+}
+
+export function AdminIconButton({
+ icon: Icon,
+ size = "sm",
+ className,
+ variant: _variant,
+ ...props
+}: {
+ icon: LucideIcon;
+ size?: "sm" | "md";
+ variant?: "ghost" | "default";
+ className?: string;
+ disabled?: boolean;
+ loading?: boolean;
+ title?: string;
+ "aria-label"?: string;
+ onClick?: ButtonProps["onClick"];
+}) {
+ return } {...props} />;
+}
+
+export function IconButton(props: Parameters[0]) {
+ return ;
+}
+
+export function AdminSegmented({
+ value,
+ onChange,
+ options,
+ ariaLabel,
+ className,
+}: {
+ value?: V;
+ onChange?: (value: V) => void;
+ options: Array<{ value: V; label?: ReactNode; icon?: ReactNode; disabled?: boolean }>;
+ ariaLabel?: string;
+ className?: string;
+}) {
+ return (
+ ({ ...option, label: option.label ?? option.value }))}
+ onChange={(next) => onChange?.(String(next) as V)}
+ aria-label={ariaLabel}
+ />
+ );
+}
+
+export function SegmentedControl(props: Parameters>[0]) {
+ return {...props} />;
+}
+
+export function AdminCallout({
+ tone = "info",
+ title,
+ action,
+ className,
+ children,
+ style,
+}: {
+ tone?: "info" | "success" | "warning" | "error" | "default";
+ title?: ReactNode;
+ action?: ReactNode;
+ className?: string;
+ children?: ReactNode;
+ style?: CSSProperties;
+}) {
+ const resolvedTone = tone === "default" ? "info" : tone;
+ return (
+
+
+ {title ?
{title}
: null}
+ {children ?
{children}
: null}
+
+ {action}
+
+ );
+}
+
+export function Callout(props: Parameters[0]) {
+ return ;
+}
diff --git a/web/src/pages/admin/ui/overlays.tsx b/web/src/pages/admin/ui/overlays.tsx
new file mode 100644
index 000000000..f9c6c2356
--- /dev/null
+++ b/web/src/pages/admin/ui/overlays.tsx
@@ -0,0 +1,48 @@
+import { Drawer, Modal, type DrawerProps, type ModalProps } from "antd";
+import type { CSSProperties } from "react";
+
+import { cn } from "@/lib/utils";
+
+export type AdminDrawerProps = DrawerProps & {
+ flush?: boolean;
+};
+
+export type AdminModalProps = ModalProps & {
+ flush?: boolean;
+};
+
+const MODAL_FLUSH_STYLES = {
+ container: { padding: 0, overflow: "hidden" as const },
+ body: { padding: 0 },
+};
+
+function mergeFlushStyles(resolved: unknown, flush: boolean, kind: "drawer" | "modal") {
+ const base = resolved && typeof resolved === "object" ? (resolved as Record) : {};
+ if (!flush) return base;
+ const body = base.body && typeof base.body === "object" ? (base.body as CSSProperties) : {};
+ if (kind === "drawer") return { ...base, body: { ...body, padding: 0 } };
+ const container = base.container && typeof base.container === "object" ? (base.container as CSSProperties) : {};
+ return {
+ ...base,
+ container: { ...container, ...MODAL_FLUSH_STYLES.container },
+ body: { ...body, ...MODAL_FLUSH_STYLES.body },
+ };
+}
+
+export function AdminDrawer({ flush = false, destroyOnHidden = true, styles, className, rootClassName, ...props }: AdminDrawerProps) {
+ const mergedStyles =
+ typeof styles === "function"
+ ? (info: { props: DrawerProps }) => mergeFlushStyles(styles(info), flush, "drawer")
+ : mergeFlushStyles(styles, flush, "drawer");
+
+ return ;
+}
+
+export function AdminModal({ flush = false, destroyOnHidden = true, styles, className, rootClassName, ...props }: AdminModalProps) {
+ const mergedStyles =
+ typeof styles === "function"
+ ? (info: { props: ModalProps }) => mergeFlushStyles(styles(info), flush, "modal")
+ : mergeFlushStyles(styles, flush, "modal");
+
+ return ;
+}
diff --git a/web/src/pages/admin/users/users-drawer.tsx b/web/src/pages/admin/users/users-drawer.tsx
index ac8253db1..e7c730025 100644
--- a/web/src/pages/admin/users/users-drawer.tsx
+++ b/web/src/pages/admin/users/users-drawer.tsx
@@ -1,5 +1,5 @@
import { App, Button, Drawer, Form, Input } from "antd";
-import { Select } from "@/components/ui/base/select";
+import { Select } from "@/pages/admin/ui/controls";
import { useEffect, useState } from "react";
import { createAdminUser, updateAdminUser, type AdminUser, type LocalUser } from "@/services/api/auth";
diff --git a/web/src/pages/admin/users/users-panel.tsx b/web/src/pages/admin/users/users-panel.tsx
index 1875fe6d2..57c235dc3 100644
--- a/web/src/pages/admin/users/users-panel.tsx
+++ b/web/src/pages/admin/users/users-panel.tsx
@@ -1,9 +1,9 @@
import { App, Button, Dropdown, Input, Select } from "antd";
-import { Checkbox } from "@/components/ui/base/checkbox";
+import { Checkbox } from "@/pages/admin/ui/controls";
import { Ban, Search, Settings2, UserPlus } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
-import { PaginationBar } from "@/components/layout/workspace-page";
+import { PaginationBar } from "@/pages/admin/components/admin-ui";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
import { bulkDisableAdminUsers, deleteAdminUser, listAdminUsers, updateAdminUser, type AdminUser, type LocalUser } from "@/services/api/auth";
import { useUserStore } from "@/stores/use-user-store";
diff --git a/web/src/pages/assets/index.tsx b/web/src/pages/assets/index.tsx
index 9ec3e75fb..b23663a04 100644
--- a/web/src/pages/assets/index.tsx
+++ b/web/src/pages/assets/index.tsx
@@ -1434,7 +1434,7 @@ function assetArchiveFacts(asset: LibraryAsset) {
{ label: "分类", value: assetCategoryLabel(asset.category) },
];
if (asset.kind === "image" || asset.kind === "video") {
- facts.push({ label: "尺寸", value: `${asset.data.width}x${asset.data.height}` });
+ facts.push({ label: "尺寸", value: assetSizeLabel(asset.data.width, asset.data.height) });
}
if (asset.kind === "video" || asset.kind === "audio") {
facts.push({ label: "时长", value: formatAssetClock(asset.data.durationMs) || "未知" });
@@ -1454,7 +1454,11 @@ function assetSummary(asset: LibraryAsset) {
if (asset.kind === "text") return asset.data.content;
if (asset.kind === "audio") return `${formatAssetDuration(asset.data.durationMs)} · ${formatBytes(asset.data.bytes)} · ${asset.data.mimeType}`;
if (asset.kind === "model") return `${asset.data.fileName} · ${formatBytes(asset.data.bytes)} · ${asset.data.mimeType}`;
- return `${asset.data.width}x${asset.data.height} · ${formatBytes(asset.data.bytes)} · ${asset.data.mimeType}`;
+ return `${assetSizeLabel(asset.data.width, asset.data.height)} · ${formatBytes(asset.data.bytes)} · ${asset.data.mimeType}`;
+}
+
+function assetSizeLabel(width: number, height: number) {
+ return width > 0 && height > 0 ? `${width}x${height}` : "未知";
}
function StorageTag({ asset }: { asset: LibraryAsset }) {
diff --git a/web/src/pages/projects/detail/chapters.tsx b/web/src/pages/projects/detail/chapters.tsx
index d1c494f2a..d9e0fe6eb 100644
--- a/web/src/pages/projects/detail/chapters.tsx
+++ b/web/src/pages/projects/detail/chapters.tsx
@@ -623,7 +623,7 @@ export default function ProjectChaptersView({ detail, refreshProject }: ProjectD