diff --git a/.github/workflows/mirror-to-gitlab.yml b/.github/workflows/mirror-to-gitlab.yml index e1ee2766..b3636722 100644 --- a/.github/workflows/mirror-to-gitlab.yml +++ b/.github/workflows/mirror-to-gitlab.yml @@ -2,9 +2,13 @@ name: Mirror to GitLab on: push: - branches: ["**"] + branches: + - main + - dev delete: - branches: ["**"] + branches: + - main + - dev jobs: mirror: diff --git a/.gitleaks.toml b/.gitleaks.toml index f23b4621..b2ea72c3 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -45,6 +45,7 @@ regexes = [ paths = [ '''(^|/)backend-service/QUICKSTART\.md$''', '''(^|/)docs/gateway/API_GATEWAY_FULL_POLICY_SETUP\.md$''', + '''(^|/)docs/operations/API_GATEWAY_FULL_POLICY_SETUP\.md$''', '''(^|/)gateway/kong/kong\.yml$''', ] diff --git a/.pr_agent.toml b/.pr_agent.toml index 71badea5..01404aec 100644 --- a/.pr_agent.toml +++ b/.pr_agent.toml @@ -58,7 +58,6 @@ extra_instructions = """ - Clarify implementation choices """ -[config] # Model configuration (uncomment to customize) # model = "gpt-4-turbo-preview" # model = "anthropic/claude-3-5-sonnet-20241022" diff --git a/AGENTS.md b/AGENTS.md index 1feb5fc0..23ace47c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,3 +56,33 @@ Query before starting: `memory.search_nodes("LexiLingo")` to load prior context. - Have two agents edit the same file concurrently. - Skip the test-writer for any new public API endpoint. - Commit without running `flutter analyze` (Flutter) or `pytest` (backend). + + +Astryx v0.1.3 · 90+ components +CLI: run every command as `npx astryx ` (shown below as `astryx ...`). + +SETUP (once, in your app entry e.g. main.tsx) — without these, components render unstyled: + import "@astryxdesign/core/reset.css"; + import "@astryxdesign/core/astryx.css"; + +WORKFLOW — discover, don't guess. Before writing UI: +1. `astryx build ""` — START HERE: returns a kit (closest [page] + [block]s + [component]s). No args = full playbook. +2. `astryx template [--skeleton]` — scaffold the [page]/[block]s it named, or study their layout. Templates are reference code. +3. `astryx component ` — props + examples for every component you use. + +RULES: +- No
— components do all layout/spacing. Full page → AppShell; sidebar nav → SideNav. +- Frame first: pick the shell (AppShell / Layout+LayoutPanel) and budget regions in px BEFORE writing content (`astryx docs layout`). +- Dense data = rows (Table, List/Item) edge-to-edge — never Card-wrapped list items. Card = dashboard widgets, galleries, settings groups only. +- Status → StatusDot/Token; Badge only for counts and enumerated states, never decoration. +- Custom styling: component props first; else style/className with tokens — var(--color-*|--spacing-*|--radius-*). No raw hex/px. (No StyleX/Tailwind compiler here — don't use xstyle/utility classes.) +- Tokens for every value (`astryx docs tokens`). Brand/accent via `astryx theme` — never override --color-* in :root. + +MORE CLI: + search "" find any component / hook / doc / template / block + component --list 90+ components by category + template --list page + block recipes + docs color, elevation, icons, illustrations, layout, migration, motion, principles, shape, spacing, styling, theme, tokens, typography + swizzle eject component source for deep customization + upgrade --apply run after any @astryxdesign/core bump + diff --git a/CLAUDE.md b/CLAUDE.md index cb69ffe1..eff03b41 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,3 +74,33 @@ This project has a code-review-graph knowledge graph. Prefer: - Flutter: `Theme.of(context)` tokens only — never hardcode colors or sizes - Python: type hints on all function signatures - Async Python: `async def` + `await` everywhere in service/handler layer + + +Astryx v0.1.3 · 90+ components +CLI: run every command as `npx astryx ` (shown below as `astryx ...`). + +SETUP (once, in your app entry e.g. main.tsx) — without these, components render unstyled: + import "@astryxdesign/core/reset.css"; + import "@astryxdesign/core/astryx.css"; + +WORKFLOW — discover, don't guess. Before writing UI: +1. `astryx build ""` — START HERE: returns a kit (closest [page] + [block]s + [component]s). No args = full playbook. +2. `astryx template [--skeleton]` — scaffold the [page]/[block]s it named, or study their layout. Templates are reference code. +3. `astryx component ` — props + examples for every component you use. + +RULES: +- No
— components do all layout/spacing. Full page → AppShell; sidebar nav → SideNav. +- Frame first: pick the shell (AppShell / Layout+LayoutPanel) and budget regions in px BEFORE writing content (`astryx docs layout`). +- Dense data = rows (Table, List/Item) edge-to-edge — never Card-wrapped list items. Card = dashboard widgets, galleries, settings groups only. +- Status → StatusDot/Token; Badge only for counts and enumerated states, never decoration. +- Custom styling: component props first; else style/className with tokens — var(--color-*|--spacing-*|--radius-*). No raw hex/px. (No StyleX/Tailwind compiler here — don't use xstyle/utility classes.) +- Tokens for every value (`astryx docs tokens`). Brand/accent via `astryx theme` — never override --color-* in :root. + +MORE CLI: + search "" find any component / hook / doc / template / block + component --list 90+ components by category + template --list page + block recipes + docs color, elevation, icons, illustrations, layout, migration, motion, principles, shape, spacing, styling, theme, tokens, typography + swizzle eject component source for deep customization + upgrade --apply run after any @astryxdesign/core bump + diff --git a/Makefile b/Makefile index 863ef5d0..a163b45c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .DEFAULT_GOAL := help -APP_DIR := lexilingo_app +APP_DIR := flutter-app FLUTTER ?= flutter DART ?= dart diff --git a/admin-service/package-lock.json b/admin-service/package-lock.json index 2e8464cf..573d03f8 100644 --- a/admin-service/package-lock.json +++ b/admin-service/package-lock.json @@ -10,32 +10,32 @@ "dependencies": { "@gsap/react": "^2.1.2", "@hookform/resolvers": "^5.2.2", - "@tanstack/react-query": "^5.101.0", + "@tanstack/react-query": "^5.101.2", "@vercel/analytics": "^2.0.1", - "antd": "^6.4.4", - "axios": "^1.18.0", + "antd": "^6.5.0", + "axios": "^1.18.1", "dayjs": "^1.11.21", "dotenv": "^17.4.2", "gsap": "^3.15.0", - "lucide-react": "^1.18.0", + "lucide-react": "^1.23.0", "react": "^19.2.7", "react-dom": "^19.2.7", - "react-hook-form": "^7.79.0", - "react-router-dom": "^7.17.0", - "recharts": "^3.8.1", - "three": "^0.184.0", - "vercel": "^54.14.0", + "react-hook-form": "^7.81.0", + "react-router-dom": "^7.18.1", + "recharts": "^3.9.2", + "three": "^0.185.1", + "vercel": "^54.20.1", "zod": "^4.4.3", "zustand": "^5.0.14" }, "devDependencies": { - "@types/node": "^25.9.3", + "@types/node": "^25.9.4", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", - "@types/three": "^0.184.1", - "@vitejs/plugin-react": "^6.0.1", + "@types/three": "^0.185.0", + "@vitejs/plugin-react": "^6.0.3", "typescript": "^6.0.3", - "vite": "^8.0.16", + "vite": "^8.1.3", "vitest": "^4.1.8" } }, @@ -92,13 +92,13 @@ } }, "node_modules/@ant-design/icons": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.2.5.tgz", - "integrity": "sha512-0hKtoKqTjGFOndUyJLJmC9Cg6k4rEO7rLo6xmgbNJH+/ZX1C57RVals2v1j1knHl9n7Q+sBOveTvn931wLOCKw==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.3.2.tgz", + "integrity": "sha512-B6O5a5XJ4wjtNOfZejXYwHW5zvKV5gYkjGf11dHGLEbKn0ABDGndo41+gfIiXyTFhvESj4XTotuud33mUFid0g==", "license": "MIT", "dependencies": { "@ant-design/colors": "^8.0.1", - "@ant-design/icons-svg": "^4.4.2", + "@ant-design/icons-svg": "^4.5.0", "@rc-component/util": "^1.11.0", "clsx": "^2.1.1" }, @@ -111,9 +111,9 @@ } }, "node_modules/@ant-design/icons-svg": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", - "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.5.0.tgz", + "integrity": "sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==", "license": "MIT" }, "node_modules/@ant-design/react-slick": { @@ -208,7 +208,6 @@ "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" @@ -220,7 +219,6 @@ "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -231,7 +229,6 @@ "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -991,13 +988,13 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", - "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.2" + "@tybys/wasm-util": "^0.10.3" }, "funding": { "type": "github", @@ -1409,12 +1406,12 @@ } }, "node_modules/@rc-component/cascader": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.16.1.tgz", - "integrity": "sha512-wxLopwM+EBed0zNNGdnGE4coYoqcO+XD42fHgn+pDvO+XzhNFbdgSlSNXdKocIYqccvqgWvoxDPNb0OVRdi59A==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.17.0.tgz", + "integrity": "sha512-3cVNG0zrQF1PoXq262L3wGCU+/YLEC1mGSVHDl577dQmA0ZKkXFbY6nwyXo+beCcM7buo49t24jkr+QZdL7O8w==", "license": "MIT", "dependencies": { - "@rc-component/select": "~1.7.1", + "@rc-component/select": "~1.8.0", "@rc-component/tree": "~1.3.2", "@rc-component/util": "^1.11.1", "clsx": "^2.1.1" @@ -1483,12 +1480,12 @@ } }, "node_modules/@rc-component/dialog": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.9.0.tgz", - "integrity": "sha512-zbAAogkg4kkKum79sLE6M+vq1jSAW25zdkafrahgcTP9t9S//SD634Znd1A4c8F2Gc12ZKnehGLsVaaOvZzD2A==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.10.0.tgz", + "integrity": "sha512-eDukNlz9vNszAGv7i3zKXdxEd3wgVmNxuJijYt8zvTh17QwTu8KK/bdURRd/lU4qaMzhO1HKKmMrwOnkaw0BvQ==", "license": "MIT", "dependencies": { - "@rc-component/motion": "^1.1.3", + "@rc-component/motion": "^1.3.3", "@rc-component/portal": "^2.1.0", "@rc-component/util": "^1.9.0", "clsx": "^2.1.1" @@ -1594,13 +1591,13 @@ } }, "node_modules/@rc-component/mentions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.9.0.tgz", - "integrity": "sha512-WUwfFKDSOF5S9UPsNsXcLYtzjTxBGsftTXWRbZuxX6BYrsySISTnujfJNgaaQ6qVzaCDJ35QUkZKvsYxip1C5g==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.10.0.tgz", + "integrity": "sha512-CI1njYUVY0NjHtLhNoVmXlJyy568Sfep9Wsak6vmGjtT6uazx98djGYlCXz2xkHhEm73g91Y3MTvzUyE5avI7w==", "license": "MIT", "dependencies": { "@rc-component/input": "~1.3.0", - "@rc-component/menu": "~1.3.0", + "@rc-component/menu": "~1.4.0", "@rc-component/trigger": "^3.0.0", "@rc-component/util": "^1.3.0", "clsx": "^2.1.1" @@ -1611,9 +1608,9 @@ } }, "node_modules/@rc-component/menu": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.3.1.tgz", - "integrity": "sha512-pSZl9nBPgKgxN0aaW7NilIBEwWsc+43S+ulGdWAg9afak96dNOGWsGx0DLLBB1VQsAJvo6bQMTDzXoPlEHsBEw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.4.1.tgz", + "integrity": "sha512-3GsVRoQ4cnF/AoIQ4P+Z1haBfgfBPQfLT1RJY3Nu4DzOnheTslfCiGSPj7bv/cLj5sW5pHqN25dDXGP3JELAlQ==", "license": "MIT", "dependencies": { "@rc-component/motion": "^1.1.4", @@ -1704,9 +1701,9 @@ } }, "node_modules/@rc-component/pagination": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.3.0.tgz", - "integrity": "sha512-12ahTY+HPITg1L2bjWKXUqBJe/oOnpA2QsChdCjthqLVf/e19StiCsv8OLKpWoHbc+8PFEkNjRqRqrLoRBHjFw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.4.0.tgz", + "integrity": "sha512-CW1g7P9V8u+e8JQdUsl2RWg+GCsoee0mtJjZUCCxn/vb3jzOwDKm6hAdwddHCVBfWJ58eGUBZz3IvnU8rRktjw==", "license": "MIT", "dependencies": { "@rc-component/util": "^1.11.1", @@ -1718,15 +1715,15 @@ } }, "node_modules/@rc-component/picker": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.10.0.tgz", - "integrity": "sha512-vVOXP2RVWozwpERGUFAehVH1Jz6o/uRrAb9qSZm1LC+iJs8rvEwFo1bzz2jlOYV+uWwu0dIuG86tnDui14Ea0w==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.11.0.tgz", + "integrity": "sha512-6qXGKtoJvO8sUd17m5cyNEbEJub0zflCHnaZTBBmj63DPRZYc0WEHN8rp6hFSl+yMCJS/dJY5G+1fQ8bLCuD7A==", "license": "MIT", "dependencies": { "@rc-component/overflow": "^1.0.0", "@rc-component/resize-observer": "^1.0.0", "@rc-component/trigger": "^3.6.15", - "@rc-component/util": "^1.3.0", + "@rc-component/util": "^1.11.1", "clsx": "^2.1.1" }, "engines": { @@ -1849,9 +1846,9 @@ } }, "node_modules/@rc-component/select": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.7.1.tgz", - "integrity": "sha512-GZ1cMJk2xQh0VHyOQjjG8drYL4iu24NcbkXioUcReQOCUr+ub/3fmRonZe6cRPEZhWMbJdeHsqnEltogDaZ5Tg==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.8.2.tgz", + "integrity": "sha512-HQ9zuYqjfZTlcEMWlU1GAPBajd2OHIMVHyjZSGVTCVARwkfCgvXZMTEn0cduy3L+ejAKkaZluOQvxovZoaJaQw==", "license": "MIT", "dependencies": { "@rc-component/overflow": "^1.0.0", @@ -1869,9 +1866,9 @@ } }, "node_modules/@rc-component/slider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.0.1.tgz", - "integrity": "sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.1.1.tgz", + "integrity": "sha512-LSzgWGYDgeCDgR4r1XlU29gbYws6HpLnvJd/uMhLeW/vQgxldeR+Wb4uzHDCHiYEbr1bnEHWdjkPxjJRHxuiig==", "license": "MIT", "dependencies": { "@rc-component/util": "^1.3.0", @@ -1937,13 +1934,13 @@ } }, "node_modules/@rc-component/tabs": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.9.1.tgz", - "integrity": "sha512-6mY08Fce6aNOHuGsxbzT+f2ekgL9mg1cGGHkittMlVGymjGg+kGupu5v90sRxcUd/paRU9jclLLXtF/PkK1FUA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.11.0.tgz", + "integrity": "sha512-hA/drZYOVa/MMIb4M2fWf3yaTyTG4qVuIABmghvEhyfw2nBob5VTH69lMCDjSVKmgODjO6nWlCV+gVn3xBrj5Q==", "license": "MIT", "dependencies": { "@rc-component/dropdown": "~1.0.0", - "@rc-component/menu": "~1.3.0", + "@rc-component/menu": "~1.4.0", "@rc-component/motion": "^1.1.3", "@rc-component/resize-observer": "^1.0.0", "@rc-component/util": "^1.11.1", @@ -2011,14 +2008,14 @@ } }, "node_modules/@rc-component/tree-select": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.10.0.tgz", - "integrity": "sha512-E1U4pn2LAbXEhLJdzIzid7WYbIuFbkTIctuFoeC6weppf8UbPR3+YYB6/ay0c0ksand4gXMRQpa1Z60Auo7VJA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.11.0.tgz", + "integrity": "sha512-EhS0X0wtUhBfK4S5TlpSY3MR9ndPMGgujtt1PJW3Ej+ToAlnS/6ohYURtCoXBYGqazUwHmgQGVUDsfpVwhWPkg==", "license": "MIT", "dependencies": { - "@rc-component/select": "~1.7.0", - "@rc-component/tree": "~1.3.0", - "@rc-component/util": "^1.4.0", + "@rc-component/select": "~1.8.0", + "@rc-component/tree": "~1.3.2", + "@rc-component/util": "^1.11.1", "clsx": "^2.1.1" }, "peerDependencies": { @@ -2125,16 +2122,6 @@ } } }, - "node_modules/@reduxjs/toolkit/node_modules/immer": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", - "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, "node_modules/@renovatebot/pep440": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@renovatebot/pep440/-/pep440-4.2.1.tgz", @@ -2264,9 +2251,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", + "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", "cpu": [ "ppc64" ], @@ -2284,9 +2271,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", + "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", "cpu": [ "s390x" ], @@ -2406,9 +2393,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.7", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", - "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, @@ -2453,9 +2440,9 @@ "license": "MIT" }, "node_modules/@tanstack/query-core": { - "version": "5.101.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.0.tgz", - "integrity": "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==", + "version": "5.101.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.2.tgz", + "integrity": "sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw==", "license": "MIT", "funding": { "type": "github", @@ -2463,12 +2450,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.101.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.0.tgz", - "integrity": "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==", + "version": "5.101.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.2.tgz", + "integrity": "sha512-seDkr6kzGzX1okaaTtZPtgA688CDPlXUz1C6xSg0ESqn04Vuc8tlrYms1s3de+znBqhPVxFRfpAfUf+6XvfPWg==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.101.0" + "@tanstack/query-core": "5.101.2" }, "funding": { "type": "github", @@ -2525,9 +2512,9 @@ "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "license": "MIT", "optional": true, "dependencies": { @@ -2628,9 +2615,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", - "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", + "version": "25.9.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.4.tgz", + "integrity": "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g==", "dev": true, "license": "MIT", "dependencies": { @@ -2665,9 +2652,9 @@ "license": "MIT" }, "node_modules/@types/three": { - "version": "0.184.1", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.184.1.tgz", - "integrity": "sha512-6q4VdiqVsrTRqmk62/BnlcAvIrnDM0zf2ZDVKI5kZiniWrSaOHaQzmbp+BNzoggc/8tgW412pL//wZIxu2PPTA==", + "version": "0.185.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.185.0.tgz", + "integrity": "sha512-O2Uy8Cj4Nonr8dWUUbifMdPe8B0Mq7EdOHb89S4+kjUw/KhbjTZrUuYlrQ1bpUKG+EP9QJnN7qNxbHGlGoLHMA==", "dev": true, "license": "MIT", "dependencies": { @@ -2734,13 +2721,14 @@ } }, "node_modules/@vercel/backends": { - "version": "0.8.14", - "resolved": "https://registry.npmjs.org/@vercel/backends/-/backends-0.8.14.tgz", - "integrity": "sha512-jgepdZh7E4ameCUSt/b28ImFd9Bz1VX71lKwD/ThtZ7nGIwi5SJ9hwyURvH60c4rNW6nKxMD/iSOlcgQOfrK3A==", + "version": "0.8.21", + "resolved": "https://registry.npmjs.org/@vercel/backends/-/backends-0.8.21.tgz", + "integrity": "sha512-c0mKXgfxMyb44+kMGbNoQJomf37QAfd6yL/2imhAnAChR1Ddkxk6uwbrOC89LcG+QmN2ZoEFUSJdMInSfreztw==", "license": "Apache-2.0", "dependencies": { - "@vercel/build-utils": "13.30.0", + "@vercel/build-utils": "13.32.2", "@vercel/nft": "1.10.0", + "@vercel/static-config": "3.4.0", "execa": "3.2.0", "fs-extra": "11.1.0", "get-port": "5.1.1", @@ -2748,7 +2736,8 @@ "path-to-regexp": "8.3.0", "resolve.exports": "2.0.3", "rolldown": "1.0.0-rc.1", - "srvx": "0.8.9", + "srvx": "0.11.16", + "ts-morph": "12.0.0", "tsx": "4.21.0", "zod": "3.22.4" } @@ -2779,9 +2768,9 @@ } }, "node_modules/@vercel/build-utils": { - "version": "13.30.0", - "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.30.0.tgz", - "integrity": "sha512-fLIa8cpELsSoWbcxshaqegwlTCfKnbgsDmA6uIb+oA797xvSmYkPu5a781aRYCRdghgyOZF7NCSVgtZjHjunnQ==", + "version": "13.32.2", + "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.32.2.tgz", + "integrity": "sha512-XgATgMjt2NHF6HHo1wii6f43qlWjaFx3o+9L7aOUPLQgqwgYp2BGwuuBjg8U6sNgut1QsmFBMvNqTAUBvack9Q==", "license": "Apache-2.0", "dependencies": { "@vercel/python-analysis": "0.11.1", @@ -2790,12 +2779,12 @@ } }, "node_modules/@vercel/cervel": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/@vercel/cervel/-/cervel-0.1.22.tgz", - "integrity": "sha512-YbL2AosH3FaBLJZu4GYdVMkFv+5cbWwwSrRBTGN52odynQUudTUQfbc7rCzPyNQLdk2QC4w2IDYRD+nqgojhVg==", + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/@vercel/cervel/-/cervel-0.1.29.tgz", + "integrity": "sha512-tjwW6bj1g9qwOO0y/3WTGiarvyWmsWOtLkPB0esFmnscMZCxRqzNh0WJqrbbWxMdC5A2LXzDDHuZ4sXpRg4Tqw==", "license": "Apache-2.0", "dependencies": { - "@vercel/backends": "0.8.14" + "@vercel/backends": "0.8.21" }, "bin": { "cervel": "bin/cervel.mjs" @@ -2850,6 +2839,12 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/@vercel/container": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@vercel/container/-/container-0.0.4.tgz", + "integrity": "sha512-lbUnpg2Iawoi0oDlFE6Se43FNUd2B2nMnCXD9Af7NsysMENFaNu2ajzgsocTxt+O6djUgW9IdBnIRHgnHs+vZQ==", + "license": "Apache-2.0" + }, "node_modules/@vercel/detect-agent": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@vercel/detect-agent/-/detect-agent-1.2.3.tgz", @@ -2860,12 +2855,12 @@ } }, "node_modules/@vercel/elysia": { - "version": "0.1.93", - "resolved": "https://registry.npmjs.org/@vercel/elysia/-/elysia-0.1.93.tgz", - "integrity": "sha512-36yzZJVU9o7/CcH1TbqeGTRwBePDaCEzv1CYfS183HdLXzsD+pOjOkTqZOBJu7h9aHpJFCCau55Ed0xOQC2jhA==", + "version": "0.1.98", + "resolved": "https://registry.npmjs.org/@vercel/elysia/-/elysia-0.1.98.tgz", + "integrity": "sha512-VJMPfvslPFP5lg7oNN+BZEXHdaejmI25OEyco5OvZWpR80MYpTPil+2+hbkoR6lN3kVUC9/xahtgsdfouOvNbA==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.8.17", + "@vercel/node": "5.8.22", "@vercel/static-config": "3.4.0" } }, @@ -2876,14 +2871,14 @@ "license": "Apache-2.0" }, "node_modules/@vercel/express": { - "version": "0.1.105", - "resolved": "https://registry.npmjs.org/@vercel/express/-/express-0.1.105.tgz", - "integrity": "sha512-F+QRQxWWKdeztW4MKPkzXl/Ajc8j1qOffCp1tt8+YPaNiqZtxpn7FjIYRXHbfR1aepfaEkDRDD7o2qxmmaUU5w==", + "version": "0.1.112", + "resolved": "https://registry.npmjs.org/@vercel/express/-/express-0.1.112.tgz", + "integrity": "sha512-Xpw1/+G/NpWYzBh3HqT+npYKo+yrLhSKC+hekBYCoddfDjSTevF1hQSbdEo7oHbZbgQo7h5Rb2vWVQ404zSmkA==", "license": "Apache-2.0", "dependencies": { - "@vercel/cervel": "0.1.22", + "@vercel/cervel": "0.1.29", "@vercel/nft": "1.10.0", - "@vercel/node": "5.8.17", + "@vercel/node": "5.8.22", "@vercel/static-config": "3.4.0", "fs-extra": "11.1.0", "path-to-regexp": "8.3.0", @@ -2901,12 +2896,12 @@ } }, "node_modules/@vercel/fastify": { - "version": "0.1.96", - "resolved": "https://registry.npmjs.org/@vercel/fastify/-/fastify-0.1.96.tgz", - "integrity": "sha512-4Uq6GVmi0ksQxIHp4FDUhgoz47uacxZm42XT04SK5gzX5v5UmZXWZPprZjkwwk/2gmP/rF2qKqJ0nu6OsI+yDw==", + "version": "0.1.101", + "resolved": "https://registry.npmjs.org/@vercel/fastify/-/fastify-0.1.101.tgz", + "integrity": "sha512-+oUCBpSs8ANQmdQB6CTCWTfdxKutCzKEMkMtUJVLVnO0eaiONADkZeaFjjZwMl4Cg6z49ifhJ3QVJD1IWwIwsA==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.8.17", + "@vercel/node": "5.8.22", "@vercel/static-config": "3.4.0" } }, @@ -3020,42 +3015,42 @@ } }, "node_modules/@vercel/gatsby-plugin-vercel-builder": { - "version": "2.2.19", - "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.2.19.tgz", - "integrity": "sha512-dA1ZWZHruRjFbLToqjcPHUyh5J7/Cl3qGFDge87ghAInuy/aXfalApzFbw61OIopGx0SSsIYi/8kbpJ+SbQ6ZQ==", + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.2.24.tgz", + "integrity": "sha512-+rqPToquzHOnSsist6FylJY++Z7ScRvl87gJshGhYYXJSCMki5zquX+D8wliOb5H0SW3KGwmTNQ10gUJt52c8w==", "license": "Apache-2.0", "dependencies": { "@sinclair/typebox": "0.25.24", - "@vercel/build-utils": "13.30.0", + "@vercel/build-utils": "13.32.2", "esbuild": "0.27.0", "etag": "1.8.1", "fs-extra": "11.1.0" } }, "node_modules/@vercel/go": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/@vercel/go/-/go-3.9.0.tgz", - "integrity": "sha512-vGbEwckvtr9UXKDz3qEHUE7VHJGG/9VQLuScgHytksfZyX8dzvNPgEuBKBbhPaMwua8pUCZKQqNxFM3Dul+bLA==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@vercel/go/-/go-3.10.2.tgz", + "integrity": "sha512-ZJqx1GzD1qqMkI92jEHp04zCwbZ7tNwcgI58e7t8HiYvILwCZ4Rvq0hfjTjsk+0Zn8A369qs4eAHvCYu38uB5A==", "license": "Apache-2.0" }, "node_modules/@vercel/h3": { - "version": "0.1.102", - "resolved": "https://registry.npmjs.org/@vercel/h3/-/h3-0.1.102.tgz", - "integrity": "sha512-UnHFLBemE2UfvQ+w2z2zxGcvyiShQ8eMdk3ag2sV71wbyatnXwmio8HOy//B2ZIEQNEid5gW2HjWOXLtrR+E2Q==", + "version": "0.1.107", + "resolved": "https://registry.npmjs.org/@vercel/h3/-/h3-0.1.107.tgz", + "integrity": "sha512-Vqo5AvE5ku3MIWAi56KlYbLIg/PfX7Xxp94xbNa7J7dbREbO8PZWD0QLKvOw1uGqyoa5YqNZZ3RdtCfDq9Z6oA==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.8.17", + "@vercel/node": "5.8.22", "@vercel/static-config": "3.4.0" } }, "node_modules/@vercel/hono": { - "version": "0.2.96", - "resolved": "https://registry.npmjs.org/@vercel/hono/-/hono-0.2.96.tgz", - "integrity": "sha512-h4G0GSo5omhXS1iqimIcekcW7THEAnUGd6eioDNnSqwS3L0CGEvvTPUekJDyJcybsqnKslH1XTZA40f4b9MPHg==", + "version": "0.2.101", + "resolved": "https://registry.npmjs.org/@vercel/hono/-/hono-0.2.101.tgz", + "integrity": "sha512-sm24D5OUJEo9cuvPHeVgouuc0zhZdIhX28BdtvIek5b7FGmvOE8VExOhteWU5YufhJR0M1y1zbbVC2ULr/arHw==", "license": "Apache-2.0", "dependencies": { "@vercel/nft": "1.10.0", - "@vercel/node": "5.8.17", + "@vercel/node": "5.8.22", "@vercel/static-config": "3.4.0", "fs-extra": "11.1.0", "path-to-regexp": "8.3.0", @@ -3083,29 +3078,29 @@ } }, "node_modules/@vercel/koa": { - "version": "0.1.76", - "resolved": "https://registry.npmjs.org/@vercel/koa/-/koa-0.1.76.tgz", - "integrity": "sha512-6MuBn5RM2nHKfHKeBqbs3aaFJlBFDhOZgpC1ubLQG2B/EvnAfljB7FPIKCl1SwANAHisX7vQACinMedCbFdi+Q==", + "version": "0.1.81", + "resolved": "https://registry.npmjs.org/@vercel/koa/-/koa-0.1.81.tgz", + "integrity": "sha512-sAavmnlTM9LhYPWAgIvWuKgpeozk/tB4ZPLe/Fl7qE6EJiN3sCjtaSatXhla5w1Zxy0veBI5u0LCcjref4Oxag==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.8.17", + "@vercel/node": "5.8.22", "@vercel/static-config": "3.4.0" } }, "node_modules/@vercel/nestjs": { - "version": "0.2.97", - "resolved": "https://registry.npmjs.org/@vercel/nestjs/-/nestjs-0.2.97.tgz", - "integrity": "sha512-KM9uBmC+a10EPJgtm0B/vfGsTaUEzxKkihTyaQ4PhSUQHJOABTv6TBpGKtwIM5P1vMLKUg9R0A2SUvkKdaALvA==", + "version": "0.2.102", + "resolved": "https://registry.npmjs.org/@vercel/nestjs/-/nestjs-0.2.102.tgz", + "integrity": "sha512-CMDoq4dFBI1rLmAuya7UPXfcg7h0y1xdNVCeFhzlZOfp7EF1+PFkn3dVF8tIX46yiUFCz94weiLHSAoh+mRD4g==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.8.17", + "@vercel/node": "5.8.22", "@vercel/static-config": "3.4.0" } }, "node_modules/@vercel/next": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@vercel/next/-/next-4.19.0.tgz", - "integrity": "sha512-9B7juRNydQ9MU7xT8OE254ymhVrNdcOKlXrZ4anem8YEa34KvXjLczQhZEPdvTz9JaCXpYiEEGmd/D3t2oJ+Uw==", + "version": "4.20.2", + "resolved": "https://registry.npmjs.org/@vercel/next/-/next-4.20.2.tgz", + "integrity": "sha512-mroXgsjkmI6i5m1dMRmFNHO8wExhhgOzFRHT0pg195uOVyuNU2JxBCtU47FowbcnLbrAmIfStWkPSHLwPRh6FA==", "license": "Apache-2.0", "dependencies": { "@vercel/nft": "1.10.0" @@ -3138,16 +3133,16 @@ } }, "node_modules/@vercel/node": { - "version": "5.8.17", - "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.8.17.tgz", - "integrity": "sha512-n2DVzblqS43LTs4BV1iLfx8tjjrTegcSsyDgRzn70h6tQePYWjl+h0bU3X1stpITZtaYJSZYwVevuX1BJNZHHg==", + "version": "5.8.22", + "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.8.22.tgz", + "integrity": "sha512-WfciIhDVh9RwFmvrWp7FAdYCBPV94bZvIo4JbaHqZbvBJ2Bmnv/Pgj1fTjjLKfnKTbLJy+8HN1FXB8KYDnwGZQ==", "license": "Apache-2.0", "dependencies": { "@edge-runtime/node-utils": "2.3.0", "@edge-runtime/primitives": "4.1.0", "@edge-runtime/vm": "3.2.0", "@types/node": "20.11.0", - "@vercel/build-utils": "13.30.0", + "@vercel/build-utils": "13.32.2", "@vercel/error-utils": "2.2.0", "@vercel/nft": "1.10.0", "@vercel/static-config": "3.4.0", @@ -3264,9 +3259,9 @@ "license": "MIT" }, "node_modules/@vercel/python": { - "version": "6.45.1", - "resolved": "https://registry.npmjs.org/@vercel/python/-/python-6.45.1.tgz", - "integrity": "sha512-2ay12lt3Cu6xS6qiaKNNwALRKxI1AE3rtHDfFOP+BU4qJ2U5k7JmPxlDuU+CXKY+chQClQM2orgThJWeMoeQkw==", + "version": "6.47.3", + "resolved": "https://registry.npmjs.org/@vercel/python/-/python-6.47.3.tgz", + "integrity": "sha512-h8j5Fln+GYPkWCIXZdhUq1ibFQofjeiV6LMgeY31NEFn/lolz/UxDwqSZswM9H1aP/5lH54VCe/iFE4dMlSWiQ==", "license": "Apache-2.0", "dependencies": { "@vercel/python-analysis": "0.11.1" @@ -3352,18 +3347,19 @@ "license": "MIT" }, "node_modules/@vercel/ruby": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@vercel/ruby/-/ruby-2.4.0.tgz", - "integrity": "sha512-YI7Amyf09hHZWOqDHgJO92XcKh6Pye8rrmJFhlP6euG3o6QjoZzJj7Z2WzjSrDRGMewzEK4uz2+CbNpNS7gLog==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@vercel/ruby/-/ruby-2.5.1.tgz", + "integrity": "sha512-e23M7pfORQyyxSUmeQ8PqEnyLJ4swhtNrnyctU5E02UCaUYFm5YAdNfdPYRP4It874H4Wyvm+LV8u7CPBrfcPA==", "license": "Apache-2.0" }, "node_modules/@vercel/rust": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@vercel/rust/-/rust-1.3.0.tgz", - "integrity": "sha512-z1X0z1NM+ISunm/scgRpuENNwcKlh7DjXn0QvKE0n10DcaYqxkBQVK8iRA9X05xSK9AzPlnB9DHdGKiXZO5buw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vercel/rust/-/rust-1.4.0.tgz", + "integrity": "sha512-/zRJtaf3pvdxZ7+tShmIo8StdbkCSBDcY7Gy7LOwNTIxm6GU9vkS+BlCwJHkOu6qNkbcSRH6ZQIeo4vNmqzKdQ==", "license": "Apache-2.0", "dependencies": { "execa": "5", + "get-port": "5.1.1", "smol-toml": "1.5.2" } }, @@ -3470,13 +3466,13 @@ } }, "node_modules/@vercel/static-build": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.10.3.tgz", - "integrity": "sha512-+IipWidX6mL1Zck8Khw4wSXHPSgDfkapvcuWA9wSCmhnk2GL2Iuu+6vab+g8UPahv+QDUYm8Tphhadg6dICZnA==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.11.4.tgz", + "integrity": "sha512-Cyjsg4yfiSSwRSq4TaYnxDzh3Cembx6hlUCKD0z3QCvEYFjTR3j1bFKk8haekwSB16qg25k9fiPEZMP7B/Azag==", "license": "Apache-2.0", "dependencies": { "@vercel/gatsby-plugin-vercel-analytics": "1.0.11", - "@vercel/gatsby-plugin-vercel-builder": "2.2.19", + "@vercel/gatsby-plugin-vercel-builder": "2.2.24", "@vercel/static-config": "3.4.0", "ts-morph": "12.0.0" } @@ -3493,13 +3489,13 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", - "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "1.0.0-rc.7" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -3703,51 +3699,51 @@ } }, "node_modules/antd": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/antd/-/antd-6.4.4.tgz", - "integrity": "sha512-lgPz4KhfhiYddV/qPYo0ieqWimCVgV2OQF72mbeGNixE753JWNnmEc7UNGy08wBS/zZ7hxrmX0pc5aX7EUaIIg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/antd/-/antd-6.5.0.tgz", + "integrity": "sha512-9zbVc9UukfGuqCvIAov01nlpDQWfARNmZQyt21ZhqLX7ilXmi4cdkp12xA48WEmXRXwZvno8A03qQuGE9JG8fg==", "license": "MIT", "dependencies": { "@ant-design/colors": "^8.0.1", "@ant-design/cssinjs": "^2.1.2", "@ant-design/cssinjs-utils": "^2.1.2", "@ant-design/fast-color": "^3.0.1", - "@ant-design/icons": "^6.2.5", + "@ant-design/icons": "^6.3.1", "@ant-design/react-slick": "~2.0.0", "@babel/runtime": "^7.29.2", - "@rc-component/cascader": "~1.16.1", + "@rc-component/cascader": "~1.17.0", "@rc-component/checkbox": "~2.0.0", "@rc-component/collapse": "~1.2.0", "@rc-component/color-picker": "~3.1.1", - "@rc-component/dialog": "~1.9.0", + "@rc-component/dialog": "~1.10.0", "@rc-component/drawer": "~1.4.2", "@rc-component/dropdown": "~1.0.2", - "@rc-component/form": "~1.8.3", + "@rc-component/form": "~1.8.5", "@rc-component/image": "~1.9.0", "@rc-component/input": "~1.3.1", "@rc-component/input-number": "~1.6.2", - "@rc-component/mentions": "~1.9.0", - "@rc-component/menu": "~1.3.1", + "@rc-component/mentions": "~1.10.0", + "@rc-component/menu": "~1.4.1", "@rc-component/motion": "^1.3.3", "@rc-component/mutate-observer": "^2.0.1", "@rc-component/notification": "~2.0.7", - "@rc-component/pagination": "~1.3.0", - "@rc-component/picker": "~1.10.0", + "@rc-component/pagination": "~1.4.0", + "@rc-component/picker": "~1.11.0", "@rc-component/progress": "~1.0.2", "@rc-component/qrcode": "~2.0.0", "@rc-component/rate": "~1.0.1", "@rc-component/resize-observer": "^1.1.2", "@rc-component/segmented": "~1.3.0", - "@rc-component/select": "~1.7.1", - "@rc-component/slider": "~1.0.1", + "@rc-component/select": "~1.8.2", + "@rc-component/slider": "~1.1.1", "@rc-component/steps": "~1.2.2", "@rc-component/switch": "~1.0.3", "@rc-component/table": "~1.10.2", - "@rc-component/tabs": "~1.9.1", + "@rc-component/tabs": "~1.11.0", "@rc-component/tooltip": "~1.4.0", "@rc-component/tour": "~2.4.0", "@rc-component/tree": "~1.3.2", - "@rc-component/tree-select": "~1.10.0", + "@rc-component/tree-select": "~1.11.0", "@rc-component/trigger": "^3.9.1", "@rc-component/upload": "~1.1.1", "@rc-component/util": "^1.11.1", @@ -3833,9 +3829,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", - "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", @@ -4109,12 +4105,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/cookie-es": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-2.0.1.tgz", - "integrity": "sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA==", - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5091,9 +5081,9 @@ } }, "node_modules/immer": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", - "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.9.tgz", + "integrity": "sha512-sc/z0Cyti70bZa0ZU4sWfAElfovFb9Ni8tArJZLuklYWxegPiK3pDOql1Rq5H0FIRAW9LSQRG6OX4KqBldbhBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -5569,9 +5559,9 @@ } }, "node_modules/lucide-react": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.18.0.tgz", - "integrity": "sha512-LZDb7H/0YfM+RJncD0hDQRCAu+vSGODqpe35TuVI8EuXaRjkczbsx7p8dY4J87F/MUSj6bpYqeI8nw8qXaAdmA==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.23.0.tgz", + "integrity": "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -5763,9 +5753,9 @@ "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==", + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", "dev": true, "funding": [ { @@ -6076,9 +6066,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", "dev": true, "funding": [ { @@ -6242,9 +6232,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.79.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.79.0.tgz", - "integrity": "sha512-mhYp/MTmXvzYX6AJcJVko0rktoIhhmRnEouObj4wF5i/tCttgJvnp1+9wRkpITZjDTqpo4IOSJqu0dBlPlV/Lw==", + "version": "7.81.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.81.0.tgz", + "integrity": "sha512-ocbmr2p5KBMoAfj4WCUvped33lVi1Kd5DuDUvQDnB6VEAacOjPI/jMbtDdbhco4y9ct4xUuCmMY0b/C9L0QHjw==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -6288,9 +6278,9 @@ } }, "node_modules/react-router": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz", - "integrity": "sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==", + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -6310,12 +6300,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.17.0.tgz", - "integrity": "sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw==", + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", + "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", "license": "MIT", "dependencies": { - "react-router": "7.17.0" + "react-router": "7.18.1" }, "engines": { "node": ">=20.0.0" @@ -6339,9 +6329,9 @@ } }, "node_modules/recharts": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", - "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.9.2.tgz", + "integrity": "sha512-G4fy+Pk46RaXgwWMh+Nzhyo/lbFAVqXo9gtetlyehe6Ehge9CsgDuOTwQDD+i1+llaLktNBiNq4bhnGlDRXFtw==", "license": "MIT", "workspaces": [ "www" @@ -6352,9 +6342,9 @@ "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", - "immer": "^10.1.1", + "immer": "^11.1.8", "react-redux": "8.x.x || 9.x.x", - "reselect": "5.1.1", + "reselect": "5.2.0", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" @@ -6393,9 +6383,9 @@ } }, "node_modules/reselect": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", - "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", "license": "MIT" }, "node_modules/resolve-from": { @@ -6676,13 +6666,10 @@ } }, "node_modules/srvx": { - "version": "0.8.9", - "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.8.9.tgz", - "integrity": "sha512-wYc3VLZHRzwYrWJhkEqkhLb31TI0SOkfYZDkUhXdp3NoCnNS0FqajiQszZZjfow/VYEuc6Q5sZh9nM6kPy2NBQ==", + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.16.tgz", + "integrity": "sha512-bp07zRuycfTY43IjAvvTFnmnJi8ikW0VFiHwOhhYcVW/L4xQ1XY4PAd4Nuum1rsA17C39zL7x+CDhrn5AL32Rw==", "license": "MIT", - "dependencies": { - "cookie-es": "^2.0.0" - }, "bin": { "srvx": "bin/srvx.mjs" }, @@ -6836,9 +6823,9 @@ } }, "node_modules/three": { - "version": "0.184.0", - "resolved": "https://registry.npmjs.org/three/-/three-0.184.0.tgz", - "integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==", + "version": "0.185.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.185.1.tgz", + "integrity": "sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==", "license": "MIT" }, "node_modules/throttle-debounce": { @@ -7530,44 +7517,45 @@ } }, "node_modules/vercel": { - "version": "54.14.2", - "resolved": "https://registry.npmjs.org/vercel/-/vercel-54.14.2.tgz", - "integrity": "sha512-XMUBq4mEYniGGIHTAIY7Egop120x6d2NLZ9wezk3032VfKRMsB3q2UYFiZduWETG/TwFSddlNJ6UI8jBskleog==", + "version": "54.20.1", + "resolved": "https://registry.npmjs.org/vercel/-/vercel-54.20.1.tgz", + "integrity": "sha512-Mssq8tAwjTstAi25XkFMG+nCjwCqvVLmRgcGQwSxwpptrLN6KBPDd+hAcaMXZtlxoocqdr3aa+bRqw0P9H2Y3g==", "license": "Apache-2.0", "dependencies": { - "@vercel/backends": "0.8.14", + "@vercel/backends": "0.8.21", "@vercel/blob": "2.4.0", - "@vercel/build-utils": "13.30.0", + "@vercel/build-utils": "13.32.2", "@vercel/cli-auth": "0.3.0", "@vercel/cli-config": "0.2.0", + "@vercel/container": "0.0.4", "@vercel/detect-agent": "1.2.3", - "@vercel/elysia": "0.1.93", - "@vercel/express": "0.1.105", - "@vercel/fastify": "0.1.96", + "@vercel/elysia": "0.1.98", + "@vercel/express": "0.1.112", + "@vercel/fastify": "0.1.101", "@vercel/fun": "1.3.0", - "@vercel/go": "3.9.0", - "@vercel/h3": "0.1.102", - "@vercel/hono": "0.2.96", + "@vercel/go": "3.10.2", + "@vercel/h3": "0.1.107", + "@vercel/hono": "0.2.101", "@vercel/hydrogen": "1.4.0", - "@vercel/koa": "0.1.76", - "@vercel/nestjs": "0.2.97", - "@vercel/next": "4.19.0", - "@vercel/node": "5.8.17", + "@vercel/koa": "0.1.81", + "@vercel/nestjs": "0.2.102", + "@vercel/next": "4.20.2", + "@vercel/node": "5.8.22", "@vercel/prepare-flags-definitions": "0.3.0", - "@vercel/python": "6.45.1", + "@vercel/python": "6.47.3", "@vercel/redwood": "2.5.0", "@vercel/remix-builder": "5.9.1", - "@vercel/ruby": "2.4.0", - "@vercel/rust": "1.3.0", - "@vercel/static-build": "2.10.3", + "@vercel/ruby": "2.5.1", + "@vercel/rust": "1.4.0", + "@vercel/static-build": "2.11.4", "chokidar": "4.0.0", "esbuild": "0.27.0", - "form-data": "^4.0.0", "jose": "5.9.6", "luxon": "^3.4.0", "proxy-agent": "6.4.0", "sandbox": "3.1.2", "smol-toml": "1.5.2", + "undici": "5.29.0", "zod": "4.1.11" }, "bin": { @@ -7578,6 +7566,18 @@ "node": ">= 18" } }, + "node_modules/vercel/node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/vercel/node_modules/zod": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", @@ -7610,16 +7610,16 @@ } }, "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz", + "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", + "postcss": "^8.5.16", + "rolldown": "~1.1.3", "tinyglobby": "^0.2.17" }, "bin": { @@ -7636,7 +7636,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -7687,44 +7687,10 @@ } } }, - "node_modules/vite/node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/vite/node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/vite/node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/vite/node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "version": "0.138.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz", + "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==", "dev": true, "license": "MIT", "funding": { @@ -7732,9 +7698,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", + "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", "cpu": [ "arm64" ], @@ -7749,9 +7715,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", + "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", "cpu": [ "arm64" ], @@ -7766,9 +7732,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", + "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", "cpu": [ "x64" ], @@ -7783,9 +7749,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", + "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", "cpu": [ "x64" ], @@ -7800,9 +7766,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", + "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", "cpu": [ "arm" ], @@ -7817,9 +7783,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", + "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", "cpu": [ "arm64" ], @@ -7837,9 +7803,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", + "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", "cpu": [ "arm64" ], @@ -7857,9 +7823,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", + "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", "cpu": [ "x64" ], @@ -7877,9 +7843,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", + "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", "cpu": [ "x64" ], @@ -7897,9 +7863,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", + "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", "cpu": [ "arm64" ], @@ -7914,9 +7880,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", + "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", "cpu": [ "wasm32" ], @@ -7924,18 +7890,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/vite/node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", + "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", "cpu": [ "arm64" ], @@ -7950,9 +7916,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz", + "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==", "cpu": [ "x64" ], @@ -7966,21 +7932,14 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/vite/node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, "node_modules/vite/node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz", + "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.138.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -7990,21 +7949,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" + "@rolldown/binding-android-arm64": "1.1.4", + "@rolldown/binding-darwin-arm64": "1.1.4", + "@rolldown/binding-darwin-x64": "1.1.4", + "@rolldown/binding-freebsd-x64": "1.1.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", + "@rolldown/binding-linux-arm64-gnu": "1.1.4", + "@rolldown/binding-linux-arm64-musl": "1.1.4", + "@rolldown/binding-linux-ppc64-gnu": "1.1.4", + "@rolldown/binding-linux-s390x-gnu": "1.1.4", + "@rolldown/binding-linux-x64-gnu": "1.1.4", + "@rolldown/binding-linux-x64-musl": "1.1.4", + "@rolldown/binding-openharmony-arm64": "1.1.4", + "@rolldown/binding-wasm32-wasi": "1.1.4", + "@rolldown/binding-win32-arm64-msvc": "1.1.4", + "@rolldown/binding-win32-x64-msvc": "1.1.4" } }, "node_modules/vitest": { diff --git a/admin-service/src/App.tsx b/admin-service/src/App.tsx index e08256e0..5c73ebb4 100644 --- a/admin-service/src/App.tsx +++ b/admin-service/src/App.tsx @@ -10,7 +10,8 @@ import { LayoutDashboard, Users, BookOpen, Layers, FileText, PenTool, BarChart3, Languages, Trophy, ShoppingBag, Megaphone, ScrollText, Activity, Settings, - Shield, Database, Bot, ArrowRight, ArrowLeft, MessageSquare, Swords, Bell + Shield, Database, Bot, ArrowRight, ArrowLeft, MessageSquare, Swords, Bell, + ClipboardCheck, Gauge } from "lucide-react"; // Lazy-loaded components @@ -31,9 +32,11 @@ const AdsPage = lazy(() => import("./pages/AdsPage").then(m => ({ default: m.Ads const LogsPage = lazy(() => import("./pages/LogsPage").then(m => ({ default: m.LogsPage }))); const MonitoringPage = lazy(() => import("./pages/MonitoringPage").then(m => ({ default: m.MonitoringPage }))); const ContentLabPage = lazy(() => import("./pages/ContentLabPage").then(m => ({ default: m.ContentLabPage }))); +const ContentQaQueuePage = lazy(() => import("./pages/ContentQaQueuePage").then(m => ({ default: m.ContentQaQueuePage }))); const DatabasePage = lazy(() => import("./pages/DatabasePage").then(m => ({ default: m.DatabasePage }))); const AiModelsPage = lazy(() => import("./pages/AiModelsPage").then(m => ({ default: m.AiModelsPage }))); const ContentAnalyticsPage = lazy(() => import("./pages/ContentAnalyticsPage").then(m => ({ default: m.ContentAnalyticsPage }))); +const AiQualityDashboardPage = lazy(() => import("./pages/AiQualityDashboardPage").then(m => ({ default: m.AiQualityDashboardPage }))); const SystemSettingsPage = lazy(() => import("./pages/SystemSettingsPage").then(m => ({ default: m.SystemSettingsPage }))); const AdminManagementPage = lazy(() => import("./pages/AdminManagementPage").then(m => ({ default: m.AdminManagementPage }))); const AiChatSettingsPage = lazy(() => import("./pages/AiChatSettingsPage").then(m => ({ default: m.AiChatSettingsPage }))); @@ -60,7 +63,9 @@ const AppRoutes = () => { { to: "/admin/lessons", label: t.nav.lessons, icon: }, { to: "/admin/topics", label: t.nav.topics, icon: }, { to: "/admin/content-lab", label: t.nav.grammarTest, icon: }, + { to: "/admin/content-qa", label: t.nav.contentQaQueue, icon: }, { to: "/admin/content-analytics", label: t.nav.contentAnalytics, icon: }, + { to: "/admin/ai-quality", label: t.nav.aiQuality, icon: }, { to: "/admin/vocabulary", label: t.nav.vocabulary, icon: }, { to: "/admin/achievements", label: t.nav.achievements, icon: }, { to: "/admin/shop", label: t.nav.shop, icon: }, @@ -104,7 +109,9 @@ const AppRoutes = () => { } /> } /> } /> + } /> } /> + } /> } /> } /> } /> diff --git a/admin-service/src/lib/adminApi.ts b/admin-service/src/lib/adminApi.ts index d30dbfc7..ab963405 100644 --- a/admin-service/src/lib/adminApi.ts +++ b/admin-service/src/lib/adminApi.ts @@ -280,6 +280,8 @@ export const uploadBadgeImage = async (file: File): Promise; + export type ShopItemType = { id: string; name: string; @@ -289,6 +291,7 @@ export type ShopItemType = { is_available: boolean; stock_quantity?: number | null; icon_url?: string | null; + effects?: ShopItemEffects | null; }; export const listShopItems = async (includeUnavailable = true) => @@ -303,17 +306,19 @@ export const createShopItem = async (params: { price_gems: number; is_available?: boolean; stock_quantity?: number; -}) => { - const url = new URL(`${ENV.backendUrl}/admin/shop`); - Object.entries(params).forEach(([k, v]) => { if (v !== undefined) url.searchParams.set(k, String(v)); }); - return apiFetch>(url.toString(), { method: "POST" }); -}; + icon_url?: string; + effects?: ShopItemEffects; +}) => + apiFetch>(`${ENV.backendUrl}/admin/shop`, { + method: "POST", + body: JSON.stringify(params), + }); -export const updateShopItem = async (id: string, params: Partial) => { - const url = new URL(`${ENV.backendUrl}/admin/shop/${id}`); - Object.entries(params).forEach(([k, v]) => { if (v !== undefined) url.searchParams.set(k, String(v)); }); - return apiFetch>(url.toString(), { method: "PUT" }); -}; +export const updateShopItem = async (id: string, params: Partial) => + apiFetch>(`${ENV.backendUrl}/admin/shop/${id}`, { + method: "PUT", + body: JSON.stringify(params), + }); export const deleteShopItem = async (id: string) => apiFetch>(`${ENV.backendUrl}/admin/shop/${id}`, { diff --git a/admin-service/src/lib/aiQualityApi.test.ts b/admin-service/src/lib/aiQualityApi.test.ts new file mode 100644 index 00000000..fce6263e --- /dev/null +++ b/admin-service/src/lib/aiQualityApi.test.ts @@ -0,0 +1,37 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const { apiFetchMock } = vi.hoisted(() => ({ + apiFetchMock: vi.fn(), +})); + +vi.mock("./api", () => ({ + apiFetch: apiFetchMock, +})); + +vi.mock("./env", () => ({ + ENV: { + backendUrl: "https://backend.example/api/v1", + }, +})); + +import { getAiQualitySummary } from "./aiQualityApi"; + +describe("aiQualityApi", () => { + beforeEach(() => { + apiFetchMock.mockReset(); + apiFetchMock.mockResolvedValue({ + events: [], + summary: { total_events: 0 }, + total: 0, + source: "ai:audit:all", + }); + }); + + it("loads the backend AI audit quality summary with a bounded limit", async () => { + await getAiQualitySummary(250); + + expect(apiFetchMock).toHaveBeenCalledWith( + "https://backend.example/api/v1/ai-audit/quality-summary?limit=250", + ); + }); +}); diff --git a/admin-service/src/lib/aiQualityApi.ts b/admin-service/src/lib/aiQualityApi.ts new file mode 100644 index 00000000..0aae40f3 --- /dev/null +++ b/admin-service/src/lib/aiQualityApi.ts @@ -0,0 +1,58 @@ +import { apiFetch } from "./api"; +import { ENV } from "./env"; + +export type AiQualityEndpointBreakdown = { + endpoint: string; + total: number; + failures: number; + average_latency_ms: number; +}; + +export type AiQualityFailure = { + request_id?: string | null; + user_id?: string | null; + endpoint?: string | null; + status?: string | null; + latency_ms?: number | null; + received_at?: string | null; + error?: string | null; +}; + +export type AiQualitySummary = { + total_events: number; + success_count: number; + failure_count: number; + success_rate: number; + average_latency_ms: number; + p95_latency_ms: number; + lexi: { + events: number; + failures: number; + average_latency_ms: number; + }; + stt: { + failures: number; + }; + tts: { + failures: number; + }; + correction: { + events: number; + failures: number; + average_score: number; + }; + endpoint_breakdown: AiQualityEndpointBreakdown[]; + latest_failures: AiQualityFailure[]; +}; + +export type AiQualityResponse = { + events: unknown[]; + summary: AiQualitySummary; + total: number; + source: string; +}; + +export const getAiQualitySummary = (limit = 500) => + apiFetch( + `${ENV.backendUrl}/ai-audit/quality-summary?limit=${encodeURIComponent(limit)}`, + ); diff --git a/admin-service/src/lib/contentAgentApi.test.ts b/admin-service/src/lib/contentAgentApi.test.ts index 8b98c5df..8a75fff8 100644 --- a/admin-service/src/lib/contentAgentApi.test.ts +++ b/admin-service/src/lib/contentAgentApi.test.ts @@ -16,11 +16,13 @@ vi.mock("./env", () => ({ import { applyContentAgentJob, + buildContentQaQueue, cancelContentAgentJob, createContentAgentJob, getContentAgentJob, getContentAgentPreview, listContentAgentJobs, + listContentQaQueue, retryContentAgentJob, uploadContentAgentFile, } from "./contentAgentApi"; @@ -121,4 +123,36 @@ describe("contentAgentApi", () => { total: 4, }); }); + + it("builds a content QA queue from agent job statuses", () => { + const queue = buildContentQaQueue([ + { id: "ready", status: "preview_ready" }, + { id: "failed", status: "failed" }, + { id: "done", status: "completed" }, + { id: "running", status: "generating" }, + ] as any); + + expect(queue.total).toBe(4); + expect(queue.reviewable.map((job) => job.id)).toEqual(["ready"]); + expect(queue.failed.map((job) => job.id)).toEqual(["failed"]); + expect(queue.applied.map((job) => job.id)).toEqual(["done"]); + }); + + it("loads content QA queue through the jobs endpoint", async () => { + apiFetchMock.mockResolvedValueOnce({ + data: { + jobs: [ + { id: "job-1", status: "preview_ready" }, + { id: "job-2", status: "failed" }, + ], + total: 2, + }, + }); + + await expect(listContentQaQueue()).resolves.toMatchObject({ + reviewable: [{ id: "job-1" }], + failed: [{ id: "job-2" }], + total: 2, + }); + }); }); diff --git a/admin-service/src/lib/contentAgentApi.ts b/admin-service/src/lib/contentAgentApi.ts index 84961aa9..b9c918f5 100644 --- a/admin-service/src/lib/contentAgentApi.ts +++ b/admin-service/src/lib/contentAgentApi.ts @@ -201,6 +201,13 @@ export type ContentAgentJobList = { total: number; }; +export type ContentQaQueue = { + reviewable: ContentAgentJob[]; + failed: ContentAgentJob[]; + applied: ContentAgentJob[]; + total: number; +}; + type ApiEnvelope = { success?: boolean; data?: T; @@ -297,6 +304,26 @@ export const listContentAgentJobs = async (): Promise => { }; }; +export const buildContentQaQueue = ( + jobs: readonly ContentAgentJob[], +): ContentQaQueue => { + const reviewable = jobs.filter((job) => job.status === "preview_ready"); + const failed = jobs.filter((job) => job.status === "failed"); + const applied = jobs.filter((job) => job.status === "completed"); + + return { + reviewable, + failed, + applied, + total: jobs.length, + }; +}; + +export const listContentQaQueue = async (): Promise => { + const { jobs } = await listContentAgentJobs(); + return buildContentQaQueue(jobs); +}; + export const getContentAgentJob = async ( jobId: string, ): Promise => { diff --git a/admin-service/src/lib/i18n/en.ts b/admin-service/src/lib/i18n/en.ts index 7e7781a6..e423a89a 100644 --- a/admin-service/src/lib/i18n/en.ts +++ b/admin-service/src/lib/i18n/en.ts @@ -67,7 +67,9 @@ const en: Translations = { lessons: "Lessons", topics: "Topics", grammarTest: "Grammar & Tests", + contentQaQueue: "Content QA", contentAnalytics: "Content Analytics", + aiQuality: "AI Quality", vocabulary: "Vocabulary", achievements: "Achievements", shop: "Shop", @@ -479,6 +481,13 @@ const en: Translations = { stockQuantity: "Stock Quantity", unlimitedPlaceholder: "Leave blank = unlimited", updateFailed: "Update failed", + useTemplate: "Use Template", + templateApplied: "Template applied for this item type — adjust as needed before saving.", + iconUrl: "Icon URL", + iconUrlPlaceholder: "e.g. assets/icon-library/clock_freeze.png", + effectsLabel: "Effects (JSON)", + effectsHint: "e.g. {\"seconds\": 10} — determines how this item behaves in-game.", + effectsInvalidJson: "Effects must be valid JSON, e.g. {\"seconds\": 10}", }, // ============== Ads/Banner ============== diff --git a/admin-service/src/lib/i18n/vi.ts b/admin-service/src/lib/i18n/vi.ts index 9f7edc4d..5616bdd3 100644 --- a/admin-service/src/lib/i18n/vi.ts +++ b/admin-service/src/lib/i18n/vi.ts @@ -65,7 +65,9 @@ const vi = { lessons: "Bài học", topics: "Topic chat", grammarTest: "Ngữ pháp & Kiểm tra", + contentQaQueue: "QA nội dung", contentAnalytics: "Phân tích nội dung", + aiQuality: "Chất lượng AI", vocabulary: "Từ vựng", achievements: "Thành tựu", shop: "Cửa hàng", @@ -477,6 +479,13 @@ const vi = { stockQuantity: "Số lượng kho", unlimitedPlaceholder: "Để trống = vô hạn", updateFailed: "Cập nhật thất bại", + useTemplate: "Dùng mẫu", + templateApplied: "Đã điền mẫu cho loại item này — có thể chỉnh lại trước khi lưu.", + iconUrl: "URL icon", + iconUrlPlaceholder: "VD: assets/icon-library/clock_freeze.png", + effectsLabel: "Hiệu ứng (JSON)", + effectsHint: "VD: {\"seconds\": 10} — quyết định cách item này hoạt động trong game.", + effectsInvalidJson: "Hiệu ứng phải là JSON hợp lệ, ví dụ {\"seconds\": 10}", }, // ============== Ads/Banner ============== diff --git a/admin-service/src/pages/AiQualityDashboardPage.tsx b/admin-service/src/pages/AiQualityDashboardPage.tsx new file mode 100644 index 00000000..b58735d3 --- /dev/null +++ b/admin-service/src/pages/AiQualityDashboardPage.tsx @@ -0,0 +1,166 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { AlertTriangle, RefreshCw } from "lucide-react"; +import { DataTable } from "../components/DataTable"; +import { EmptyState } from "../components/EmptyState"; +import { SectionHeader } from "../components/SectionHeader"; +import { StatCard } from "../components/StatCard"; +import { StatusPill } from "../components/StatusPill"; +import { + getAiQualitySummary, + type AiQualityEndpointBreakdown, + type AiQualityFailure, + type AiQualityResponse, +} from "../lib/aiQualityApi"; + +const pctTone = (value: number): "success" | "warning" | "danger" => + value >= 95 ? "success" : value >= 85 ? "warning" : "danger"; + +export const AiQualityDashboardPage = () => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const load = useCallback(async () => { + setLoading(true); + setError(null); + try { + setData(await getAiQualitySummary(500)); + } catch (err: any) { + setError(err?.message || "Failed to load AI quality metrics"); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + void load(); + }, [load]); + + const summary = data?.summary; + const hasEvents = Boolean(summary?.total_events); + + return ( +
+ + Refresh + + } + /> + + {error &&
{error}
} + {loading &&
Loading AI quality metrics...
} + + {!loading && !hasEvents ? ( + } + title="No AI audit events yet" + description="Metrics will appear after ai-service starts ingesting audit events into backend-service." + /> + ) : summary ? ( + <> +
+ + + + +
+ +
+ + + + +
+ +
+

Endpoint breakdown

+ {!summary.endpoint_breakdown.length ? ( + + ) : ( + + rows={summary.endpoint_breakdown} + columns={[ + { + header: "Endpoint", + render: (row) => {row.endpoint}, + }, + { + header: "Events", + render: (row) => {row.total}, + align: "center", + }, + { + header: "Failures", + render: (row) => ( + + ), + align: "center", + }, + { + header: "Avg latency", + render: (row) => {Math.round(row.average_latency_ms)} ms, + align: "right", + }, + ]} + /> + )} +
+ +
+

Latest failures

+ {!summary.latest_failures.length ? ( + + ) : ( + + rows={summary.latest_failures} + columns={[ + { + header: "Request", + render: (row) => ( +
+
{row.request_id || "unknown"}
+
{row.user_id || "unknown user"}
+
+ ), + }, + { + header: "Endpoint", + render: (row) => {row.endpoint || "unknown"}, + }, + { + header: "Status", + render: (row) => , + align: "center", + }, + { + header: "Latency", + render: (row) => {row.latency_ms ? `${Math.round(row.latency_ms)} ms` : "--"}, + align: "right", + }, + { + header: "Error", + render: (row) => {row.error || "No message"}, + }, + ]} + /> + )} +
+ +
+ + + {summary.failure_count} failed of {summary.total_events} audited events. + +
+ + ) : null} +
+ ); +}; diff --git a/admin-service/src/pages/ContentQaQueuePage.tsx b/admin-service/src/pages/ContentQaQueuePage.tsx new file mode 100644 index 00000000..539d18ae --- /dev/null +++ b/admin-service/src/pages/ContentQaQueuePage.tsx @@ -0,0 +1,272 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { CheckCircle, RefreshCw, RotateCcw, XCircle } from "lucide-react"; +import { EmptyState } from "../components/EmptyState"; +import { SectionHeader } from "../components/SectionHeader"; +import { StatCard } from "../components/StatCard"; +import { StatusPill } from "../components/StatusPill"; +import { + applyContentAgentJob, + cancelContentAgentJob, + getContentAgentPreview, + listContentQaQueue, + retryContentAgentJob, + type ContentAgentJob, + type ContentAgentPreview, + type ContentQaQueue, +} from "../lib/contentAgentApi"; + +const statusTone = (status: string): "success" | "warning" | "info" | "danger" | "neutral" => { + if (status === "preview_ready") return "warning"; + if (status === "completed") return "success"; + if (status === "failed") return "danger"; + return "info"; +}; + +const summarizePreview = (preview: ContentAgentPreview | null) => { + if (!preview) { + return { courses: 0, units: 0, lessons: 0, vocabulary: 0, exercises: 0 }; + } + + return preview.courses.reduce( + (acc, course) => { + acc.courses += 1; + acc.units += course.units.length; + course.units.forEach((unit) => { + acc.lessons += unit.lessons.length; + unit.lessons.forEach((lesson) => { + acc.vocabulary += lesson.vocabulary.length; + acc.exercises += lesson.exercises.length; + }); + }); + return acc; + }, + { courses: 0, units: 0, lessons: 0, vocabulary: 0, exercises: 0 }, + ); +}; + +const JobList = ({ + title, + jobs, + selectedId, + onSelect, +}: { + title: string; + jobs: ContentAgentJob[]; + selectedId?: string; + onSelect: (job: ContentAgentJob) => void; +}) => ( +
+

{title}

+ {!jobs.length ? ( + + ) : ( +
+ {jobs.map((job) => ( + + ))} +
+ )} +
+); + +export const ContentQaQueuePage = () => { + const [queue, setQueue] = useState(null); + const [selected, setSelected] = useState(null); + const [preview, setPreview] = useState(null); + const [loading, setLoading] = useState(false); + const [previewLoading, setPreviewLoading] = useState(false); + const [error, setError] = useState(null); + + const loadQueue = useCallback(async () => { + setLoading(true); + setError(null); + try { + const nextQueue = await listContentQaQueue(); + setQueue(nextQueue); + setSelected((current) => { + const jobs = [ + ...nextQueue.reviewable, + ...nextQueue.failed, + ...nextQueue.applied, + ]; + return ( + (current ? jobs.find((job) => job.id === current.id) : null) ?? + nextQueue.reviewable[0] ?? + nextQueue.failed[0] ?? + nextQueue.applied[0] ?? + null + ); + }); + } catch (err: any) { + setError(err?.message || "Failed to load content QA queue"); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + void loadQueue(); + }, [loadQueue]); + + useEffect(() => { + if (!selected) { + setPreview(null); + return; + } + + setPreviewLoading(true); + getContentAgentPreview(selected.id) + .then(setPreview) + .catch(() => setPreview(null)) + .finally(() => setPreviewLoading(false)); + }, [selected]); + + const summary = useMemo(() => summarizePreview(preview), [preview]); + const validationErrors = [ + ...(selected?.blocking_errors || []), + ...(preview?.quality.blocking_errors || []), + ]; + const validationWarnings = [ + ...(selected?.warnings || []), + ...(preview?.quality.warnings || []), + ]; + const createdCourseIds = + selected?.created_entity_ids.course_ids ?? + selected?.created_entity_ids.courses ?? + []; + + const runAction = async (action: "apply" | "cancel" | "retry") => { + if (!selected) return; + setError(null); + try { + if (action === "apply") await applyContentAgentJob(selected.id); + if (action === "cancel") await cancelContentAgentJob(selected.id); + if (action === "retry") await retryContentAgentJob(selected.id); + await loadQueue(); + } catch (err: any) { + setError(err?.message || `Failed to ${action} content job`); + } + }; + + return ( +
+ + Refresh + + } + /> + + {error &&
{error}
} + +
+ + + + +
+ +
+
+ + + +
+ +
+ {!selected ? ( + + ) : ( +
+
+
+

{selected.config.title_focus || "Generated content preview"}

+
{selected.id}
+
+ +
+ +
+ + + + +
+ +
+

Validation

+ {previewLoading &&
Loading preview...
} + {!validationErrors.length && !validationWarnings.length ? ( + + ) : ( +
+ {validationErrors.map((message, index) => ( +
{message}
+ ))} + {validationWarnings.map((message, index) => ( +
{message}
+ ))} +
+ )} +
+ +
+

Source policy

+ {!preview?.source_manifest?.length ? ( +
No source manifest attached.
+ ) : ( +
+ {preview.source_manifest.map((source, index) => ( +
+ {source.source_name} · {source.license_mode || "unknown"} · {source.record_count || 0} records +
+ ))} +
+ )} +
+ +
+

Apply history

+
Created courses: {createdCourseIds.length ? createdCourseIds.join(", ") : "none yet"}
+
Completed: {selected.completed_at ? new Date(selected.completed_at).toLocaleString() : "not completed"}
+
+ +
+ + + +
+
+ )} +
+
+
+ ); +}; diff --git a/admin-service/src/pages/ShopPage.tsx b/admin-service/src/pages/ShopPage.tsx index a63b4bb2..c3a58262 100644 --- a/admin-service/src/pages/ShopPage.tsx +++ b/admin-service/src/pages/ShopPage.tsx @@ -12,7 +12,75 @@ import { } from "../lib/adminApi"; import { useI18n } from "../lib/i18n"; -const ITEM_TYPES = ["streak_freeze", "double_xp", "hint_pack", "cosmetic", "power_up", "time_boost"]; +// Mirrors the item_type values actually seeded in backend-service/app/core/shop_catalog.py. +const ITEM_TYPES = [ + // Boosts + "streak_freeze", + "double_xp", + "hint_pack", + "heart_refill", + // Cosmetics + "avatar", + "theme", + // In-game power-ups + "time_freeze", + "extra_time", + "skip_token", + "reveal_hint", + "translate_hint", + "mistake_shield", + "extra_heart", + "lucky_clover", + "score_multiplier", + "pair_swap", +]; + +// Quick-create templates mirroring backend-service/app/core/shop_catalog.py, so +// picking an item_type fills in a sensible, already-working default instead of +// the admin having to know each item's expected effects payload shape by heart. +const ITEM_TYPE_TEMPLATES: Record< + string, + { name: string; description: string; price_gems: number; effects: Record } +> = { + streak_freeze: { name: "Streak Freeze", description: "Add one streak freeze", price_gems: 25, effects: { quantity: 1 } }, + double_xp: { name: "Double XP (1 hour)", description: "Earn double XP for 1 hour", price_gems: 25, effects: { duration_hours: 1, multiplier: 2 } }, + hint_pack: { name: "Hint Pack (5)", description: "Add 5 hints to your active lesson", price_gems: 12, effects: { quantity: 5 } }, + heart_refill: { name: "Heart Refill", description: "Restore your active lesson to 3 hearts", price_gems: 15, effects: { hearts: 3 } }, + avatar: { name: "New Avatar", description: "Exclusive profile avatar", price_gems: 50, effects: { avatar_url: "" } }, + theme: { name: "New Theme", description: "Unlockable app theme", price_gems: 50, effects: {} }, + time_freeze: { name: "Time Freeze", description: "Pause the countdown timer for 10 seconds in any timed game", price_gems: 10, effects: { seconds: 10 } }, + extra_time: { name: "Extra Time", description: "Add 20 seconds straight to the clock in any timed game", price_gems: 15, effects: { seconds: 20 } }, + skip_token: { name: "Skip Token", description: "Skip the current word or question with no penalty", price_gems: 12, effects: {} }, + reveal_hint: { name: "Magnifying Glass", description: "Free reveal: the next letter, or eliminate 2 wrong options", price_gems: 8, effects: { mode: "letter" } }, + translate_hint: { name: "Quick Translate", description: "Reveal the Vietnamese translation of the current word", price_gems: 8, effects: { mode: "translation" } }, + mistake_shield: { name: "Shield", description: "Negate the next wrong answer or life loss", price_gems: 18, effects: {} }, + extra_heart: { name: "Extra Heart", description: "Start Hangman with one extra life", price_gems: 15, effects: { lives: 1 } }, + lucky_clover: { name: "Lucky Clover", description: "30% chance to auto-correct your next wrong answer", price_gems: 20, effects: { chance: 0.3 } }, + score_multiplier: { name: "Score Multiplier", description: "Double your in-game score for the rest of this session", price_gems: 22, effects: { multiplier: 2 } }, + pair_swap: { name: "Pair Swap", description: "Undo one wrong match in Matching Game for a free retry", price_gems: 10, effects: {} }, +}; + +type ShopItemForm = { + name: string; + description: string; + item_type: string; + price_gems: number; + is_available: boolean; + stock_quantity: number | undefined; + icon_url: string; + effectsText: string; +}; + +const EMPTY_FORM: ShopItemForm = { + name: "", + description: "", + item_type: "streak_freeze", + price_gems: 50, + is_available: true, + stock_quantity: undefined, + icon_url: "", + effectsText: "{}", +}; export const ShopPage = () => { const { t } = useI18n(); @@ -24,20 +92,29 @@ export const ShopPage = () => { const [showForm, setShowForm] = useState(false); const [editingId, setEditingId] = useState(null); const [saving, setSaving] = useState(false); - const [form, setForm] = useState({ - name: "", - description: "", - item_type: "streak_freeze", - price_gems: 50, - is_available: true, - stock_quantity: undefined as number | undefined, - }); + const [effectsError, setEffectsError] = useState(null); + const [form, setForm] = useState(EMPTY_FORM); const resetForm = () => { - setForm({ name: "", description: "", item_type: "streak_freeze", price_gems: 50, is_available: true, stock_quantity: undefined }); + setForm(EMPTY_FORM); + setEffectsError(null); setEditingId(null); }; + const applyTemplate = (itemType: string) => { + const template = ITEM_TYPE_TEMPLATES[itemType]; + if (!template) return; + setForm((prev) => ({ + ...prev, + item_type: itemType, + name: template.name, + description: template.description, + price_gems: template.price_gems, + effectsText: JSON.stringify(template.effects, null, 2), + })); + setEffectsError(null); + }; + const loadItems = async () => { setLoading(true); setError(null); @@ -62,12 +139,25 @@ export const ShopPage = () => { price_gems: item.price_gems, is_available: item.is_available, stock_quantity: item.stock_quantity ?? undefined, + icon_url: item.icon_url ?? "", + effectsText: JSON.stringify(item.effects ?? {}, null, 2), }); + setEffectsError(null); setShowForm(true); }; const handleSave = async (e: React.FormEvent) => { e.preventDefault(); + + let effects: Record | undefined; + try { + effects = form.effectsText.trim() ? JSON.parse(form.effectsText) : {}; + } catch { + setEffectsError(t.shop.effectsInvalidJson); + return; + } + setEffectsError(null); + setSaving(true); setError(null); try { @@ -78,9 +168,20 @@ export const ShopPage = () => { price_gems: form.price_gems, is_available: form.is_available, stock_quantity: form.stock_quantity ?? undefined, + icon_url: form.icon_url || undefined, + effects, }); } else { - await createShopItem(form); + await createShopItem({ + name: form.name, + description: form.description, + item_type: form.item_type, + price_gems: form.price_gems, + is_available: form.is_available, + stock_quantity: form.stock_quantity, + icon_url: form.icon_url || undefined, + effects, + }); } resetForm(); setShowForm(false); @@ -197,9 +298,31 @@ export const ShopPage = () => { {/* Create/Edit Modal */} {showForm && (
setShowForm(false)}> -
e.stopPropagation()} style={{ maxWidth: 520 }}> +
e.stopPropagation()} style={{ maxWidth: 560 }}>

{editingId ? t.shop.editItem : t.shop.createNew}

+
+ + {!editingId && ( + + )} +