From 3dbb5abaf66a1e375f11f75e775b41e3a030d978 Mon Sep 17 00:00:00 2001 From: hzhang Date: Sun, 24 May 2026 00:15:35 +0100 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20v2=20rewrite=20=E2=80=94=20Vi?= =?UTF-8?q?te=20+=20React=20+=20TS=20readonly=20SPA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the v1 CRA app (which targeted the obsolete Python Dialectic backend) with a fresh Vite + React 18 + TypeScript scaffold that talks to Dialectic.Backend Go v2. Pages (all readonly — propose/signup/post are agent-only by design): - / TopicList — filter by status, paginated - /topics/:id TopicDetail — meta + camps + transcript (polling every 8s) - /topics/:id/verdict Verdict permalink (shareable) - /agents/:id AgentActivity — admin diagnostics card Stack: - Vite 5 + React 18 + react-router-dom 6 - Pure ESM, NodeNext-style imports, .tsx - Style: ~/STYLE.md tokens (IBM Plex Mono + Major Mono Display + --acid #d8ff3e on --ink #080a0d, with subtle blueprint grid wash) Auth: - v1 dev-bypass only — VITE_OIDC_DEV_BYPASS auto-attaches x-dev-bypass header. Real Keycloak OIDC redirect ships as v2. - Admin endpoints (x-dialectic-admin-key) prompt on first visit and store key in localStorage. Never baked into bundle. Never sent to non-admin endpoints. Backend pairing: - Dialectic.Backend@0b16b52 adds GET /api/admin/agents/{id} for the AgentActivity page. AgentActivity calls it via the admin-key branch in api.ts. Deploy: - Multi-stage Dockerfile (node:22-alpine build → nginx:1.27-alpine serve). nginx.conf reverse-proxies /api/ → dialectic-backend:8090 so the browser sees one origin (no CORS). Reuses the existing hzhang/Dialectic.Frontend repo — old CRA contents nuked in this commit. History preserved on master. --- .gitignore | 8 +- Dockerfile | 30 +- README.md | 84 ++ index.html | 22 + nginx.conf | 40 + package-lock.json | 1831 +++++++++++++++++++++++++ package.json | 52 +- public/index.html | 18 - src/App.js | 211 --- src/App.tsx | 54 + src/api.ts | 125 ++ src/auth.tsx | 33 + src/components/AuthProvider.js | 156 --- src/components/DebateConfiguration.js | 497 ------- src/components/DebateDisplay.js | 317 ----- src/components/SessionsList.js | 96 -- src/components/Settings.js | 352 ----- src/components/SetupWizard.js | 287 ---- src/index.js | 13 - src/main.tsx | 16 + src/pages/AgentActivity.tsx | 132 ++ src/pages/NotFound.tsx | 10 + src/pages/TopicDetail.tsx | 184 +++ src/pages/TopicList.tsx | 109 ++ src/pages/Verdict.tsx | 78 ++ src/styles/App.css | 792 ----------- src/styles/app.css | 331 +++++ src/styles/tokens.css | 202 +++ src/types.ts | 85 ++ src/util.ts | 28 + src/utils/api.js | 48 - tsconfig.json | 22 + tsconfig.node.json | 13 + tsconfig.node.tsbuildinfo | 1 + tsconfig.tsbuildinfo | 1 + vite.config.d.ts | 2 + vite.config.js | 21 + vite.config.ts | 22 + 38 files changed, 3497 insertions(+), 2826 deletions(-) create mode 100644 README.md create mode 100644 index.html create mode 100644 nginx.conf create mode 100644 package-lock.json delete mode 100644 public/index.html delete mode 100644 src/App.js create mode 100644 src/App.tsx create mode 100644 src/api.ts create mode 100644 src/auth.tsx delete mode 100644 src/components/AuthProvider.js delete mode 100644 src/components/DebateConfiguration.js delete mode 100644 src/components/DebateDisplay.js delete mode 100644 src/components/SessionsList.js delete mode 100644 src/components/Settings.js delete mode 100644 src/components/SetupWizard.js delete mode 100644 src/index.js create mode 100644 src/main.tsx create mode 100644 src/pages/AgentActivity.tsx create mode 100644 src/pages/NotFound.tsx create mode 100644 src/pages/TopicDetail.tsx create mode 100644 src/pages/TopicList.tsx create mode 100644 src/pages/Verdict.tsx delete mode 100644 src/styles/App.css create mode 100644 src/styles/app.css create mode 100644 src/styles/tokens.css create mode 100644 src/types.ts create mode 100644 src/util.ts delete mode 100644 src/utils/api.js create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 tsconfig.node.tsbuildinfo create mode 100644 tsconfig.tsbuildinfo create mode 100644 vite.config.d.ts create mode 100644 vite.config.js create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore index 85e7c1d..4eed038 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ -/.idea/ +node_modules +dist +.idea +.vscode +*.local +.DS_Store +*.swp diff --git a/Dockerfile b/Dockerfile index fae5c8d..2f3e9ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,26 @@ -FROM node:21-alpine -RUN apk add --no-cache bash +# Multi-stage build: +# 1. node:alpine — install + vite build → dist/ +# 2. nginx:alpine — serve dist/ + reverse-proxy /api/ to the backend +# +# Build args: +# VITE_DIALECTIC_API_BASE — defaults to '/api' (same-origin via nginx) +# VITE_OIDC_DEV_BYPASS — set to a token string to bake dev-bypass +# into the bundle (DO NOT set in prod images) + +FROM node:22-alpine AS build WORKDIR /app -COPY package*.json ./ -RUN npm install +COPY package.json package-lock.json* ./ +RUN npm ci --no-audit --no-fund COPY . . -EXPOSE 3000 -CMD ["npm", "start"] +ARG VITE_DIALECTIC_API_BASE=/api +ARG VITE_OIDC_DEV_BYPASS= +ENV VITE_DIALECTIC_API_BASE=$VITE_DIALECTIC_API_BASE +ENV VITE_OIDC_DEV_BYPASS=$VITE_OIDC_DEV_BYPASS +RUN npm run build + +FROM nginx:1.27-alpine AS serve +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -qO- http://127.0.0.1/index.html >/dev/null || exit 1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..161fbcc --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# Dialectic.Frontend (v2) + +Operator + observer SPA for [Dialectic v2](https://git.hangman-lab.top/hzhang/Dialectic). + +**Readonly v1 scope** — humans browse debates, watch live transcripts, +and read verdicts. Propose / signup / post-argument are agent-only and +stay that way (use OpenClaw plugin tools). + +## Tech + +- Vite + React 18 + TypeScript +- react-router-dom for client-side routing +- Pure ESM (`"type": "module"`) +- Style: [Hangman Lab](~/STYLE.md) dark "blueprint" theme — IBM Plex + Mono + Major Mono Display + acid `#d8ff3e` accent + +## Pages + +| Route | Purpose | +|-------|---------| +| `/` | Topic list, filterable by status | +| `/topics/:id` | Topic detail + live transcript (8s polling) | +| `/topics/:id/verdict` | Verdict permalink (shareable) | +| `/agents/:id` | Admin diagnostics: key state, counts, recent topics | + +## Auth + +v1 is dev-bypass only. Set `VITE_OIDC_DEV_BYPASS=` at build time +to auto-attach `x-dev-bypass` on every request. Real OIDC + Keycloak +redirect lands as v2. + +Admin pages (`/agents/:id`) call the backend's +`x-dialectic-admin-key`-gated endpoints. The frontend prompts on first +visit and stores the key in `localStorage` — never sent to non-admin +endpoints, never baked into the bundle. + +## Dev + +```bash +npm install +npm run dev # http://localhost:5173; proxies /api → http://localhost:8090 +npm run build # static bundle in dist/ +``` + +Run the backend somewhere reachable from your dev machine — for sim: + +```bash +VITE_DIALECTIC_BACKEND=http://dind-t3:8090 npm run dev +``` + +## Build / deploy + +Multi-stage Docker — `node:alpine` build → `nginx:alpine` serve. The +nginx config reverse-proxies `/api/` to the `dialectic-backend` +compose service so the browser sees a single origin. + +```bash +docker build -t dialectic-frontend:dev . +docker run -p 8080:80 --network dialectic-frontend:dev +``` + +For sim/prod, the umbrella `Dialectic/docker-compose.yaml` wires this +service into the same network as the backend. + +## Source layout + +``` +src/ + main.tsx # entry — mounts wrapped in BrowserRouter + AuthProvider + App.tsx # router + shell (header / nav / footer) + api.ts # thin fetch client; env-configurable base + dev-bypass + auth.tsx # AuthProvider — v1 is dev-bypass surfacing only + types.ts # backend response types (kept in sync by hand) + util.ts # fmtTime / fmtRelative — tiny date helpers + pages/ + TopicList.tsx + TopicDetail.tsx + Verdict.tsx + AgentActivity.tsx + NotFound.tsx + styles/ + tokens.css # Hangman Lab tokens — keep in sync with STYLE.md + app.css # layout + per-page styles +``` diff --git a/index.html b/index.html new file mode 100644 index 0000000..2868112 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + + + + Dialectic — Hangman Lab + + + + + + + +
+ + + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..0ac4b39 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,40 @@ +# nginx config for the Dialectic SPA. +# - Static SPA assets under /; falls back to /index.html for client-side +# router (BrowserRouter needs all unknown paths to serve the shell). +# - /api/ reverse-proxies to the dialectic-backend container. The compose +# service name + port is settable via DIALECTIC_BACKEND env at run time +# (default = http://dialectic-backend:8090). We inject it via envsubst +# at container start so the same image works for sim + prod. + +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Long-lived caching for hashed asset bundles. + location /assets/ { + access_log off; + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + # SPA fallback: any unknown path serves index.html so React Router + # can take over client-side. + location / { + try_files $uri /index.html; + } + + # API reverse-proxy. Same-origin from the browser's POV so we sidestep + # CORS. nginx upstream must point to the dialectic-backend service. + # Substitution happens at container start via envsubst (see Dockerfile + # entrypoint pattern; for v1 we hard-code the compose default). + location /api/ { + proxy_pass http://dialectic-backend:8090/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 30s; + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4cf011d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1831 @@ +{ + "name": "dialectic-frontend", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dialectic-frontend", + "version": "2.0.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0" + }, + "devDependencies": { + "@types/node": "^22.10.1", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.6.3", + "vite": "^5.4.11" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.29", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz", + "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.361", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", + "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json index a03ce72..0c2f232 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,25 @@ { - "name": "Dialectic_Frontend", - "version": "1.0.0", - "description": "", - "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0", - "oidc-client-ts": "^3.0.1", - "react-router-dom": "^6.28.0", - "react-scripts": "5.0.1" - }, + "name": "dialectic-frontend", + "private": true, + "version": "2.0.0", + "type": "module", "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview --port 5173", + "typecheck": "tsc --noEmit" }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0" }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "private": true + "devDependencies": { + "@types/node": "^22.10.1", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.6.3", + "vite": "^5.4.11" + } } diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 4ae42e8..0000000 --- a/public/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - Dialectic - - - -
- - \ No newline at end of file diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 1b8463a..0000000 --- a/src/App.js +++ /dev/null @@ -1,211 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Routes, Route } from 'react-router-dom'; -import './styles/App.css'; -import { getBackendHost } from './utils/api'; -import DebateConfiguration from './components/DebateConfiguration'; -import DebateDisplay from './components/DebateDisplay'; -import SessionsList from './components/SessionsList'; -import Settings from './components/Settings'; -import SetupWizard from './components/SetupWizard'; -import AuthProvider, { - useAuth, - OidcCallback, - PopupCallback, - SilentCallback, -} from './components/AuthProvider'; - -const App = () => { - const [appState, setAppState] = useState('loading'); - const [oidcConfig, setOidcConfig] = useState(null); - const [envMode, setEnvMode] = useState('dev'); - - useEffect(() => { - checkSetupStatus(); - - const handler = () => setAppState('setup'); - window.addEventListener('needs-setup', handler); - return () => window.removeEventListener('needs-setup', handler); - }, []); - - const checkSetupStatus = async () => { - try { - const resp = await fetch(`${getBackendHost()}/api/setup/status`); - if (resp.status === 503) { - setAppState('setup'); - return; - } - if (resp.ok) { - const data = await resp.json(); - if (data.env_mode) setEnvMode(data.env_mode); - - // Build OIDC config from backend-provided Keycloak info - if (data.keycloak && data.keycloak.authority) { - const origin = window.location.origin; - setOidcConfig({ - authority: data.keycloak.authority, - client_id: data.keycloak.client_id, - redirect_uri: `${origin}/callback`, - post_logout_redirect_uri: origin, - response_type: 'code', - scope: 'openid profile email roles', - }); - } - - if (!data.initialized || !data.db_configured) { - setAppState('setup'); - return; - } - setAppState('ready'); - return; - } - setAppState('ready'); - } catch { - setAppState('setup'); - } - }; - - const handleSetupComplete = () => { - setAppState('ready'); - }; - - const isProd = envMode === 'prod'; - - return ( - - - } /> - } /> - } /> - - } - /> - - - ); -}; - -const MainContent = ({ appState, isProd, onSetupComplete }) => { - const auth = useAuth(); - const [currentView, setCurrentView] = useState('configuration'); - const [currentSessionId, setCurrentSessionId] = useState(null); - - const handleCreateDebate = (sessionId) => { - setCurrentSessionId(sessionId); - setCurrentView('debate'); - }; - - const handleViewSessions = () => setCurrentView('sessions'); - const handleViewSettings = () => setCurrentView('settings'); - - const handleBackToConfig = () => { - setCurrentView('configuration'); - setCurrentSessionId(null); - }; - - const handleLoadSession = (sessionId) => { - setCurrentSessionId(sessionId); - setCurrentView('debate'); - }; - - // --- Loading --- - if (appState === 'loading') { - return ( -
-
-

Dialectica - 多模型辩论框架

-

正在检查系统状态...

-
-
- ); - } - - // --- Setup wizard --- - if (appState === 'setup') { - return ( -
-
-

Dialectica - 多模型辩论框架

-

系统初始化配置

-
-
- -
-
-

Dialectica - 基于多模型的结构化辩论框架

-
-
- ); - } - - // --- Ready (dev, or prod guest/authenticated) --- - const isGuest = isProd && !auth?.isAuthenticated; - const username = auth?.user?.profile?.preferred_username; - - return ( -
-
-

Dialectica - 多模型辩论框架

-

让不同大语言模型就特定议题进行结构化辩论

