diff --git a/load_path.py b/load_path.py
index 44f70da4..c9d9cb3c 100644
--- a/load_path.py
+++ b/load_path.py
@@ -10,6 +10,7 @@ paths="""/ any
/i18n_getmsgs any
/tenant any
/tenant/index.ui any
+/get_domain_info.dspy any
/uapi logined
/uapi/uapi logined
/uapi/uapi/delete_uapi.dspy logined
@@ -54,6 +55,7 @@ paths="""/ any
/llmage/show_same_catelog_llm.ui logined
/llmage/model_estimate.dspy logined
/llmage/model_plaza.ui logined
+/llmage/dim_test.ui logined
/llmage/plaza_model_cards.ui logined
/llmage/api/get_plaza_models.dspy logined
/llmage/api/plaza_categories.dspy logined
diff --git a/wwwroot/get_domain_info.dspy b/wwwroot/get_domain_info.dspy
new file mode 100644
index 00000000..d948bfcd
--- /dev/null
+++ b/wwwroot/get_domain_info.dspy
@@ -0,0 +1,13 @@
+# 多租户域名路由:解析当前请求域名对应的租户/分销商
+env = request._run_ns
+host = request.headers.get('X-Forwarded-Host') or request.host
+host = host.split(':')[0]
+try:
+ rec = await env.get_domain_by_host(request, host)
+except Exception as e:
+ return json.dumps({'success': False, 'domain': host, 'error': str(e)}, ensure_ascii=False)
+if rec:
+ result = {'success': True, 'domain': host, 'resellerid': rec.resellerid, 'orgid': rec.orgid}
+else:
+ result = {'success': False, 'domain': host, 'error': 'Domain not found'}
+return json.dumps(result, ensure_ascii=False)
diff --git a/wwwroot/shell_theme.css b/wwwroot/shell_theme.css
deleted file mode 120000
index fe14dae1..00000000
--- a/wwwroot/shell_theme.css
+++ /dev/null
@@ -1 +0,0 @@
-../../dashboard_for_sage/wwwroot/shell_theme.css
\ No newline at end of file
diff --git a/wwwroot/shell_theme.css b/wwwroot/shell_theme.css
new file mode 100644
index 00000000..8028d26e
--- /dev/null
+++ b/wwwroot/shell_theme.css
@@ -0,0 +1,843 @@
+/* Sage Modern UI Theme System
+ Dark/Light theme switching via CSS custom properties
+ Applied to element data-theme attribute
+*/
+
+/* ===== Dark Theme (default) ===== */
+:root,
+[data-theme="dark"] {
+ --sage-bg-primary: #0B1120;
+ --sage-bg-secondary: #111827;
+ --sage-bg-card: #1E293B;
+ --sage-bg-hover: #334155;
+ --sage-bg-input: #0F172A;
+ --sage-bg-toolbar: #111827;
+
+ --sage-border-primary: #334155;
+ --sage-border-weak: #1E293B;
+ --sage-border-focus: #3B82F6;
+
+ --sage-text-primary: #F1F5F9;
+ --sage-text-secondary: #94A3B8;
+ --sage-text-muted: #64748B;
+ --sage-text-inverse: #0F172A;
+
+ --sage-brand: #3B82F6;
+ --sage-brand-hover: #2563EB;
+ --sage-success: #22C55E;
+ --sage-warning: #F59E0B;
+ --sage-danger: #EF4444;
+ --sage-info: #06B6D4;
+
+ --sage-sidebar-width: 240px;
+ --sage-sidebar-collapsed-width: 64px;
+ --sage-topbar-height: 56px;
+ --sage-radius: 12px;
+ --sage-radius-sm: 8px;
+
+ --sage-shadow-card: 0 1px 3px rgba(0,0,0,0.3);
+ --sage-shadow-lg: 0 4px 12px rgba(0,0,0,0.4);
+}
+
+/* ===== Light Theme ===== */
+[data-theme="light"] {
+ --sage-bg-primary: #F8FAFC;
+ --sage-bg-secondary: #FFFFFF;
+ --sage-bg-card: #FFFFFF;
+ --sage-bg-hover: #F1F5F9;
+ --sage-bg-input: #F8FAFC;
+ --sage-bg-toolbar: #FFFFFF;
+
+ --sage-border-primary: #E2E8F0;
+ --sage-border-weak: #F1F5F9;
+ --sage-border-focus: #3B82F6;
+
+ --sage-text-primary: #0F172A;
+ --sage-text-secondary: #475569;
+ --sage-text-muted: #94A3B8;
+ --sage-text-inverse: #FFFFFF;
+
+ --sage-brand: #3B82F6;
+ --sage-brand-hover: #2563EB;
+ --sage-success: #16A34A;
+ --sage-warning: #D97706;
+ --sage-danger: #DC2626;
+ --sage-info: #0891B2;
+
+ --sage-shadow-card: 0 1px 3px rgba(0,0,0,0.08);
+ --sage-shadow-lg: 0 4px 12px rgba(0,0,0,0.1);
+}
+
+/* ===== Global Overrides ===== */
+body {
+ margin: 0;
+ padding: 0;
+ background-color: var(--sage-bg-primary);
+ color: var(--sage-text-primary);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ transition: background-color 0.2s ease, color 0.2s ease;
+}
+
+/* Ensure theme variables override bricks.css hardcoded values */
+[data-theme="dark"] body {
+ background-color: var(--sage-bg-primary);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="light"] body {
+ background-color: var(--sage-bg-primary);
+ color: var(--sage-text-primary);
+}
+
+/* ===== Shell Layout ===== */
+.sage-shell {
+ width: 100%;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ background-color: var(--sage-bg-primary);
+ color: var(--sage-text-primary);
+ transition: background-color 0.2s ease, color 0.2s ease;
+}
+
+.sage-topbar {
+ height: var(--sage-topbar-height);
+ background-color: var(--sage-bg-secondary);
+ border-bottom: 1px solid var(--sage-border-primary);
+ display: flex;
+ align-items: center;
+ padding: 0 16px;
+ gap: 12px;
+ flex-shrink: 0;
+ z-index: 100;
+}
+
+.sage-body {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+}
+
+.sage-sidebar {
+ width: var(--sage-sidebar-width);
+ background-color: var(--sage-bg-secondary);
+ border-right: 1px solid var(--sage-border-primary);
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+ overflow-x: hidden;
+ flex-shrink: 0;
+ transition: width 0.3s ease, transform 0.3s ease;
+ position: relative;
+ z-index: 50;
+}
+
+.sage-sidebar.collapsed {
+ width: var(--sage-sidebar-collapsed-width);
+}
+
+.sage-sidebar.collapsed .sidebar-text {
+ opacity: 0;
+ width: 0;
+ overflow: hidden;
+}
+
+/* Menu collapsed state - hide text labels, center icons */
+.menu-collapsed .filler {
+ display: none !important;
+}
+.menu-collapsed .menuitem {
+ justify-content: center;
+ padding: 8px 0;
+}
+.menu-collapsed .menuitem:hover {
+ background-color: var(--sage-bg-hover);
+}
+
+/* Mobile: sidebar as overlay */
+@media (max-width: 768px) {
+ .sage-sidebar {
+ position: fixed;
+ left: 0;
+ top: var(--sage-topbar-height);
+ height: calc(100vh - var(--sage-topbar-height));
+ transform: translateX(0);
+ box-shadow: var(--sage-shadow-lg);
+ }
+
+ .sage-sidebar.collapsed {
+ transform: translateX(-100%);
+ width: var(--sage-sidebar-width); /* Keep full width when hidden on mobile */
+ }
+
+ .sage-main {
+ width: 100%;
+ }
+}
+
+.sage-main {
+ flex: 1;
+ overflow: hidden;
+ overflow-x: hidden;
+ padding: 24px;
+ background-color: var(--sage-bg-primary);
+ min-width: 0; /* Prevent flex item from overflowing */
+ min-height: 0; /* Allow flex children to shrink below content size */
+ transition: margin-left 0.3s ease;
+}
+
+/* Responsive padding for mobile */
+@media (max-width: 768px) {
+ .sage-main {
+ padding: 16px;
+ }
+}
+
+@media (max-width: 480px) {
+ .sage-main {
+ padding: 12px;
+ }
+}
+
+/* ===== Stat Cards ===== */
+.stat-card {
+ background-color: var(--sage-bg-card);
+ border-radius: var(--sage-radius);
+ padding: 20px;
+ border: 1px solid var(--sage-border-primary);
+ box-shadow: var(--sage-shadow-card);
+ transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.2s ease;
+ cursor: pointer;
+}
+
+.stat-card:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--sage-shadow-lg);
+}
+
+.stat-card .stat-icon {
+ width: 40px;
+ height: 40px;
+ border-radius: var(--sage-radius-sm);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 12px;
+}
+
+.stat-card .stat-value {
+ font-size: 28px;
+ font-weight: 700;
+ color: var(--sage-text-primary);
+ line-height: 1.2;
+}
+
+.stat-card .stat-label {
+ font-size: 14px;
+ color: var(--sage-text-secondary);
+ margin-top: 4px;
+}
+
+.stat-card .stat-trend {
+ font-size: 13px;
+ margin-top: 8px;
+}
+
+.stat-card .stat-trend.up {
+ color: var(--sage-success);
+}
+
+.stat-card .stat-trend.down {
+ color: var(--sage-danger);
+}
+
+/* ===== Quick Links ===== */
+.quick-link {
+ background-color: var(--sage-bg-card);
+ border-radius: var(--sage-radius);
+ padding: 20px;
+ border: 1px solid var(--sage-border-primary);
+ box-shadow: var(--sage-shadow-card);
+ transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.2s ease;
+ cursor: pointer;
+ text-align: center;
+}
+
+.quick-link:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--sage-shadow-lg);
+ border-color: var(--sage-brand);
+}
+
+/* ===== Section Headers ===== */
+.section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 16px;
+}
+
+.section-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: var(--sage-text-primary);
+}
+
+/* ===== Buttons ===== */
+.sage-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 16px;
+ border-radius: var(--sage-radius-sm);
+ border: none;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 500;
+ transition: background-color 0.15s ease;
+}
+
+.sage-btn-primary {
+ background-color: var(--sage-brand);
+ color: #FFFFFF;
+}
+
+.sage-btn-primary:hover {
+ background-color: var(--sage-brand-hover);
+}
+
+.sage-btn-ghost {
+ background-color: transparent;
+ color: var(--sage-text-secondary);
+ border: 1px solid var(--sage-border-primary);
+}
+
+.sage-btn-ghost:hover {
+ background-color: var(--sage-bg-hover);
+ color: var(--sage-text-primary);
+}
+
+/* ===== Theme Toggle Button ===== */
+.theme-toggle {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ border: 1px solid var(--sage-border-primary);
+ background-color: transparent;
+ color: var(--sage-text-secondary);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background-color 0.15s ease, color 0.15s ease;
+}
+
+.theme-toggle:hover {
+ background-color: var(--sage-bg-hover);
+ color: var(--sage-text-primary);
+}
+
+/* ===== Sidebar Toggle ===== */
+.sidebar-toggle {
+ width: 36px;
+ height: 36px;
+ border-radius: var(--sage-radius-sm);
+ border: 1px solid var(--sage-border-primary);
+ background-color: transparent;
+ color: var(--sage-text-secondary);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background-color 0.15s ease;
+}
+
+.sidebar-toggle:hover {
+ background-color: var(--sage-bg-hover);
+ color: var(--sage-text-primary);
+}
+
+/* ===== Breadcrumb ===== */
+.breadcrumb {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ color: var(--sage-text-secondary);
+}
+
+.breadcrumb a {
+ color: var(--sage-text-secondary);
+ text-decoration: none;
+}
+
+.breadcrumb a:hover {
+ color: var(--sage-brand);
+}
+
+.breadcrumb .separator {
+ color: var(--sage-text-muted);
+}
+
+.breadcrumb .current {
+ color: var(--sage-text-primary);
+ font-weight: 500;
+}
+
+/* ===== DataViewer Overrides for theme ===== */
+.tabular-row {
+ cursor: pointer;
+}
+
+[data-theme="dark"] .dataviewer-toolbar,
+[data-theme="dark"] .tabular,
+[data-theme="dark"] .data-row,
+[data-theme="dark"] body {
+ background-color: var(--sage-bg-primary);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="dark"] .tabular-header-row {
+ background-color: var(--sage-bg-secondary);
+ color: var(--sage-text-secondary);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="dark"] .tabular-row {
+ color: var(--sage-text-primary);
+}
+
+[data-theme="dark"] .tabular-row:nth-child(odd) {
+ background-color: var(--sage-bg-card);
+}
+
+[data-theme="dark"] .tabular-row:nth-child(even) {
+ background-color: var(--sage-bg-secondary);
+}
+
+[data-theme="dark"] .tabular-row-selected {
+ background-color: var(--sage-bg-hover) !important;
+}
+[data-theme="dark"] .tabular-row-selected,
+[data-theme="dark"] .tabular-row-selected .tabular-cell {
+ color: var(--sage-brand) !important;
+}
+
+/* Light theme selected row */
+[data-theme="light"] .tabular-row-selected {
+ background-color: #E0F2FE !important;
+}
+[data-theme="light"] .tabular-row-selected,
+[data-theme="light"] .tabular-row-selected .tabular-cell {
+ color: #0369A1 !important;
+}
+
+[data-theme="dark"] .tabular-cell {
+ border-color: var(--sage-border-weak);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="dark"] .popup,
+[data-theme="dark"] .modal,
+[data-theme="dark"] .message {
+ background-color: var(--sage-bg-card);
+ color: var(--sage-text-primary);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="dark"] .titlebar {
+ background-color: var(--sage-bg-secondary);
+}
+
+[data-theme="dark"] .inputbox {
+ background-color: var(--sage-bg-input);
+ color: var(--sage-text-primary);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="dark"] .htoolbar,
+[data-theme="dark"] .vtoolbar {
+ background-color: var(--sage-bg-toolbar);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="dark"] .accordion-item {
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="dark"] .accordion-item:nth-child(odd) {
+ background-color: var(--sage-bg-card);
+}
+
+[data-theme="dark"] .accordion-item:nth-child(even) {
+ background-color: var(--sage-bg-secondary);
+}
+
+[data-theme="dark"] .accordion-item-header {
+ background-color: var(--sage-bg-secondary);
+}
+
+[data-theme="dark"] .accordion-item-info {
+ background-color: var(--sage-bg-card);
+}
+
+[data-theme="dark"] .accordion-item-selected {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="dark"] .accordion-item-info-selected {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="dark"] .card {
+ background-color: var(--sage-bg-card);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="dark"] .subcard {
+ background-color: var(--sage-bg-secondary);
+}
+
+[data-theme="dark"] .curpos {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="dark"] .selected {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="dark"] .tabpanel {
+ background-color: var(--sage-bg-card);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="dark"] .tabpanel-content {
+ background-color: var(--sage-bg-secondary);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="dark"] .toolbar-button {
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="dark"] .toolbar-button-active {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="dark"] .auto-textarea {
+ background-color: var(--sage-bg-input);
+ color: var(--sage-text-primary);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="dark"] .droparea {
+ border-color: var(--sage-border-primary);
+ color: var(--sage-text-secondary);
+}
+
+[data-theme="dark"] .droparea:hover {
+ border-color: var(--sage-brand);
+ color: var(--sage-brand);
+ background: var(--sage-bg-hover);
+}
+
+[data-theme="dark"] .thinking-content {
+ background-color: var(--sage-bg-secondary);
+}
+
+[data-theme="dark"] .resp-error {
+ background-color: rgba(239, 68, 68, 0.15);
+ color: var(--sage-danger);
+}
+
+[data-theme="dark"] .resp-content {
+ background-color: var(--sage-bg-card);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="dark"] .llm_title {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="dark"] .progress-container {
+ background-color: var(--sage-bg-secondary);
+}
+
+[data-theme="dark"] pre {
+ background-color: var(--sage-bg-secondary);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="dark"] .llm_msg {
+ background-color: var(--sage-bg-card);
+ color: var(--sage-text-primary);
+ box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);
+}
+
+[data-theme="dark"] .user_msg {
+ background-color: var(--sage-bg-hover);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="light"] .dataviewer-toolbar,
+[data-theme="light"] .tabular,
+[data-theme="light"] .data-row,
+[data-theme="light"] body {
+ background-color: var(--sage-bg-card);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="light"] .tabular-header-row {
+ background-color: var(--sage-bg-hover);
+ color: var(--sage-text-secondary);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="light"] .tabular-row {
+ color: var(--sage-text-primary);
+}
+
+[data-theme="light"] .tabular-row:nth-child(odd) {
+ background-color: #f9fafb;
+}
+
+[data-theme="light"] .tabular-row:nth-child(even) {
+ background-color: var(--sage-bg-card);
+}
+
+[data-theme="light"] .tabular-row-selected {
+ color: var(--sage-brand-hover);
+}
+
+[data-theme="light"] .tabular-cell {
+ border-color: var(--sage-border-weak);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="light"] .popup,
+[data-theme="light"] .modal,
+[data-theme="light"] .message {
+ background-color: var(--sage-bg-card);
+ color: var(--sage-text-primary);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="light"] .titlebar {
+ background-color: var(--sage-bg-secondary);
+}
+
+[data-theme="light"] .inputbox {
+ background-color: var(--sage-bg-input);
+ color: var(--sage-text-primary);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="light"] .htoolbar,
+[data-theme="light"] .vtoolbar {
+ background-color: var(--sage-bg-toolbar);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="light"] .accordion-item {
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="light"] .accordion-item:nth-child(odd) {
+ background-color: var(--sage-bg-card);
+}
+
+[data-theme="light"] .accordion-item:nth-child(even) {
+ background-color: #f9fafb;
+}
+
+[data-theme="light"] .accordion-item-header {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="light"] .accordion-item-info {
+ background-color: var(--sage-bg-card);
+}
+
+[data-theme="light"] .accordion-item-selected {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="light"] .accordion-item-info-selected {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="light"] .card {
+ background-color: var(--sage-bg-card);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="light"] .subcard {
+ background-color: #f9fafb;
+}
+
+[data-theme="light"] .curpos {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="light"] .selected {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="light"] .tabpanel {
+ background-color: var(--sage-bg-card);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="light"] .tabpanel-content {
+ background-color: var(--sage-bg-secondary);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="light"] .toolbar-button {
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="light"] .toolbar-button-active {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="light"] .auto-textarea {
+ background-color: var(--sage-bg-input);
+ color: var(--sage-text-primary);
+ border-color: var(--sage-border-primary);
+}
+
+[data-theme="light"] .droparea {
+ border-color: var(--sage-border-primary);
+ color: var(--sage-text-secondary);
+}
+
+[data-theme="light"] .droparea:hover {
+ border-color: var(--sage-brand);
+ color: var(--sage-brand);
+ background: var(--sage-bg-hover);
+}
+
+[data-theme="light"] .thinking-content {
+ background-color: var(--sage-bg-secondary);
+}
+
+[data-theme="light"] .resp-error {
+ background-color: rgba(239, 68, 68, 0.1);
+ color: var(--sage-danger);
+}
+
+[data-theme="light"] .resp-content {
+ background-color: var(--sage-bg-card);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="light"] .llm_title {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="light"] .progress-container {
+ background-color: var(--sage-bg-hover);
+}
+
+[data-theme="light"] pre {
+ background-color: var(--sage-bg-secondary);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="light"] .llm_msg {
+ background-color: var(--sage-bg-card);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="light"] .user_msg {
+ background-color: var(--sage-bg-hover);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="light"] .tabular { background-color: #f5f5f5; color: #1E293B; }
+[data-theme="light"] .tabular-header-row { background-color: #E2E8F0; color: #0F172A; font-weight: 600; }
+[data-theme="light"] .tabular-header-row .tabular-cell { color: #0F172A; }
+[data-theme="light"] .tabular-row { color: #1E293B; }
+[data-theme="light"] .tabular-row:nth-child(odd) { background-color: #FFFFFF; }
+[data-theme="light"] .tabular-row:nth-child(even) { background-color: #F8FAFC; }
+[data-theme="light"] .tabular-cell { border-color: #E2E8F0; color: #1E293B; }
+[data-theme="light"] .card { color: #1E293B; }
+[data-theme="light"] .inputbox { color: #1E293B; background-color: #FFFFFF; }
+[data-theme="light"] .popup, [data-theme="light"] .modal { color: #1E293B; }
+[data-theme="light"] .accordion-item-info { color: #475569; }
+[data-theme="light"] .toolbar-button { color: #334155; }
+[data-theme="light"] .message { color: #1E293B; }
+
+/* ===== Menu Overrides ===== */
+[data-theme="dark"] .menu-item {
+ color: var(--sage-text-secondary);
+}
+
+[data-theme="dark"] .menu-item:hover,
+[data-theme="dark"] .menu-item.active {
+ background-color: var(--sage-bg-hover);
+ color: var(--sage-text-primary);
+}
+
+[data-theme="light"] .menu-item {
+ color: var(--sage-text-secondary);
+}
+
+[data-theme="light"] .menu-item:hover,
+[data-theme="light"] .menu-item.active {
+ background-color: var(--sage-bg-hover);
+ color: var(--sage-brand);
+}
+
+/* ===== Utility Classes ===== */
+.sage-brand-title {
+ color: var(--sage-text-primary);
+ font-weight: bold;
+}
+
+.sage-text-secondary {
+ color: var(--sage-text-secondary);
+}
+
+/* ===== Responsive - Topbar ===== */
+@media (max-width: 768px) {
+ .sage-topbar {
+ padding: 0 8px;
+ gap: 6px;
+ }
+ .sage-brand-title {
+ font-size: 16px;
+ }
+}
+
+@media (max-width: 480px) {
+ .sage-brand-title {
+ display: none;
+ }
+}
+
+/* ===== Responsive - Stat Cards ===== */
+@media (max-width: 768px) {
+ .stat-card {
+ padding: 14px;
+ }
+ .stat-card .stat-value {
+ font-size: 22px;
+ }
+ .stat-card .stat-label {
+ font-size: 12px;
+ }
+ .stat-card .stat-icon {
+ width: 32px;
+ height: 32px;
+ margin-bottom: 8px;
+ }
+ .quick-link {
+ padding: 14px;
+ }
+ .section-title {
+ font-size: 16px;
+ }
+}
diff --git a/wwwroot/shell_theme.js b/wwwroot/shell_theme.js
deleted file mode 120000
index af0c87ec..00000000
--- a/wwwroot/shell_theme.js
+++ /dev/null
@@ -1 +0,0 @@
-../../dashboard_for_sage/wwwroot/shell_theme.js
\ No newline at end of file
diff --git a/wwwroot/shell_theme.js b/wwwroot/shell_theme.js
new file mode 100644
index 00000000..367a7227
--- /dev/null
+++ b/wwwroot/shell_theme.js
@@ -0,0 +1,263 @@
+/* Sage Modern UI Shell Theme & Layout Controller
+ Handles: theme switching (dark/light), sidebar collapse, localStorage persistence
+*/
+(function() {
+ 'use strict';
+
+ var THEME_KEY = 'sage_ui_theme';
+ var SIDEBAR_KEY = 'sage_sidebar_collapsed';
+
+ // Initialize theme on page load
+ function initTheme() {
+ var saved = null;
+ try { saved = localStorage.getItem(THEME_KEY); } catch(e) {}
+ var theme = saved || 'dark';
+ document.documentElement.setAttribute('data-theme', theme);
+ updateThemeIcon(theme);
+ }
+
+ // Toggle between dark and light
+ function toggleTheme() {
+ var current = document.documentElement.getAttribute('data-theme') || 'dark';
+ var next = current === 'dark' ? 'light' : 'dark';
+ document.documentElement.setAttribute('data-theme', next);
+ try { localStorage.setItem(THEME_KEY, next); } catch(e) {}
+ updateThemeIcon(next);
+ }
+
+ // Update the theme toggle icon based on current theme
+ function updateThemeIcon(theme) {
+ var btn = document.getElementById('theme_toggle_btn');
+ if (!btn) return;
+ if (theme === 'light') {
+ // Show moon icon (switch to dark)
+ btn.innerHTML = '';
+ } else {
+ // Show sun icon (switch to light)
+ btn.innerHTML = '';
+ }
+ }
+
+ // Initialize sidebar state
+ function initSidebar() {
+ var collapsed = false;
+ try { collapsed = localStorage.getItem(SIDEBAR_KEY) === 'true'; } catch(e) {}
+
+ // Auto-collapse on mobile
+ if (isMobile()) {
+ collapsed = true;
+ }
+
+ var sidebar = document.getElementById('sage_sidebar');
+ if (sidebar && collapsed) {
+ sidebar.classList.add('collapsed');
+ sidebar.style.width = '64px';
+ }
+ }
+
+ // Handle window resize - auto-collapse on mobile
+ function handleResize() {
+ var sidebar = document.getElementById('sage_sidebar');
+ if (!sidebar) return;
+
+ if (isMobile() && !sidebar.classList.contains('collapsed')) {
+ sidebar.classList.add('collapsed');
+ sidebar.style.width = '64px';
+ try { localStorage.setItem(SIDEBAR_KEY, 'true'); } catch(e) {}
+ updateSidebarIcon(true);
+ }
+ }
+
+ // Check if we're on mobile viewport
+ function isMobile() {
+ return window.innerWidth <= 768;
+ }
+
+ // Close sidebar when clicking outside on mobile
+ function setupMobileOverlay() {
+ document.addEventListener('click', function(e) {
+ if (!isMobile()) return;
+ var sidebar = document.getElementById('sage_sidebar');
+ var toggleBtn = document.getElementById('sidebar_toggle_btn');
+ if (!sidebar || sidebar.classList.contains('collapsed')) return;
+ // If click is outside sidebar and toggle button, close sidebar
+ if (!sidebar.contains(e.target) && (!toggleBtn || !toggleBtn.contains(e.target))) {
+ sidebar.classList.add('collapsed');
+ sidebar.style.width = '64px';
+ try { localStorage.setItem(SIDEBAR_KEY, 'true'); } catch(ex) {}
+ updateSidebarIcon(true);
+ }
+ });
+ }
+
+ function updateSidebarIcon(isCollapsed) {
+ var btn = document.getElementById('sidebar_toggle_btn');
+ if (!btn) return;
+ if (isCollapsed) {
+ btn.innerHTML = '';
+ } else {
+ btn.innerHTML = '';
+ }
+ }
+
+ // Toggle sidebar collapse
+ function toggleSidebar() {
+ var sidebar = document.getElementById('sage_sidebar');
+ if (!sidebar) return;
+ sidebar.classList.toggle('collapsed');
+ var isCollapsed = sidebar.classList.contains('collapsed');
+ try { localStorage.setItem(SIDEBAR_KEY, isCollapsed); } catch(e) {}
+ updateSidebarIcon(isCollapsed);
+ // Override inline width style (bricks sets inline style which beats CSS class)
+ sidebar.style.width = isCollapsed ? '64px' : '240px';
+ // Toggle Menu widget collapse state
+ if (typeof bricks !== 'undefined') {
+ var menu = bricks.getWidgetById('global_nav_menu', bricks.app);
+ if (menu && menu.toggle_collapse) {
+ menu.toggle_collapse();
+ }
+ }
+ }
+
+ // Initialize SPA Router
+ function initRouter() {
+ if (typeof bricks === 'undefined' || !bricks.Router) {
+ console.log('[Shell] Router not available');
+ return;
+ }
+ bricks.Router.init({
+ targets: [
+ { id: 'sage_main_content', param: 'page' }
+ ]
+ });
+ }
+
+ // Called by user_logined event — skip dashboard if Router has a saved route
+ window.sageOnLogin = async function(dashboardUrl) {
+ var target = bricks.getWidgetById('sage_main_content', bricks.app);
+ if (!target) return;
+
+ var hasRoute = false;
+ if (bricks.Router && bricks.Router._enabled) {
+ hasRoute = bricks.Router.current('sage_main_content')
+ || new URLSearchParams(window.location.search).has('page');
+ }
+
+ if (hasRoute) {
+ console.log('[Shell] Router has route, skip dashboard load');
+ return;
+ }
+
+ // No route — load dashboard via Router
+ if (dashboardUrl && bricks.Router && bricks.Router._enabled) {
+ bricks.Router.navigate('sage_main_content', dashboardUrl);
+ } else if (dashboardUrl) {
+ var desc = { widgettype: 'urlwidget', options: { url: dashboardUrl } };
+ var w = await bricks.widgetBuild(desc, bricks.app);
+ if (w) {
+ target.clear_widgets();
+ target.add_widget(w);
+ }
+ }
+ };
+
+ // Reload global menu after login/logout
+ window.sageReloadMenu = async function() {
+ if (typeof bricks === 'undefined') return;
+ var sidebar = bricks.getWidgetById('sage_sidebar', bricks.app);
+ if (!sidebar) {
+ console.log('[Shell] sage_sidebar not found');
+ return;
+ }
+ // Check if sidebar is currently collapsed before clearing
+ var isCollapsed = sidebar.el && sidebar.el.classList.contains('collapsed');
+ // Clear existing children
+ sidebar.subwidgets.forEach(function(w) { w.destroy && w.destroy(); });
+ sidebar.subwidgets = [];
+ sidebar.el.innerHTML = '';
+ // Rebuild menu urlwidget
+ var menuUrl = bricks.app.baseUrl + '/global_menu.ui?_webbricks_=1';
+ var desc = {
+ "widgettype": "urlwidget",
+ "options": { "url": menuUrl }
+ };
+ try {
+ var w = await bricks.widgetBuild(desc, sidebar);
+ if (w) {
+ sidebar.addSubWidget(w);
+ // Re-apply sidebar width and collapsed state to newly built menu
+ sidebar.el.style.width = isCollapsed ? '64px' : '240px';
+ updateSidebarIcon(isCollapsed);
+ if (isCollapsed) {
+ var menu = bricks.getWidgetById('global_nav_menu', bricks.app);
+ if (menu && menu.collapse) {
+ menu.collapse();
+ }
+ }
+ console.log('[Shell] Menu reloaded');
+ }
+ } catch(e) {
+ console.log('[Shell] Menu reload error:', e);
+ }
+ };
+
+ // Run on DOM ready
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', function() {
+ initTheme();
+ initSidebar();
+ initRouter();
+ setupMobileOverlay();
+ window.addEventListener('resize', handleResize);
+ });
+ } else {
+ initTheme();
+ initSidebar();
+ initRouter();
+ setupMobileOverlay();
+ window.addEventListener('resize', handleResize);
+ }
+
+ // Bricks widgets render asynchronously after DOMContentLoaded.
+ // Retry updateThemeIcon until the button element exists.
+ (function retryThemeIcon() {
+ var btn = document.getElementById('theme_toggle_btn');
+ if (btn) {
+ var theme = document.documentElement.getAttribute('data-theme') || 'dark';
+ updateThemeIcon(theme);
+ } else {
+ setTimeout(retryThemeIcon, 200);
+ }
+ })();
+
+ // Retry sidebar icon init until the button element exists
+ (function retrySidebarIcon() {
+ var sidebar = document.getElementById('sage_sidebar');
+ var btn = document.getElementById('sidebar_toggle_btn');
+ if (btn && sidebar) {
+ var isCollapsed = sidebar.classList.contains('collapsed');
+ updateSidebarIcon(isCollapsed);
+ // Sync inline width with collapsed state (bricks may have overridden it)
+ sidebar.style.width = isCollapsed ? '64px' : '240px';
+ // Apply collapsed state to Menu if sidebar was already collapsed
+ if (isCollapsed && typeof bricks !== 'undefined') {
+ var menu = bricks.getWidgetById('global_nav_menu', bricks.app);
+ if (menu && menu.collapse) {
+ menu.collapse();
+ } else {
+ setTimeout(retrySidebarIcon, 200);
+ return;
+ }
+ }
+ } else {
+ setTimeout(retrySidebarIcon, 200);
+ }
+ })();
+
+ // Expose global functions for bricks bind access
+ window.sageToggleTheme = toggleTheme;
+ window.sageToggleSidebar = toggleSidebar;
+ window.sageInitTheme = initTheme;
+ window.sageInitSidebar = initSidebar;
+
+})();