feat(admin-ui): scaffold Vite + React + TypeScript frontend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
whit3rabbit
2026-04-05 14:27:12 -05:00
co-authored by Claude Sonnet 4.6
parent a4f20dca39
commit 20ac4022c1
7 changed files with 3549 additions and 1883 deletions
+4
View File
@@ -40,3 +40,7 @@ Thumbs.db
# Script caches and venv
scripts/.cache/
scripts/.venv/
# Admin UI build artifacts
crates/proxy/admin-ui/node_modules/
crates/proxy/admin-ui/dist/
+25
View File
@@ -0,0 +1,25 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
},
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
{
"name": "anyllm-admin-ui",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint src",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@tanstack/react-query": "^5.0.0",
"zustand": "^5.0.0"
},
"devDependencies": {
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^9.0.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.0",
"typescript": "^5.0.0",
"vite": "^6.0.0",
"vite-plugin-singlefile": "^2.0.0"
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
+34
View File
@@ -0,0 +1,34 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { viteSingleFile } from 'vite-plugin-singlefile'
import { readFileSync, writeFileSync } from 'fs'
import { resolve } from 'path'
function injectCspNonce() {
return {
name: 'inject-csp-nonce',
apply: 'build' as const,
closeBundle() {
const outFile = resolve(__dirname, 'dist/index.html')
let html = readFileSync(outFile, 'utf-8')
// Add nonce to generated <style> and <script> tags (skip tags that already have one)
html = html.replace(/<style(?![^>]*nonce)/g, '<style nonce="__CSP_NONCE__"')
html = html.replace(/<script(?![^>]*nonce)/g, '<script nonce="__CSP_NONCE__"')
writeFileSync(outFile, html)
},
}
}
export default defineConfig({
plugins: [react(), viteSingleFile({ inlineScripts: false }), injectCspNonce()],
build: {
outDir: 'dist',
target: 'es2020',
assetsInlineLimit: 100_000_000,
rollupOptions: {
output: {
inlineDynamicImports: true,
},
},
},
})