- {isProd && ( -
- {auth?.isAuthenticated ? ( - <> - {username} - - - ) : ( - - )} -
- )} -
- -
- {currentView === 'configuration' && ( - - )} - - {currentView === 'debate' && ( - - )} - - {currentView === 'sessions' && ( - - )} - - {currentView === 'settings' && ( - - )} -
- -
-

Dialectica - 基于多模型的结构化辩论框架

-
-
- ); -}; - -export default App; diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..b1025a0 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,54 @@ +import { NavLink, Route, Routes } from 'react-router-dom'; +import { useAuth } from './auth'; +import { TopicListPage } from './pages/TopicList'; +import { TopicDetailPage } from './pages/TopicDetail'; +import { VerdictPage } from './pages/Verdict'; +import { AgentActivityPage } from './pages/AgentActivity'; +import { NotFoundPage } from './pages/NotFound'; +import './styles/app.css'; + +export function App() { + const { user, devBypass } = useAuth(); + return ( +
+
+
+ + dialectic + + +
+ {user ? ( + {user.label} + ) : ( + readonly + )} +
+
+ {devBypass && ( +
+ DEV BYPASS ACTIVE — auto-attaching x-dev-bypass on + every request; do not run this build in production +
+ )} +
+
+ + } /> + } /> + } /> + } /> + } /> + +
+
+ hangman lab · dialectic v2 +
+
+ ); +} diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 0000000..5d7ca5a --- /dev/null +++ b/src/api.ts @@ -0,0 +1,125 @@ +// Thin fetch wrapper for the Dialectic backend. +// +// Auth: this SPA targets human operators / observers. v1 auth model +// is dev-bypass only — `x-dev-bypass: ` is auto-attached when +// VITE_OIDC_DEV_BYPASS is set at build time. Real OIDC + Keycloak +// redirect ships as v2; dev-bypass covers the entire MVP scope. +// +// Backend base: configurable via VITE_DIALECTIC_API_BASE (default +// '/api', which works both behind the vite dev proxy and behind nginx +// in prod). +// +// Admin endpoints (under /api/admin/*) need a separate header +// `x-dialectic-admin-key`. The frontend reads it from +// localStorage['dialectic-admin-key'] (set via a one-liner in the +// AgentActivity page) so it never gets baked into the bundle. + +import type { + Argument, + AgentSummary, + Topic, + TopicDetail, + TopicStatus, + Verdict, + Visibility, +} from './types'; + +const API_BASE = import.meta.env.VITE_DIALECTIC_API_BASE ?? '/api'; +const DEV_BYPASS = import.meta.env.VITE_OIDC_DEV_BYPASS ?? ''; + +class HttpError extends Error { + constructor(public status: number, public body: string) { + super(`HTTP ${status}: ${body.slice(0, 200)}`); + } +} + +async function request( + method: string, + path: string, + opts: { body?: unknown; admin?: boolean; signal?: AbortSignal } = {}, +): Promise { + const headers: Record = {}; + if (DEV_BYPASS) headers['x-dev-bypass'] = DEV_BYPASS; + if (opts.admin) { + const adminKey = localStorage.getItem('dialectic-admin-key') ?? ''; + if (adminKey) headers['x-dialectic-admin-key'] = adminKey; + } + if (opts.body !== undefined) headers['content-type'] = 'application/json'; + + const res = await fetch(`${API_BASE}${path}`, { + method, + headers, + body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined, + signal: opts.signal, + }); + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new HttpError(res.status, text); + } + // Some endpoints (e.g. PATCH success) return empty body. + const text = await res.text(); + return (text ? JSON.parse(text) : null) as T; +} + +// -------------------------------------------------------------------- +// Topics + +export function listTopics(filter: { + status?: TopicStatus; + visibility?: Visibility; + limit?: number; + offset?: number; + signal?: AbortSignal; +}): Promise<{ topics: Topic[]; count: number }> { + const qs = new URLSearchParams(); + if (filter.status) qs.set('status', filter.status); + if (filter.visibility) qs.set('visibility', filter.visibility); + if (filter.limit) qs.set('limit', String(filter.limit)); + if (filter.offset) qs.set('offset', String(filter.offset)); + const suffix = qs.toString() ? `?${qs}` : ''; + return request('GET', `/topics${suffix}`, { signal: filter.signal }); +} + +export function getTopic( + id: string, + signal?: AbortSignal, +): Promise { + return request('GET', `/topics/${encodeURIComponent(id)}`, { signal }); +} + +export function listArguments( + topicId: string, + signal?: AbortSignal, +): Promise<{ arguments: Argument[]; count: number }> { + return request('GET', `/topics/${encodeURIComponent(topicId)}/arguments`, { + signal, + }); +} + +export function getVerdict( + topicId: string, + signal?: AbortSignal, +): Promise { + // 404 is "no verdict yet" — surface as null rather than throw. + return request('GET', `/topics/${encodeURIComponent(topicId)}/verdict`, { + signal, + }).catch((e) => { + if (e instanceof HttpError && e.status === 404) return null; + throw e; + }); +} + +// -------------------------------------------------------------------- +// Admin + +export function getAgentSummary( + agentId: string, + signal?: AbortSignal, +): Promise { + return request('GET', `/admin/agents/${encodeURIComponent(agentId)}`, { + admin: true, + signal, + }); +} + +export { HttpError }; diff --git a/src/auth.tsx b/src/auth.tsx new file mode 100644 index 0000000..bc95c8d --- /dev/null +++ b/src/auth.tsx @@ -0,0 +1,33 @@ +// AuthProvider — minimal v1: dev-bypass only, no OIDC redirect yet. +// +// Real OIDC + Keycloak ships when the backend's OIDC middleware is +// fully wired (Phase 4 in DIALECTIC-V2-DESIGN.md). Until then this +// just surfaces whether dev-bypass is on so the UI can show a banner +// ("dev mode — running as operator") and route admin-gated pages. + +import { createContext, useContext, type ReactNode } from 'react'; + +interface AuthCtx { + // Whether dev-bypass token is configured at build time. + devBypass: boolean; + // Display label for the "current user" — in dev-bypass mode this is + // the literal env value (a placeholder until real OIDC). + user: { id: string; label: string } | null; +} + +const Ctx = createContext({ + devBypass: false, + user: null, +}); + +export function AuthProvider({ children }: { children: ReactNode }) { + const devBypass = Boolean(import.meta.env.VITE_OIDC_DEV_BYPASS); + const user = devBypass + ? { id: 'dev-operator', label: 'dev-operator (bypass)' } + : null; + return {children}; +} + +export function useAuth(): AuthCtx { + return useContext(Ctx); +} diff --git a/src/components/AuthProvider.js b/src/components/AuthProvider.js deleted file mode 100644 index eef9212..0000000 --- a/src/components/AuthProvider.js +++ /dev/null @@ -1,156 +0,0 @@ -import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; -import { UserManager, WebStorageStateStore } from 'oidc-client-ts'; - -const AuthContext = createContext(null); - -export const useAuth = () => useContext(AuthContext); - -// Module-level token getter for use outside React (e.g. apiFetch) -let _getTokenFn = () => null; -export function getAccessTokenGlobal() { - return _getTokenFn(); -} - -/** - * Create a UserManager with consistent storage settings. - * All components must use this to avoid storage mismatches. - */ -function createUserManager(oidcConfig) { - return new UserManager({ - ...oidcConfig, - userStore: new WebStorageStateStore({ store: window.localStorage }), - }); -} - -/** - * OIDC authentication provider. - * Wraps children with auth context when ENV_MODE=prod. - */ -const AuthProvider = ({ oidcConfig, children }) => { - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); - const [manager, setManager] = useState(null); - - useEffect(() => { - if (!oidcConfig || !oidcConfig.authority) { - setLoading(false); - return; - } - - const mgr = createUserManager(oidcConfig); - setManager(mgr); - - // Try to load existing session - mgr.getUser().then((u) => { - if (u && !u.expired) { - setUser(u); - } - setLoading(false); - }); - - mgr.events.addUserLoaded((u) => setUser(u)); - mgr.events.addUserUnloaded(() => setUser(null)); - mgr.events.addSilentRenewError(() => setUser(null)); - - return () => { - mgr.events.removeUserLoaded(() => {}); - mgr.events.removeUserUnloaded(() => {}); - }; - }, [oidcConfig]); - - // Keep global token getter in sync with current user - useEffect(() => { - _getTokenFn = () => user?.access_token || null; - return () => { _getTokenFn = () => null; }; - }, [user]); - - const login = useCallback(() => { - if (manager) manager.signinRedirect(); - }, [manager]); - - const logout = useCallback(() => { - if (manager) manager.signoutRedirect(); - }, [manager]); - - const getAccessToken = useCallback(() => { - return user?.access_token || null; - }, [user]); - - const value = { - user, - loading, - isAuthenticated: !!user && !user.expired, - login, - logout, - getAccessToken, - }; - - return {children}; -}; - -/** - * Handle OIDC redirect callback. - * Mount this component at /callback route. - */ -export const OidcCallback = ({ oidcConfig }) => { - const [error, setError] = useState(null); - - useEffect(() => { - if (!oidcConfig) return; - const mgr = createUserManager(oidcConfig); - mgr.signinRedirectCallback() - .then(() => { - window.location.href = '/'; - }) - .catch((err) => { - console.error('OIDC callback error:', err); - setError(err.message || String(err)); - }); - }, [oidcConfig]); - - if (error) { - return ( -
-

登录失败

-

{error}

