feat: add live demo dashboard on GitHub Pages

Add a demo mode to the dashboard that fetches graph data from Supabase
Storage instead of the local Vite dev server, bypasses the token gate,
and deploys alongside the homepage at /Understand-Anything/demo/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lum1104
2026-04-03 11:49:00 +08:00
Unverified
parent 2ec1c37efa
commit d6a80224b6
5 changed files with 85 additions and 10 deletions
+16 -1
View File
@@ -5,6 +5,8 @@ on:
branches: [main]
paths:
- 'homepage/**'
- 'understand-anything-plugin/packages/dashboard/**'
- 'understand-anything-plugin/packages/core/**'
- '.github/workflows/deploy-homepage.yml'
workflow_dispatch:
@@ -34,10 +36,23 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Build
- name: Build homepage
working-directory: homepage
run: pnpm build
- name: Build core
run: pnpm --filter @understand-anything/core build
- name: Build demo dashboard
run: pnpm --filter @understand-anything/dashboard build:demo
env:
VITE_GRAPH_URL: ${{ vars.DEMO_GRAPH_URL }}
VITE_DOMAIN_GRAPH_URL: ${{ vars.DEMO_DOMAIN_GRAPH_URL }}
VITE_META_URL: ${{ vars.DEMO_META_URL }}
- name: Merge demo into homepage output
run: cp -r understand-anything-plugin/packages/dashboard/dist homepage/dist/demo
- uses: actions/upload-pages-artifact@v3
with:
path: homepage/dist
+19
View File
@@ -15,6 +15,7 @@ const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
</p>
<div class="hero-actions">
<a href="#install" class="hero-cta">Get Started</a>
<a href="/Understand-Anything/demo/" class="hero-demo">Live Demo &rarr;</a>
<a href={githubUrl} target="_blank" rel="noopener noreferrer" class="hero-secondary">
View on GitHub &rarr;
</a>
@@ -107,6 +108,24 @@ const githubUrl = 'https://github.com/Lum1104/Understand-Anything';
transform: translateY(-2px);
}
.hero-demo {
border: 1.5px solid var(--accent);
color: var(--accent);
padding: 0.85rem 2.5rem;
border-radius: 8px;
font-weight: 600;
font-size: 1.1rem;
text-decoration: none;
transition: background 0.3s ease, box-shadow 0.3s ease, transform 0.2s ease;
}
.hero-demo:hover {
background: rgba(212, 165, 116, 0.1);
box-shadow: 0 0 20px var(--accent-glow);
transform: translateY(-2px);
text-decoration: none;
}
.hero-secondary {
color: var(--text-muted);
font-size: 1rem;
@@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"build:demo": "tsc -b && vite build --config vite.config.demo.ts",
"preview": "vite preview"
},
"dependencies": {
@@ -24,13 +24,31 @@ import { ThemeProvider } from "./themes/index.ts";
import { ThemePicker } from "./components/ThemePicker.tsx";
import type { ThemeConfig } from "./themes/index.ts";
const DEMO_MODE = import.meta.env.VITE_DEMO_MODE === "true";
const SESSION_TOKEN_KEY = "understand-anything-token";
/** Resolve data file URL — in demo mode, use env var URLs; otherwise use local paths with token. */
function dataUrl(fileName: string, token: string | null): string {
if (DEMO_MODE) {
const envMap: Record<string, string | undefined> = {
"knowledge-graph.json": import.meta.env.VITE_GRAPH_URL,
"domain-graph.json": import.meta.env.VITE_DOMAIN_GRAPH_URL,
"meta.json": import.meta.env.VITE_META_URL,
"diff-overlay.json": import.meta.env.VITE_DIFF_OVERLAY_URL,
};
const url = envMap[fileName];
if (url) return url;
}
const path = `/${fileName}`;
return token ? `${path}?token=${encodeURIComponent(token)}` : path;
}
/**
* Resolve the access token from the URL query string or sessionStorage.
* If found in the URL, persist to sessionStorage and strip the param from the address bar.
*/
function resolveInitialToken(): string | null {
if (DEMO_MODE) return "__demo__";
const params = new URLSearchParams(window.location.search);
const urlToken = params.get("token");
if (urlToken) {
@@ -46,11 +64,6 @@ function resolveInitialToken(): string | null {
return sessionStorage.getItem(SESSION_TOKEN_KEY);
}
/** Build a URL with the token query param appended. */
function tokenUrl(path: string, token: string | null): string {
return token ? `${path}?token=${encodeURIComponent(token)}` : path;
}
function App() {
const [accessToken, setAccessToken] = useState<string | null>(resolveInitialToken);
@@ -59,6 +72,11 @@ function App() {
setAccessToken(token);
}, []);
// In demo mode, skip token gate entirely
if (DEMO_MODE) {
return <Dashboard accessToken="__demo__" />;
}
// Show the token gate when no token is available
if (accessToken === null) {
return <TokenGate onTokenValid={handleTokenValid} />;
@@ -90,7 +108,7 @@ function Dashboard({ accessToken }: { accessToken: string }) {
const setDomainGraph = useDashboardStore((s) => s.setDomainGraph);
useEffect(() => {
fetch(tokenUrl("/meta.json", accessToken))
fetch(dataUrl("meta.json", accessToken))
.then((r) => (r.ok ? r.json() : null))
.then((meta) => {
if (meta?.theme) setMetaTheme(meta.theme);
@@ -215,7 +233,7 @@ function Dashboard({ accessToken }: { accessToken: string }) {
useKeyboardShortcuts(shortcuts);
useEffect(() => {
fetch(tokenUrl("/knowledge-graph.json", accessToken))
fetch(dataUrl("knowledge-graph.json", accessToken))
.then((res) => res.json())
.then((data: unknown) => {
const result = validateGraph(data);
@@ -244,7 +262,7 @@ function Dashboard({ accessToken }: { accessToken: string }) {
}, [setGraph]);
useEffect(() => {
fetch(tokenUrl("/diff-overlay.json", accessToken))
fetch(dataUrl("diff-overlay.json", accessToken))
.then((res) => {
if (!res.ok) return null;
return res.json();
@@ -270,7 +288,7 @@ function Dashboard({ accessToken }: { accessToken: string }) {
}, [setDiffOverlay]);
useEffect(() => {
fetch(tokenUrl("/domain-graph.json", accessToken))
fetch(dataUrl("domain-graph.json", accessToken))
.then((res) => {
if (!res.ok) return null;
return res.json();
@@ -0,0 +1,22 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "path";
export default defineConfig({
base: "/Understand-Anything/demo/",
resolve: {
alias: {
"@understand-anything/core/schema": path.resolve(__dirname, "../core/dist/schema.js"),
"@understand-anything/core/search": path.resolve(__dirname, "../core/dist/search.js"),
"@understand-anything/core/types": path.resolve(__dirname, "../core/dist/types.js"),
},
},
define: {
"import.meta.env.VITE_DEMO_MODE": JSON.stringify("true"),
},
plugins: [react(), tailwindcss()],
});