- 返回首页 -
- ); - } - - return
登录中...
; -}; - -/** - * Handle OIDC popup callback. - * Mount this component at /popup_callback route. - */ -export const PopupCallback = ({ oidcConfig }) => { - useEffect(() => { - if (!oidcConfig) return; - const mgr = createUserManager(oidcConfig); - mgr.signinPopupCallback().catch((err) => { - console.error('OIDC popup callback error:', err); - }); - }, [oidcConfig]); - - return
登录中...
; -}; - -/** - * Handle OIDC silent renew callback. - * Mount this component at /silent_callback route. - */ -export const SilentCallback = ({ oidcConfig }) => { - useEffect(() => { - if (!oidcConfig) return; - const mgr = createUserManager(oidcConfig); - mgr.signinSilentCallback().catch((err) => { - console.error('OIDC silent callback error:', err); - }); - }, [oidcConfig]); - - return null; -}; - -export default AuthProvider; diff --git a/src/components/DebateConfiguration.js b/src/components/DebateConfiguration.js deleted file mode 100644 index d26056d..0000000 --- a/src/components/DebateConfiguration.js +++ /dev/null @@ -1,497 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import './../styles/App.css'; -import {getBackendHost} from "../utils/api"; - -const DebateConfiguration = ({ onCreateDebate, onViewSessions, onViewSettings, isGuest }) => { - const [formData, setFormData] = useState({ - topic: '', - proProvider: 'openai', - proModel: 'gpt-4', - conProvider: 'claude', - conModel: 'claude-3-opus', - maxRounds: 5, - maxTokens: 500, - webSearchEnabled: false, - webSearchMode: 'auto' - }); - - const [availableModels, setAvailableModels] = useState({ - openai: [], - claude: [], - qwen: [], - deepseek: [] - }); - - const [availableProviders, setAvailableProviders] = useState([ - { provider: 'openai', display_name: 'OpenAI' }, - { provider: 'claude', display_name: 'Claude' }, - { provider: 'qwen', display_name: 'Qwen' }, - { provider: 'deepseek', display_name: 'DeepSeek' } - ]); - - const [isLoading, setIsLoading] = useState(false); - const [modelsLoading, setModelsLoading] = useState({}); - - useEffect(() => { - // Load available providers when component mounts - loadAvailableProviders(); - }, []); - - const loadAvailableProviders = async () => { - try { - // Get all API keys from backend to determine which providers are available - const providers = ['openai', 'claude', 'qwen', 'deepseek']; - const available = []; - - for (const provider of providers) { - try { - const response = await fetch(`http://localhost:8000/api-keys/${provider}`); - if (response.ok) { - const data = await response.json(); - if (data.api_key) { - // For Qwen and DeepSeek, we can check if the key is valid - if (provider === 'qwen' || provider === 'deepseek') { - // For now, just check if key exists - we'll implement validation later if needed - available.push({ - provider: provider, - display_name: provider.charAt(0).toUpperCase() + provider.slice(1) - }); - } else { - // For OpenAI and Claude, we're skipping validation as requested - available.push({ - provider: provider, - display_name: provider.charAt(0).toUpperCase() + provider.slice(1) - }); - } - } - } - } catch (error) { - // If there's an error getting the API key, skip this provider - console.error(`Error getting API key for ${provider}:`, error); - } - } - - if (available.length > 0) { - setAvailableProviders(available); - - // Update form data to use valid providers - if (!available.some(p => p.provider === formData.proProvider)) { - setFormData(prev => ({ - ...prev, - proProvider: available[0]?.provider || prev.proProvider - })); - } - - if (!available.some(p => p.provider === formData.conProvider)) { - setFormData(prev => ({ - ...prev, - conProvider: available[0]?.provider || prev.conProvider - })); - } - } else { - // If no providers have API keys, show all providers but disable functionality - setAvailableProviders([ - { provider: 'openai', display_name: 'OpenAI' }, - { provider: 'claude', display_name: 'Claude' }, - { provider: 'qwen', display_name: 'Qwen' }, - { provider: 'deepseek', display_name: 'DeepSeek' } - ]); - } - } catch (error) { - console.error('Error loading available providers:', error); - } - }; - - useEffect(() => { - // Update model options when provider changes - if (formData.proProvider) { - loadModelsForProvider(formData.proProvider); - } - if (formData.conProvider) { - loadModelsForProvider(formData.conProvider); - } - }, [formData.proProvider, formData.conProvider]); - - const loadModelsForProvider = async (provider) => { - setModelsLoading(prev => ({ ...prev, [provider]: true })); - try { - // Special handling for Qwen - fetch directly from Qwen API - if (provider === 'qwen') { - try { - // Call backend to get Qwen models (backend will fetch from Qwen API) - const qwenResponse = await fetch(`${getBackendHost()}/models/${provider}`); - if (qwenResponse.ok) { - const qwenData = await qwenResponse.json(); - console.log('Backend Qwen models response:', qwenData); // Debug log - - const models = qwenData.models || []; - console.log('Fetched Qwen models:', models); // Debug log - - setAvailableModels(prev => ({ - ...prev, - [provider]: models - })); - - // Update selected model if current selection is not in the new list - if (provider === formData.proProvider && models.length > 0) { - const currentModelExists = models.some(model => model.model_identifier === formData.proModel); - if (!currentModelExists) { - setFormData(prev => ({ - ...prev, - proModel: models[0].model_identifier - })); - } - } - - if (provider === formData.conProvider && models.length > 0) { - const currentModelExists = models.some(model => model.model_identifier === formData.conModel); - if (!currentModelExists) { - setFormData(prev => ({ - ...prev, - conModel: models[0].model_identifier - })); - } - } - } else { - console.error('Error fetching models from backend for Qwen:', qwenResponse.status, await qwenResponse.text()); - // Use fallback models if API call fails - const fallbackModels = [ - { model_identifier: 'qwen3-max', display_name: 'Qwen3 Max' }, - { model_identifier: 'qwen3-plus', display_name: 'Qwen3 Plus' }, - { model_identifier: 'qwen3-flash', display_name: 'Qwen3 Flash' }, - { model_identifier: 'qwen-max', display_name: 'Qwen Max' }, - { model_identifier: 'qwen-plus', display_name: 'Qwen Plus' }, - { model_identifier: 'qwen-turbo', display_name: 'Qwen Turbo' } - ]; - - setAvailableModels(prev => ({ - ...prev, - [provider]: fallbackModels - })); - - if (provider === formData.proProvider) { - setFormData(prev => ({ - ...prev, - proModel: fallbackModels[0].model_identifier - })); - } - - if (provider === formData.conProvider) { - setFormData(prev => ({ - ...prev, - conModel: fallbackModels[0].model_identifier - })); - } - } - } catch (error) { - console.error('Network error when fetching Qwen models from backend:', error); - // Use fallback models if network request fails - const fallbackModels = [ - { model_identifier: 'qwen-max', display_name: 'Qwen Max' }, - { model_identifier: 'qwen-plus', display_name: 'Qwen Plus' }, - { model_identifier: 'qwen-turbo', display_name: 'Qwen Turbo' } - ]; - - setAvailableModels(prev => ({ - ...prev, - [provider]: fallbackModels - })); - - if (provider === formData.proProvider) { - setFormData(prev => ({ - ...prev, - proModel: fallbackModels[0].model_identifier - })); - } - - if (provider === formData.conProvider) { - setFormData(prev => ({ - ...prev, - conModel: fallbackModels[0].model_identifier - })); - } - } - } else { - // For other providers, use the backend API - const response = await fetch(`http://localhost:8000/models/${provider}`); - if (response.ok) { - const data = await response.json(); - setAvailableModels(prev => ({ - ...prev, - [provider]: data.models || [] - })); - - // Update selected model if current selection is not in the new list - if (provider === formData.proProvider && data.models && data.models.length > 0) { - const currentModelExists = data.models.some(model => model.model_identifier === formData.proModel); - if (!currentModelExists) { - setFormData(prev => ({ - ...prev, - proModel: data.models[0].model_identifier - })); - } - } - - if (provider === formData.conProvider && data.models && data.models.length > 0) { - const currentModelExists = data.models.some(model => model.model_identifier === formData.conModel); - if (!currentModelExists) { - setFormData(prev => ({ - ...prev, - conModel: data.models[0].model_identifier - })); - } - } - } - } - } catch (error) { - console.error(`Error loading models for ${provider}:`, error); - } finally { - setModelsLoading(prev => ({ ...prev, [provider]: false })); - } - }; - - const handleChange = (e) => { - const { name, value, type, checked } = e.target; - setFormData(prev => ({ - ...prev, - [name]: type === 'checkbox' ? checked : value - })); - }; - - - const handleSubmit = async (e) => { - e.preventDefault(); - setIsLoading(true); - - // Validate inputs - if (!formData.topic.trim()) { - alert('请输入辩论主题'); - setIsLoading(false); - return; - } - - // Create debate request object - const debateRequest = { - topic: formData.topic, - participants: [ - { - model_identifier: formData.proModel, - provider: formData.proProvider, - stance: "pro" - }, - { - model_identifier: formData.conModel, - provider: formData.conProvider, - stance: "con" - } - ], - constraints: { - max_rounds: parseInt(formData.maxRounds), - max_tokens_per_turn: parseInt(formData.maxTokens), - web_search_enabled: formData.webSearchEnabled, - web_search_mode: formData.webSearchMode - } - }; - - try { - const response = await fetch('http://localhost:8000/debate/create', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(debateRequest) - }); - - if (!response.ok) { - throw new Error(`创建辩论失败: ${response.statusText}`); - } - - const result = await response.json(); - alert(`辩论创建成功!会话ID: ${result.session_id}`); - - // Pass the session ID to the parent component - onCreateDebate(result.session_id); - } catch (error) { - console.error('Error creating debate:', error); - alert(`创建辩论失败: ${error.message}`); - } finally { - setIsLoading(false); - } - }; - - return ( -
-

辩论配置

-
-
- - -
- -
- -
-
- - - {modelsLoading[formData.proProvider] ? ( -
加载模型中...
- ) : ( - - )} - - 正方 -
- -
- - - {modelsLoading[formData.conProvider] ? ( -
加载模型中...
- ) : ( - - )} - - 反方 -
-
-
- -
- - -
- -
- - -
- -
- - {formData.webSearchEnabled && ( -
- - -
- )} -
- - - {isGuest && ( -

- 请先登录后再创建辩论 -

- )} -
- -
- - {onViewSettings && ( - - )} -
-
- ); -}; - -export default DebateConfiguration; \ No newline at end of file diff --git a/src/components/DebateDisplay.js b/src/components/DebateDisplay.js deleted file mode 100644 index 54e7924..0000000 --- a/src/components/DebateDisplay.js +++ /dev/null @@ -1,317 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import './../styles/App.css'; -import {getBackendHost} from "../utils/api"; - -const DebateDisplay = ({ sessionId, onBackToConfig, isGuest }) => { - const [debateRounds, setDebateRounds] = useState([]); - const [summary, setSummary] = useState(''); - const [isStreaming, setIsStreaming] = useState(false); - const [eventSource, setEventSource] = useState(null); - const debateStreamRef = useRef(null); - const debateCompletedRef = useRef(false); - const [evidenceLibrary, setEvidenceLibrary] = useState([]); - const [evidenceLibraryExpanded, setEvidenceLibraryExpanded] = useState(false); - - useEffect(() => { - if (sessionId && !isStreaming) { - startDebateStreaming(sessionId); - } - - // Cleanup on unmount - return () => { - if (eventSource) { - eventSource.close(); - } - }; - }, [sessionId]); - - useEffect(() => { - // Scroll to bottom when new content is added - if (debateStreamRef.current) { - debateStreamRef.current.scrollTop = debateStreamRef.current.scrollHeight; - } - }, [debateRounds]); - - const startDebateStreaming = (sessionId) => { - setIsStreaming(true); - debateCompletedRef.current = false; - - // Kick off the debate on the backend (fire and forget — runs in background) - fetch(`http://localhost:8000/debate/${sessionId}/start`, { method: 'POST' }) - .then(res => res.json()) - .then(result => console.log('Debate completed:', result)) - .catch(err => console.error('Debate start error:', err)); - - // Connect to SSE endpoint for real-time updates - const es = new EventSource(`${getBackendHost()}/debate/${sessionId}/stream`); - setEventSource(es); - - es.addEventListener('update', function(event) { - const data = JSON.parse(event.data); - handleUpdate(data); - }); - - es.addEventListener('complete', function(event) { - const data = JSON.parse(event.data); - handleComplete(data); - }); - - es.addEventListener('error', function(event) { - // Check if event.data exists before parsing - if (event.data) { - try { - const data = JSON.parse(event.data); - handleError(data.error); - } catch (e) { - console.error('Failed to parse error event data:', e); - handleError('Unknown error occurred'); - } - } - }); - - es.onerror = function(err) { - // Server closes the connection after "complete" event — not a real error - if (debateCompletedRef.current) { - es.close(); - return; - } - console.error('SSE connection error:', err); - es.close(); - setIsStreaming(false); - setEventSource(null); - }; - }; - - const handleUpdate = (data) => { - if (data.rounds) { - setDebateRounds([...data.rounds]); - } - if (data.evidence_library) { - setEvidenceLibrary([...data.evidence_library]); - } - }; - - const handleComplete = (data) => { - debateCompletedRef.current = true; - if (eventSource) { - eventSource.close(); - setEventSource(null); - } - setIsStreaming(false); - - // Show summary if available - if (data.summary) { - setSummary(data.summary); - } - if (data.evidence_library) { - setEvidenceLibrary([...data.evidence_library]); - } - - alert('辩论已完成!'); - }; - - const handleError = (errorMsg) => { - if (eventSource) { - eventSource.close(); - setEventSource(null); - } - setIsStreaming(false); - - alert(`错误: ${errorMsg}`); - }; - - const handleStopDebate = async () => { - if (!sessionId) { - alert('没有活动的辩论会话'); - return; - } - - if (!window.confirm('确定要停止当前辩论吗?')) { - return; - } - - try { - const response = await fetch(`http://localhost:8000/debate/${sessionId}`, { - method: 'DELETE' - }); - - if (!response.ok) { - throw new Error(`停止辩论失败: ${response.statusText}`); - } - - const result = await response.json(); - alert(`辩论已停止: ${result.status}`); - - // Close SSE connection if active - if (eventSource) { - eventSource.close(); - setEventSource(null); - } - setIsStreaming(false); - - } catch (error) { - console.error('Error stopping debate:', error); - alert(`停止辩论失败: ${error.message}`); - } - }; - - const formatSummary = (summaryText) => { - // Simple formatting - split by newlines and paragraphs - return summaryText.split('\n').map((paragraph, index) => ( -

{paragraph}

- )); - }; - - const [expandedEvidence, setExpandedEvidence] = useState({}); - - const toggleEvidence = (roundKey) => { - setExpandedEvidence(prev => ({ - ...prev, - [roundKey]: !prev[roundKey] - })); - }; - - const renderSearchEvidence = (evidence, roundKey) => { - if (!evidence || !evidence.results || evidence.results.length === 0) return null; - const isExpanded = expandedEvidence[roundKey]; - - return ( -
-
toggleEvidence(roundKey)}> - {isExpanded ? '▼' : '▶'} - 网络搜索参考 - {evidence.results.length} 条结果 -
- {isExpanded && ( -
-
搜索词: "{evidence.query}"
- {evidence.results.map((result, i) => ( -
- - {result.title} - -

{result.snippet}

-
- ))} -
- )} -
- ); - }; - - const renderEvidenceLibrary = () => { - if (!evidenceLibrary || evidenceLibrary.length === 0) return null; - - return ( -
-
setEvidenceLibraryExpanded(!evidenceLibraryExpanded)} - > - {evidenceLibraryExpanded ? '▼' : '▶'} - 证据库 - {evidenceLibrary.length} 条来源 -
- {evidenceLibraryExpanded && ( -
- {evidenceLibrary.map((entry, i) => ( -
-
- - {entry.title} - - {entry.score != null && ( - 相关度: {(entry.score * 100).toFixed(0)}% - )} -
-

{entry.snippet}

-
- {entry.references.map((ref, j) => ( - - 第{ref.round_number}轮 · {ref.stance === 'pro' ? '正方' : '反方'} - - ))} -
-
- ))} -
- )} -
- ); - }; - - const addRoundToDisplay = (round, index) => { - const stanceClass = round.stance === 'pro' ? 'pro-speaker' : 'con-speaker'; - const stanceText = round.stance === 'pro' ? '正方' : '反方'; - const stanceLabelClass = round.stance === 'pro' ? 'pro-stance' : 'con-stance'; - const roundKey = `${index}-${round.round_number}`; - - return ( -
-
-
{round.speaker} ({round.round_number}轮)
-
{stanceText}
-
-
{round.content}
- {renderSearchEvidence(round.search_evidence, roundKey)} -
- ); - }; - - return ( -
-

辩论进行中

-
- {!isGuest && ( - <> - - - - )} - -
- -
-
- {debateRounds.length > 0 ? ( - debateRounds.map((round, index) => addRoundToDisplay(round, index)) - ) : ( -

辩论尚未开始,点击"开始辩论"按钮启动辩论...

- )} -
- - {renderEvidenceLibrary()} - - {summary && ( -
-

辩论总结

-
- {formatSummary(summary)} -
-
- )} -
-
- ); -}; - -export default DebateDisplay; \ No newline at end of file diff --git a/src/components/SessionsList.js b/src/components/SessionsList.js deleted file mode 100644 index 011365f..0000000 --- a/src/components/SessionsList.js +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import './../styles/App.css'; -import {getBackendHost} from "../utils/api"; - -const SessionsList = ({ onLoadSession, onBackToConfig }) => { - const [sessions, setSessions] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - fetchSessions(); - }, []); - - const fetchSessions = async () => { - try { - setLoading(true); - const response = await fetch(`${getBackendHost()}/sessions`); - - if (!response.ok) { - throw new Error(`获取会话列表失败: ${response.statusText}`); - } - - const result = await response.json(); - setSessions(result.sessions || []); - } catch (err) { - setError(err.message); - console.error('Error fetching sessions:', err); - } finally { - setLoading(false); - } - }; - - const handleLoadSession = (sessionId) => { - onLoadSession(sessionId); - }; - - const formatDate = (dateString) => { - return new Date(dateString).toLocaleString(); - }; - - if (loading) { - return ( -
-

辩论会话列表

-

加载中...

-
- ); - } - - if (error) { - return ( -
-

辩论会话列表

-

错误: {error}

- -
- ); - } - - return ( -
-

辩论会话列表

-
- {sessions.length === 0 ? ( -

暂无辩论会话记录

- ) : ( - sessions.map(session => ( -
-
{session.topic}
-
- ID: {session.session_id.substring(0, 8)}... - 状态: {session.status} - 时间: {formatDate(session.created_at)} -
-
- -
-
- )) - )} -
-
- -
-
- ); -}; - -export default SessionsList; \ No newline at end of file diff --git a/src/components/Settings.js b/src/components/Settings.js deleted file mode 100644 index e990e1c..0000000 --- a/src/components/Settings.js +++ /dev/null @@ -1,352 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { getBackendHost } from '../utils/api'; -import '../styles/App.css'; - -const TABS = [ - { id: 'database', label: '数据库' }, - { id: 'keycloak', label: 'Keycloak' }, - { id: 'tls', label: '证书' }, - { id: 'apikeys', label: 'API Keys' }, -]; - -const Settings = ({ onBackToConfig }) => { - const [activeTab, setActiveTab] = useState('database'); - const backend = getBackendHost(); - - // ---- system config (db / kc / tls) ---- - const [dbConfig, setDbConfig] = useState({ host: '', port: 3306, user: '', password: '', database: '' }); - const [kcConfig, setKcConfig] = useState({ host: '', realm: '', client_id: '' }); - const [tlsConfig, setTlsConfig] = useState({ cert_path: '', key_path: '', force_https: false }); - const [configSaving, setConfigSaving] = useState(false); - const [dbTestResult, setDbTestResult] = useState(null); - const [dbTesting, setDbTesting] = useState(false); - const [kcTestResult, setKcTestResult] = useState(null); - const [kcTesting, setKcTesting] = useState(false); - - // ---- API keys (existing logic) ---- - const [apiKeys, setApiKeys] = useState({ openai: '', claude: '', qwen: '', deepseek: '', tavily: '' }); - const [loading, setLoading] = useState({}); - const [saved, setSaved] = useState({}); - const [validationStatus, setValidationStatus] = useState({}); - - // ---- Load config on mount ---- - useEffect(() => { - fetchConfig(); - fetchApiKeys(); - }, []); - - const fetchConfig = async () => { - try { - const resp = await fetch(`${backend}/api/setup/config`); - if (resp.ok) { - const data = await resp.json(); - if (data.database) setDbConfig(prev => ({ ...prev, ...data.database })); - if (data.keycloak) setKcConfig(prev => ({ ...prev, ...data.keycloak })); - if (data.tls) setTlsConfig(prev => ({ ...prev, ...data.tls })); - } - } catch (err) { - console.error('Error fetching config:', err); - } - }; - - const saveConfig = async () => { - setConfigSaving(true); - try { - const payload = { - database: dbConfig, - keycloak: kcConfig.host ? kcConfig : null, - tls: tlsConfig.cert_path ? tlsConfig : null, - }; - const resp = await fetch(`${backend}/api/setup/config`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }); - if (resp.ok) { - alert('配置已保存'); - } else { - const err = await resp.json(); - alert(`保存失败: ${err.detail || resp.statusText}`); - } - } catch (err) { - alert(`保存失败: ${err.message}`); - } finally { - setConfigSaving(false); - } - }; - - const testDb = async () => { - setDbTesting(true); - setDbTestResult(null); - try { - const resp = await fetch(`${backend}/api/setup/test-db`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(dbConfig), - }); - setDbTestResult(await resp.json()); - } catch (err) { - setDbTestResult({ success: false, message: err.message }); - } finally { - setDbTesting(false); - } - }; - - const testKc = async () => { - setKcTesting(true); - setKcTestResult(null); - try { - const resp = await fetch(`${backend}/api/setup/test-keycloak`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(kcConfig), - }); - setKcTestResult(await resp.json()); - } catch (err) { - setKcTestResult({ success: false, message: err.message }); - } finally { - setKcTesting(false); - } - }; - - // ---- API Key handlers (preserved from original) ---- - const fetchApiKeys = async () => { - const providers = ['openai', 'claude', 'qwen', 'deepseek', 'tavily']; - for (const provider of providers) { - try { - const response = await fetch(`${backend}/api-keys/${provider}`); - if (response.ok) { - const data = await response.json(); - setApiKeys(prev => ({ ...prev, [provider]: data.api_key || '' })); - } - } catch (error) { - console.error(`Error fetching ${provider} API key:`, error); - } - } - }; - - const handleApiKeyChange = (provider, value) => { - setApiKeys(prev => ({ ...prev, [provider]: value })); - }; - - const saveApiKey = async (provider) => { - setLoading(prev => ({ ...prev, [provider]: true })); - setSaved(prev => ({ ...prev, [provider]: false })); - - if (!apiKeys[provider]) { - setValidationStatus(prev => ({ ...prev, [provider]: { isValid: false, message: 'API key 不能为空' } })); - setLoading(prev => ({ ...prev, [provider]: false })); - return; - } - - // Validate - try { - const formData = new FormData(); - formData.append('api_key', apiKeys[provider]); - const vResp = await fetch(`${backend}/validate-api-key/${provider}`, { method: 'POST', body: formData }); - if (vResp.ok) { - const vResult = await vResp.json(); - if (!vResult.valid) { - setValidationStatus(prev => ({ ...prev, [provider]: { isValid: false, message: vResult.message } })); - setLoading(prev => ({ ...prev, [provider]: false })); - return; - } - } - } catch (err) { - setValidationStatus(prev => ({ ...prev, [provider]: { isValid: false, message: err.message } })); - setLoading(prev => ({ ...prev, [provider]: false })); - return; - } - - // Save - try { - const response = await fetch(`${backend}/api-keys/${provider}`, { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: `api_key=${encodeURIComponent(apiKeys[provider])}`, - }); - if (response.ok) { - setSaved(prev => ({ ...prev, [provider]: true })); - setValidationStatus(prev => ({ ...prev, [provider]: { isValid: true, message: 'Valid' } })); - setTimeout(() => setSaved(prev => ({ ...prev, [provider]: false })), 2000); - } else { - const errorText = await response.text(); - setValidationStatus(prev => ({ ...prev, [provider]: { isValid: false, message: errorText } })); - } - } catch (error) { - setValidationStatus(prev => ({ ...prev, [provider]: { isValid: false, message: error.message } })); - } finally { - setLoading(prev => ({ ...prev, [provider]: false })); - } - }; - - const handleSaveAllApiKeys = async () => { - for (const provider of Object.keys(apiKeys)) { - await saveApiKey(provider); - } - }; - - // ---- Tab content renderers ---- - const renderDatabaseTab = () => ( -
-
- - setDbConfig(prev => ({ ...prev, host: e.target.value }))} /> -
-
- - setDbConfig(prev => ({ ...prev, port: parseInt(e.target.value) || 0 }))} /> -
-
- - setDbConfig(prev => ({ ...prev, user: e.target.value }))} /> -
-
- - setDbConfig(prev => ({ ...prev, password: e.target.value || '********' }))} - /> -
-
- - setDbConfig(prev => ({ ...prev, database: e.target.value }))} /> -
-
- - {dbTestResult && ( - - {dbTestResult.success ? '\u2713 ' : '\u2717 '}{dbTestResult.message} - - )} -
-
- ); - - const renderKeycloakTab = () => ( -
-
- - setKcConfig(prev => ({ ...prev, host: e.target.value }))} placeholder="https://login.example.com" /> -
-
- - setKcConfig(prev => ({ ...prev, realm: e.target.value }))} /> -
-
- - setKcConfig(prev => ({ ...prev, client_id: e.target.value }))} /> -
-
- - {kcTestResult && ( - - {kcTestResult.success ? '\u2713 ' : '\u2717 '}{kcTestResult.message} - - )} -
-
- ); - - const renderTlsTab = () => ( -
-
- - setTlsConfig(prev => ({ ...prev, cert_path: e.target.value }))} placeholder="/etc/ssl/certs/cert.pem" /> -
-
- - setTlsConfig(prev => ({ ...prev, key_path: e.target.value }))} placeholder="/etc/ssl/private/key.pem" /> -
-
- -
-
- ); - - const renderApiKeysTab = () => ( -
-
- {Object.entries(apiKeys).map(([provider, value]) => ( -
- -
- handleApiKeyChange(provider, e.target.value)} - placeholder={`Enter your ${provider} API key`} - /> - - {saved[provider] && \u2713 已保存} - {validationStatus[provider] && ( - - {validationStatus[provider].isValid ? '\u2713 有效' : `\u2717 无效: ${validationStatus[provider].message}`} - - )} -
-
- ))} -
-
- -
-
- ); - - return ( -
-

系统设置

- - {/* Tab bar */} -
- {TABS.map((tab) => ( - - ))} -
- - {/* Tab content */} - {activeTab === 'database' && renderDatabaseTab()} - {activeTab === 'keycloak' && renderKeycloakTab()} - {activeTab === 'tls' && renderTlsTab()} - {activeTab === 'apikeys' && renderApiKeysTab()} - - {/* Save / Back (for non-apikey tabs) */} - {activeTab !== 'apikeys' && ( -
- -
- )} - -
- -
-
- ); -}; - -export default Settings; diff --git a/src/components/SetupWizard.js b/src/components/SetupWizard.js deleted file mode 100644 index dc8011a..0000000 --- a/src/components/SetupWizard.js +++ /dev/null @@ -1,287 +0,0 @@ -import React, { useState } from 'react'; -import { getBackendHost } from '../utils/api'; -import '../styles/App.css'; - -const SetupWizard = ({ onSetupComplete }) => { - const [step, setStep] = useState(1); - const totalSteps = 3; - - // --- Step 1: Database --- - const [dbConfig, setDbConfig] = useState({ - host: 'db', - port: 3306, - user: 'dialectica', - password: '', - database: 'dialectica', - }); - const [dbTestResult, setDbTestResult] = useState(null); - const [dbTesting, setDbTesting] = useState(false); - - // --- Step 2: Keycloak --- - const [kcConfig, setKcConfig] = useState({ - host: '', - realm: '', - client_id: '', - }); - const [kcTestResult, setKcTestResult] = useState(null); - const [kcTesting, setKcTesting] = useState(false); - - // --- Step 3: TLS --- - const [tlsConfig, setTlsConfig] = useState({ - cert_path: '', - key_path: '', - force_https: false, - }); - - const [initializing, setInitializing] = useState(false); - - const backend = getBackendHost(); - - // ---- handlers ---- - const handleDbChange = (e) => { - const { name, value } = e.target; - setDbConfig((prev) => ({ - ...prev, - [name]: name === 'port' ? parseInt(value) || 0 : value, - })); - }; - - const handleKcChange = (e) => { - const { name, value } = e.target; - setKcConfig((prev) => ({ ...prev, [name]: value })); - }; - - const handleTlsChange = (e) => { - const { name, value, type, checked } = e.target; - setTlsConfig((prev) => ({ - ...prev, - [name]: type === 'checkbox' ? checked : value, - })); - }; - - // ---- test actions ---- - const testDb = async () => { - setDbTesting(true); - setDbTestResult(null); - try { - const resp = await fetch(`${backend}/api/setup/test-db`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(dbConfig), - }); - const data = await resp.json(); - setDbTestResult(data); - } catch (err) { - setDbTestResult({ success: false, message: err.message }); - } finally { - setDbTesting(false); - } - }; - - const testKc = async () => { - setKcTesting(true); - setKcTestResult(null); - try { - const resp = await fetch(`${backend}/api/setup/test-keycloak`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(kcConfig), - }); - const data = await resp.json(); - setKcTestResult(data); - } catch (err) { - setKcTestResult({ success: false, message: err.message }); - } finally { - setKcTesting(false); - } - }; - - // ---- save & initialise ---- - const handleFinish = async () => { - setInitializing(true); - try { - // Save config - const payload = { - database: dbConfig, - keycloak: kcConfig.host ? kcConfig : null, - tls: tlsConfig.cert_path ? tlsConfig : null, - }; - const saveResp = await fetch(`${backend}/api/setup/config`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }); - if (!saveResp.ok) { - const err = await saveResp.json(); - alert(`保存配置失败: ${err.detail || saveResp.statusText}`); - setInitializing(false); - return; - } - - // Initialize - const initResp = await fetch(`${backend}/api/setup/initialize`, { - method: 'POST', - }); - if (!initResp.ok) { - const err = await initResp.json(); - alert(`初始化失败: ${err.detail || initResp.statusText}`); - setInitializing(false); - return; - } - - onSetupComplete(); - } catch (err) { - alert(`初始化失败: ${err.message}`); - } finally { - setInitializing(false); - } - }; - - // ---- render helpers ---- - const renderStepIndicator = () => ( -
- {['数据库', 'Keycloak', 'TLS 证书'].map((label, i) => ( -
- {i + 1 < step ? '\u2713' : i + 1} - {label} -
- ))} -
- ); - - const renderDbStep = () => ( -
-

Step 1: 数据库配置

-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
- - {dbTestResult && ( - - {dbTestResult.success ? '\u2713 ' : '\u2717 '}{dbTestResult.message} - - )} -
-
- ); - - const renderKcStep = () => ( -
-

Step 2: Keycloak 配置

-

dev 模式下可跳过此步,直接点击"下一步"。

-
- - -
-
- - -
-
- - -
- -
- - {kcTestResult && ( - - {kcTestResult.success ? '\u2713 ' : '\u2717 '}{kcTestResult.message} - - )} -
-
- ); - - const renderTlsStep = () => ( -
-

Step 3: TLS 证书(可选)

-

如不需要 HTTPS,可留空直接完成初始化。

-
- - -
-
- - -
-
- -
-
- ); - - return ( -
-

系统初始化

-

- 首次部署,请完成以下配置以启动系统。 -

- - {renderStepIndicator()} - - {step === 1 && renderDbStep()} - {step === 2 && renderKcStep()} - {step === 3 && renderTlsStep()} - -
- {step > 1 && ( - - )} - {step < totalSteps && ( - - )} - {step === totalSteps && ( - - )} -
-
- ); -}; - -export default SetupWizard; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index bb5095e..0000000 --- a/src/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { BrowserRouter } from 'react-router-dom'; -import App from './App'; - -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render( - - - - - -); \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..5f98833 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { App } from './App'; +import { AuthProvider } from './auth'; +import './styles/tokens.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + , +); diff --git a/src/pages/AgentActivity.tsx b/src/pages/AgentActivity.tsx new file mode 100644 index 0000000..18dcf91 --- /dev/null +++ b/src/pages/AgentActivity.tsx @@ -0,0 +1,132 @@ +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { getAgentSummary, HttpError } from '../api'; +import type { AgentSummary } from '../types'; +import { Link } from 'react-router-dom'; +import { fmtRelative, fmtTime } from '../util'; + +export function AgentActivityPage() { + const { id } = useParams<{ id: string }>(); + const [data, setData] = useState(null); + const [err, setErr] = useState(null); + const [needsKey, setNeedsKey] = useState(false); + const [pendingKey, setPendingKey] = useState(''); + + useEffect(() => { + if (!id) return; + const ac = new AbortController(); + getAgentSummary(id, ac.signal) + .then((d) => { + setData(d); + setErr(null); + setNeedsKey(false); + }) + .catch((e) => { + if ((e as Error).name === 'AbortError') return; + if (e instanceof HttpError && (e.status === 401 || e.status === 403)) { + setNeedsKey(true); + setErr(null); + } else { + setErr((e as Error).message); + } + }); + return () => ac.abort(); + }, [id]); + + function saveKey() { + if (!pendingKey.trim()) return; + localStorage.setItem('dialectic-admin-key', pendingKey.trim()); + setPendingKey(''); + window.location.reload(); + } + + if (!id) return
missing agent id
; + + return ( +
+ agent +

{id}

+ + {needsKey && ( +
+
+ this page calls an admin endpoint — paste your + x-dialectic-admin-key (stored in localStorage; never + sent except to /api/admin/*). +
+ setPendingKey(e.target.value)} + placeholder="admin key…" + /> +
+ + key never leaves this browser +
+
+ )} + + {err &&
{err}
} + + {data && ( + <> +
+
+
+ {data.key_provisioned ? 'yes' : 'no'} +
+
key provisioned
+
+
+
{data.signups_count}
+
signups
+
+
+
{data.arguments_count}
+
arguments
+
+
+
{data.verdicts_count}
+
verdicts
+
+
+ +

recent topics

+ {data.recent_topics.length === 0 ? ( +
no recent topics for this agent
+ ) : ( + + + + + + + + + + + {data.recent_topics.map((t) => ( + + + + + + + ))} + +
topicrolestatuslast action
+ {t.title} + + {t.role} + + {t.status} + + {fmtRelative(t.last_action_at)} +
+ )} + + )} +
+ ); +} diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx new file mode 100644 index 0000000..88c49ba --- /dev/null +++ b/src/pages/NotFound.tsx @@ -0,0 +1,10 @@ +import { Link } from 'react-router-dom'; + +export function NotFoundPage() { + return ( +
+

404

+

no route. back to topics

+
+ ); +} diff --git a/src/pages/TopicDetail.tsx b/src/pages/TopicDetail.tsx new file mode 100644 index 0000000..8a274a4 --- /dev/null +++ b/src/pages/TopicDetail.tsx @@ -0,0 +1,184 @@ +import { useEffect, useRef, useState } from 'react'; +import { Link, useParams } from 'react-router-dom'; +import { getTopic, listArguments } from '../api'; +import type { Argument, TopicDetail } from '../types'; +import { fmtTime, fmtRelative } from '../util'; + +// Polling interval for the live transcript. 8s strikes a balance — fast +// enough that a new argument appears within an attentive viewer's +// attention span, slow enough that idle tabs don't hammer the backend. +const POLL_MS = 8000; + +export function TopicDetailPage() { + const { id } = useParams<{ id: string }>(); + const [topic, setTopic] = useState(null); + const [args, setArgs] = useState([]); + const [err, setErr] = useState(null); + const [lastTick, setLastTick] = useState(0); + const stopped = useRef(false); + + useEffect(() => { + if (!id) return; + stopped.current = false; + const ac = new AbortController(); + + async function tick() { + try { + const [t, a] = await Promise.all([ + getTopic(id!, ac.signal), + listArguments(id!, ac.signal), + ]); + if (stopped.current) return; + setTopic(t); + setArgs(a.arguments ?? []); + setLastTick(Date.now()); + setErr(null); + } catch (e) { + const err = e as Error; + if (err.name !== 'AbortError') setErr(err.message); + } + } + + tick(); + const h = setInterval(tick, POLL_MS); + return () => { + stopped.current = true; + ac.abort(); + clearInterval(h); + }; + }, [id]); + + if (!id) return
missing topic id in url
; + if (err && !topic) return
{err}
; + if (!topic) return
loading…
; + + const proCamp = topic.camps?.find((c) => c.camp === 'pro'); + const conCamp = topic.camps?.find((c) => c.camp === 'con'); + const judgeCamp = topic.camps?.find((c) => c.camp === 'judge'); + + return ( +
+
+ + topic · {topic.status} + +

{topic.title}

+

{topic.summary}

+
+ +
+
metadata
+
+
+
visibility
+
{topic.visibility}
+
+
+
verdict schema
+
{topic.verdict_schema_id}
+
+
+
signup window
+
+ {fmtTime(topic.signup_open_at)} → {fmtTime(topic.signup_close_at)} +
+
+
+
debate window
+
+ {fmtTime(topic.debate_start_at)} → {fmtTime(topic.debate_end_at)} +
+
+
+
creator
+
{topic.creator_user_id}
+
+ {topic.cancelled_reason && ( +
+
cancelled reason
+
+ {topic.cancelled_reason} +
+
+ )} +
+
+ + {topic.camps && topic.camps.length > 0 && ( +
+
camps (allocated)
+
+
+
pro
+
+ {proCamp ? ( + {proCamp.agent_id} + ) : ( + + )} +
+
+
+
con
+
+ {conCamp ? ( + {conCamp.agent_id} + ) : ( + + )} +
+
+
+
judge
+
+ {judgeCamp ? ( + {judgeCamp.agent_id} + ) : ( + + )} +
+
+
+
+ )} + +
+
+ transcript ({args.length}) + + + polling every {POLL_MS / 1000}s · last refresh {fmtRelative(new Date(lastTick).toISOString())} + +
+ {args.length === 0 ? ( +
no arguments posted yet
+ ) : ( + args.map((a) => ( +
+
+ {a.camp.toUpperCase()} + + {a.agent_id} + + + + {fmtRelative(a.posted_at)} + +
+
{a.content}
+
+ )) + )} +
+ + {topic.status === 'completed' && ( +
+
verdict
+ view verdict + rationale → +
+ )} + + {err &&
refresh error: {err}
} +
+ ); +} diff --git a/src/pages/TopicList.tsx b/src/pages/TopicList.tsx new file mode 100644 index 0000000..93ff8f0 --- /dev/null +++ b/src/pages/TopicList.tsx @@ -0,0 +1,109 @@ +import { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; +import { listTopics } from '../api'; +import type { Topic, TopicStatus } from '../types'; +import { fmtTime, fmtRelative } from '../util'; + +const STATUS_OPTIONS: Array = [ + 'all', + 'signup_open', + 'signup_closed', + 'debating', + 'completed', + 'cancelled', +]; + +export function TopicListPage() { + const [status, setStatus] = useState('all'); + const [topics, setTopics] = useState([]); + const [loading, setLoading] = useState(true); + const [err, setErr] = useState(null); + + useEffect(() => { + const ac = new AbortController(); + setLoading(true); + setErr(null); + listTopics({ + status: status === 'all' ? undefined : status, + limit: 100, + signal: ac.signal, + }) + .then((r) => setTopics(r.topics)) + .catch((e: Error) => { + if (e.name !== 'AbortError') setErr(e.message); + }) + .finally(() => setLoading(false)); + return () => ac.abort(); + }, [status]); + + return ( +
+ debates +

topics

+

+ Public + visible-to-you topics. Filter by lifecycle status. Click a row for the + live transcript. +

+ +
+ + + + {loading ? 'loading…' : `${topics.length} ${topics.length === 1 ? 'topic' : 'topics'}`} + +
+ + {err &&
{err}
} + + {!err && !loading && topics.length === 0 && ( +
no topics match this filter
+ )} + + {topics.length > 0 && ( + + + + + + + + + + + {topics.map((t) => ( + + + + + + + ))} + +
topicstatussignup closedebate window
+ + {t.title} + +
{t.summary}
+
+ {t.status} + + {fmtRelative(t.signup_close_at)} + + {fmtTime(t.debate_start_at)} → {fmtTime(t.debate_end_at)} +
+ )} +
+ ); +} diff --git a/src/pages/Verdict.tsx b/src/pages/Verdict.tsx new file mode 100644 index 0000000..ffb5f62 --- /dev/null +++ b/src/pages/Verdict.tsx @@ -0,0 +1,78 @@ +import { useEffect, useState } from 'react'; +import { Link, useParams } from 'react-router-dom'; +import { getTopic, getVerdict } from '../api'; +import type { TopicDetail, Verdict } from '../types'; +import { fmtTime } from '../util'; + +export function VerdictPage() { + const { id } = useParams<{ id: string }>(); + const [topic, setTopic] = useState(null); + const [verdict, setVerdict] = useState(null); + const [err, setErr] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!id) return; + const ac = new AbortController(); + setLoading(true); + Promise.all([getTopic(id, ac.signal), getVerdict(id, ac.signal)]) + .then(([t, v]) => { + setTopic(t); + setVerdict(v); + setErr(null); + }) + .catch((e: Error) => { + if (e.name !== 'AbortError') setErr(e.message); + }) + .finally(() => setLoading(false)); + return () => ac.abort(); + }, [id]); + + if (!id) return
missing topic id
; + if (loading) return
loading…
; + if (err) return
{err}
; + if (!topic) return
topic not found
; + + return ( +
+
+ verdict permalink +

{topic.title}

+

{topic.summary}

+

+ schema: {topic.verdict_schema_id} + {' · '}debate window: {fmtTime(topic.debate_start_at)} → {fmtTime(topic.debate_end_at)} + {' · '} + back to topic +

+
+ + {!verdict ? ( +
+ no verdict yet — topic status is {topic.status} + {topic.status === 'debating' && ', judge has not submitted'} + {topic.status === 'cancelled' && ', topic was cancelled before a verdict could be reached'} +
+ ) : ( + <> +
+
verdict (structured)
+
{JSON.stringify(verdict.verdict, null, 2)}
+
+ judge: {verdict.judge_agent_id} + + produced: {fmtTime(verdict.produced_at)} + {(verdict.tokens_input + verdict.tokens_output) > 0 && ( + tokens: {verdict.tokens_input} in / {verdict.tokens_output} out + )} +
+
+
+
rationale
+
{verdict.rationale}
+
+ + )} +
+ ); +} diff --git a/src/styles/App.css b/src/styles/App.css deleted file mode 100644 index 2dda47d..0000000 --- a/src/styles/App.css +++ /dev/null @@ -1,792 +0,0 @@ -/* Reset and base styles */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - line-height: 1.6; - color: #333; - background-color: #f5f7fa; -} - -.App { - max-width: 1200px; - margin: 0 auto; - padding: 20px; - min-height: 100vh; - display: flex; - flex-direction: column; -} - -/* Header styles */ -.app-header { - text-align: center; - padding: 2rem 0; - margin-bottom: 2rem; - background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); - color: white; - border-radius: 10px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - position: relative; -} - -.app-header h1 { - font-size: 2.5rem; - margin-bottom: 0.5rem; -} - -.app-header p { - font-size: 1.2rem; - opacity: 0.9; -} - -/* Header auth info (prod mode) */ -.header-auth { - position: absolute; - top: 1rem; - right: 1.5rem; - display: flex; - align-items: center; - gap: 0.75rem; -} - -.header-username { - font-size: 0.9rem; - opacity: 0.9; -} - -.header-logout-btn { - background: rgba(255, 255, 255, 0.2); - color: white; - border: 1px solid rgba(255, 255, 255, 0.4); - padding: 0.35rem 0.85rem; - border-radius: 5px; - cursor: pointer; - font-size: 0.85rem; - transition: background 0.2s; -} - -.header-logout-btn:hover { - background: rgba(255, 255, 255, 0.35); -} - -/* Login prompt page */ -.login-prompt { - text-align: center; - padding: 3rem 2rem; - max-width: 480px; - margin: 2rem auto; -} - -.login-prompt h2 { - margin-bottom: 1rem; - color: #2d3748; -} - -.login-prompt p { - color: #718096; - margin-bottom: 2rem; - font-size: 1.05rem; -} - -.login-button { - padding: 0.85rem 2.5rem; - font-size: 1.1rem; -} - -/* Main layout */ -.app-main { - flex: 1; - display: flex; - flex-direction: column; - gap: 2rem; -} - -.section { - background: white; - border-radius: 10px; - padding: 2rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); -} - -.hidden { - display: none; -} - -/* Form styles */ -.form-group { - margin-bottom: 1.5rem; -} - -.label { - display: block; - margin-bottom: 0.5rem; - font-weight: 600; - color: #444; -} - -.input, .select { - width: 100%; - padding: 0.75rem; - border: 1px solid #ddd; - border-radius: 5px; - font-size: 1rem; - transition: border-color 0.3s; -} - -.input:focus, .select:focus { - outline: none; - border-color: #2575fc; - box-shadow: 0 0 0 2px rgba(37, 117, 252, 0.2); -} - -.model-selection { - display: flex; - flex-wrap: wrap; - gap: 1rem; -} - -.model-item { - flex: 1; - min-width: 250px; - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.stance-label { - align-self: flex-start; - background: #eef2ff; - color: #4f46e5; - padding: 0.25rem 0.75rem; - border-radius: 20px; - font-size: 0.875rem; - font-weight: 600; -} - -.button { - background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); - color: white; - border: none; - padding: 0.75rem 1.5rem; - border-radius: 5px; - cursor: pointer; - font-size: 1rem; - font-weight: 600; - transition: transform 0.2s, box-shadow 0.2s; - display: inline-block; - text-align: center; - text-decoration: none; -} - -.button:hover { - transform: translateY(-2px); - box-shadow: 0 4px 8px rgba(37, 117, 252, 0.3); -} - -.button:active { - transform: translateY(0); -} - -.create-debate-button { - background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); - padding: 1rem 2rem; - font-size: 1.1rem; - width: 100%; - margin-top: 1rem; -} - -/* Debate section styles */ -.debate-controls { - display: flex; - gap: 1rem; - margin-bottom: 1.5rem; - flex-wrap: wrap; -} - -.start-debate-button { - background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); -} - -.stop-debate-button { - background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%); -} - -.view-sessions-button { - background: linear-gradient(135deg, #4A00E0 0%, #8E2DE2 100%); -} - -.back-button { - background: linear-gradient(135deg, #4A00E0 0%, #8E2DE2 100%); - align-self: flex-start; -} - -.debate-stream { - border: 1px solid #eee; - border-radius: 8px; - padding: 1.5rem; - min-height: 400px; - max-height: 80vh; - overflow-y: auto; - background-color: #fafafa; -} - -.round-container { - margin-bottom: 1.5rem; - padding-bottom: 1.5rem; - border-bottom: 1px dashed #ddd; -} - -.round-container:last-child { - border-bottom: none; - margin-bottom: 0; - padding-bottom: 0; -} - -.speaker-header { - display: flex; - align-items: center; - margin-bottom: 0.75rem; - padding: 0.5rem; - border-radius: 5px; -} - -.pro-speaker { - background-color: #dbeafe; - border-left: 4px solid #3b82f6; -} - -.con-speaker { - background-color: #ffe4e6; - border-left: 4px solid #ef4444; -} - -.speaker-info { - font-weight: 600; -} - -.speaker-stance { - margin-left: auto; - padding: 0.25rem 0.75rem; - border-radius: 20px; - font-size: 0.8rem; - font-weight: 600; -} - -.pro-stance { - background-color: #3b82f6; - color: white; -} - -.con-stance { - background-color: #ef4444; - color: white; -} - -.speaker-content { - padding: 0.5rem 1rem; - background-color: white; - border-radius: 5px; - box-shadow: 0 1px 3px rgba(0,0,0,0.1); - line-height: 1.8; - white-space: pre-wrap; - word-break: break-word; -} - -.debate-summary { - margin-top: 2rem; - padding: 1.5rem; - background-color: #f0f9ff; - border-radius: 8px; - border-left: 4px solid #0ea5e9; -} - -.debate-summary h3 { - color: #0ea5e9; - margin-bottom: 1rem; -} - -/* Sessions list styles */ -.sessions-list-item { - padding: 1rem; - border: 1px solid #eee; - border-radius: 5px; - margin-bottom: 0.75rem; - background-color: #fff; - box-shadow: 0 2px 4px rgba(0,0,0,0.05); -} - -.session-topic { - font-weight: 600; - color: #2d3748; -} - -.session-meta { - display: flex; - gap: 1rem; - margin-top: 0.5rem; - font-size: 0.9rem; - color: #718096; -} - -.session-actions { - margin-top: 0.75rem; -} - -.view-session-button { - background: linear-gradient(135deg, #4A00E0 0%, #8E2DE2 100%); - padding: 0.5rem 1rem; - font-size: 0.9rem; -} - -/* Settings Page Styles */ -.settings-container { - display: flex; - flex-direction: column; - gap: 2rem; -} - -.api-key-form { - display: flex; - flex-direction: column; - gap: 1.5rem; -} - -.api-key-input-group { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.api-key-input-group label { - font-weight: 600; - color: #444; -} - -.input-with-buttons { - display: flex; - gap: 0.5rem; -} - -.input-with-buttons .input { - flex: 1; -} - -.input-with-buttons .button { - align-self: stretch; - white-space: nowrap; -} - -.save-status { - align-self: center; - color: #38a169; - font-weight: 600; -} - -.validation-status { - align-self: center; - font-weight: 600; -} - -.validation-status.valid { - color: #38a169; /* Green for valid */ -} - -.validation-status.invalid { - color: #e53e3e; /* Red for invalid */ -} - -.settings-actions { - display: flex; - gap: 1rem; - justify-content: center; - margin-top: 1rem; -} - -/* Checkbox label */ -.checkbox-label { - display: flex; - align-items: center; - gap: 0.5rem; - cursor: pointer; - font-weight: 600; - color: #444; -} - -.checkbox-label input[type="checkbox"] { - width: 18px; - height: 18px; - cursor: pointer; -} - -/* Search Evidence Styles */ -.search-evidence { - margin-top: 0.75rem; - border: 1px solid #e2e8f0; - border-radius: 6px; - overflow: hidden; - background-color: #f8fafc; -} - -.search-evidence-header { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem 0.75rem; - cursor: pointer; - user-select: none; - background-color: #edf2f7; - transition: background-color 0.2s; -} - -.search-evidence-header:hover { - background-color: #e2e8f0; -} - -.search-evidence-icon { - font-size: 0.7rem; - color: #718096; -} - -.search-evidence-title { - font-weight: 600; - font-size: 0.85rem; - color: #4a5568; -} - -.search-evidence-badge { - margin-left: auto; - background-color: #bee3f8; - color: #2b6cb0; - padding: 0.15rem 0.5rem; - border-radius: 10px; - font-size: 0.75rem; - font-weight: 600; -} - -.search-evidence-body { - padding: 0.75rem; -} - -.search-evidence-query { - font-size: 0.8rem; - color: #718096; - margin-bottom: 0.5rem; - font-style: italic; -} - -.search-evidence-item { - padding: 0.5rem 0; - border-bottom: 1px solid #e2e8f0; -} - -.search-evidence-item:last-child { - border-bottom: none; -} - -.search-evidence-link { - color: #2b6cb0; - font-weight: 600; - font-size: 0.85rem; - text-decoration: none; -} - -.search-evidence-link:hover { - text-decoration: underline; -} - -.search-evidence-snippet { - font-size: 0.8rem; - color: #4a5568; - margin-top: 0.25rem; - line-height: 1.5; -} - -/* Evidence Library Styles */ -.evidence-library-panel { - margin-top: 1.5rem; - border: 1px solid #fbbf24; - border-radius: 8px; - overflow: hidden; - background-color: #fffbeb; -} - -.evidence-library-header { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1rem; - cursor: pointer; - user-select: none; - background-color: #fef3c7; - transition: background-color 0.2s; -} - -.evidence-library-header:hover { - background-color: #fde68a; -} - -.evidence-library-icon { - font-size: 0.75rem; - color: #92400e; -} - -.evidence-library-title { - font-weight: 700; - font-size: 1rem; - color: #92400e; -} - -.evidence-library-badge { - margin-left: auto; - background-color: #f59e0b; - color: white; - padding: 0.2rem 0.6rem; - border-radius: 10px; - font-size: 0.75rem; - font-weight: 600; -} - -.evidence-library-body { - padding: 1rem; -} - -.evidence-library-entry { - padding: 0.75rem; - margin-bottom: 0.75rem; - background-color: white; - border: 1px solid #e5e7eb; - border-radius: 6px; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.evidence-library-entry:last-child { - margin-bottom: 0; -} - -.evidence-library-entry-header { - display: flex; - align-items: center; - gap: 0.5rem; - margin-bottom: 0.5rem; -} - -.evidence-library-link { - color: #b45309; - font-weight: 600; - font-size: 0.9rem; - text-decoration: none; - flex: 1; -} - -.evidence-library-link:hover { - text-decoration: underline; - color: #92400e; -} - -.evidence-library-score { - font-size: 0.75rem; - color: #6b7280; - white-space: nowrap; -} - -.evidence-library-snippet { - font-size: 0.8rem; - color: #4b5563; - line-height: 1.5; - margin-bottom: 0.5rem; -} - -.evidence-library-refs { - display: flex; - flex-wrap: wrap; - gap: 0.4rem; -} - -.evidence-library-ref-tag { - display: inline-block; - padding: 0.15rem 0.5rem; - border-radius: 10px; - font-size: 0.7rem; - font-weight: 600; -} - -.evidence-library-ref-tag.ref-pro { - background-color: #dbeafe; - color: #1d4ed8; -} - -.evidence-library-ref-tag.ref-con { - background-color: #ffe4e6; - color: #dc2626; -} - -/* Setup Wizard Styles */ -.setup-wizard { - max-width: 600px; - margin: 0 auto; -} - -.setup-steps { - display: flex; - justify-content: center; - gap: 1.5rem; - margin-bottom: 2rem; -} - -.setup-step-indicator { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.3rem; - opacity: 0.5; -} - -.setup-step-indicator.active { - opacity: 1; -} - -.setup-step-indicator.done { - opacity: 0.8; -} - -.setup-step-number { - width: 32px; - height: 32px; - border-radius: 50%; - background: #e2e8f0; - display: flex; - align-items: center; - justify-content: center; - font-weight: 700; - font-size: 0.9rem; - color: #4a5568; -} - -.setup-step-indicator.active .setup-step-number { - background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); - color: white; -} - -.setup-step-indicator.done .setup-step-number { - background: #38a169; - color: white; -} - -.setup-step-label { - font-size: 0.8rem; - color: #4a5568; - font-weight: 600; -} - -.setup-step-content h3 { - margin-bottom: 1rem; - color: #2d3748; -} - -.setup-hint { - font-size: 0.9rem; - color: #718096; - margin-bottom: 1rem; - font-style: italic; -} - -.setup-test-row { - display: flex; - align-items: center; - gap: 1rem; - margin-top: 1rem; -} - -.setup-test-result { - font-weight: 600; - font-size: 0.9rem; -} - -.setup-test-result.success { - color: #38a169; -} - -.setup-test-result.fail { - color: #e53e3e; -} - -.setup-nav { - display: flex; - justify-content: space-between; - margin-top: 2rem; - gap: 1rem; -} - -/* Settings Tabs */ -.settings-tabs { - display: flex; - gap: 0; - border-bottom: 2px solid #e2e8f0; - margin-bottom: 1.5rem; -} - -.settings-tab { - background: none; - border: none; - padding: 0.75rem 1.25rem; - font-size: 0.95rem; - font-weight: 600; - color: #718096; - cursor: pointer; - border-bottom: 2px solid transparent; - margin-bottom: -2px; - transition: color 0.2s, border-color 0.2s; -} - -.settings-tab:hover { - color: #4a5568; -} - -.settings-tab.active { - color: #2575fc; - border-bottom-color: #2575fc; -} - -.settings-tab-content { - min-height: 200px; -} - -/* Footer styles */ -.app-footer { - text-align: center; - padding: 2rem 0 1rem; - color: #718096; - font-size: 0.9rem; - margin-top: 2rem; -} - -/* Responsive design */ -@media (max-width: 768px) { - .App { - padding: 10px; - } - - .app-header h1 { - font-size: 2rem; - } - - .section { - padding: 1.5rem; - } - - .model-selection { - flex-direction: column; - } - - .debate-controls { - flex-direction: column; - } - - .button { - width: 100%; - } -} \ No newline at end of file diff --git a/src/styles/app.css b/src/styles/app.css new file mode 100644 index 0000000..ddff83c --- /dev/null +++ b/src/styles/app.css @@ -0,0 +1,331 @@ +.app { + display: flex; + flex-direction: column; + min-height: 100vh; + /* Subtle blueprint grid wash on the body, per ~/STYLE.md. */ + background-image: radial-gradient( + 1000px 560px at 78% -8%, + rgba(216, 255, 62, 0.06), + transparent 60% + ), + repeating-linear-gradient( + 0deg, + transparent 0 47px, + rgba(29, 39, 51, 0.35) 47px 48px + ), + repeating-linear-gradient( + 90deg, + transparent 0 47px, + rgba(29, 39, 51, 0.35) 47px 48px + ); +} + +.app-header { + background: var(--chrome); + border-bottom: 1px solid var(--line); +} +.app-header-inner { + max-width: 1200px; + margin: 0 auto; + display: flex; + align-items: center; + gap: 24px; + padding: 14px 24px; +} +.app-brand { + font-size: 1.6rem; + color: var(--acid); + text-shadow: 0 0 24px rgba(216, 255, 62, 0.25); + text-decoration: none; + border: none; +} +.app-brand:hover { + border: none; +} +.app-nav { + display: flex; + gap: 18px; + margin-left: 24px; +} +.app-nav a { + font-size: 0.85rem; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--dim); + border: none; + padding: 4px 0; +} +.app-nav a:hover { + color: var(--text); + border: none; +} +.app-nav a.active { + color: var(--acid); + border-bottom: 1px solid var(--acid); +} +.app-user { + margin-left: auto; +} +.app-dev-banner { + background: rgba(255, 90, 82, 0.1); + color: var(--danger); + border-top: 1px solid rgba(255, 90, 82, 0.4); + padding: 6px 24px; + font-size: 0.75rem; + letter-spacing: 0.12em; + text-align: center; +} +.app-dev-banner code { + background: rgba(255, 90, 82, 0.18); + padding: 0 4px; + border-radius: 2px; +} + +.app-main { + flex: 1; + max-width: 1200px; + width: 100%; + margin: 0 auto; + padding: 24px; +} + +.app-footer { + border-top: 1px solid var(--line); + background: var(--chrome); + padding: 12px 24px; + text-align: center; +} + +/* ----- Topic list ----- */ +.tl-filters { + display: flex; + gap: 12px; + margin-bottom: 16px; + align-items: center; + flex-wrap: wrap; +} +.tl-filters label { + display: flex; + align-items: center; + gap: 8px; + color: var(--dim); + font-size: 0.78rem; + letter-spacing: 0.18em; + text-transform: uppercase; +} +.tl-table { + width: 100%; + border-collapse: collapse; + background: var(--panel); + border: 1px solid var(--line); +} +.tl-table th, +.tl-table td { + padding: 10px 12px; + text-align: left; + border-bottom: 1px solid var(--line); + vertical-align: top; +} +.tl-table th { + background: var(--panel-2); + font-size: 0.78rem; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--dim); + font-weight: 500; +} +.tl-table tr:hover td { + background: var(--panel-3); +} +.tl-row-title { + font-weight: 500; + color: var(--text-strong); +} +.tl-row-summary { + color: var(--dim); + font-size: 0.85em; + margin-top: 4px; + max-width: 600px; +} +.tl-row-time { + color: var(--text-2); + font-size: 0.85em; + white-space: nowrap; +} + +/* ----- Topic detail ----- */ +.td { + display: grid; + gap: 16px; + grid-template-columns: 1fr; +} +.td-meta { + display: grid; + gap: 12px; + grid-template-columns: 1fr 1fr; +} +.td-meta .label { + color: var(--dim); + font-size: 0.75rem; + letter-spacing: 0.18em; + text-transform: uppercase; + margin-bottom: 4px; +} +.td-meta .value { + color: var(--text); + font-size: 0.95em; +} +.td-summary { + color: var(--text-1); + line-height: 1.6; +} +.td-camps { + display: flex; + gap: 12px; + flex-wrap: wrap; +} +.td-camp { + background: var(--panel-2); + padding: 8px 12px; + border-radius: var(--radius); + border: 1px solid var(--line); + min-width: 140px; +} +.td-camp-label { + font-size: 0.75rem; + letter-spacing: 0.18em; + text-transform: uppercase; +} +.td-camp-agent { + margin-top: 4px; + color: var(--text-strong); +} +.td-arg { + border-left: 3px solid var(--line); + padding: 12px 16px; + margin-bottom: 12px; + background: var(--panel); +} +.td-arg-pro { + border-left-color: var(--ok); +} +.td-arg-con { + border-left-color: var(--danger); +} +.td-arg-judge { + border-left-color: var(--acid); +} +.td-arg-meta { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 12px; + margin-bottom: 8px; + font-size: 0.82em; +} +.td-arg-content { + white-space: pre-wrap; + color: var(--text); + font-size: 0.95em; + line-height: 1.6; +} +.td-empty { + padding: 32px; + text-align: center; + color: var(--dim); +} + +/* ----- Verdict ----- */ +.vd-rationale { + white-space: pre-wrap; + color: var(--text); + line-height: 1.7; +} +.vd-json { + background: var(--ink); + border: 1px solid var(--line); + padding: 12px; + border-radius: var(--radius); + font-size: 0.9em; + overflow-x: auto; + color: var(--acid); +} + +/* ----- AgentActivity ----- */ +.aa-summary-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 12px; + margin-bottom: 16px; +} +.aa-card { + background: var(--panel); + border: 1px solid var(--line); + padding: 14px; + border-radius: var(--radius); + text-align: center; +} +.aa-card-value { + font-family: var(--font-d); + font-size: 2.4rem; + color: var(--acid); + text-transform: lowercase; +} +.aa-card-label { + margin-top: 4px; + font-size: 0.75rem; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--dim); +} +.aa-admin-prompt { + background: var(--panel); + border: 1px solid var(--warn); + padding: 14px; + margin-bottom: 16px; + border-radius: var(--radius); +} +.aa-admin-prompt input { + width: 100%; + margin-top: 8px; + font-family: var(--font-m); +} + +/* shared util */ +.row { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} +.spacer { + flex: 1; +} +.muted { + color: var(--dim); +} +.err { + color: var(--danger); + background: rgba(255, 90, 82, 0.08); + border: 1px solid rgba(255, 90, 82, 0.4); + padding: 12px; + border-radius: var(--radius); + font-family: var(--font-m); +} +h1 { + font-family: var(--font-d); + text-transform: lowercase; + letter-spacing: 0.02em; + font-size: 2rem; + color: var(--text-strong); + margin-bottom: 6px; + font-weight: 400; +} +h2 { + font-size: 0.95rem; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--text-1); + margin: 16px 0 8px; + font-weight: 500; +} diff --git a/src/styles/tokens.css b/src/styles/tokens.css new file mode 100644 index 0000000..4e29db9 --- /dev/null +++ b/src/styles/tokens.css @@ -0,0 +1,202 @@ +/* Hangman Lab design tokens — per ~/STYLE.md + * Dark "blueprint" surface, monospace everything, one sharp acid accent. + * If you change these, propagate to the Gitea theme too (single source of + * truth lives in STYLE.md). */ + +:root { + /* Surfaces */ + --ink: #080a0d; /* page background */ + --panel: #0f141b; /* cards / box body */ + --panel-2: #11161d; /* card header / raised */ + --panel-3: #141b24; /* hovered card */ + --chrome: #0b0e12; /* navbar / footer */ + --line: #1d2733; /* borders / dividers */ + + /* Text ramp */ + --text: #cdd8e8; /* primary text */ + --text-strong: #e9f0fa; + --text-1: #b6c2d4; + --text-2: #9aa8bd; + --dim: #647691; /* muted / eyebrow */ + + /* Accent — THE acid */ + --acid: #d8ff3e; + --acid-dim: #bbe233; + --on-acid: #0c0f08; + --acid-wash: #d8ff3e14; + --acid-active: #d8ff3e1f; + + /* Status / semantic */ + --danger: #ff5a52; + --warn: #f5a623; + --ok: #5fe07a; + + /* Fonts */ + --font-d: "Major Mono Display", monospace; + --font-m: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + + /* Layout */ + --radius: 4px; + --shadow-card: 0 0 0 1px var(--line); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body, +#root { + min-height: 100vh; + background: var(--ink); + color: var(--text); + font-family: var(--font-m); + font-size: 14px; + line-height: 1.55; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +a { + color: var(--acid); + text-decoration: none; + border-bottom: 1px dashed transparent; + transition: border-color 120ms ease; +} +a:hover { + border-bottom-color: var(--acid); +} + +button { + font-family: inherit; + font-size: inherit; + background: transparent; + color: var(--text); + border: 1px solid var(--line); + border-radius: var(--radius); + padding: 6px 12px; + cursor: pointer; + transition: background 120ms ease, border-color 120ms ease, color 120ms ease; +} +button:hover { + background: var(--acid-wash); + border-color: var(--acid); + color: var(--acid); +} +button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +input, +select, +textarea { + font-family: inherit; + font-size: inherit; + background: var(--ink); + color: var(--text); + border: 1px solid var(--line); + border-radius: var(--radius); + padding: 6px 10px; +} +input:focus, +select:focus, +textarea:focus { + outline: 1px solid var(--acid); + outline-offset: 1px; + border-color: var(--acid); +} + +/* Selection — acid wash, ink ink */ +::selection { + background: var(--acid); + color: var(--on-acid); +} + +/* Scrollbar — dark + thin */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} +::-webkit-scrollbar-track { + background: var(--ink); +} +::-webkit-scrollbar-thumb { + background: var(--line); + border-radius: var(--radius); +} +::-webkit-scrollbar-thumb:hover { + background: var(--dim); +} + +/* Brand mark (Major Mono Display) — lowercase always */ +.brand-d { + font-family: var(--font-d); + text-transform: lowercase; + letter-spacing: 0.02em; +} + +/* Eyebrow text — uppercase, wide spacing, dim */ +.eyebrow { + font-size: 0.78rem; + letter-spacing: 0.42em; + text-transform: uppercase; + color: var(--dim); + display: inline-flex; + align-items: center; + gap: 8px; +} +.eyebrow::before { + content: ""; + width: 8px; + height: 8px; + background: var(--acid); + border-radius: 50%; + animation: hlx-pulse 2s ease-in-out infinite; +} +@keyframes hlx-pulse { + 0%, 100% { opacity: 0.45; } + 50% { opacity: 1; } +} + +/* Card / panel */ +.panel { + background: var(--panel); + border: 1px solid var(--line); + border-radius: var(--radius); + padding: 16px; +} +.panel-header { + background: var(--panel-2); + border-bottom: 1px solid var(--line); + padding: 12px 16px; + margin: -16px -16px 16px; + border-radius: var(--radius) var(--radius) 0 0; + font-size: 0.85rem; + color: var(--text-1); +} + +/* Status pill */ +.pill { + display: inline-block; + padding: 2px 8px; + border-radius: 999px; + font-size: 0.75rem; + letter-spacing: 0.08em; + text-transform: uppercase; + background: var(--panel-2); + color: var(--text-1); + border: 1px solid var(--line); +} +.pill-debating { color: var(--acid); border-color: var(--acid); } +.pill-completed { color: var(--ok); border-color: var(--ok); } +.pill-cancelled { color: var(--danger); border-color: var(--danger); } +.pill-signup_open { color: var(--text-strong); } +.pill-signup_closed { color: var(--text-1); } + +/* Camp tags */ +.camp-pro { color: var(--ok); } +.camp-con { color: var(--danger); } +.camp-judge { color: var(--acid); } diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..6355153 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,85 @@ +// Mirrors the Dialectic.Backend models — kept in sync by hand. If a +// field is added on the backend (models/topic.go / store responses), +// also add it here so the UI can use it. + +export type TopicStatus = + | 'created' + | 'signup_open' + | 'signup_closed' + | 'debating' + | 'completed' + | 'cancelled'; + +export type Visibility = 'public' | 'private'; + +export type Camp = 'pro' | 'con' | 'judge'; + +export interface Topic { + id: string; + title: string; + summary: string; + visibility: Visibility; + verdict_schema_id: string; + status: TopicStatus; + signup_open_at: string; + signup_close_at: string; + debate_start_at: string; + debate_end_at: string; + creator_user_id: string; + visibility_changed_by?: string | null; + visibility_changed_at?: string | null; + cancelled_reason?: string | null; + created_at: string; + updated_at: string; +} + +export interface CampAllocation { + id: string; + topic_id: string; + camp: Camp; + agent_id: string; + allocated_at: string; +} + +// GET /api/topics/{id} returns the full Topic spread + `camps` sibling. +export interface TopicDetail extends Topic { + camps: CampAllocation[] | null; +} + +export interface Argument { + id: string; + topic_id: string; + round_id: string; + camp: Camp; + agent_id: string; + content: string; + posted_at: string; +} + +export interface Verdict { + id: string; + topic_id: string; + judge_agent_id: string; + // verdict shape depends on the topic's verdict_schema_id; UI shows raw JSON. + verdict: Record; + rationale: string; + tokens_input: number; + tokens_output: number; + produced_at: string; +} + +// Admin endpoint (next commit) shape — declare now so frontend compiles. +export interface AgentSummary { + agent_id: string; + key_provisioned: boolean; + signups_count: number; + arguments_count: number; + verdicts_count: number; + recent_topics: Array<{ + topic_id: string; + title: string; + status: TopicStatus; + role: Camp | 'volunteer'; + last_action_at: string; + }>; +} diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..2a56ee4 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,28 @@ +// Tiny date helper — show RFC3339 timestamps in a compact "UTC" form. +// Avoids pulling in a full Date/intl library; UI is operator-facing +// (not end-user-localized) so UTC is the safer default. +export function fmtTime(iso: string | null | undefined): string { + if (!iso) return '—'; + const d = new Date(iso); + if (isNaN(d.getTime())) return iso; + const pad = (n: number) => String(n).padStart(2, '0'); + return ( + `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ` + + `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())} UTC` + ); +} + +export function fmtRelative(iso: string | null | undefined): string { + if (!iso) return '—'; + const ms = Date.now() - new Date(iso).getTime(); + if (isNaN(ms)) return iso; + const abs = Math.abs(ms); + const sec = Math.round(abs / 1000); + if (sec < 60) return ms >= 0 ? `${sec}s ago` : `in ${sec}s`; + const min = Math.round(sec / 60); + if (min < 60) return ms >= 0 ? `${min}m ago` : `in ${min}m`; + const hr = Math.round(min / 60); + if (hr < 24) return ms >= 0 ? `${hr}h ago` : `in ${hr}h`; + const day = Math.round(hr / 24); + return ms >= 0 ? `${day}d ago` : `in ${day}d`; +} diff --git a/src/utils/api.js b/src/utils/api.js deleted file mode 100644 index 1215305..0000000 --- a/src/utils/api.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Global API wrapper with 503 SERVICE_NOT_CONFIGURED interception. - * - * When the backend replies 503 with error_code "SERVICE_NOT_CONFIGURED", - * a custom event "needs-setup" is dispatched on window so App.js can - * switch to the SetupWizard view. - */ - -import { getAccessTokenGlobal } from '../components/AuthProvider'; - -let _backendHost = null; - -export function getBackendHost() { - if (_backendHost !== null) return _backendHost; - _backendHost = process.env.REACT_APP_DIALECTIC_BACKEND_HOST || 'http://localhost:8000'; - return _backendHost; -} - -export function setBackendHost(host) { - _backendHost = host; -} - -export async function apiFetch(path, options = {}) { - const url = path.startsWith('http') ? path : `${getBackendHost()}${path}`; - const token = getAccessTokenGlobal(); - if (token) { - options.headers = { ...options.headers, Authorization: `Bearer ${token}` }; - } - - const resp = await fetch(url, options); - - if (resp.status === 503) { - // Clone so callers can still read the body if needed - const cloned = resp.clone(); - try { - const body = await cloned.json(); - if (body.error_code === 'SERVICE_NOT_CONFIGURED') { - window.dispatchEvent( - new CustomEvent('needs-setup', { detail: body }) - ); - } - } catch { - // ignore JSON parse errors - } - } - - return resp; -} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..601a495 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "types": ["vite/client"] + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..157aa2d --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "allowSyntheticDefaultImports": true, + "strict": true, + "skipLibCheck": true, + "composite": true, + "types": ["node"] + }, + "include": ["vite.config.ts"] +} diff --git a/tsconfig.node.tsbuildinfo b/tsconfig.node.tsbuildinfo new file mode 100644 index 0000000..8a41edf --- /dev/null +++ b/tsconfig.node.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.dom.asynciterable.d.ts","./node_modules/typescript/lib/lib.webworker.importscripts.d.ts","./node_modules/typescript/lib/lib.scripthost.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/typescript/lib/lib.es2022.full.d.ts","./node_modules/@types/node/compatibility/disposable.d.ts","./node_modules/@types/node/compatibility/indexable.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/compatibility/index.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/file.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/filereader.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/@types/estree/index.d.ts","./node_modules/rollup/dist/rollup.d.ts","./node_modules/rollup/dist/parseAst.d.ts","./node_modules/vite/types/hmrPayload.d.ts","./node_modules/vite/types/customEvent.d.ts","./node_modules/vite/types/hot.d.ts","./node_modules/vite/dist/node/types.d-aGj9QkWt.d.ts","./node_modules/esbuild/lib/main.d.ts","./node_modules/source-map-js/source-map.d.ts","./node_modules/postcss/lib/previous-map.d.ts","./node_modules/postcss/lib/input.d.ts","./node_modules/postcss/lib/css-syntax-error.d.ts","./node_modules/postcss/lib/declaration.d.ts","./node_modules/postcss/lib/root.d.ts","./node_modules/postcss/lib/warning.d.ts","./node_modules/postcss/lib/lazy-result.d.ts","./node_modules/postcss/lib/no-work-result.d.ts","./node_modules/postcss/lib/processor.d.ts","./node_modules/postcss/lib/result.d.ts","./node_modules/postcss/lib/document.d.ts","./node_modules/postcss/lib/rule.d.ts","./node_modules/postcss/lib/node.d.ts","./node_modules/postcss/lib/comment.d.ts","./node_modules/postcss/lib/container.d.ts","./node_modules/postcss/lib/at-rule.d.ts","./node_modules/postcss/lib/list.d.ts","./node_modules/postcss/lib/postcss.d.ts","./node_modules/postcss/lib/postcss.d.mts","./node_modules/vite/dist/node/runtime.d.ts","./node_modules/vite/types/importGlob.d.ts","./node_modules/vite/types/metadata.d.ts","./node_modules/vite/dist/node/index.d.ts","./node_modules/@babel/types/lib/index.d.ts","./node_modules/@types/babel__generator/index.d.ts","./node_modules/@babel/parser/typings/babel-parser.d.ts","./node_modules/@types/babel__template/index.d.ts","./node_modules/@types/babel__traverse/index.d.ts","./node_modules/@types/babel__core/index.d.ts","./node_modules/@vitejs/plugin-react/dist/index.d.ts","./vite.config.ts"],"fileIdsList":[[69,117,134,135,200],[69,117,134,135],[69,117,134,135,200,201,202,203,204],[69,117,134,135,200,202],[69,114,115,117,134,135],[69,116,117,134,135],[117,134,135],[69,117,122,134,135,152],[69,117,118,123,128,134,135,137,149,160],[69,117,118,119,128,134,135,137],[64,65,66,69,117,134,135],[69,117,120,134,135,161],[69,117,121,122,129,134,135,138],[69,117,122,134,135,149,157],[69,117,123,125,128,134,135,137],[69,116,117,124,134,135],[69,117,125,126,134,135],[69,117,127,128,134,135],[69,116,117,128,134,135],[69,117,128,129,130,134,135,149,160],[69,117,128,129,130,134,135,144,149,152],[69,110,117,125,128,131,134,135,137,149,160],[69,117,128,129,131,132,134,135,137,149,157,160],[69,117,131,133,134,135,149,157,160],[67,68,69,70,71,72,73,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166],[69,117,128,134,135],[69,117,134,135,136,160],[69,117,125,128,134,135,137,149],[69,117,134,135,138],[69,117,134,135,139],[69,116,117,134,135,140],[69,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166],[69,117,134,135,142],[69,117,134,135,143],[69,117,128,134,135,144,145],[69,117,134,135,144,146,161,163],[69,117,129,134,135],[69,117,128,134,135,149,150,152],[69,117,134,135,151,152],[69,117,134,135,149,150],[69,117,134,135,152],[69,117,134,135,153],[69,114,117,134,135,149,154,160],[69,117,128,134,135,155,156],[69,117,134,135,155,156],[69,117,122,134,135,137,149,157],[69,117,134,135,158],[69,117,134,135,137,159],[69,117,131,134,135,143,160],[69,117,122,134,135,161],[69,117,134,135,149,162],[69,117,134,135,136,163],[69,117,134,135,164],[69,110,117,134,135],[69,110,117,128,130,134,135,140,149,152,160,162,163,165],[69,117,134,135,149,166],[69,117,134,135,199,205],[69,117,134,135,191],[69,117,134,135,189,191],[69,117,134,135,180,188,189,190,192,194],[69,117,134,135,178],[69,117,134,135,181,186,191,194],[69,117,134,135,177,194],[69,117,134,135,181,182,185,186,187,194],[69,117,134,135,181,182,183,185,186,194],[69,117,134,135,178,179,180,181,182,186,187,188,190,191,192,194],[69,117,134,135,194],[69,117,134,135,176,178,179,180,181,182,183,185,186,187,188,189,190,191,192,193],[69,117,134,135,176,194],[69,117,134,135,181,183,184,186,187,194],[69,117,134,135,185,194],[69,117,134,135,186,187,191,194],[69,117,134,135,179,189],[69,117,134,135,169,198],[69,117,134,135,168,169],[69,82,86,117,134,135,160],[69,82,117,134,135,149,160],[69,77,117,134,135],[69,79,82,117,134,135,157,160],[69,117,134,135,137,157],[69,117,134,135,167],[69,77,117,134,135,167],[69,79,82,117,134,135,137,160],[69,74,75,78,81,117,128,134,135,149,160],[69,82,89,117,134,135],[69,74,80,117,134,135],[69,82,103,104,117,134,135],[69,78,82,117,134,135,152,160,167],[69,103,117,134,135,167],[69,76,77,117,134,135,167],[69,82,117,134,135],[69,76,77,78,79,80,81,82,83,84,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,104,105,106,107,108,109,117,134,135],[69,82,97,117,134,135],[69,82,89,90,117,134,135],[69,80,82,90,91,117,134,135],[69,81,117,134,135],[69,74,77,82,117,134,135],[69,82,86,90,91,117,134,135],[69,86,117,134,135],[69,80,82,85,117,134,135,160],[69,74,79,82,89,117,134,135],[69,117,134,135,149],[69,77,82,103,117,134,135,165,167],[69,117,128,129,131,132,133,134,135,137,149,157,160,166,167,169,170,171,172,173,174,175,195,196,197,198],[69,117,134,135,171,172,173,174],[69,117,134,135,171,172,173],[69,117,134,135,171],[69,117,134,135,172],[69,117,134,135,169],[69,117,134,135,199,206]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7a3c8b952931daebdfc7a2897c53c0a1c73624593fa070e46bd537e64dcd20a","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"3cbad9a1ba4453443026ed38e4b8be018abb26565fa7c944376463ad9df07c41","impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"58647d85d0f722a1ce9de50955df60a7489f0593bf1a7015521efe901c06d770","impliedFormat":1},{"version":"73b5fa37db36eeac90c4d752e39586f1b57187400c4f5280fd05f16437287a45","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"a3fc63c0d7b031693f665f5494412ba4b551fe644ededccc0ab5922401079c95","impliedFormat":1},{"version":"80523c00b8544a2000ae0143e4a90a00b47f99823eb7926c1e03c494216fc363","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"746911b62b329587939560deb5c036aca48aece03147b021fa680223255d5183","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c8d3e5a18ba35629954e48c4cc8f11dc88224650067a172685c736b27a34a4dc","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2b55d426ff2b9087485e52ac4bc7cfafe1dc420fc76dad926cd46526567c501a","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"8a1a0d0a4a06a8d278947fcb66bf684f117bf147f89b06e50662d79a53be3e9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"9f55299850d4f0921e79b6bf344b47c420ce0f507b9dcf593e532b09ea7eeea1","impliedFormat":1},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1},{"version":"ee70b8037ecdf0de6c04f35277f253663a536d7e38f1539d270e4e916d225a3f","affectsGlobalScope":true,"impliedFormat":1},{"version":"a660aa95476042d3fdcc1343cf6bb8fdf24772d31712b1db321c5a4dcc325434","impliedFormat":1},{"version":"282f98006ed7fa9bb2cd9bdbe2524595cfc4bcd58a0bb3232e4519f2138df811","impliedFormat":1},{"version":"6222e987b58abfe92597e1273ad7233626285bc2d78409d4a7b113d81a83496b","impliedFormat":1},{"version":"cbe726263ae9a7bf32352380f7e8ab66ee25b3457137e316929269c19e18a2be","impliedFormat":1},{"version":"8b96046bf5fb0a815cba6b0880d9f97b7f3a93cf187e8dcfe8e2792e97f38f87","impliedFormat":99},{"version":"bacf2c84cf448b2cd02c717ad46c3d7fd530e0c91282888c923ad64810a4d511","affectsGlobalScope":true,"impliedFormat":1},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","impliedFormat":1},{"version":"52dcc257df5119fb66d864625112ce5033ac51a4c2afe376a0b299d2f7f76e4a","impliedFormat":1},{"version":"e5bab5f871ef708d52d47b3e5d0aa72a08ee7a152f33931d9a60809711a2a9a3","impliedFormat":1},{"version":"e16dc2a81595736024a206c7d5c8a39bfe2e6039208ef29981d0d95434ba8fcf","impliedFormat":1},{"version":"cc4a4903fb698ca1d961d4c10dce658aa3a479faf40509d526f122b044eaf6a4","impliedFormat":1},{"version":"19ee8416e6473ed6c7adb868fa796b5653cf0fa2a337658e677eaa0d134388c3","impliedFormat":1},{"version":"1328ab4e442614b28cdb3d4b414cf68325c0da0dca07287a338d0654b7a00261","impliedFormat":1},{"version":"a039dc21f045919f3cbee2ec13812cc6cc3eebc99dae4be00973230f468d19a6","impliedFormat":1},{"version":"3fbe57af01460e49dcd29df55d6931e1672bc6f1be0fb073d11410bc16f9037d","impliedFormat":1},{"version":"f760be449e8562ec5c09bb5187e8e1eabf3c113c0c58cddda53ef8c69f3e2131","impliedFormat":1},{"version":"44325ed13294fce6ab825b82947bbeed2611db7dad9d9135260192f375e5a189","impliedFormat":1},{"version":"e392e8fb5b514eafc585601c1d781485aa6dd6a320e75daf1064a4c6918a1b45","impliedFormat":1},{"version":"46e4a36e8ddbdfb4e7330e11c81c970dc8b218611df9183d39c41c5f8c653b55","impliedFormat":1},{"version":"370bde134aa8c2abc926d0e99d3a4d5d5dba65c6ee65459137e4f02670cbf841","impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","impliedFormat":1},{"version":"e3cf0611709328b449ec13f8c436712d62003620ce480139fae46ce001c2ee9f","impliedFormat":1},{"version":"8d369483f0c2b9ee388129cfdb6a43bc8112b377e86a41884bd06e19ce04f4c1","impliedFormat":99},{"version":"82e687ebd99518bc63ea04b0c3810fb6e50aa6942decd0ca6f7a56d9b9a212a6","impliedFormat":99},{"version":"7f698624bbbb060ece7c0e51b7236520ebada74b747d7523c7df376453ed6fea","impliedFormat":1},{"version":"8f07f2b6514744ac96e51d7cb8518c0f4de319471237ea10cf688b8d0e9d0225","impliedFormat":1},{"version":"257b83faa134d971c738a6b9e4c47e59bb7b23274719d92197580dd662bfafc3","impliedFormat":99},{"version":"556ccd493ec36c7d7cb130d51be66e147b91cc1415be383d71da0f1e49f742a9","impliedFormat":1},{"version":"b6d03c9cfe2cf0ba4c673c209fcd7c46c815b2619fd2aad59fc4229aaef2ed43","impliedFormat":1},{"version":"95aba78013d782537cc5e23868e736bec5d377b918990e28ed56110e3ae8b958","impliedFormat":1},{"version":"670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","impliedFormat":1},{"version":"13b77ab19ef7aadd86a1e54f2f08ea23a6d74e102909e3c00d31f231ed040f62","impliedFormat":1},{"version":"069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","impliedFormat":1},{"version":"26e0ffceb2198feb1ef460d5d14111c69ad07d44c5a67fd4bfeb74c969aa9afb","impliedFormat":99},{"version":"9d80e6340ceffc3f17678f9a6a179df7f73c20d1f5f28ab14729799a7d1191b6","signature":"4b96dd19fd2949d28ce80e913412b0026dc421e5bf6c31d87c7b5eb11b5753b4"}],"root":[207],"options":{"allowSyntheticDefaultImports":true,"composite":true,"module":99,"skipLibCheck":true,"strict":true,"target":9},"referencedMap":[[202,1],[200,2],[205,3],[201,1],[203,4],[204,1],[168,2],[114,5],[115,5],[116,6],[69,7],[117,8],[118,9],[119,10],[64,2],[67,11],[65,2],[66,2],[120,12],[121,13],[122,14],[123,15],[124,16],[125,17],[126,17],[127,18],[128,19],[129,20],[130,21],[70,2],[68,2],[131,22],[132,23],[133,24],[167,25],[134,26],[135,2],[136,27],[137,28],[138,29],[139,30],[140,31],[141,32],[142,33],[143,34],[144,35],[145,35],[146,36],[147,2],[148,37],[149,38],[151,39],[150,40],[152,41],[153,42],[154,43],[155,44],[156,45],[157,46],[158,47],[159,48],[160,49],[161,50],[162,51],[163,52],[164,53],[71,2],[72,2],[73,2],[111,54],[112,2],[113,2],[165,55],[166,56],[206,57],[175,2],[192,58],[190,59],[191,60],[179,61],[180,59],[187,62],[178,63],[183,64],[193,2],[184,65],[189,66],[195,67],[194,68],[177,69],[185,70],[186,71],[181,72],[188,58],[182,73],[170,74],[169,75],[176,2],[61,2],[62,2],[12,2],[10,2],[11,2],[16,2],[15,2],[2,2],[17,2],[18,2],[19,2],[20,2],[21,2],[22,2],[23,2],[24,2],[3,2],[25,2],[26,2],[4,2],[27,2],[31,2],[28,2],[29,2],[30,2],[32,2],[33,2],[34,2],[5,2],[35,2],[36,2],[37,2],[38,2],[6,2],[42,2],[39,2],[40,2],[41,2],[43,2],[7,2],[44,2],[49,2],[50,2],[45,2],[46,2],[47,2],[48,2],[8,2],[54,2],[51,2],[52,2],[53,2],[55,2],[9,2],[56,2],[63,2],[57,2],[58,2],[60,2],[59,2],[1,2],[14,2],[13,2],[89,76],[99,77],[88,76],[109,78],[80,79],[79,80],[108,81],[102,82],[107,83],[82,84],[96,85],[81,86],[105,87],[77,88],[76,81],[106,89],[78,90],[83,91],[84,2],[87,91],[74,2],[110,92],[100,93],[91,94],[92,95],[94,96],[90,97],[93,98],[103,81],[85,99],[86,100],[95,101],[75,102],[98,93],[97,91],[101,2],[104,103],[199,104],[196,105],[174,106],[172,107],[171,2],[173,108],[197,2],[198,109],[207,110]],"latestChangedDtsFile":"./vite.config.d.ts","version":"5.9.3"} \ No newline at end of file diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo new file mode 100644 index 0000000..25cea0e --- /dev/null +++ b/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/App.tsx","./src/api.ts","./src/auth.tsx","./src/main.tsx","./src/types.ts","./src/util.ts","./src/pages/AgentActivity.tsx","./src/pages/NotFound.tsx","./src/pages/TopicDetail.tsx","./src/pages/TopicList.tsx","./src/pages/Verdict.tsx"],"version":"5.9.3"} \ No newline at end of file diff --git a/vite.config.d.ts b/vite.config.d.ts new file mode 100644 index 0000000..340562a --- /dev/null +++ b/vite.config.d.ts @@ -0,0 +1,2 @@ +declare const _default: import("vite").UserConfig; +export default _default; diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..52cc30f --- /dev/null +++ b/vite.config.js @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +// Dev server proxies /api → the dialectic-backend so the SPA can run +// without CORS knobs at dev time. In prod the SPA + backend sit behind +// the same hostname (nginx rewrites /api/ → backend container). +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, + proxy: { + '/api': { + target: process.env.VITE_DIALECTIC_BACKEND ?? 'http://localhost:8090', + changeOrigin: false, + }, + }, + }, + build: { + outDir: 'dist', + sourcemap: true, + }, +}); diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..a90cf4f --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// Dev server proxies /api → the dialectic-backend so the SPA can run +// without CORS knobs at dev time. In prod the SPA + backend sit behind +// the same hostname (nginx rewrites /api/ → backend container). +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, + proxy: { + '/api': { + target: process.env.VITE_DIALECTIC_BACKEND ?? 'http://localhost:8090', + changeOrigin: false, + }, + }, + }, + build: { + outDir: 'dist', + sourcemap: true, + }, +});