commit 936af0c4ec7ef4d8eea31b992639425f0b3fdfcc Author: William Tso Date: Fri Mar 7 17:45:17 2025 +0800 init diff --git a/.env.promopt b/.env.promopt new file mode 100644 index 0000000..c0bb410 --- /dev/null +++ b/.env.promopt @@ -0,0 +1,15 @@ +PORT=4000 + +SUPABASE_URL="xxx" +SUPABASE_KEY="xxx" +SUPABASE_ANON_KEY="xxx" +DATABASE_URL="xxx" + +REDIS_HOST="localhost" +REDIS_PORT="6379" +REDIS_PASSWORD="" +DOMAIN="upj.to" +ENABLED_ROUTES=all + +# Pulsar Connection +PULSAR_SERVICE_URL=pulsar://localhost:6650 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..953ea0a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +docker/clickhouse/config.xml +docker/clickhouse/data +docker/clickhouse/users.xml + diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5c84c4 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +本系統是一個社群平台成效管理系統,主要功能包括: + +管理網紅文章、影片的成效數據 + +統計各個行銷活動的影響力,計算觀看數、按讚數等 + +追蹤留言者(當作網紅),並查詢其影響力數據 + +支援多平台(YouTube, Instagram, TikTok, Facebook, Twitter) + +記錄網紅粉絲數、文章觀看數、按讚數變化 + +追蹤每篇文章的評論內容,並進行情感分析 + +提供 API 讓第三方應用存取分析結果 \ No newline at end of file diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..717aac0 --- /dev/null +++ b/backend/.env @@ -0,0 +1,28 @@ +PORT=4000 + +SUPABASE_URL="https://xtqhluzornazlmkonucr.supabase.co" +SUPABASE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0cWhsdXpvcm5hemxta29udWNyIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTI0NDcxMywiZXhwIjoyMDU2ODIwNzEzfQ.E9lHlmdoqCZ9zYg0ammuW6ua-__tEEATCrwYv3-Th3I" +SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0cWhsdXpvcm5hemxta29udWNyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDEyNDQ3MTMsImV4cCI6MjA1NjgyMDcxM30.PUXbyHOgkvFE6T5fvCFTV7LJq-MbMkRtNdw2VoKJOAg" +DATABASE_URL="postgresql://postgres:KR$kH9fdwZAd@tdS@db.xtqhluzornazlmkonucr.supabase.co:5432/postgres" + +REDIS_HOST="localhost" +REDIS_PORT="6379" +REDIS_PASSWORD="" +DOMAIN="upj.to" +ENABLED_ROUTES=all + +# ClickHouse Configuration +CLICKHOUSE_HOST=localhost +CLICKHOUSE_PORT=8123 +CLICKHOUSE_USER=admin +CLICKHOUSE_PASSWORD=your_secure_password +CLICKHOUSE_DATABASE=promote + +# BullMQ Configuration +BULL_REDIS_HOST="localhost" +BULL_REDIS_PORT="6379" +BULL_REDIS_PASSWORD="" + +# JWT Configuration +JWT_SECRET="your-jwt-secret-key" +JWT_EXPIRES_IN="7d" \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..1fe9e40 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,28 @@ +PORT=4000 + +SUPABASE_URL="your-supabase-url" +SUPABASE_KEY="your-supabase-key" +SUPABASE_ANON_KEY="your-supabase-anon-key" +DATABASE_URL="your-database-url" + +REDIS_HOST="localhost" +REDIS_PORT="6379" +REDIS_PASSWORD="" +DOMAIN="upj.to" +ENABLED_ROUTES=all + +# ClickHouse Configuration +CLICKHOUSE_HOST="localhost" +CLICKHOUSE_PORT="8123" +CLICKHOUSE_USER="admin" +CLICKHOUSE_PASSWORD="your_secure_password" +CLICKHOUSE_DATABASE="promote" + +# BullMQ Configuration +BULL_REDIS_HOST="localhost" +BULL_REDIS_PORT="6379" +BULL_REDIS_PASSWORD="" + +# JWT Configuration +JWT_SECRET="your-jwt-secret-key" +JWT_EXPIRES_IN="7d" \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..9424d5e --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,47 @@ +# 依赖 +node_modules/ +pnpm-lock.yaml +package-lock.json +yarn.lock + +# 构建输出 +dist/ +build/ + +# 环境配置 +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# 日志 +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# 系统文件 +.DS_Store +Thumbs.db + +# IDE 和编辑器 +.idea/ +.vscode/ +*.sublime-project +*.sublime-workspace +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# 临时文件 +.tmp/ +.temp/ +.cache/ + +# 覆盖率报告 +coverage/ \ No newline at end of file diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..7416a6d --- /dev/null +++ b/backend/README.md @@ -0,0 +1,111 @@ +# Promote Backend API + +Backend API for the Promote platform, built with Hono.js, Supabase, ClickHouse, Redis, and BullMQ. + +## Features + +- **Authentication**: JWT-based authentication with Supabase Auth +- **Analytics Tracking**: Track views, likes, and followers using ClickHouse +- **Caching**: Redis for fast API responses +- **Task Scheduling**: BullMQ for background processing + +## Tech Stack + +- **Framework**: [Hono.js](https://honojs.dev/) +- **Authentication**: [Supabase Auth](https://supabase.com/docs/guides/auth) + JWT +- **Database**: + - [Supabase (PostgreSQL)](https://supabase.com/) for main data + - [ClickHouse](https://clickhouse.com/) for analytics events +- **Caching**: [Redis](https://redis.io/) +- **Task Queue**: [BullMQ](https://docs.bullmq.io/) + +## Getting Started + +### Prerequisites + +- Node.js 18+ +- pnpm +- Redis +- ClickHouse +- Supabase account + +### Installation + +1. Clone the repository +2. Install dependencies: + +```bash +cd backend +pnpm install +``` + +3. Create a `.env` file based on the `.env.example` file +4. Start the development server: + +```bash +pnpm dev +``` + +## API Endpoints + +### Authentication + +- `POST /api/auth/register` - Register a new user +- `POST /api/auth/login` - Login a user +- `GET /api/auth/verify` - Verify a token + +### Analytics + +- `POST /api/analytics/view` - Track a view event +- `POST /api/analytics/like` - Track a like/unlike event +- `POST /api/analytics/follow` - Track a follow/unfollow event +- `GET /api/analytics/content/:id` - Get analytics for a content +- `GET /api/analytics/user/:id` - Get analytics for a user + +## Development + +### Build + +```bash +pnpm build +``` + +### Start Production Server + +```bash +pnpm start +``` + +### Linting + +```bash +pnpm lint +``` + +### Testing + +```bash +pnpm test +``` + +## Project Structure + +``` +backend/ +├── src/ +│ ├── config/ # Configuration files +│ ├── controllers/ # Route controllers +│ ├── middlewares/ # Middleware functions +│ ├── models/ # Data models +│ ├── routes/ # API routes +│ ├── services/ # Business logic +│ ├── utils/ # Utility functions +│ └── index.ts # Entry point +├── .env # Environment variables +├── package.json # Dependencies and scripts +└── tsconfig.json # TypeScript configuration +``` + +## License + +This project is licensed under the ISC License. \ No newline at end of file diff --git a/backend/dist/config/index.js b/backend/dist/config/index.js new file mode 100644 index 0000000..db1de1e --- /dev/null +++ b/backend/dist/config/index.js @@ -0,0 +1,51 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.config = void 0; +const dotenv_1 = __importDefault(require("dotenv")); +const path_1 = require("path"); +// Load environment variables from .env file +dotenv_1.default.config({ path: (0, path_1.join)(__dirname, '../../.env') }); +exports.config = { + port: process.env.PORT || 4000, + // Supabase configuration + supabase: { + url: process.env.SUPABASE_URL || '', + key: process.env.SUPABASE_KEY || '', + anonKey: process.env.SUPABASE_ANON_KEY || '', + }, + // Redis configuration + redis: { + host: process.env.REDIS_HOST || 'localhost', + port: parseInt(process.env.REDIS_PORT || '6379', 10), + password: process.env.REDIS_PASSWORD || '', + }, + // ClickHouse configuration + clickhouse: { + host: process.env.CLICKHOUSE_HOST || 'localhost', + port: process.env.CLICKHOUSE_PORT || '8123', + user: process.env.CLICKHOUSE_USER || 'admin', + password: process.env.CLICKHOUSE_PASSWORD || 'your_secure_password', + database: process.env.CLICKHOUSE_DATABASE || 'promote', + }, + // BullMQ configuration + bull: { + redis: { + host: process.env.BULL_REDIS_HOST || 'localhost', + port: parseInt(process.env.BULL_REDIS_PORT || '6379', 10), + password: process.env.BULL_REDIS_PASSWORD || '', + }, + }, + // JWT configuration + jwt: { + secret: process.env.JWT_SECRET || 'your-secret-key', + expiresIn: process.env.JWT_EXPIRES_IN || '7d', + }, + // Domain configuration + domain: process.env.DOMAIN || 'upj.to', + // Enabled routes + enabledRoutes: process.env.ENABLED_ROUTES || 'all', +}; +exports.default = exports.config; diff --git a/backend/dist/controllers/commentsController.js b/backend/dist/controllers/commentsController.js new file mode 100644 index 0000000..dd7ab7c --- /dev/null +++ b/backend/dist/controllers/commentsController.js @@ -0,0 +1,111 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.deleteComment = exports.createComment = exports.getComments = void 0; +const supabase_1 = __importDefault(require("../utils/supabase")); +const getComments = async (c) => { + try { + const { post_id, limit = '10', offset = '0' } = c.req.query(); + let query; + if (post_id) { + // 获取特定帖子的评论 + query = supabase_1.default.rpc('get_comments_for_post', { post_id_param: post_id }); + } + else { + // 获取所有评论 + query = supabase_1.default.rpc('get_comments_with_posts'); + } + // 应用分页 + query = query.range(Number(offset), Number(offset) + Number(limit) - 1); + const { data: comments, error, count } = await query; + if (error) { + return c.json({ error: error.message }, 500); + } + return c.json({ + comments, + count, + limit: Number(limit), + offset: Number(offset) + }); + } + catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; +exports.getComments = getComments; +const createComment = async (c) => { + try { + const { post_id, content } = await c.req.json(); + const user_id = c.get('user')?.id; + if (!user_id) { + return c.json({ error: 'Unauthorized' }, 401); + } + const { data: comment, error } = await supabase_1.default + .from('comments') + .insert({ + post_id, + content, + user_id + }) + .select(` + comment_id, + content, + sentiment_score, + created_at, + updated_at, + post_id, + user_id + `) + .single(); + if (error) { + return c.json({ error: error.message }, 500); + } + // 获取用户信息 + const { data: userProfile, error: userError } = await supabase_1.default + .from('user_profiles') + .select('id, full_name, avatar_url') + .eq('id', user_id) + .single(); + if (!userError && userProfile) { + comment.user_profile = userProfile; + } + return c.json(comment, 201); + } + catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; +exports.createComment = createComment; +const deleteComment = async (c) => { + try { + const { comment_id } = c.req.param(); + const user_id = c.get('user')?.id; + if (!user_id) { + return c.json({ error: 'Unauthorized' }, 401); + } + // Check if the comment belongs to the user + const { data: comment, error: fetchError } = await supabase_1.default + .from('comments') + .select() + .eq('comment_id', comment_id) + .eq('user_id', user_id) + .single(); + if (fetchError || !comment) { + return c.json({ error: 'Comment not found or unauthorized' }, 404); + } + const { error: deleteError } = await supabase_1.default + .from('comments') + .delete() + .eq('comment_id', comment_id); + if (deleteError) { + return c.json({ error: deleteError.message }, 500); + } + return c.body(null, 204); + } + catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; +exports.deleteComment = deleteComment; diff --git a/backend/dist/controllers/influencersController.js b/backend/dist/controllers/influencersController.js new file mode 100644 index 0000000..704a4ff --- /dev/null +++ b/backend/dist/controllers/influencersController.js @@ -0,0 +1,116 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getInfluencerStats = exports.getInfluencerById = exports.getInfluencers = void 0; +const supabase_1 = __importDefault(require("../utils/supabase")); +const getInfluencers = async (c) => { + try { + const { platform, limit = '10', offset = '0', min_followers, max_followers, sort_by = 'followers_count', sort_order = 'desc' } = c.req.query(); + let query = supabase_1.default + .from('influencers') + .select(` + influencer_id, + name, + platform, + profile_url, + followers_count, + video_count, + platform_count, + created_at, + updated_at + `); + // Apply filters + if (platform) { + query = query.eq('platform', platform); + } + if (min_followers) { + query = query.gte('followers_count', Number(min_followers)); + } + if (max_followers) { + query = query.lte('followers_count', Number(max_followers)); + } + // Apply sorting + if (sort_by && ['followers_count', 'video_count', 'created_at'].includes(sort_by)) { + query = query.order(sort_by, { ascending: sort_order === 'asc' }); + } + // Apply pagination + query = query.range(Number(offset), Number(offset) + Number(limit) - 1); + const { data: influencers, error, count } = await query; + if (error) { + return c.json({ error: error.message }, 500); + } + return c.json({ + influencers, + count, + limit: Number(limit), + offset: Number(offset) + }); + } + catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; +exports.getInfluencers = getInfluencers; +const getInfluencerById = async (c) => { + try { + const { influencer_id } = c.req.param(); + const { data: influencer, error } = await supabase_1.default + .from('influencers') + .select(` + influencer_id, + name, + platform, + profile_url, + followers_count, + video_count, + platform_count, + created_at, + updated_at, + posts ( + post_id, + title, + description, + published_at + ) + `) + .eq('influencer_id', influencer_id) + .single(); + if (error) { + return c.json({ error: 'Influencer not found' }, 404); + } + return c.json(influencer); + } + catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; +exports.getInfluencerById = getInfluencerById; +const getInfluencerStats = async (c) => { + try { + const { platform } = c.req.query(); + let query = supabase_1.default + .from('influencers') + .select('platform, followers_count, video_count'); + if (platform) { + query = query.eq('platform', platform); + } + const { data: stats, error } = await query; + if (error) { + return c.json({ error: error.message }, 500); + } + const aggregatedStats = { + total_influencers: stats.length, + total_followers: stats.reduce((sum, item) => sum + (item.followers_count || 0), 0), + total_videos: stats.reduce((sum, item) => sum + (item.video_count || 0), 0), + average_followers: Math.round(stats.reduce((sum, item) => sum + (item.followers_count || 0), 0) / (stats.length || 1)), + average_videos: Math.round(stats.reduce((sum, item) => sum + (item.video_count || 0), 0) / (stats.length || 1)) + }; + return c.json(aggregatedStats); + } + catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; +exports.getInfluencerStats = getInfluencerStats; diff --git a/backend/dist/index.js b/backend/dist/index.js new file mode 100644 index 0000000..4510096 --- /dev/null +++ b/backend/dist/index.js @@ -0,0 +1,163 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const node_server_1 = require("@hono/node-server"); +const hono_1 = require("hono"); +const cors_1 = require("hono/cors"); +const logger_1 = require("hono/logger"); +const config_1 = __importDefault(require("./config")); +const auth_1 = __importDefault(require("./routes/auth")); +const analytics_1 = __importDefault(require("./routes/analytics")); +const community_1 = __importDefault(require("./routes/community")); +const posts_1 = __importDefault(require("./routes/posts")); +const projectComments_1 = __importDefault(require("./routes/projectComments")); +const comments_1 = __importDefault(require("./routes/comments")); +const influencers_1 = __importDefault(require("./routes/influencers")); +const redis_1 = require("./utils/redis"); +const clickhouse_1 = require("./utils/clickhouse"); +const queue_1 = require("./utils/queue"); +const initDatabase_1 = require("./utils/initDatabase"); +const swagger_1 = require("./swagger"); +// Create Hono app +const app = new hono_1.Hono(); +// Middleware +app.use('*', (0, logger_1.logger)()); +app.use('*', (0, cors_1.cors)({ + origin: '*', + allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowHeaders: ['Content-Type', 'Authorization'], + exposeHeaders: ['Content-Length'], + maxAge: 86400, +})); +// Health check route +app.get('/', (c) => { + return c.json({ + status: 'ok', + message: 'Promote API is running', + version: '1.0.0', + }); +}); +// 数据库初始化路由 +app.post('/api/admin/init-db', async (c) => { + try { + const result = await (0, initDatabase_1.initDatabase)(); + return c.json({ + success: result, + message: result ? 'Database initialized successfully' : 'Database initialization failed' + }); + } + catch (error) { + console.error('Error initializing database:', error); + return c.json({ + success: false, + message: 'Error initializing database', + error: error instanceof Error ? error.message : String(error) + }, 500); + } +}); +// 创建测试数据路由 +app.post('/api/admin/create-sample-data', async (c) => { + try { + const result = await (0, initDatabase_1.createSampleData)(); + return c.json({ + success: result, + message: result ? 'Sample data created successfully' : 'Sample data creation failed' + }); + } + catch (error) { + console.error('Error creating sample data:', error); + return c.json({ + success: false, + message: 'Error creating sample data', + error: error instanceof Error ? error.message : String(error) + }, 500); + } +}); +// Routes +app.route('/api/auth', auth_1.default); +app.route('/api/analytics', analytics_1.default); +app.route('/api/community', community_1.default); +app.route('/api/posts', posts_1.default); +app.route('/api/project-comments', projectComments_1.default); +app.route('/api/comments', comments_1.default); +app.route('/api/influencers', influencers_1.default); +// Swagger UI +const swaggerApp = (0, swagger_1.createSwaggerUI)(); +app.route('', swaggerApp); +// Initialize services and start server +const startServer = async () => { + try { + // Connect to Redis + try { + await (0, redis_1.connectRedis)(); + console.log('Connected to Redis'); + } + catch (error) { + console.error('Failed to connect to Redis:', error); + console.log('Continuing with mock Redis client...'); + } + // Initialize ClickHouse + try { + await (0, clickhouse_1.initClickHouse)(); + console.log('ClickHouse initialized'); + } + catch (error) { + console.error('Failed to initialize ClickHouse:', error); + console.log('Continuing with limited analytics functionality...'); + } + // 检查数据库连接,但不自动初始化或修改数据库 + try { + await (0, initDatabase_1.checkDatabaseConnection)(); + } + catch (error) { + console.error('Database connection check failed:', error); + console.log('Some features may not work correctly if database is not properly set up'); + } + console.log('NOTICE: Database will NOT be automatically initialized on startup'); + console.log('Use /api/admin/init-db endpoint to manually initialize the database if needed'); + // Initialize BullMQ workers + let workers; + try { + workers = (0, queue_1.initWorkers)(); + console.log('BullMQ workers initialized'); + } + catch (error) { + console.error('Failed to initialize BullMQ workers:', error); + console.log('Background processing will not be available...'); + workers = { analyticsWorker: null, notificationsWorker: null }; + } + // Start server + const port = Number(config_1.default.port); + console.log(`Server starting on port ${port}...`); + (0, node_server_1.serve)({ + fetch: app.fetch, + port, + }); + console.log(`Server running at http://localhost:${port}`); + console.log(`Swagger UI available at http://localhost:${port}/swagger`); + console.log(`Initialize database at http://localhost:${port}/api/admin/init-db (POST)`); + console.log(`Create sample data at http://localhost:${port}/api/admin/create-sample-data (POST)`); + // Handle graceful shutdown + const shutdown = async () => { + console.log('Shutting down server...'); + // Close workers if they exist + if (workers.analyticsWorker) { + await workers.analyticsWorker.close(); + } + if (workers.notificationsWorker) { + await workers.notificationsWorker.close(); + } + process.exit(0); + }; + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); + } + catch (error) { + console.error('Failed to start server:', error); + process.exit(1); + } +}; +// Start the server +startServer(); diff --git a/backend/dist/middlewares/auth.js b/backend/dist/middlewares/auth.js new file mode 100644 index 0000000..dc2997c --- /dev/null +++ b/backend/dist/middlewares/auth.js @@ -0,0 +1,85 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.verifySupabaseToken = exports.generateToken = exports.authMiddleware = void 0; +const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); +const config_1 = __importDefault(require("../config")); +const supabase_1 = __importDefault(require("../utils/supabase")); +// Middleware to verify JWT token +const authMiddleware = async (c, next) => { + try { + // Get authorization header + const authHeader = c.req.header('Authorization'); + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return c.json({ error: 'Unauthorized: No token provided' }, 401); + } + // Extract token + const token = authHeader.split(' ')[1]; + try { + // 验证 JWT token + const decoded = jsonwebtoken_1.default.verify(token, config_1.default.jwt.secret); + // 特殊处理 Swagger 测试 token + if (decoded.sub === 'swagger-test-user' && decoded.email === 'swagger@test.com') { + // 为 Swagger 测试设置一个模拟用户 + c.set('user', { + id: 'swagger-test-user', + email: 'swagger@test.com', + name: 'Swagger Test User' + }); + // 继续到下一个中间件或路由处理器 + await next(); + return; + } + // 设置用户信息到上下文 + c.set('user', { + id: decoded.sub, + email: decoded.email + }); + // 继续到下一个中间件或路由处理器 + await next(); + } + catch (jwtError) { + if (jwtError instanceof jsonwebtoken_1.default.JsonWebTokenError) { + return c.json({ error: 'Unauthorized: Invalid token' }, 401); + } + if (jwtError instanceof jsonwebtoken_1.default.TokenExpiredError) { + return c.json({ error: 'Unauthorized: Token expired' }, 401); + } + throw jwtError; + } + } + catch (error) { + console.error('Auth middleware error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}; +exports.authMiddleware = authMiddleware; +// Generate JWT token +const generateToken = (userId, email) => { + const secret = config_1.default.jwt.secret; + const expiresIn = config_1.default.jwt.expiresIn; + return jsonwebtoken_1.default.sign({ + sub: userId, + email, + }, secret, { + expiresIn, + }); +}; +exports.generateToken = generateToken; +// Verify Supabase token +const verifySupabaseToken = async (token) => { + try { + const { data, error } = await supabase_1.default.auth.getUser(token); + if (error || !data.user) { + return null; + } + return data.user; + } + catch (error) { + console.error('Supabase token verification error:', error); + return null; + } +}; +exports.verifySupabaseToken = verifySupabaseToken; diff --git a/backend/dist/routes/analytics.js b/backend/dist/routes/analytics.js new file mode 100644 index 0000000..4bcc780 --- /dev/null +++ b/backend/dist/routes/analytics.js @@ -0,0 +1,453 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const auth_1 = require("../middlewares/auth"); +const clickhouse_1 = __importDefault(require("../utils/clickhouse")); +const queue_1 = require("../utils/queue"); +const redis_1 = require("../utils/redis"); +const supabase_1 = __importDefault(require("../utils/supabase")); +const analyticsRouter = new hono_1.Hono(); +// Apply auth middleware to all routes +analyticsRouter.use('*', auth_1.authMiddleware); +// Track a view event +analyticsRouter.post('/view', async (c) => { + try { + const { content_id } = await c.req.json(); + const user = c.get('user'); + if (!content_id) { + return c.json({ error: 'Content ID is required' }, 400); + } + // Get IP and user agent + const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || '0.0.0.0'; + const userAgent = c.req.header('user-agent') || 'unknown'; + // Insert view event into ClickHouse + await clickhouse_1.default.query({ + query: ` + INSERT INTO promote.view_events (user_id, content_id, ip, user_agent) + VALUES (?, ?, ?, ?) + `, + values: [ + user.id, + content_id, + ip, + userAgent + ] + }); + // Queue analytics processing job + await (0, queue_1.addAnalyticsJob)('process_views', { + user_id: user.id, + content_id, + timestamp: new Date().toISOString() + }); + // Increment view count in Redis cache + const redis = await (0, redis_1.getRedisClient)(); + await redis.incr(`views:${content_id}`); + return c.json({ message: 'View tracked successfully' }); + } + catch (error) { + console.error('View tracking error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// Track a like event +analyticsRouter.post('/like', async (c) => { + try { + const { content_id, action } = await c.req.json(); + const user = c.get('user'); + if (!content_id || !action) { + return c.json({ error: 'Content ID and action are required' }, 400); + } + if (action !== 'like' && action !== 'unlike') { + return c.json({ error: 'Action must be either "like" or "unlike"' }, 400); + } + // Insert like event into ClickHouse + await clickhouse_1.default.query({ + query: ` + INSERT INTO promote.like_events (user_id, content_id, action) + VALUES (?, ?, ?) + `, + values: [ + user.id, + content_id, + action === 'like' ? 1 : 2 + ] + }); + // Queue analytics processing job + await (0, queue_1.addAnalyticsJob)('process_likes', { + user_id: user.id, + content_id, + action, + timestamp: new Date().toISOString() + }); + // Update like count in Redis cache + const redis = await (0, redis_1.getRedisClient)(); + const likeKey = `likes:${content_id}`; + if (action === 'like') { + await redis.incr(likeKey); + } + else { + await redis.decr(likeKey); + } + return c.json({ message: `${action} tracked successfully` }); + } + catch (error) { + console.error('Like tracking error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// Track a follow event +analyticsRouter.post('/follow', async (c) => { + try { + const { followed_id, action } = await c.req.json(); + const user = c.get('user'); + if (!followed_id || !action) { + return c.json({ error: 'Followed ID and action are required' }, 400); + } + if (action !== 'follow' && action !== 'unfollow') { + return c.json({ error: 'Action must be either "follow" or "unfollow"' }, 400); + } + // Insert follower event into ClickHouse + await clickhouse_1.default.query({ + query: ` + INSERT INTO promote.follower_events (follower_id, followed_id, action) + VALUES (?, ?, ?) + `, + values: [ + user.id, + followed_id, + action === 'follow' ? 1 : 2 + ] + }); + // Queue analytics processing job + await (0, queue_1.addAnalyticsJob)('process_followers', { + follower_id: user.id, + followed_id, + action, + timestamp: new Date().toISOString() + }); + // Update follower count in Redis cache + const redis = await (0, redis_1.getRedisClient)(); + const followerKey = `followers:${followed_id}`; + if (action === 'follow') { + await redis.incr(followerKey); + } + else { + await redis.decr(followerKey); + } + return c.json({ message: `${action} tracked successfully` }); + } + catch (error) { + console.error('Follow tracking error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// Get analytics for a content +analyticsRouter.get('/content/:id', async (c) => { + try { + const contentId = c.req.param('id'); + // Get counts from Redis cache + const redis = await (0, redis_1.getRedisClient)(); + const [views, likes] = await Promise.all([ + redis.get(`views:${contentId}`), + redis.get(`likes:${contentId}`) + ]); + return c.json({ + content_id: contentId, + views: parseInt(views || '0'), + likes: parseInt(likes || '0') + }); + } + catch (error) { + console.error('Content analytics error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// Get analytics for a user +analyticsRouter.get('/user/:id', async (c) => { + try { + const userId = c.req.param('id'); + // Get follower count from Redis cache + const redis = await (0, redis_1.getRedisClient)(); + const followers = await redis.get(`followers:${userId}`); + // Get content view and like counts from ClickHouse + const viewsResult = await clickhouse_1.default.query({ + query: ` + SELECT content_id, COUNT(*) as view_count + FROM promote.view_events + WHERE user_id = ? + GROUP BY content_id + `, + values: [userId] + }); + const likesResult = await clickhouse_1.default.query({ + query: ` + SELECT content_id, SUM(CASE WHEN action = 1 THEN 1 ELSE -1 END) as like_count + FROM promote.like_events + WHERE user_id = ? + GROUP BY content_id + `, + values: [userId] + }); + // Extract data from results + const viewsData = 'rows' in viewsResult ? viewsResult.rows : []; + const likesData = 'rows' in likesResult ? likesResult.rows : []; + return c.json({ + user_id: userId, + followers: parseInt(followers || '0'), + content_analytics: { + views: viewsData, + likes: likesData + } + }); + } + catch (error) { + console.error('User analytics error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 社群分析相关路由 +// 获取项目的顶级影响者 +analyticsRouter.get('/project/:id/top-influencers', async (c) => { + try { + const projectId = c.req.param('id'); + // 从ClickHouse查询项目的顶级影响者 + const result = await clickhouse_1.default.query({ + query: ` + SELECT + influencer_id, + SUM(metric_value) AS total_views + FROM events + WHERE + project_id = ? AND + event_type = 'post_view_change' + GROUP BY influencer_id + ORDER BY total_views DESC + LIMIT 10 + `, + values: [projectId] + }); + // 提取数据 + const influencerData = 'rows' in result ? result.rows : []; + // 如果有数据,从Supabase获取影响者详细信息 + if (influencerData.length > 0) { + const influencerIds = influencerData.map((item) => item.influencer_id); + const { data: influencerDetails, error } = await supabase_1.default + .from('influencers') + .select('influencer_id, name, platform, followers_count, video_count') + .in('influencer_id', influencerIds); + if (error) { + console.error('Error fetching influencer details:', error); + return c.json({ error: 'Error fetching influencer details' }, 500); + } + // 合并数据 + const enrichedData = influencerData.map((item) => { + const details = influencerDetails?.find((detail) => detail.influencer_id === item.influencer_id) || {}; + return { + ...item, + ...details + }; + }); + return c.json(enrichedData); + } + return c.json(influencerData); + } + catch (error) { + console.error('Error fetching top influencers:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取影响者的粉丝变化趋势(过去6个月) +analyticsRouter.get('/influencer/:id/follower-trend', async (c) => { + try { + const influencerId = c.req.param('id'); + // 从ClickHouse查询影响者的粉丝变化趋势 + const result = await clickhouse_1.default.query({ + query: ` + SELECT + toStartOfMonth(timestamp) AS month, + SUM(metric_value) AS follower_change + FROM events + WHERE + influencer_id = ? AND + event_type = 'follower_change' AND + timestamp >= subtractMonths(now(), 6) + GROUP BY month + ORDER BY month ASC + `, + values: [influencerId] + }); + // 提取数据 + const trendData = 'rows' in result ? result.rows : []; + return c.json({ + influencer_id: influencerId, + follower_trend: trendData + }); + } + catch (error) { + console.error('Error fetching follower trend:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取帖子的点赞变化(过去30天) +analyticsRouter.get('/post/:id/like-trend', async (c) => { + try { + const postId = c.req.param('id'); + // 从ClickHouse查询帖子的点赞变化 + const result = await clickhouse_1.default.query({ + query: ` + SELECT + toDate(timestamp) AS day, + SUM(metric_value) AS like_change + FROM events + WHERE + post_id = ? AND + event_type = 'post_like_change' AND + timestamp >= subtractDays(now(), 30) + GROUP BY day + ORDER BY day ASC + `, + values: [postId] + }); + // 提取数据 + const trendData = 'rows' in result ? result.rows : []; + return c.json({ + post_id: postId, + like_trend: trendData + }); + } + catch (error) { + console.error('Error fetching like trend:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取影响者详细信息 +analyticsRouter.get('/influencer/:id/details', async (c) => { + try { + const influencerId = c.req.param('id'); + // 从Supabase获取影响者详细信息 + const { data, error } = await supabase_1.default + .from('influencers') + .select('influencer_id, name, platform, profile_url, external_id, followers_count, video_count, platform_count, created_at') + .eq('influencer_id', influencerId) + .single(); + if (error) { + console.error('Error fetching influencer details:', error); + return c.json({ error: 'Error fetching influencer details' }, 500); + } + if (!data) { + return c.json({ error: 'Influencer not found' }, 404); + } + return c.json(data); + } + catch (error) { + console.error('Error fetching influencer details:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取影响者的帖子列表 +analyticsRouter.get('/influencer/:id/posts', async (c) => { + try { + const influencerId = c.req.param('id'); + // 从Supabase获取影响者的帖子列表 + const { data, error } = await supabase_1.default + .from('posts') + .select('post_id, influencer_id, platform, post_url, title, description, published_at, created_at') + .eq('influencer_id', influencerId) + .order('published_at', { ascending: false }); + if (error) { + console.error('Error fetching influencer posts:', error); + return c.json({ error: 'Error fetching influencer posts' }, 500); + } + return c.json(data || []); + } + catch (error) { + console.error('Error fetching influencer posts:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取帖子的评论列表 +analyticsRouter.get('/post/:id/comments', async (c) => { + try { + const postId = c.req.param('id'); + // 从Supabase获取帖子的评论列表 + const { data, error } = await supabase_1.default + .from('comments') + .select('comment_id, post_id, user_id, content, sentiment_score, created_at') + .eq('post_id', postId) + .order('created_at', { ascending: false }); + if (error) { + console.error('Error fetching post comments:', error); + return c.json({ error: 'Error fetching post comments' }, 500); + } + return c.json(data || []); + } + catch (error) { + console.error('Error fetching post comments:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取项目的平台分布 +analyticsRouter.get('/project/:id/platform-distribution', async (c) => { + try { + const projectId = c.req.param('id'); + // 从ClickHouse查询项目的平台分布 + const result = await clickhouse_1.default.query({ + query: ` + SELECT + platform, + COUNT(DISTINCT influencer_id) AS influencer_count + FROM events + WHERE project_id = ? + GROUP BY platform + ORDER BY influencer_count DESC + `, + values: [projectId] + }); + // 提取数据 + const distributionData = 'rows' in result ? result.rows : []; + return c.json({ + project_id: projectId, + platform_distribution: distributionData + }); + } + catch (error) { + console.error('Error fetching platform distribution:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取项目的互动类型分布 +analyticsRouter.get('/project/:id/interaction-types', async (c) => { + try { + const projectId = c.req.param('id'); + // 从ClickHouse查询项目的互动类型分布 + const result = await clickhouse_1.default.query({ + query: ` + SELECT + event_type, + COUNT(*) AS event_count, + SUM(metric_value) AS total_value + FROM events + WHERE + project_id = ? AND + event_type IN ('click', 'comment', 'share') + GROUP BY event_type + ORDER BY event_count DESC + `, + values: [projectId] + }); + // 提取数据 + const interactionData = 'rows' in result ? result.rows : []; + return c.json({ + project_id: projectId, + interaction_types: interactionData + }); + } + catch (error) { + console.error('Error fetching interaction types:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +exports.default = analyticsRouter; diff --git a/backend/dist/routes/auth.js b/backend/dist/routes/auth.js new file mode 100644 index 0000000..1f15d83 --- /dev/null +++ b/backend/dist/routes/auth.js @@ -0,0 +1,140 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const auth_1 = require("../middlewares/auth"); +const supabase_1 = __importDefault(require("../utils/supabase")); +const authRouter = new hono_1.Hono(); +// Register a new user +authRouter.post('/register', async (c) => { + try { + const { email, password, name } = await c.req.json(); + // Validate input + if (!email || !password || !name) { + return c.json({ error: 'Email, password, and name are required' }, 400); + } + // Register user with Supabase + const { data: authData, error: authError } = await supabase_1.default.auth.signUp({ + email, + password, + }); + if (authError) { + return c.json({ error: authError.message }, 400); + } + if (!authData.user) { + return c.json({ error: 'Failed to create user' }, 500); + } + // Create user profile in the database + const { error: profileError } = await supabase_1.default + .from('users') + .insert({ + id: authData.user.id, + email: authData.user.email, + name, + created_at: new Date().toISOString(), + }); + if (profileError) { + // Attempt to clean up the auth user if profile creation fails + await supabase_1.default.auth.admin.deleteUser(authData.user.id); + return c.json({ error: profileError.message }, 500); + } + // Generate JWT token + const token = (0, auth_1.generateToken)(authData.user.id, authData.user.email); + return c.json({ + message: 'User registered successfully', + user: { + id: authData.user.id, + email: authData.user.email, + name, + }, + token, + }, 201); + } + catch (error) { + console.error('Registration error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// Login user +authRouter.post('/login', async (c) => { + try { + const { email, password } = await c.req.json(); + const { data, error } = await supabase_1.default.auth.signInWithPassword({ + email, + password + }); + if (error) { + return c.json({ error: error.message }, 400); + } + // 使用与 authMiddleware 一致的方式创建 JWT + const token = (0, auth_1.generateToken)(data.user.id, data.user.email || ''); + // 只返回必要的用户信息和令牌 + return c.json({ + success: true, + token, + user: { + id: data.user.id, + email: data.user.email + } + }); + } + catch (error) { + console.error(error); + return c.json({ error: 'Server error' }, 500); + } +}); +// Verify token +authRouter.get('/verify', async (c) => { + try { + const token = c.req.header('Authorization')?.split(' ')[1]; + if (!token) { + return c.json({ error: 'No token provided' }, 401); + } + const user = await (0, auth_1.verifySupabaseToken)(token); + if (!user) { + return c.json({ error: 'Invalid token' }, 401); + } + return c.json({ + message: 'Token is valid', + user: { + id: user.id, + email: user.email, + }, + }); + } + catch (error) { + console.error('Token verification error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// Refresh token +authRouter.post('/refresh-token', async (c) => { + try { + const token = c.req.header('Authorization')?.split(' ')[1]; + if (!token) { + return c.json({ error: 'No token provided' }, 401); + } + // 验证当前token + const user = await (0, auth_1.verifySupabaseToken)(token); + if (!user) { + return c.json({ error: 'Invalid token' }, 401); + } + // 生成新token + const newToken = (0, auth_1.generateToken)(user.id, user.email || ''); + return c.json({ + message: 'Token refreshed successfully', + token: newToken, + user: { + id: user.id, + email: user.email, + }, + }); + } + catch (error) { + console.error('Token refresh error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +exports.default = authRouter; diff --git a/backend/dist/routes/comments.js b/backend/dist/routes/comments.js new file mode 100644 index 0000000..049e3af --- /dev/null +++ b/backend/dist/routes/comments.js @@ -0,0 +1,12 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const commentsController_1 = require("../controllers/commentsController"); +const auth_1 = require("../middlewares/auth"); +const commentsRouter = new hono_1.Hono(); +// Public routes +commentsRouter.get('/', commentsController_1.getComments); +// Protected routes +commentsRouter.post('/', auth_1.authMiddleware, commentsController_1.createComment); +commentsRouter.delete('/:comment_id', auth_1.authMiddleware, commentsController_1.deleteComment); +exports.default = commentsRouter; diff --git a/backend/dist/routes/community.js b/backend/dist/routes/community.js new file mode 100644 index 0000000..11e4aab --- /dev/null +++ b/backend/dist/routes/community.js @@ -0,0 +1,649 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const auth_1 = require("../middlewares/auth"); +const clickhouse_1 = __importDefault(require("../utils/clickhouse")); +const supabase_1 = __importDefault(require("../utils/supabase")); +const communityRouter = new hono_1.Hono(); +// Apply auth middleware to all routes +communityRouter.use('*', auth_1.authMiddleware); +// 创建新项目 +communityRouter.post('/projects', async (c) => { + try { + const { name, description, start_date, end_date } = await c.req.json(); + const user = c.get('user'); + if (!name) { + return c.json({ error: 'Project name is required' }, 400); + } + // 在Supabase中创建项目 + const { data, error } = await supabase_1.default + .from('projects') + .insert({ + name, + description, + start_date, + end_date, + created_by: user.id + }) + .select() + .single(); + if (error) { + console.error('Error creating project:', error); + return c.json({ error: 'Failed to create project' }, 500); + } + return c.json({ + message: 'Project created successfully', + project: data + }, 201); + } + catch (error) { + console.error('Error creating project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取项目列表 +communityRouter.get('/projects', async (c) => { + try { + const user = c.get('user'); + // 从Supabase获取项目列表 + const { data, error } = await supabase_1.default + .from('projects') + .select('*') + .eq('created_by', user.id) + .order('created_at', { ascending: false }); + if (error) { + console.error('Error fetching projects:', error); + return c.json({ error: 'Failed to fetch projects' }, 500); + } + return c.json(data || []); + } + catch (error) { + console.error('Error fetching projects:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取项目详情 +communityRouter.get('/projects/:id', async (c) => { + try { + const projectId = c.req.param('id'); + // 从Supabase获取项目详情 + const { data, error } = await supabase_1.default + .from('projects') + .select('*') + .eq('id', projectId) + .single(); + if (error) { + console.error('Error fetching project:', error); + return c.json({ error: 'Failed to fetch project' }, 500); + } + if (!data) { + return c.json({ error: 'Project not found' }, 404); + } + return c.json(data); + } + catch (error) { + console.error('Error fetching project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 更新项目 +communityRouter.put('/projects/:id', async (c) => { + try { + const projectId = c.req.param('id'); + const { name, description, start_date, end_date, status } = await c.req.json(); + const user = c.get('user'); + // 检查项目是否存在并属于当前用户 + const { data: existingProject, error: fetchError } = await supabase_1.default + .from('projects') + .select('*') + .eq('id', projectId) + .eq('created_by', user.id) + .single(); + if (fetchError || !existingProject) { + return c.json({ error: 'Project not found or you do not have permission to update it' }, 404); + } + // 更新项目 + const { data, error } = await supabase_1.default + .from('projects') + .update({ + name, + description, + start_date, + end_date, + status, + updated_at: new Date().toISOString() + }) + .eq('id', projectId) + .select() + .single(); + if (error) { + console.error('Error updating project:', error); + return c.json({ error: 'Failed to update project' }, 500); + } + return c.json({ + message: 'Project updated successfully', + project: data + }); + } + catch (error) { + console.error('Error updating project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 删除项目 +communityRouter.delete('/projects/:id', async (c) => { + try { + const projectId = c.req.param('id'); + const user = c.get('user'); + // 检查项目是否存在并属于当前用户 + const { data: existingProject, error: fetchError } = await supabase_1.default + .from('projects') + .select('*') + .eq('id', projectId) + .eq('created_by', user.id) + .single(); + if (fetchError || !existingProject) { + return c.json({ error: 'Project not found or you do not have permission to delete it' }, 404); + } + // 删除项目 + const { error } = await supabase_1.default + .from('projects') + .delete() + .eq('id', projectId); + if (error) { + console.error('Error deleting project:', error); + return c.json({ error: 'Failed to delete project' }, 500); + } + return c.json({ + message: 'Project deleted successfully' + }); + } + catch (error) { + console.error('Error deleting project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 添加影响者到项目 +communityRouter.post('/projects/:id/influencers', async (c) => { + try { + const projectId = c.req.param('id'); + const { influencer_id, platform, external_id, name, profile_url } = await c.req.json(); + const user = c.get('user'); + // 检查项目是否存在并属于当前用户 + const { data: existingProject, error: fetchError } = await supabase_1.default + .from('projects') + .select('*') + .eq('id', projectId) + .eq('created_by', user.id) + .single(); + if (fetchError || !existingProject) { + return c.json({ error: 'Project not found or you do not have permission to update it' }, 404); + } + // 检查影响者是否已存在 + let influencerData; + if (influencer_id) { + // 如果提供了影响者ID,检查是否存在 + const { data, error } = await supabase_1.default + .from('influencers') + .select('*') + .eq('influencer_id', influencer_id) + .single(); + if (!error && data) { + influencerData = data; + } + } + else if (external_id && platform) { + // 如果提供了外部ID和平台,检查是否存在 + const { data, error } = await supabase_1.default + .from('influencers') + .select('*') + .eq('external_id', external_id) + .eq('platform', platform) + .single(); + if (!error && data) { + influencerData = data; + } + } + // 如果影响者不存在,创建新的影响者 + if (!influencerData) { + if (!name || !platform) { + return c.json({ error: 'Name and platform are required for new influencers' }, 400); + } + const { data, error } = await supabase_1.default + .from('influencers') + .insert({ + name, + platform, + external_id, + profile_url + }) + .select() + .single(); + if (error) { + console.error('Error creating influencer:', error); + return c.json({ error: 'Failed to create influencer' }, 500); + } + influencerData = data; + } + // 将影响者添加到项目 + const { data: projectInfluencer, error } = await supabase_1.default + .from('project_influencers') + .insert({ + project_id: projectId, + influencer_id: influencerData.influencer_id + }) + .select() + .single(); + if (error) { + console.error('Error adding influencer to project:', error); + return c.json({ error: 'Failed to add influencer to project' }, 500); + } + return c.json({ + message: 'Influencer added to project successfully', + project_influencer: projectInfluencer, + influencer: influencerData + }, 201); + } + catch (error) { + console.error('Error adding influencer to project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取项目的影响者列表 +communityRouter.get('/projects/:id/influencers', async (c) => { + try { + const projectId = c.req.param('id'); + // 从Supabase获取项目的影响者列表 + const { data, error } = await supabase_1.default + .from('project_influencers') + .select(` + project_id, + influencers ( + influencer_id, + name, + platform, + profile_url, + external_id, + followers_count, + video_count + ) + `) + .eq('project_id', projectId); + if (error) { + console.error('Error fetching project influencers:', error); + return c.json({ error: 'Failed to fetch project influencers' }, 500); + } + // 格式化数据 + const influencers = data?.map(item => item.influencers) || []; + return c.json(influencers); + } + catch (error) { + console.error('Error fetching project influencers:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 从项目中移除影响者 +communityRouter.delete('/projects/:projectId/influencers/:influencerId', async (c) => { + try { + const projectId = c.req.param('projectId'); + const influencerId = c.req.param('influencerId'); + const user = c.get('user'); + // 检查项目是否存在并属于当前用户 + const { data: existingProject, error: fetchError } = await supabase_1.default + .from('projects') + .select('*') + .eq('id', projectId) + .eq('created_by', user.id) + .single(); + if (fetchError || !existingProject) { + return c.json({ error: 'Project not found or you do not have permission to update it' }, 404); + } + // 从项目中移除影响者 + const { error } = await supabase_1.default + .from('project_influencers') + .delete() + .eq('project_id', projectId) + .eq('influencer_id', influencerId); + if (error) { + console.error('Error removing influencer from project:', error); + return c.json({ error: 'Failed to remove influencer from project' }, 500); + } + return c.json({ + message: 'Influencer removed from project successfully' + }); + } + catch (error) { + console.error('Error removing influencer from project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 添加事件数据 +communityRouter.post('/events', async (c) => { + try { + const { project_id, influencer_id, post_id, platform, event_type, metric_value, event_metadata } = await c.req.json(); + if (!project_id || !influencer_id || !platform || !event_type || metric_value === undefined) { + return c.json({ + error: 'Project ID, influencer ID, platform, event type, and metric value are required' + }, 400); + } + // 验证事件类型 + const validEventTypes = [ + 'follower_change', + 'post_like_change', + 'post_view_change', + 'click', + 'comment', + 'share' + ]; + if (!validEventTypes.includes(event_type)) { + return c.json({ + error: `Invalid event type. Must be one of: ${validEventTypes.join(', ')}` + }, 400); + } + // 验证平台 + const validPlatforms = ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook']; + if (!validPlatforms.includes(platform)) { + return c.json({ + error: `Invalid platform. Must be one of: ${validPlatforms.join(', ')}` + }, 400); + } + // 将事件数据插入ClickHouse + await clickhouse_1.default.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, ?, ?, ?, ?) + `, + values: [ + project_id, + influencer_id, + post_id || null, + platform, + event_type, + metric_value, + event_metadata ? JSON.stringify(event_metadata) : '{}' + ] + }); + return c.json({ + message: 'Event data added successfully' + }, 201); + } + catch (error) { + console.error('Error adding event data:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 批量添加事件数据 +communityRouter.post('/events/batch', async (c) => { + try { + const { events } = await c.req.json(); + if (!Array.isArray(events) || events.length === 0) { + return c.json({ error: 'Events array is required and must not be empty' }, 400); + } + // 验证事件类型和平台 + const validEventTypes = [ + 'follower_change', + 'post_like_change', + 'post_view_change', + 'click', + 'comment', + 'share' + ]; + const validPlatforms = ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook']; + // 验证每个事件 + for (const event of events) { + const { project_id, influencer_id, platform, event_type, metric_value } = event; + if (!project_id || !influencer_id || !platform || !event_type || metric_value === undefined) { + return c.json({ + error: 'Project ID, influencer ID, platform, event type, and metric value are required for all events' + }, 400); + } + if (!validEventTypes.includes(event_type)) { + return c.json({ + error: `Invalid event type: ${event_type}. Must be one of: ${validEventTypes.join(', ')}` + }, 400); + } + if (!validPlatforms.includes(platform)) { + return c.json({ + error: `Invalid platform: ${platform}. Must be one of: ${validPlatforms.join(', ')}` + }, 400); + } + } + // 准备批量插入数据 + const values = events.map(event => `( + '${event.project_id}', + '${event.influencer_id}', + ${event.post_id ? `'${event.post_id}'` : 'NULL'}, + '${event.platform}', + '${event.event_type}', + ${event.metric_value}, + '${event.event_metadata ? JSON.stringify(event.event_metadata) : '{}'}' + )`).join(','); + // 批量插入事件数据 + await clickhouse_1.default.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES ${values} + ` + }); + return c.json({ + message: `${events.length} events added successfully` + }, 201); + } + catch (error) { + console.error('Error adding batch event data:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 添加帖子 +communityRouter.post('/posts', async (c) => { + try { + const { influencer_id, platform, post_url, title, description, published_at } = await c.req.json(); + if (!influencer_id || !platform || !post_url) { + return c.json({ + error: 'Influencer ID, platform, and post URL are required' + }, 400); + } + // 验证平台 + const validPlatforms = ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook']; + if (!validPlatforms.includes(platform)) { + return c.json({ + error: `Invalid platform. Must be one of: ${validPlatforms.join(', ')}` + }, 400); + } + // 检查帖子是否已存在 + const { data: existingPost, error: checkError } = await supabase_1.default + .from('posts') + .select('*') + .eq('post_url', post_url) + .single(); + if (!checkError && existingPost) { + return c.json({ + error: 'Post with this URL already exists', + post: existingPost + }, 409); + } + // 创建新帖子 + const { data, error } = await supabase_1.default + .from('posts') + .insert({ + influencer_id, + platform, + post_url, + title, + description, + published_at: published_at || new Date().toISOString() + }) + .select() + .single(); + if (error) { + console.error('Error creating post:', error); + return c.json({ error: 'Failed to create post' }, 500); + } + return c.json({ + message: 'Post created successfully', + post: data + }, 201); + } + catch (error) { + console.error('Error creating post:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 添加评论 +communityRouter.post('/comments', async (c) => { + try { + const { post_id, user_id, content, sentiment_score } = await c.req.json(); + if (!post_id || !content) { + return c.json({ + error: 'Post ID and content are required' + }, 400); + } + // 创建新评论 + const { data, error } = await supabase_1.default + .from('comments') + .insert({ + post_id, + user_id: user_id || c.get('user').id, + content, + sentiment_score: sentiment_score || 0 + }) + .select() + .single(); + if (error) { + console.error('Error creating comment:', error); + return c.json({ error: 'Failed to create comment' }, 500); + } + return c.json({ + message: 'Comment created successfully', + comment: data + }, 201); + } + catch (error) { + console.error('Error creating comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取项目的事件统计 +communityRouter.get('/projects/:id/event-stats', async (c) => { + try { + const projectId = c.req.param('id'); + // 从ClickHouse查询项目的事件统计 + const result = await clickhouse_1.default.query({ + query: ` + SELECT + event_type, + COUNT(*) AS event_count, + SUM(metric_value) AS total_value + FROM events + WHERE project_id = ? + GROUP BY event_type + ORDER BY event_count DESC + `, + values: [projectId] + }); + // 提取数据 + const statsData = 'rows' in result ? result.rows : []; + return c.json({ + project_id: projectId, + event_stats: statsData + }); + } + catch (error) { + console.error('Error fetching event stats:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取项目的时间趋势 +communityRouter.get('/projects/:id/time-trend', async (c) => { + try { + const projectId = c.req.param('id'); + const { event_type, interval = 'day', days = '30' } = c.req.query(); + if (!event_type) { + return c.json({ error: 'Event type is required' }, 400); + } + // 验证事件类型 + const validEventTypes = [ + 'follower_change', + 'post_like_change', + 'post_view_change', + 'click', + 'comment', + 'share' + ]; + if (!validEventTypes.includes(event_type)) { + return c.json({ + error: `Invalid event type. Must be one of: ${validEventTypes.join(', ')}` + }, 400); + } + // 验证时间间隔 + const validIntervals = ['hour', 'day', 'week', 'month']; + if (!validIntervals.includes(interval)) { + return c.json({ + error: `Invalid interval. Must be one of: ${validIntervals.join(', ')}` + }, 400); + } + // 构建时间间隔函数 + let timeFunction; + switch (interval) { + case 'hour': + timeFunction = 'toStartOfHour'; + break; + case 'day': + timeFunction = 'toDate'; + break; + case 'week': + timeFunction = 'toStartOfWeek'; + break; + case 'month': + timeFunction = 'toStartOfMonth'; + break; + } + // 从ClickHouse查询项目的时间趋势 + const result = await clickhouse_1.default.query({ + query: ` + SELECT + ${timeFunction}(timestamp) AS time_period, + SUM(metric_value) AS value + FROM events + WHERE + project_id = ? AND + event_type = ? AND + timestamp >= subtractDays(now(), ?) + GROUP BY time_period + ORDER BY time_period ASC + `, + values: [projectId, event_type, parseInt(days)] + }); + // 提取数据 + const trendData = 'rows' in result ? result.rows : []; + return c.json({ + project_id: projectId, + event_type, + interval, + days: parseInt(days), + trend: trendData + }); + } + catch (error) { + console.error('Error fetching time trend:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +exports.default = communityRouter; diff --git a/backend/dist/routes/influencers.js b/backend/dist/routes/influencers.js new file mode 100644 index 0000000..3e1c696 --- /dev/null +++ b/backend/dist/routes/influencers.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const influencersController_1 = require("../controllers/influencersController"); +const influencersRouter = new hono_1.Hono(); +// Public routes +influencersRouter.get('/', influencersController_1.getInfluencers); +influencersRouter.get('/stats', influencersController_1.getInfluencerStats); +influencersRouter.get('/:influencer_id', influencersController_1.getInfluencerById); +exports.default = influencersRouter; diff --git a/backend/dist/routes/posts.js b/backend/dist/routes/posts.js new file mode 100644 index 0000000..ee77019 --- /dev/null +++ b/backend/dist/routes/posts.js @@ -0,0 +1,584 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const auth_1 = require("../middlewares/auth"); +const supabase_1 = __importDefault(require("../utils/supabase")); +const clickhouse_1 = __importDefault(require("../utils/clickhouse")); +const redis_1 = require("../utils/redis"); +const postsRouter = new hono_1.Hono(); +// Apply auth middleware to most routes +postsRouter.use('*', auth_1.authMiddleware); +// 创建新帖子 +postsRouter.post('/', async (c) => { + try { + const { influencer_id, platform, post_url, title, description, published_at } = await c.req.json(); + if (!influencer_id || !platform || !post_url) { + return c.json({ + error: 'influencer_id, platform, and post_url are required' + }, 400); + } + // 验证平台 + const validPlatforms = ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook']; + if (!validPlatforms.includes(platform)) { + return c.json({ + error: `Invalid platform. Must be one of: ${validPlatforms.join(', ')}` + }, 400); + } + // 检查帖子URL是否已存在 + const { data: existingPost, error: checkError } = await supabase_1.default + .from('posts') + .select('*') + .eq('post_url', post_url) + .single(); + if (!checkError && existingPost) { + return c.json({ + error: 'Post with this URL already exists', + post: existingPost + }, 409); + } + // 创建新帖子 + const { data: post, error } = await supabase_1.default + .from('posts') + .insert({ + influencer_id, + platform, + post_url, + title, + description, + published_at: published_at || new Date().toISOString() + }) + .select() + .single(); + if (error) { + console.error('Error creating post:', error); + return c.json({ error: 'Failed to create post' }, 500); + } + return c.json({ + message: 'Post created successfully', + post + }, 201); + } + catch (error) { + console.error('Error creating post:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取帖子列表 +postsRouter.get('/', async (c) => { + try { + const { influencer_id, platform, limit = '20', offset = '0', sort = 'published_at', order = 'desc' } = c.req.query(); + // 构建查询 + let query = supabase_1.default.from('posts').select(` + *, + influencer:influencers(name, platform, profile_url, followers_count) + `); + // 添加过滤条件 + if (influencer_id) { + query = query.eq('influencer_id', influencer_id); + } + if (platform) { + query = query.eq('platform', platform); + } + // 添加排序和分页 + query = query.order(sort, { ascending: order === 'asc' }); + query = query.range(parseInt(offset), parseInt(offset) + parseInt(limit) - 1); + // 执行查询 + const { data, error, count } = await query; + if (error) { + console.error('Error fetching posts:', error); + return c.json({ error: 'Failed to fetch posts' }, 500); + } + // 获取帖子的统计数据 + if (data && data.length > 0) { + const postIds = data.map(post => post.post_id); + // 尝试从缓存获取数据 + const redis = await (0, redis_1.getRedisClient)(); + const cachedStats = await Promise.all(postIds.map(async (postId) => { + const [views, likes] = await Promise.all([ + redis.get(`post:views:${postId}`), + redis.get(`post:likes:${postId}`) + ]); + return { + post_id: postId, + views: views ? parseInt(views) : null, + likes: likes ? parseInt(likes) : null + }; + })); + // 找出缓存中没有的帖子ID + const missingIds = postIds.filter(id => { + const stat = cachedStats.find(s => s.post_id === id); + return stat?.views === null || stat?.likes === null; + }); + // 如果有缺失的统计数据,从ClickHouse获取 + if (missingIds.length > 0) { + try { + // 查询帖子的观看数 + const viewsResult = await clickhouse_1.default.query({ + query: ` + SELECT + post_id, + SUM(metric_value) AS views + FROM events + WHERE + post_id IN (${missingIds.map(id => `'${id}'`).join(',')}) AND + event_type = 'post_view_change' + GROUP BY post_id + ` + }); + // 查询帖子的点赞数 + const likesResult = await clickhouse_1.default.query({ + query: ` + SELECT + post_id, + SUM(metric_value) AS likes + FROM events + WHERE + post_id IN (${missingIds.map(id => `'${id}'`).join(',')}) AND + event_type = 'post_like_change' + GROUP BY post_id + ` + }); + // 处理结果 + const viewsData = 'rows' in viewsResult ? viewsResult.rows : []; + const likesData = 'rows' in likesResult ? likesResult.rows : []; + // 更新缓存并填充统计数据 + for (const viewStat of viewsData) { + if (viewStat && typeof viewStat === 'object' && 'post_id' in viewStat && 'views' in viewStat) { + // 更新缓存 + await redis.set(`post:views:${viewStat.post_id}`, String(viewStat.views)); + // 更新缓存统计数据 + const cacheStat = cachedStats.find(s => s.post_id === viewStat.post_id); + if (cacheStat) { + cacheStat.views = Number(viewStat.views); + } + } + } + for (const likeStat of likesData) { + if (likeStat && typeof likeStat === 'object' && 'post_id' in likeStat && 'likes' in likeStat) { + // 更新缓存 + await redis.set(`post:likes:${likeStat.post_id}`, String(likeStat.likes)); + // 更新缓存统计数据 + const cacheStat = cachedStats.find(s => s.post_id === likeStat.post_id); + if (cacheStat) { + cacheStat.likes = Number(likeStat.likes); + } + } + } + } + catch (chError) { + console.error('Error fetching stats from ClickHouse:', chError); + } + } + // 合并统计数据到帖子数据 + data.forEach(post => { + const stats = cachedStats.find(s => s.post_id === post.post_id); + post.stats = { + views: stats?.views || 0, + likes: stats?.likes || 0 + }; + }); + } + return c.json({ + posts: data || [], + total: count || 0, + limit: parseInt(limit), + offset: parseInt(offset) + }); + } + catch (error) { + console.error('Error fetching posts:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取单个帖子详情 +postsRouter.get('/:id', async (c) => { + try { + const postId = c.req.param('id'); + // 获取帖子详情 + const { data: post, error } = await supabase_1.default + .from('posts') + .select(` + *, + influencer:influencers(name, platform, profile_url, followers_count) + `) + .eq('post_id', postId) + .single(); + if (error) { + console.error('Error fetching post:', error); + return c.json({ error: 'Failed to fetch post' }, 500); + } + if (!post) { + return c.json({ error: 'Post not found' }, 404); + } + // 获取帖子统计数据 + try { + // 先尝试从Redis缓存获取 + const redis = await (0, redis_1.getRedisClient)(); + const [cachedViews, cachedLikes] = await Promise.all([ + redis.get(`post:views:${postId}`), + redis.get(`post:likes:${postId}`) + ]); + // 如果缓存中有数据,直接使用 + if (cachedViews !== null && cachedLikes !== null) { + post.stats = { + views: parseInt(cachedViews), + likes: parseInt(cachedLikes) + }; + } + else { + // 如果缓存中没有,从ClickHouse获取 + // 查询帖子的观看数 + const viewsResult = await clickhouse_1.default.query({ + query: ` + SELECT SUM(metric_value) AS views + FROM events + WHERE + post_id = ? AND + event_type = 'post_view_change' + `, + values: [postId] + }); + // 查询帖子的点赞数 + const likesResult = await clickhouse_1.default.query({ + query: ` + SELECT SUM(metric_value) AS likes + FROM events + WHERE + post_id = ? AND + event_type = 'post_like_change' + `, + values: [postId] + }); + // 处理结果 + let viewsData = 0; + if ('rows' in viewsResult && viewsResult.rows.length > 0 && viewsResult.rows[0] && typeof viewsResult.rows[0] === 'object' && 'views' in viewsResult.rows[0]) { + viewsData = Number(viewsResult.rows[0].views) || 0; + } + let likesData = 0; + if ('rows' in likesResult && likesResult.rows.length > 0 && likesResult.rows[0] && typeof likesResult.rows[0] === 'object' && 'likes' in likesResult.rows[0]) { + likesData = Number(likesResult.rows[0].likes) || 0; + } + // 更新缓存 + await redis.set(`post:views:${postId}`, String(viewsData)); + await redis.set(`post:likes:${postId}`, String(likesData)); + // 添加统计数据 + post.stats = { + views: viewsData, + likes: likesData + }; + } + // 获取互动时间线 + const timelineResult = await clickhouse_1.default.query({ + query: ` + SELECT + toDate(timestamp) as date, + event_type, + SUM(metric_value) as value + FROM events + WHERE + post_id = ? AND + event_type IN ('post_view_change', 'post_like_change') + GROUP BY date, event_type + ORDER BY date ASC + `, + values: [postId] + }); + const timelineData = 'rows' in timelineResult ? timelineResult.rows : []; + // 添加时间线数据 + post.timeline = timelineData; + // 获取评论数量 + const { count } = await supabase_1.default + .from('comments') + .select('*', { count: 'exact', head: true }) + .eq('post_id', postId); + post.comment_count = count || 0; + } + catch (statsError) { + console.error('Error fetching post stats:', statsError); + // 继续返回帖子数据,但没有统计信息 + post.stats = { views: 0, likes: 0 }; + post.timeline = []; + post.comment_count = 0; + } + return c.json(post); + } + catch (error) { + console.error('Error fetching post:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 更新帖子 +postsRouter.put('/:id', async (c) => { + try { + const postId = c.req.param('id'); + const user = c.get('user'); + const { title, description } = await c.req.json(); + // 先检查帖子是否存在 + const { data: existingPost, error: fetchError } = await supabase_1.default + .from('posts') + .select('*') + .eq('post_id', postId) + .single(); + if (fetchError || !existingPost) { + return c.json({ error: 'Post not found' }, 404); + } + // 更新帖子 + const { data: updatedPost, error } = await supabase_1.default + .from('posts') + .update({ + title, + description, + updated_at: new Date().toISOString() + }) + .eq('post_id', postId) + .select() + .single(); + if (error) { + console.error('Error updating post:', error); + return c.json({ error: 'Failed to update post' }, 500); + } + return c.json({ + message: 'Post updated successfully', + post: updatedPost + }); + } + catch (error) { + console.error('Error updating post:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 删除帖子 +postsRouter.delete('/:id', async (c) => { + try { + const postId = c.req.param('id'); + const user = c.get('user'); + // 删除帖子 + const { error } = await supabase_1.default + .from('posts') + .delete() + .eq('post_id', postId); + if (error) { + console.error('Error deleting post:', error); + return c.json({ error: 'Failed to delete post' }, 500); + } + // 清除缓存 + try { + const redis = await (0, redis_1.getRedisClient)(); + await Promise.all([ + redis.del(`post:views:${postId}`), + redis.del(`post:likes:${postId}`) + ]); + } + catch (cacheError) { + console.error('Error clearing cache:', cacheError); + } + return c.json({ + message: 'Post deleted successfully' + }); + } + catch (error) { + console.error('Error deleting post:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取帖子的评论 +postsRouter.get('/:id/comments', async (c) => { + try { + const postId = c.req.param('id'); + const { limit = '20', offset = '0' } = c.req.query(); + // 获取评论 + const { data: comments, error, count } = await supabase_1.default + .from('comments') + .select('*', { count: 'exact' }) + .eq('post_id', postId) + .order('created_at', { ascending: false }) + .range(parseInt(offset), parseInt(offset) + parseInt(limit) - 1); + if (error) { + console.error('Error fetching comments:', error); + return c.json({ error: 'Failed to fetch comments' }, 500); + } + // 如果有评论,获取用户信息 + if (comments && comments.length > 0) { + const userIds = [...new Set(comments.map(comment => comment.user_id))]; + // 获取用户信息 + const { data: userProfiles, error: userError } = await supabase_1.default + .from('user_profiles') + .select('id, full_name, avatar_url') + .in('id', userIds); + if (!userError && userProfiles) { + // 将用户信息添加到评论中 + comments.forEach(comment => { + const userProfile = userProfiles.find(profile => profile.id === comment.user_id); + comment.user_profile = userProfile || null; + }); + } + else { + console.error('Error fetching user profiles:', userError); + } + } + return c.json({ + comments: comments || [], + total: count || 0, + limit: parseInt(limit), + offset: parseInt(offset) + }); + } + catch (error) { + console.error('Error fetching comments:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 添加评论到帖子 +postsRouter.post('/:id/comments', async (c) => { + try { + const postId = c.req.param('id'); + const user = c.get('user'); + const { content, sentiment_score } = await c.req.json(); + if (!content) { + return c.json({ error: 'Comment content is required' }, 400); + } + // 创建评论 + const { data: comment, error } = await supabase_1.default + .from('comments') + .insert({ + post_id: postId, + user_id: user.id, + content, + sentiment_score: sentiment_score || 0 + }) + .select() + .single(); + if (error) { + console.error('Error creating comment:', error); + return c.json({ error: 'Failed to create comment' }, 500); + } + // 尝试记录评论事件到ClickHouse + try { + // 获取帖子信息 + const { data: post } = await supabase_1.default + .from('posts') + .select('influencer_id, platform') + .eq('post_id', postId) + .single(); + if (post) { + await clickhouse_1.default.query({ + query: ` + INSERT INTO events ( + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, 'comment', ?, ?) + `, + values: [ + post.influencer_id, + postId, + post.platform, + 1, + JSON.stringify({ + comment_id: comment.comment_id, + user_id: user.id, + sentiment_score: sentiment_score || 0 + }) + ] + }); + } + } + catch (eventError) { + console.error('Error recording comment event:', eventError); + // 不影响主流程,继续返回评论数据 + } + return c.json({ + message: 'Comment added successfully', + comment + }, 201); + } + catch (error) { + console.error('Error adding comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 更新评论 +postsRouter.put('/comments/:id', async (c) => { + try { + const commentId = c.req.param('id'); + const user = c.get('user'); + const { content, sentiment_score } = await c.req.json(); + // 先检查评论是否存在且属于当前用户 + const { data: existingComment, error: fetchError } = await supabase_1.default + .from('comments') + .select('*') + .eq('comment_id', commentId) + .eq('user_id', user.id) + .single(); + if (fetchError || !existingComment) { + return c.json({ + error: 'Comment not found or you do not have permission to update it' + }, 404); + } + // 更新评论 + const { data: updatedComment, error } = await supabase_1.default + .from('comments') + .update({ + content, + sentiment_score: sentiment_score !== undefined ? sentiment_score : existingComment.sentiment_score, + updated_at: new Date().toISOString() + }) + .eq('comment_id', commentId) + .select() + .single(); + if (error) { + console.error('Error updating comment:', error); + return c.json({ error: 'Failed to update comment' }, 500); + } + return c.json({ + message: 'Comment updated successfully', + comment: updatedComment + }); + } + catch (error) { + console.error('Error updating comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 删除评论 +postsRouter.delete('/comments/:id', async (c) => { + try { + const commentId = c.req.param('id'); + const user = c.get('user'); + // 先检查评论是否存在且属于当前用户 + const { data: existingComment, error: fetchError } = await supabase_1.default + .from('comments') + .select('*') + .eq('comment_id', commentId) + .eq('user_id', user.id) + .single(); + if (fetchError || !existingComment) { + return c.json({ + error: 'Comment not found or you do not have permission to delete it' + }, 404); + } + // 删除评论 + const { error } = await supabase_1.default + .from('comments') + .delete() + .eq('comment_id', commentId); + if (error) { + console.error('Error deleting comment:', error); + return c.json({ error: 'Failed to delete comment' }, 500); + } + return c.json({ + message: 'Comment deleted successfully' + }); + } + catch (error) { + console.error('Error deleting comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +exports.default = postsRouter; diff --git a/backend/dist/routes/projectComments.js b/backend/dist/routes/projectComments.js new file mode 100644 index 0000000..3e217ee --- /dev/null +++ b/backend/dist/routes/projectComments.js @@ -0,0 +1,395 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const auth_1 = require("../middlewares/auth"); +const supabase_1 = __importDefault(require("../utils/supabase")); +const clickhouse_1 = __importDefault(require("../utils/clickhouse")); +const projectCommentsRouter = new hono_1.Hono(); +// Apply auth middleware to all routes +projectCommentsRouter.use('*', auth_1.authMiddleware); +// 获取项目的评论列表 +projectCommentsRouter.get('/projects/:id/comments', async (c) => { + try { + const projectId = c.req.param('id'); + const { limit = '20', offset = '0', parent_id = null } = c.req.query(); + // 检查项目是否存在 + const { data: project, error: projectError } = await supabase_1.default + .from('projects') + .select('id, name') + .eq('id', projectId) + .single(); + if (projectError) { + console.error('Error fetching project:', projectError); + return c.json({ error: 'Project not found' }, 404); + } + // 构建评论查询 + let commentsQuery = supabase_1.default + .from('project_comments') + .select(` + comment_id, + project_id, + user_id, + content, + sentiment_score, + status, + is_pinned, + parent_id, + created_at, + updated_at, + user:user_id(id, email) + `, { count: 'exact' }); + // 过滤条件 + commentsQuery = commentsQuery.eq('project_id', projectId); + // 如果指定了父评论ID,则获取子评论 + if (parent_id) { + commentsQuery = commentsQuery.eq('parent_id', parent_id); + } + else { + // 否则获取顶级评论(没有父评论的评论) + commentsQuery = commentsQuery.is('parent_id', null); + } + // 排序和分页 + const isPinned = parent_id ? false : true; // 只有顶级评论才考虑置顶 + if (isPinned) { + commentsQuery = commentsQuery.order('is_pinned', { ascending: false }); + } + commentsQuery = commentsQuery.order('created_at', { ascending: false }); + commentsQuery = commentsQuery.range(parseInt(offset), parseInt(offset) + parseInt(limit) - 1); + // 执行查询 + const { data: comments, error: commentsError, count } = await commentsQuery; + if (commentsError) { + console.error('Error fetching project comments:', commentsError); + return c.json({ error: 'Failed to fetch project comments' }, 500); + } + // 获取每个顶级评论的回复数量 + if (comments && !parent_id) { + const commentIds = comments.map(comment => comment.comment_id); + if (commentIds.length > 0) { + // 手动构建SQL查询来计算每个父评论的回复数量 + const { data: replyCounts, error: replyCountError } = await supabase_1.default + .rpc('get_reply_counts_for_comments', { parent_ids: commentIds }); + if (!replyCountError && replyCounts) { + // 将回复数量添加到评论中 + for (const comment of comments) { + const replyCountItem = replyCounts.find((r) => r.parent_id === comment.comment_id); + comment.reply_count = replyCountItem ? replyCountItem.count : 0; + } + } + } + } + return c.json({ + project, + comments: comments || [], + total: count || 0, + limit: parseInt(limit), + offset: parseInt(offset) + }); + } + catch (error) { + console.error('Error fetching project comments:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 添加评论到项目 +projectCommentsRouter.post('/projects/:id/comments', async (c) => { + try { + const projectId = c.req.param('id'); + const user = c.get('user'); + const { content, sentiment_score = 0, parent_id = null } = await c.req.json(); + if (!content) { + return c.json({ error: 'Comment content is required' }, 400); + } + // 检查项目是否存在 + const { data: project, error: projectError } = await supabase_1.default + .from('projects') + .select('id') + .eq('id', projectId) + .single(); + if (projectError) { + console.error('Error fetching project:', projectError); + return c.json({ error: 'Project not found' }, 404); + } + // 如果指定了父评论ID,检查父评论是否存在 + if (parent_id) { + const { data: parentComment, error: parentError } = await supabase_1.default + .from('project_comments') + .select('comment_id') + .eq('comment_id', parent_id) + .eq('project_id', projectId) + .single(); + if (parentError || !parentComment) { + return c.json({ error: 'Parent comment not found' }, 404); + } + } + // 创建评论 + const { data: comment, error: commentError } = await supabase_1.default + .from('project_comments') + .insert({ + project_id: projectId, + user_id: user.id, + content, + sentiment_score, + parent_id + }) + .select() + .single(); + if (commentError) { + console.error('Error creating project comment:', commentError); + return c.json({ error: 'Failed to create comment' }, 500); + } + // 记录评论事件到ClickHouse + try { + await clickhouse_1.default.query({ + query: ` + INSERT INTO events ( + project_id, + event_type, + metric_value, + event_metadata + ) VALUES (?, 'project_comment', ?, ?) + `, + values: [ + projectId, + 1, + JSON.stringify({ + comment_id: comment.comment_id, + user_id: user.id, + parent_id: parent_id || null, + content: content.substring(0, 100), // 只存储部分内容以减小数据量 + sentiment_score: sentiment_score + }) + ] + }); + } + catch (chError) { + console.error('Error recording project comment event:', chError); + // 继续执行,不中断主流程 + } + return c.json({ + message: 'Comment added successfully', + comment + }, 201); + } + catch (error) { + console.error('Error adding project comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 更新项目评论 +projectCommentsRouter.put('/comments/:id', async (c) => { + try { + const commentId = c.req.param('id'); + const user = c.get('user'); + const { content, sentiment_score, is_pinned } = await c.req.json(); + // 检查评论是否存在且属于当前用户或用户是项目拥有者 + const { data: comment, error: fetchError } = await supabase_1.default + .from('project_comments') + .select(` + comment_id, + project_id, + user_id, + projects!inner(created_by) + `) + .eq('comment_id', commentId) + .single(); + if (fetchError || !comment) { + return c.json({ error: 'Comment not found' }, 404); + } + // 确保我们能够安全地访问projects中的created_by字段 + const projectOwner = comment.projects && + Array.isArray(comment.projects) && + comment.projects.length > 0 ? + comment.projects[0].created_by : null; + // 检查用户是否有权限更新评论 + const isCommentOwner = comment.user_id === user.id; + const isProjectOwner = projectOwner === user.id; + if (!isCommentOwner && !isProjectOwner) { + return c.json({ + error: 'You do not have permission to update this comment' + }, 403); + } + // 准备更新数据 + const updateData = {}; + // 评论创建者可以更新内容和情感分数 + if (isCommentOwner) { + if (content !== undefined) { + updateData.content = content; + } + if (sentiment_score !== undefined) { + updateData.sentiment_score = sentiment_score; + } + } + // 项目所有者可以更新状态和置顶 + if (isProjectOwner) { + if (is_pinned !== undefined) { + updateData.is_pinned = is_pinned; + } + } + // 更新时间 + updateData.updated_at = new Date().toISOString(); + // 如果没有内容要更新,返回错误 + if (Object.keys(updateData).length === 1) { // 只有updated_at + return c.json({ error: 'No valid fields to update' }, 400); + } + // 更新评论 + const { data: updatedComment, error } = await supabase_1.default + .from('project_comments') + .update(updateData) + .eq('comment_id', commentId) + .select() + .single(); + if (error) { + console.error('Error updating project comment:', error); + return c.json({ error: 'Failed to update comment' }, 500); + } + return c.json({ + message: 'Comment updated successfully', + comment: updatedComment + }); + } + catch (error) { + console.error('Error updating project comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 删除项目评论 +projectCommentsRouter.delete('/comments/:id', async (c) => { + try { + const commentId = c.req.param('id'); + const user = c.get('user'); + // 检查评论是否存在且属于当前用户或用户是项目拥有者 + const { data: comment, error: fetchError } = await supabase_1.default + .from('project_comments') + .select(` + comment_id, + project_id, + user_id, + projects!inner(created_by) + `) + .eq('comment_id', commentId) + .single(); + if (fetchError || !comment) { + return c.json({ error: 'Comment not found' }, 404); + } + // 确保我们能够安全地访问projects中的created_by字段 + const projectOwner = comment.projects && + Array.isArray(comment.projects) && + comment.projects.length > 0 ? + comment.projects[0].created_by : null; + // 检查用户是否有权限删除评论 + const isCommentOwner = comment.user_id === user.id; + const isProjectOwner = projectOwner === user.id; + if (!isCommentOwner && !isProjectOwner) { + return c.json({ + error: 'You do not have permission to delete this comment' + }, 403); + } + // 删除评论 + const { error } = await supabase_1.default + .from('project_comments') + .delete() + .eq('comment_id', commentId); + if (error) { + console.error('Error deleting project comment:', error); + return c.json({ error: 'Failed to delete comment' }, 500); + } + return c.json({ + message: 'Comment deleted successfully' + }); + } + catch (error) { + console.error('Error deleting project comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +// 获取项目评论的统计信息 +projectCommentsRouter.get('/projects/:id/comments/stats', async (c) => { + try { + const projectId = c.req.param('id'); + // 检查项目是否存在 + const { data: project, error: projectError } = await supabase_1.default + .from('projects') + .select('id, name') + .eq('id', projectId) + .single(); + if (projectError) { + console.error('Error fetching project:', projectError); + return c.json({ error: 'Project not found' }, 404); + } + // 从Supabase获取评论总数 + const { count } = await supabase_1.default + .from('project_comments') + .select('*', { count: 'exact', head: true }) + .eq('project_id', projectId); + // 从Supabase获取情感分析统计 + const { data: sentimentStats } = await supabase_1.default + .from('project_comments') + .select('sentiment_score') + .eq('project_id', projectId); + let averageSentiment = 0; + let positiveCount = 0; + let neutralCount = 0; + let negativeCount = 0; + if (sentimentStats && sentimentStats.length > 0) { + // 计算平均情感分数 + const totalSentiment = sentimentStats.reduce((acc, curr) => acc + (curr.sentiment_score || 0), 0); + averageSentiment = totalSentiment / sentimentStats.length; + // 分类情感分数 + sentimentStats.forEach(stat => { + const score = stat.sentiment_score || 0; + if (score > 0.3) { + positiveCount++; + } + else if (score < -0.3) { + negativeCount++; + } + else { + neutralCount++; + } + }); + } + let timeTrend = []; + try { + const result = await clickhouse_1.default.query({ + query: ` + SELECT + toDate(timestamp) as date, + count() as comment_count + FROM events + WHERE + project_id = ? AND + event_type = 'project_comment' AND + timestamp >= subtractDays(now(), 30) + GROUP BY date + ORDER BY date ASC + `, + values: [projectId] + }); + timeTrend = 'rows' in result ? result.rows : []; + } + catch (chError) { + console.error('Error fetching comment time trend:', chError); + // 继续执行,返回空趋势数据 + } + return c.json({ + project_id: projectId, + project_name: project.name, + total_comments: count || 0, + sentiment: { + average: averageSentiment, + positive: positiveCount, + neutral: neutralCount, + negative: negativeCount + }, + time_trend: timeTrend + }); + } + catch (error) { + console.error('Error fetching project comment stats:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); +exports.default = projectCommentsRouter; diff --git a/backend/dist/swagger/index.js b/backend/dist/swagger/index.js new file mode 100644 index 0000000..45df67d --- /dev/null +++ b/backend/dist/swagger/index.js @@ -0,0 +1,1863 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createSwaggerUI = exports.openAPISpec = void 0; +const swagger_ui_1 = require("@hono/swagger-ui"); +const hono_1 = require("hono"); +const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); +const config_1 = __importDefault(require("../config")); +// 创建 OpenAPI 规范 +exports.openAPISpec = { + openapi: '3.0.0', + info: { + title: 'Promote API', + version: '1.0.0', + description: 'API documentation for the Promote platform', + }, + servers: [ + { + url: 'http://localhost:4000', + description: 'Local development server', + }, + ], + paths: { + '/': { + get: { + summary: 'Health check', + description: 'Returns the API status', + responses: { + '200': { + description: 'API is running', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string', example: 'ok' }, + message: { type: 'string', example: 'Promote API is running' }, + version: { type: 'string', example: '1.0.0' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/auth/register': { + post: { + summary: 'Register a new user', + description: 'Creates a new user account', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['email', 'password', 'name'], + properties: { + email: { type: 'string', format: 'email', example: 'user@example.com' }, + password: { type: 'string', format: 'password', example: 'securepassword' }, + name: { type: 'string', example: 'John Doe' }, + }, + }, + }, + }, + }, + responses: { + '201': { + description: 'User registered successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'User registered successfully' }, + user: { + type: 'object', + properties: { + id: { type: 'string', example: '123e4567-e89b-12d3-a456-426614174000' }, + email: { type: 'string', example: 'user@example.com' }, + name: { type: 'string', example: 'John Doe' }, + }, + }, + token: { type: 'string', example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Email, password, and name are required' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/auth/login': { + post: { + summary: 'Login user', + description: 'Authenticates a user and returns a JWT token', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['email', 'password'], + properties: { + email: { type: 'string', format: 'email', example: 'vitalitymailg@gmail.com' }, + password: { type: 'string', format: 'password', example: 'password123' }, + }, + }, + examples: { + demoUser: { + summary: '示例用户', + value: { + email: 'vitalitymailg@gmail.com', + password: 'password123' + } + } + } + }, + }, + }, + responses: { + '200': { + description: 'Login successful', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'Login successful' }, + user: { + type: 'object', + properties: { + id: { type: 'string', example: '123e4567-e89b-12d3-a456-426614174000' }, + email: { type: 'string', example: 'vitalitymailg@gmail.com' }, + name: { type: 'string', example: 'Vitality User' }, + }, + }, + token: { type: 'string', example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Invalid credentials' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/auth/verify': { + get: { + summary: 'Verify token', + description: 'Verifies a JWT token', + security: [ + { + bearerAuth: [], + }, + ], + responses: { + '200': { + description: 'Token is valid', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'Token is valid' }, + user: { + type: 'object', + properties: { + id: { type: 'string', example: '123e4567-e89b-12d3-a456-426614174000' }, + email: { type: 'string', example: 'vitalitymailg@gmail.com' }, + }, + }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Invalid token' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/analytics/view': { + post: { + summary: 'Track view event', + description: 'Records a view event for content', + security: [ + { + bearerAuth: [], + }, + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['content_id'], + properties: { + content_id: { type: 'string', example: 'content-123' }, + }, + }, + }, + }, + }, + responses: { + '200': { + description: 'View tracked successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'View tracked successfully' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Content ID is required' }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Unauthorized: No token provided' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/analytics/like': { + post: { + summary: 'Track like event', + description: 'Records a like or unlike event for content', + security: [ + { + bearerAuth: [], + }, + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['content_id', 'action'], + properties: { + content_id: { type: 'string', example: 'content-123' }, + action: { type: 'string', enum: ['like', 'unlike'], example: 'like' }, + }, + }, + }, + }, + }, + responses: { + '200': { + description: 'Like/unlike tracked successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'like tracked successfully' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Content ID and action are required' }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Unauthorized: No token provided' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/analytics/follow': { + post: { + summary: 'Track follow event', + description: 'Records a follow or unfollow event for a user', + security: [ + { + bearerAuth: [], + }, + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['followed_id', 'action'], + properties: { + followed_id: { type: 'string', example: 'user-123' }, + action: { type: 'string', enum: ['follow', 'unfollow'], example: 'follow' }, + }, + }, + }, + }, + }, + responses: { + '200': { + description: 'Follow/unfollow tracked successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'follow tracked successfully' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Followed ID and action are required' }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Unauthorized: No token provided' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/analytics/content/{id}': { + get: { + summary: 'Get content analytics', + description: 'Returns analytics data for a specific content', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'string', + }, + description: 'Content ID', + example: 'content-123', + }, + ], + responses: { + '200': { + description: 'Content analytics data', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + content_id: { type: 'string', example: 'content-123' }, + views: { type: 'integer', example: 1250 }, + likes: { type: 'integer', example: 87 }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Unauthorized: No token provided' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/analytics/user/{id}': { + get: { + summary: 'Get user analytics', + description: 'Returns analytics data for a specific user', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'string', + }, + description: 'User ID', + example: 'user-123', + }, + ], + responses: { + '200': { + description: 'User analytics data', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + user_id: { type: 'string', example: 'user-123' }, + followers: { type: 'integer', example: 542 }, + content_analytics: { + type: 'object', + properties: { + views: { + type: 'array', + items: { + type: 'object', + properties: { + content_id: { type: 'string', example: 'content-123' }, + view_count: { type: 'integer', example: 1250 }, + }, + }, + }, + likes: { + type: 'array', + items: { + type: 'object', + properties: { + content_id: { type: 'string', example: 'content-123' }, + like_count: { type: 'integer', example: 87 }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Unauthorized: No token provided' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/posts': { + get: { + summary: '获取帖子列表', + description: '返回分页的帖子列表,支持过滤和排序', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'influencer_id', + in: 'query', + description: '按影响者ID过滤', + schema: { type: 'string', format: 'uuid' }, + required: false + }, + { + name: 'platform', + in: 'query', + description: '按平台过滤', + schema: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + }, + required: false + }, + { + name: 'limit', + in: 'query', + description: '每页返回的记录数', + schema: { type: 'integer', default: 20 }, + required: false + }, + { + name: 'offset', + in: 'query', + description: '分页偏移量', + schema: { type: 'integer', default: 0 }, + required: false + }, + { + name: 'sort', + in: 'query', + description: '排序字段', + schema: { type: 'string', default: 'published_at' }, + required: false + }, + { + name: 'order', + in: 'query', + description: '排序方向', + schema: { + type: 'string', + enum: ['asc', 'desc'], + default: 'desc' + }, + required: false + } + ], + responses: { + '200': { + description: '成功获取帖子列表', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + posts: { + type: 'array', + items: { + $ref: '#/components/schemas/Post' + } + }, + total: { type: 'integer' }, + limit: { type: 'integer' }, + offset: { type: 'integer' } + } + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + }, + post: { + summary: '创建新帖子', + description: '创建一个新的帖子', + security: [{ bearerAuth: [] }], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['influencer_id', 'platform', 'post_url'], + properties: { + influencer_id: { type: 'string', format: 'uuid' }, + platform: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + }, + post_url: { type: 'string', format: 'uri' }, + title: { type: 'string' }, + description: { type: 'string' }, + published_at: { type: 'string', format: 'date-time' } + } + } + } + } + }, + responses: { + '201': { + description: '帖子创建成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + post: { + $ref: '#/components/schemas/Post' + } + } + } + } + } + }, + '400': { + description: '请求参数错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '409': { + description: '帖子URL已存在', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string' }, + post: { + $ref: '#/components/schemas/Post' + } + } + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + } + }, + '/api/posts/{id}': { + get: { + summary: '获取单个帖子详情', + description: '返回指定ID的帖子详情,包括统计数据和时间线', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '帖子ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + responses: { + '200': { + description: '成功获取帖子详情', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/PostDetail' + } + } + } + }, + '404': { + description: '帖子不存在', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + }, + put: { + summary: '更新帖子', + description: '更新指定ID的帖子信息', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '帖子ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + title: { type: 'string' }, + description: { type: 'string' } + } + } + } + } + }, + responses: { + '200': { + description: '帖子更新成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + post: { + $ref: '#/components/schemas/Post' + } + } + } + } + } + }, + '404': { + description: '帖子不存在', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + }, + delete: { + summary: '删除帖子', + description: '删除指定ID的帖子', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '帖子ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + responses: { + '200': { + description: '帖子删除成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' } + } + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + } + }, + '/api/posts/{id}/comments': { + get: { + summary: '获取帖子评论', + description: '返回指定帖子的评论列表', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '帖子ID', + required: true, + schema: { type: 'string', format: 'uuid' } + }, + { + name: 'limit', + in: 'query', + description: '每页返回的记录数', + schema: { type: 'integer', default: 20 }, + required: false + }, + { + name: 'offset', + in: 'query', + description: '分页偏移量', + schema: { type: 'integer', default: 0 }, + required: false + } + ], + responses: { + '200': { + description: '成功获取评论列表', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + comments: { + type: 'array', + items: { + $ref: '#/components/schemas/Comment' + } + }, + total: { type: 'integer' }, + limit: { type: 'integer' }, + offset: { type: 'integer' } + } + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + }, + post: { + summary: '添加评论', + description: '为指定帖子添加新评论', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '帖子ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['content'], + properties: { + content: { type: 'string' }, + sentiment_score: { type: 'number' } + } + } + } + } + }, + responses: { + '201': { + description: '评论添加成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + comment: { + $ref: '#/components/schemas/Comment' + } + } + } + } + } + }, + '400': { + description: '请求参数错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + } + }, + '/api/posts/comments/{id}': { + put: { + summary: '更新评论', + description: '更新指定ID的评论', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '评论ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + content: { type: 'string' }, + sentiment_score: { type: 'number' } + } + } + } + } + }, + responses: { + '200': { + description: '评论更新成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + comment: { + $ref: '#/components/schemas/Comment' + } + } + } + } + } + }, + '404': { + description: '评论不存在或无权限', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + }, + delete: { + summary: '删除评论', + description: '删除指定ID的评论', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '评论ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + responses: { + '200': { + description: '评论删除成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' } + } + } + } + } + }, + '404': { + description: '评论不存在或无权限', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + } + }, + '/api/comments': { + get: { + tags: ['Comments'], + summary: 'Get comments', + description: 'Retrieve a list of comments with optional filtering by post_id', + parameters: [ + { + name: 'post_id', + in: 'query', + description: 'Filter comments by post ID', + required: false, + schema: { + type: 'string', + format: 'uuid' + } + }, + { + name: 'limit', + in: 'query', + description: 'Number of comments to return', + required: false, + schema: { + type: 'integer', + default: 10 + } + }, + { + name: 'offset', + in: 'query', + description: 'Number of comments to skip', + required: false, + schema: { + type: 'integer', + default: 0 + } + } + ], + responses: { + '200': { + description: 'List of comments', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + comments: { + type: 'array', + items: { + $ref: '#/components/schemas/Comment' + } + }, + count: { + type: 'integer' + }, + limit: { + type: 'integer' + }, + offset: { + type: 'integer' + } + } + } + } + } + } + } + }, + post: { + tags: ['Comments'], + summary: 'Create a comment', + description: 'Create a new comment on a post', + security: [ + { + bearerAuth: [] + } + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['post_id', 'content'], + properties: { + post_id: { + type: 'string', + format: 'uuid' + }, + content: { + type: 'string' + } + } + } + } + } + }, + responses: { + '201': { + description: 'Comment created successfully', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Comment' + } + } + } + }, + '401': { + description: 'Unauthorized' + } + } + } + }, + '/api/comments/{comment_id}': { + delete: { + tags: ['Comments'], + summary: 'Delete a comment', + description: 'Delete a comment by ID (only for comment owner)', + security: [ + { + bearerAuth: [] + } + ], + parameters: [ + { + name: 'comment_id', + in: 'path', + description: 'Comment ID to delete', + required: true, + schema: { + type: 'string', + format: 'uuid' + } + } + ], + responses: { + '204': { + description: 'Comment deleted successfully' + }, + '401': { + description: 'Unauthorized' + }, + '404': { + description: 'Comment not found or unauthorized' + } + } + } + }, + '/api/influencers': { + get: { + tags: ['Influencers'], + summary: 'Get influencers', + description: 'Retrieve a list of influencers with optional filtering and sorting', + parameters: [ + { + name: 'platform', + in: 'query', + description: 'Filter by platform', + required: false, + schema: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + } + }, + { + name: 'min_followers', + in: 'query', + description: 'Minimum number of followers', + required: false, + schema: { + type: 'integer' + } + }, + { + name: 'max_followers', + in: 'query', + description: 'Maximum number of followers', + required: false, + schema: { + type: 'integer' + } + }, + { + name: 'sort_by', + in: 'query', + description: 'Field to sort by', + required: false, + schema: { + type: 'string', + enum: ['followers_count', 'video_count', 'created_at'], + default: 'followers_count' + } + }, + { + name: 'sort_order', + in: 'query', + description: 'Sort order', + required: false, + schema: { + type: 'string', + enum: ['asc', 'desc'], + default: 'desc' + } + }, + { + name: 'limit', + in: 'query', + description: 'Number of influencers to return', + required: false, + schema: { + type: 'integer', + default: 10 + } + }, + { + name: 'offset', + in: 'query', + description: 'Number of influencers to skip', + required: false, + schema: { + type: 'integer', + default: 0 + } + } + ], + responses: { + '200': { + description: 'List of influencers', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + influencers: { + type: 'array', + items: { + $ref: '#/components/schemas/Influencer' + } + }, + count: { + type: 'integer' + }, + limit: { + type: 'integer' + }, + offset: { + type: 'integer' + } + } + } + } + } + } + } + } + }, + '/api/influencers/stats': { + get: { + tags: ['Influencers'], + summary: 'Get influencer statistics', + description: 'Retrieve aggregated statistics about influencers', + parameters: [ + { + name: 'platform', + in: 'query', + description: 'Filter statistics by platform', + required: false, + schema: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + } + } + ], + responses: { + '200': { + description: 'Influencer statistics', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + total_influencers: { + type: 'integer' + }, + total_followers: { + type: 'integer' + }, + total_videos: { + type: 'integer' + }, + average_followers: { + type: 'integer' + }, + average_videos: { + type: 'integer' + } + } + } + } + } + } + } + } + }, + '/api/influencers/{influencer_id}': { + get: { + tags: ['Influencers'], + summary: 'Get influencer by ID', + description: 'Retrieve detailed information about a specific influencer', + parameters: [ + { + name: 'influencer_id', + in: 'path', + description: 'Influencer ID', + required: true, + schema: { + type: 'string', + format: 'uuid' + } + } + ], + responses: { + '200': { + description: 'Influencer details', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/InfluencerWithPosts' + } + } + } + }, + '404': { + description: 'Influencer not found' + } + } + } + } + }, + components: { + schemas: { + Post: { + type: 'object', + properties: { + post_id: { type: 'string', format: 'uuid' }, + influencer_id: { type: 'string', format: 'uuid' }, + platform: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + }, + post_url: { type: 'string', format: 'uri' }, + title: { type: 'string' }, + description: { type: 'string' }, + published_at: { type: 'string', format: 'date-time' }, + created_at: { type: 'string', format: 'date-time' }, + updated_at: { type: 'string', format: 'date-time' }, + influencer: { + type: 'object', + properties: { + name: { type: 'string' }, + platform: { type: 'string' }, + profile_url: { type: 'string' }, + followers_count: { type: 'integer' } + } + }, + stats: { + type: 'object', + properties: { + views: { type: 'integer' }, + likes: { type: 'integer' } + } + } + } + }, + PostDetail: { + type: 'object', + properties: { + post_id: { type: 'string', format: 'uuid' }, + influencer_id: { type: 'string', format: 'uuid' }, + platform: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + }, + post_url: { type: 'string', format: 'uri' }, + title: { type: 'string' }, + description: { type: 'string' }, + published_at: { type: 'string', format: 'date-time' }, + created_at: { type: 'string', format: 'date-time' }, + updated_at: { type: 'string', format: 'date-time' }, + influencer: { + type: 'object', + properties: { + name: { type: 'string' }, + platform: { type: 'string' }, + profile_url: { type: 'string' }, + followers_count: { type: 'integer' } + } + }, + stats: { + type: 'object', + properties: { + views: { type: 'integer' }, + likes: { type: 'integer' } + } + }, + timeline: { + type: 'array', + items: { + type: 'object', + properties: { + date: { type: 'string', format: 'date' }, + event_type: { type: 'string' }, + value: { type: 'integer' } + } + } + }, + comment_count: { type: 'integer' } + } + }, + Comment: { + type: 'object', + properties: { + comment_id: { + type: 'string', + format: 'uuid' + }, + content: { + type: 'string' + }, + sentiment_score: { + type: 'number', + description: '情感分析分数,范围从 -1 到 1' + }, + created_at: { + type: 'string', + format: 'date-time' + }, + updated_at: { + type: 'string', + format: 'date-time' + }, + post_id: { + type: 'string', + format: 'uuid' + }, + user_id: { + type: 'string', + format: 'uuid' + }, + user_profile: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + }, + full_name: { + type: 'string' + }, + avatar_url: { + type: 'string', + format: 'uri' + } + } + }, + post: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + }, + title: { + type: 'string' + }, + platform: { + type: 'string', + enum: ['facebook', 'threads', 'instagram', 'linkedin', 'xiaohongshu', 'youtube'] + }, + content_type: { + type: 'string', + enum: ['post', 'reel', 'video', 'short'] + }, + influencer: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + }, + name: { + type: 'string' + }, + type: { + type: 'string', + enum: ['user', 'kol', 'official'] + } + } + } + } + }, + status: { + type: 'string', + enum: ['pending', 'approved', 'rejected'] + }, + reply_status: { + type: 'string', + enum: ['none', 'draft', 'sent'] + }, + language: { + type: 'string', + enum: ['zh-TW', 'zh-CN', 'en'] + } + } + }, + Influencer: { + type: 'object', + properties: { + influencer_id: { + type: 'string', + format: 'uuid' + }, + name: { + type: 'string' + }, + platform: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + }, + profile_url: { + type: 'string' + }, + followers_count: { + type: 'integer' + }, + video_count: { + type: 'integer' + }, + platform_count: { + type: 'integer' + }, + created_at: { + type: 'string', + format: 'date-time' + }, + updated_at: { + type: 'string', + format: 'date-time' + } + } + }, + InfluencerWithPosts: { + allOf: [ + { + $ref: '#/components/schemas/Influencer' + }, + { + type: 'object', + properties: { + posts: { + type: 'array', + items: { + type: 'object', + properties: { + post_id: { + type: 'string', + format: 'uuid' + }, + title: { + type: 'string' + }, + description: { + type: 'string' + }, + published_at: { + type: 'string', + format: 'date-time' + } + } + } + } + } + } + ] + }, + Error: { + type: 'object', + properties: { + error: { type: 'string' } + } + } + }, + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + description: '登录后获取的 token 可直接在此处使用。在 Authorize 按钮中输入 "Bearer your-token" 或直接输入 token(不带 Bearer 前缀)。' + }, + }, + }, +}; +// 创建 Swagger UI 路由 +const createSwaggerUI = () => { + const app = new hono_1.Hono(); + // 设置 Swagger UI 路由 + app.get('/swagger', (0, swagger_ui_1.swaggerUI)({ + url: '/api/swagger.json', + })); + // 提供 OpenAPI 规范的 JSON 端点 + app.get('/api/swagger.json', (c) => { + return c.json(exports.openAPISpec); + }); + // 添加临时的 token 生成端点,仅用于 Swagger 测试 + app.get('/api/swagger/token', async (c) => { + try { + // 创建一个临时 token,与 authMiddleware 中的验证方式一致 + const token = jsonwebtoken_1.default.sign({ + sub: 'swagger-test-user', + email: 'swagger@test.com', + }, config_1.default.jwt.secret, { + expiresIn: '1h', + }); + return c.json({ + message: '此 token 仅用于 Swagger UI 测试', + token, + usage: '在 Authorize 对话框中输入: Bearer [token]' + }); + } + catch (error) { + console.error('Error generating swagger token:', error); + return c.json({ error: 'Failed to generate token' }, 500); + } + }); + return app; +}; +exports.createSwaggerUI = createSwaggerUI; diff --git a/backend/dist/utils/clickhouse.js b/backend/dist/utils/clickhouse.js new file mode 100644 index 0000000..b2d504f --- /dev/null +++ b/backend/dist/utils/clickhouse.js @@ -0,0 +1,87 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.initClickHouse = void 0; +const client_1 = require("@clickhouse/client"); +const config_1 = __importDefault(require("../config")); +// Create ClickHouse client with error handling +const createClickHouseClient = () => { + try { + return (0, client_1.createClient)({ + host: `http://${config_1.default.clickhouse.host}:${config_1.default.clickhouse.port}`, + username: config_1.default.clickhouse.user, + password: config_1.default.clickhouse.password, + database: config_1.default.clickhouse.database, + }); + } + catch (error) { + console.error('Error creating ClickHouse client:', error); + // Return a mock client for development that logs operations instead of executing them + return { + query: async ({ query, values }) => { + console.log('ClickHouse query (mock):', query, values); + return { rows: [] }; + }, + close: async () => { + console.log('ClickHouse connection closed (mock)'); + } + }; + } +}; +const clickhouse = createClickHouseClient(); +// Initialize ClickHouse database and tables +const initClickHouse = async () => { + try { + // Create database if not exists + await clickhouse.query({ + query: `CREATE DATABASE IF NOT EXISTS ${config_1.default.clickhouse.database}`, + }); + // Create tables for tracking events + await clickhouse.query({ + query: ` + CREATE TABLE IF NOT EXISTS ${config_1.default.clickhouse.database}.view_events ( + user_id String, + content_id String, + timestamp DateTime DEFAULT now(), + ip String, + user_agent String + ) ENGINE = MergeTree() + PARTITION BY toYYYYMM(timestamp) + ORDER BY (user_id, content_id, timestamp) + `, + }); + await clickhouse.query({ + query: ` + CREATE TABLE IF NOT EXISTS ${config_1.default.clickhouse.database}.like_events ( + user_id String, + content_id String, + timestamp DateTime DEFAULT now(), + action Enum('like' = 1, 'unlike' = 2) + ) ENGINE = MergeTree() + PARTITION BY toYYYYMM(timestamp) + ORDER BY (user_id, content_id, timestamp) + `, + }); + await clickhouse.query({ + query: ` + CREATE TABLE IF NOT EXISTS ${config_1.default.clickhouse.database}.follower_events ( + follower_id String, + followed_id String, + timestamp DateTime DEFAULT now(), + action Enum('follow' = 1, 'unfollow' = 2) + ) ENGINE = MergeTree() + PARTITION BY toYYYYMM(timestamp) + ORDER BY (follower_id, followed_id, timestamp) + `, + }); + console.log('ClickHouse database and tables initialized'); + } + catch (error) { + console.error('Error initializing ClickHouse:', error); + console.log('Continuing with limited functionality...'); + } +}; +exports.initClickHouse = initClickHouse; +exports.default = clickhouse; diff --git a/backend/dist/utils/initDatabase.js b/backend/dist/utils/initDatabase.js new file mode 100644 index 0000000..c960dde --- /dev/null +++ b/backend/dist/utils/initDatabase.js @@ -0,0 +1,492 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.initDatabase = exports.checkDatabaseConnection = exports.createSampleData = exports.initSupabaseFunctions = exports.initClickHouseTables = exports.initSupabaseTables = void 0; +const supabase_1 = __importDefault(require("./supabase")); +const clickhouse_1 = __importDefault(require("./clickhouse")); +const promises_1 = __importDefault(require("fs/promises")); +const path_1 = __importDefault(require("path")); +/** + * 初始化 Supabase (PostgreSQL) 数据库表 + */ +const initSupabaseTables = async () => { + try { + console.log('开始初始化 Supabase 数据表...'); + // 创建用户扩展表 + await supabase_1.default.rpc('create_user_profiles_if_not_exists'); + // 创建项目表 + await supabase_1.default.rpc('create_projects_table_if_not_exists'); + // 创建网红(影响者)表 + await supabase_1.default.rpc('create_influencers_table_if_not_exists'); + // 创建项目-网红关联表 + await supabase_1.default.rpc('create_project_influencers_table_if_not_exists'); + // 创建帖子表 + await supabase_1.default.rpc('create_posts_table_if_not_exists'); + // 创建评论表 + await supabase_1.default.rpc('create_comments_table_if_not_exists'); + // 创建项目评论表 + await supabase_1.default.rpc('create_project_comments_table_if_not_exists'); + console.log('Supabase 数据表初始化完成'); + return true; + } + catch (error) { + console.error('初始化 Supabase 数据表失败:', error); + return false; + } +}; +exports.initSupabaseTables = initSupabaseTables; +/** + * 初始化 ClickHouse 数据库表 + */ +const initClickHouseTables = async () => { + try { + console.log('开始初始化 ClickHouse 数据表...'); + // 创建事件表 + await clickhouse_1.default.query({ + query: ` + CREATE TABLE IF NOT EXISTS events ( + event_id UUID DEFAULT generateUUIDv4(), + project_id UUID, + influencer_id UUID, + post_id UUID NULL, + platform String, + event_type Enum( + 'follower_change' = 1, + 'post_like_change' = 2, + 'post_view_change' = 3, + 'click' = 4, + 'comment' = 5, + 'share' = 6, + 'project_comment' = 7 + ), + metric_value Int64, + event_metadata String, + timestamp DateTime DEFAULT now() + ) ENGINE = MergeTree() + PARTITION BY toYYYYMM(timestamp) + ORDER BY (platform, influencer_id, post_id, event_type, timestamp) + ` + }); + // 创建统计视图 - 按天统计 + await clickhouse_1.default.query({ + query: ` + CREATE MATERIALIZED VIEW IF NOT EXISTS daily_stats + ENGINE = SummingMergeTree() + PARTITION BY toYYYYMM(date) + ORDER BY (date, platform, influencer_id, event_type) + AS SELECT + toDate(timestamp) AS date, + platform, + influencer_id, + event_type, + SUM(metric_value) AS total_value, + COUNT(*) AS event_count + FROM events + GROUP BY date, platform, influencer_id, event_type + ` + }); + // 创建统计视图 - 按月统计 + await clickhouse_1.default.query({ + query: ` + CREATE MATERIALIZED VIEW IF NOT EXISTS monthly_stats + ENGINE = SummingMergeTree() + ORDER BY (month, platform, influencer_id, event_type) + AS SELECT + toStartOfMonth(timestamp) AS month, + platform, + influencer_id, + event_type, + SUM(metric_value) AS total_value, + COUNT(*) AS event_count + FROM events + GROUP BY month, platform, influencer_id, event_type + ` + }); + // 创建帖子互动统计视图 + await clickhouse_1.default.query({ + query: ` + CREATE MATERIALIZED VIEW IF NOT EXISTS post_interaction_stats + ENGINE = SummingMergeTree() + ORDER BY (post_id, event_type, date) + AS SELECT + post_id, + event_type, + toDate(timestamp) AS date, + SUM(metric_value) AS value, + COUNT(*) AS count + FROM events + WHERE post_id IS NOT NULL + GROUP BY post_id, event_type, date + ` + }); + // 创建项目互动统计视图 + await clickhouse_1.default.query({ + query: ` + CREATE MATERIALIZED VIEW IF NOT EXISTS project_interaction_stats + ENGINE = SummingMergeTree() + ORDER BY (project_id, event_type, date) + AS SELECT + project_id, + event_type, + toDate(timestamp) AS date, + SUM(metric_value) AS value, + COUNT(*) AS count + FROM events + WHERE project_id IS NOT NULL AND event_type = 'project_comment' + GROUP BY project_id, event_type, date + ` + }); + console.log('ClickHouse 数据表初始化完成'); + return true; + } + catch (error) { + console.error('初始化 ClickHouse 数据表失败:', error); + return false; + } +}; +exports.initClickHouseTables = initClickHouseTables; +/** + * 初始化 Supabase 存储函数 + */ +const initSupabaseFunctions = async () => { + try { + console.log('开始初始化 Supabase 存储过程...'); + // 创建用户简档表的存储过程 + await supabase_1.default.rpc('create_function_create_user_profiles_if_not_exists'); + // 创建项目表的存储过程 + await supabase_1.default.rpc('create_function_create_projects_table_if_not_exists'); + // 创建网红表的存储过程 + await supabase_1.default.rpc('create_function_create_influencers_table_if_not_exists'); + // 创建项目-网红关联表的存储过程 + await supabase_1.default.rpc('create_function_create_project_influencers_table_if_not_exists'); + // 创建帖子表的存储过程 + await supabase_1.default.rpc('create_function_create_posts_table_if_not_exists'); + // 创建评论表的存储过程 + await supabase_1.default.rpc('create_function_create_comments_table_if_not_exists'); + // 创建项目评论表的存储过程 + await supabase_1.default.rpc('create_function_create_project_comments_table_if_not_exists'); + // 创建评论相关的SQL函数 + console.log('创建评论相关的SQL函数...'); + const commentsSQL = await promises_1.default.readFile(path_1.default.join(__dirname, 'supabase-comments-functions.sql'), 'utf8'); + // 使用Supabase执行SQL + const { error: commentsFunctionsError } = await supabase_1.default.rpc('pgclient_execute', { query: commentsSQL }); + if (commentsFunctionsError) { + console.error('创建评论SQL函数失败:', commentsFunctionsError); + } + else { + console.log('评论SQL函数创建成功'); + } + console.log('Supabase 存储过程初始化完成'); + return true; + } + catch (error) { + console.error('初始化 Supabase 存储过程失败:', error); + return false; + } +}; +exports.initSupabaseFunctions = initSupabaseFunctions; +/** + * 创建测试数据 + */ +const createSampleData = async () => { + try { + console.log('开始创建测试数据...'); + // 创建测试用户 + const { data: user, error: userError } = await supabase_1.default.auth.admin.createUser({ + email: 'test@example.com', + password: 'password123', + user_metadata: { + full_name: '测试用户' + } + }); + if (userError) { + console.error('创建测试用户失败:', userError); + return false; + } + // 创建测试项目 + const { data: project, error: projectError } = await supabase_1.default + .from('projects') + .insert({ + name: '测试营销活动', + description: '这是一个测试营销活动', + created_by: user.user.id + }) + .select() + .single(); + if (projectError) { + console.error('创建测试项目失败:', projectError); + return false; + } + // 创建项目评论 + await supabase_1.default + .from('project_comments') + .insert([ + { + project_id: project.id, + user_id: user.user.id, + content: '这是对项目的一条测试评论', + sentiment_score: 0.8 + }, + { + project_id: project.id, + user_id: user.user.id, + content: '这个项目很有前景', + sentiment_score: 0.9 + }, + { + project_id: project.id, + user_id: user.user.id, + content: '需要关注这个项目的进展', + sentiment_score: 0.7 + } + ]); + // 创建测试网红 + const platforms = ['youtube', 'instagram', 'tiktok']; + const influencers = []; + for (let i = 1; i <= 10; i++) { + const platform = platforms[Math.floor(Math.random() * platforms.length)]; + const { data: influencer, error: influencerError } = await supabase_1.default + .from('influencers') + .insert({ + name: `测试网红 ${i}`, + platform, + profile_url: `https://${platform}.com/user${i}`, + external_id: `user_${platform}_${i}`, + followers_count: Math.floor(Math.random() * 1000000) + 1000, + video_count: Math.floor(Math.random() * 500) + 10 + }) + .select() + .single(); + if (influencerError) { + console.error(`创建测试网红 ${i} 失败:`, influencerError); + continue; + } + influencers.push(influencer); + // 将网红添加到项目 + await supabase_1.default + .from('project_influencers') + .insert({ + project_id: project.id, + influencer_id: influencer.influencer_id + }); + // 为每个网红创建 3-5 个帖子 + const postCount = Math.floor(Math.random() * 3) + 3; + for (let j = 1; j <= postCount; j++) { + const { data: post, error: postError } = await supabase_1.default + .from('posts') + .insert({ + influencer_id: influencer.influencer_id, + platform, + post_url: `https://${platform}.com/user${i}/post${j}`, + title: `测试帖子 ${j} - 由 ${influencer.name} 发布`, + description: `这是一个测试帖子的描述 ${j}`, + published_at: new Date(Date.now() - Math.floor(Math.random() * 30) * 24 * 60 * 60 * 1000).toISOString() + }) + .select() + .single(); + if (postError) { + console.error(`创建测试帖子 ${j} 失败:`, postError); + continue; + } + // 为每个帖子创建 2-10 个评论 + const commentCount = Math.floor(Math.random() * 9) + 2; + for (let k = 1; k <= commentCount; k++) { + await supabase_1.default + .from('comments') + .insert({ + post_id: post.post_id, + user_id: user.user.id, + content: `这是对帖子 ${post.title} 的测试评论 ${k}`, + sentiment_score: (Math.random() * 2 - 1) // -1 到 1 之间的随机数 + }); + } + // 创建 ClickHouse 事件数据 + // 粉丝变化事件 + await clickhouse_1.default.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, 'follower_change', ?, ?) + `, + values: [ + project.id, + influencer.influencer_id, + platform, + Math.floor(Math.random() * 1000) - 200, // -200 到 800 之间的随机数 + JSON.stringify({ source: 'api_crawler' }) + ] + }); + // 帖子点赞变化事件 + await clickhouse_1.default.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, ?, 'post_like_change', ?, ?) + `, + values: [ + project.id, + influencer.influencer_id, + post.post_id, + platform, + Math.floor(Math.random() * 500) + 10, // 10 到 510 之间的随机数 + JSON.stringify({ source: 'api_crawler' }) + ] + }); + // 帖子观看数变化事件 + await clickhouse_1.default.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, ?, 'post_view_change', ?, ?) + `, + values: [ + project.id, + influencer.influencer_id, + post.post_id, + platform, + Math.floor(Math.random() * 5000) + 100, // 100 到 5100 之间的随机数 + JSON.stringify({ source: 'api_crawler' }) + ] + }); + // 互动事件 + const interactionTypes = ['click', 'comment', 'share']; + const interactionType = interactionTypes[Math.floor(Math.random() * interactionTypes.length)]; + await clickhouse_1.default.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, ?, ?, ?, ?) + `, + values: [ + project.id, + influencer.influencer_id, + post.post_id, + platform, + interactionType, + 1, + JSON.stringify({ + ip: '192.168.1.' + Math.floor(Math.random() * 255), + user_agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + }) + ] + }); + } + } + // 创建项目评论事件 + for (let i = 1; i <= 5; i++) { + await clickhouse_1.default.query({ + query: ` + INSERT INTO events ( + project_id, + event_type, + metric_value, + event_metadata + ) VALUES (?, 'project_comment', ?, ?) + `, + values: [ + project.id, + 1, + JSON.stringify({ + user_id: user.user.id, + timestamp: new Date().toISOString(), + comment: `项目评论事件 ${i}` + }) + ] + }); + } + console.log('测试数据创建完成'); + return true; + } + catch (error) { + console.error('创建测试数据失败:', error); + return false; + } +}; +exports.createSampleData = createSampleData; +/** + * 检查数据库连接 + */ +const checkDatabaseConnection = async () => { + try { + console.log('检查数据库连接...'); + // 检查 Supabase 连接 + try { + // 仅检查连接是否正常,不执行实际查询 + const { data, error } = await supabase_1.default.auth.getSession(); + if (error) { + console.error('Supabase 连接测试失败:', error); + return false; + } + console.log('Supabase 连接正常'); + } + catch (supabaseError) { + console.error('Supabase 连接测试失败:', supabaseError); + return false; + } + // 检查 ClickHouse 连接 + try { + // 使用简单查询代替ping方法 + const result = await clickhouse_1.default.query({ query: 'SELECT 1' }); + console.log('ClickHouse 连接正常'); + } + catch (error) { + console.error('ClickHouse 连接测试失败:', error); + return false; + } + console.log('数据库连接检查完成,所有连接均正常'); + return true; + } + catch (error) { + console.error('数据库连接检查失败:', error); + return false; + } +}; +exports.checkDatabaseConnection = checkDatabaseConnection; +/** + * 初始化数据库 - 此函数现在仅作为手动初始化的入口点 + * 只有通过管理API明确调用时才会执行实际的初始化 + */ +const initDatabase = async () => { + try { + console.log('开始数据库初始化...'); + console.log('警告: 此操作将修改数据库结构,请确保您知道自己在做什么'); + // 初始化 Supabase 函数 + await (0, exports.initSupabaseFunctions)(); + // 初始化 Supabase 表 + await (0, exports.initSupabaseTables)(); + // 初始化 ClickHouse 表 + await (0, exports.initClickHouseTables)(); + console.log('数据库初始化完成'); + return true; + } + catch (error) { + console.error('数据库初始化失败:', error); + return false; + } +}; +exports.initDatabase = initDatabase; diff --git a/backend/dist/utils/queue.js b/backend/dist/utils/queue.js new file mode 100644 index 0000000..848f61f --- /dev/null +++ b/backend/dist/utils/queue.js @@ -0,0 +1,158 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.addNotificationJob = exports.addAnalyticsJob = exports.initWorkers = exports.QUEUE_NAMES = void 0; +const bullmq_1 = require("bullmq"); +const config_1 = __importDefault(require("../config")); +// Define queue names +exports.QUEUE_NAMES = { + ANALYTICS: 'analytics', + NOTIFICATIONS: 'notifications', +}; +// Create Redis connection options +const redisOptions = { + host: config_1.default.bull.redis.host, + port: config_1.default.bull.redis.port, + password: config_1.default.bull.redis.password, +}; +// Create queues with error handling +let analyticsQueue; +let notificationsQueue; +try { + analyticsQueue = new bullmq_1.Queue(exports.QUEUE_NAMES.ANALYTICS, { + connection: redisOptions, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + delay: 1000, + }, + }, + }); + notificationsQueue = new bullmq_1.Queue(exports.QUEUE_NAMES.NOTIFICATIONS, { + connection: redisOptions, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + delay: 1000, + }, + }, + }); +} +catch (error) { + console.error('Error initializing BullMQ queues:', error); + // Create mock queues for development + analyticsQueue = { + add: async (name, data) => { + console.log(`Mock analytics job added: ${name}`, data); + return { id: 'mock-job-id' }; + }, + close: async () => console.log('Mock analytics queue closed'), + }; + notificationsQueue = { + add: async (name, data) => { + console.log(`Mock notification job added: ${name}`, data); + return { id: 'mock-job-id' }; + }, + close: async () => console.log('Mock notifications queue closed'), + }; +} +// Initialize workers +const initWorkers = () => { + try { + // Analytics worker + const analyticsWorker = new bullmq_1.Worker(exports.QUEUE_NAMES.ANALYTICS, async (job) => { + console.log(`Processing analytics job ${job.id}`); + const { type, data } = job.data; + switch (type) { + case 'process_views': + // Process view analytics + console.log('Processing view analytics', data); + break; + case 'process_likes': + // Process like analytics + console.log('Processing like analytics', data); + break; + case 'process_followers': + // Process follower analytics + console.log('Processing follower analytics', data); + break; + default: + console.log(`Unknown analytics job type: ${type}`); + } + }, { connection: redisOptions }); + // Notifications worker + const notificationsWorker = new bullmq_1.Worker(exports.QUEUE_NAMES.NOTIFICATIONS, async (job) => { + console.log(`Processing notification job ${job.id}`); + const { type, data } = job.data; + switch (type) { + case 'new_follower': + // Send new follower notification + console.log('Sending new follower notification', data); + break; + case 'new_like': + // Send new like notification + console.log('Sending new like notification', data); + break; + default: + console.log(`Unknown notification job type: ${type}`); + } + }, { connection: redisOptions }); + // Handle worker events + analyticsWorker.on('completed', (job) => { + console.log(`Analytics job ${job.id} completed`); + }); + analyticsWorker.on('failed', (job, err) => { + console.error(`Analytics job ${job?.id} failed with error ${err.message}`); + }); + notificationsWorker.on('completed', (job) => { + console.log(`Notification job ${job.id} completed`); + }); + notificationsWorker.on('failed', (job, err) => { + console.error(`Notification job ${job?.id} failed with error ${err.message}`); + }); + return { + analyticsWorker, + notificationsWorker, + }; + } + catch (error) { + console.error('Error initializing BullMQ workers:', error); + // Return mock workers + return { + analyticsWorker: { + close: async () => console.log('Mock analytics worker closed'), + }, + notificationsWorker: { + close: async () => console.log('Mock notifications worker closed'), + }, + }; + } +}; +exports.initWorkers = initWorkers; +// Helper function to add jobs to queues +const addAnalyticsJob = async (type, data, options = {}) => { + try { + return await analyticsQueue.add(type, { type, data }, options); + } + catch (error) { + console.error('Error adding analytics job:', error); + console.log('Job details:', { type, data }); + return null; + } +}; +exports.addAnalyticsJob = addAnalyticsJob; +const addNotificationJob = async (type, data, options = {}) => { + try { + return await notificationsQueue.add(type, { type, data }, options); + } + catch (error) { + console.error('Error adding notification job:', error); + console.log('Job details:', { type, data }); + return null; + } +}; +exports.addNotificationJob = addNotificationJob; diff --git a/backend/dist/utils/redis.js b/backend/dist/utils/redis.js new file mode 100644 index 0000000..67a2c55 --- /dev/null +++ b/backend/dist/utils/redis.js @@ -0,0 +1,80 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getRedisClient = exports.connectRedis = exports.redisClient = void 0; +const redis_1 = require("redis"); +const config_1 = __importDefault(require("../config")); +// Create Redis client +const redisClient = (0, redis_1.createClient)({ + url: `redis://${config_1.default.redis.password ? `${config_1.default.redis.password}@` : ''}${config_1.default.redis.host}:${config_1.default.redis.port}`, +}); +exports.redisClient = redisClient; +// Handle Redis connection errors +redisClient.on('error', (err) => { + console.error('Redis Client Error:', err); +}); +// Create a mock Redis client for development when real connection fails +const createMockRedisClient = () => { + const store = new Map(); + return { + isOpen: true, + connect: async () => console.log('Mock Redis client connected'), + get: async (key) => store.get(key) || null, + set: async (key, value) => { + store.set(key, value); + return 'OK'; + }, + incr: async (key) => { + const current = parseInt(store.get(key) || '0', 10); + const newValue = current + 1; + store.set(key, newValue.toString()); + return newValue; + }, + decr: async (key) => { + const current = parseInt(store.get(key) || '0', 10); + const newValue = Math.max(0, current - 1); + store.set(key, newValue.toString()); + return newValue; + }, + quit: async () => console.log('Mock Redis client disconnected'), + }; +}; +// Connect to Redis +let mockRedisClient = null; +const connectRedis = async () => { + try { + if (!redisClient.isOpen) { + await redisClient.connect(); + console.log('Redis client connected'); + } + return redisClient; + } + catch (error) { + console.error('Failed to connect to Redis:', error); + console.log('Using mock Redis client for development...'); + if (!mockRedisClient) { + mockRedisClient = createMockRedisClient(); + } + return mockRedisClient; + } +}; +exports.connectRedis = connectRedis; +// Export the appropriate client +const getRedisClient = async () => { + try { + if (redisClient.isOpen) { + return redisClient; + } + return await connectRedis(); + } + catch (error) { + if (!mockRedisClient) { + mockRedisClient = createMockRedisClient(); + } + return mockRedisClient; + } +}; +exports.getRedisClient = getRedisClient; +exports.default = redisClient; diff --git a/backend/dist/utils/supabase.js b/backend/dist/utils/supabase.js new file mode 100644 index 0000000..fc6f9fb --- /dev/null +++ b/backend/dist/utils/supabase.js @@ -0,0 +1,18 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const supabase_js_1 = require("@supabase/supabase-js"); +const config_1 = __importDefault(require("../config")); +// Validate Supabase URL +const validateSupabaseUrl = (url) => { + if (!url || !url.startsWith('http')) { + console.warn('Invalid Supabase URL provided. Using a placeholder for development.'); + return 'https://example.supabase.co'; + } + return url; +}; +// Create a single supabase client for interacting with your database +const supabase = (0, supabase_js_1.createClient)(validateSupabaseUrl(config_1.default.supabase.url), config_1.default.supabase.key || 'dummy-key'); +exports.default = supabase; diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..7b99cac --- /dev/null +++ b/backend/package.json @@ -0,0 +1,38 @@ +{ + "name": "backend", + "version": "1.0.0", + "description": "Backend API for promote platform", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "lint": "eslint src --ext .ts", + "test": "vitest run" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0", + "dependencies": { + "@clickhouse/client": "^0.2.10", + "@hono/node-server": "^1.13.8", + "@hono/swagger-ui": "^0.5.1", + "@supabase/supabase-js": "^2.49.1", + "bullmq": "^5.4.1", + "dotenv": "^16.4.7", + "hono": "^4.7.4", + "jsonwebtoken": "^9.0.2", + "redis": "^4.7.0" + }, + "devDependencies": { + "@types/jsonwebtoken": "^9.0.6", + "@types/node": "^20.11.30", + "@typescript-eslint/eslint-plugin": "^7.4.0", + "@typescript-eslint/parser": "^7.4.0", + "eslint": "^8.57.0", + "tsx": "^4.7.1", + "typescript": "^5.4.3", + "vitest": "^1.4.0" + } +} \ No newline at end of file diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml new file mode 100644 index 0000000..66b5507 --- /dev/null +++ b/backend/pnpm-lock.yaml @@ -0,0 +1,2878 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@clickhouse/client': + specifier: ^0.2.10 + version: 0.2.10 + '@hono/node-server': + specifier: ^1.13.8 + version: 1.13.8(hono@4.7.4) + '@hono/swagger-ui': + specifier: ^0.5.1 + version: 0.5.1(hono@4.7.4) + '@supabase/supabase-js': + specifier: ^2.49.1 + version: 2.49.1 + bullmq: + specifier: ^5.4.1 + version: 5.41.7 + dotenv: + specifier: ^16.4.7 + version: 16.4.7 + hono: + specifier: ^4.7.4 + version: 4.7.4 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 + redis: + specifier: ^4.7.0 + version: 4.7.0 + devDependencies: + '@types/jsonwebtoken': + specifier: ^9.0.6 + version: 9.0.9 + '@types/node': + specifier: ^20.11.30 + version: 20.17.23 + '@typescript-eslint/eslint-plugin': + specifier: ^7.4.0 + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1)(typescript@5.8.2) + '@typescript-eslint/parser': + specifier: ^7.4.0 + version: 7.18.0(eslint@8.57.1)(typescript@5.8.2) + eslint: + specifier: ^8.57.0 + version: 8.57.1 + tsx: + specifier: ^4.7.1 + version: 4.19.3 + typescript: + specifier: ^5.4.3 + version: 5.8.2 + vitest: + specifier: ^1.4.0 + version: 1.6.1(@types/node@20.17.23) + +packages: + + '@clickhouse/client-common@0.2.10': + resolution: {integrity: sha512-BvTY0IXS96y9RUeNCpKL4HUzHmY80L0lDcGN0lmUD6zjOqYMn78+xyHYJ/AIAX7JQsc+/KwFt2soZutQTKxoGQ==} + + '@clickhouse/client@0.2.10': + resolution: {integrity: sha512-ZwBgzjEAFN/ogS0ym5KHVbR7Hx/oYCX01qGp2baEyfN2HM73kf/7Vp3GvMHWRy+zUXISONEtFv7UTViOXnmFrg==} + engines: {node: '>=16'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.0': + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.0': + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.0': + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.0': + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.0': + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.0': + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.0': + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.0': + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.0': + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.0': + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.0': + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@hono/node-server@1.13.8': + resolution: {integrity: sha512-fsn8ucecsAXUoVxrUil0m13kOEq4mkX4/4QozCqmY+HpGfKl74OYSn8JcMA8GnG0ClfdRI4/ZSeG7zhFaVg+wg==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@hono/swagger-ui@0.5.1': + resolution: {integrity: sha512-XpUCfszLJ9b1rtFdzqOSHfdg9pfBiC2J5piEjuSanYpDDTIwpMz0ciiv5N3WWUaQpz9fEgH8lttQqL41vIFuDA==} + peerDependencies: + hono: '*' + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@ioredis/commands@1.2.0': + resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@redis/bloom@1.2.0': + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/client@1.6.0': + resolution: {integrity: sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==} + engines: {node: '>=14'} + + '@redis/graph@1.1.1': + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/json@1.0.7': + resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/search@1.2.0': + resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/time-series@1.1.0': + resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@rollup/rollup-android-arm-eabi@4.34.9': + resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.34.9': + resolution: {integrity: sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.34.9': + resolution: {integrity: sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.34.9': + resolution: {integrity: sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.34.9': + resolution: {integrity: sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.34.9': + resolution: {integrity: sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': + resolution: {integrity: sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.34.9': + resolution: {integrity: sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.34.9': + resolution: {integrity: sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.34.9': + resolution: {integrity: sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': + resolution: {integrity: sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': + resolution: {integrity: sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.34.9': + resolution: {integrity: sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.34.9': + resolution: {integrity: sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.34.9': + resolution: {integrity: sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.34.9': + resolution: {integrity: sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.34.9': + resolution: {integrity: sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.34.9': + resolution: {integrity: sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.34.9': + resolution: {integrity: sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==} + cpu: [x64] + os: [win32] + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@supabase/auth-js@2.68.0': + resolution: {integrity: sha512-odG7nb7aOmZPUXk6SwL2JchSsn36Ppx11i2yWMIc/meUO2B2HK9YwZHPK06utD9Ql9ke7JKDbwGin/8prHKxxQ==} + + '@supabase/functions-js@2.4.4': + resolution: {integrity: sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==} + + '@supabase/node-fetch@2.6.15': + resolution: {integrity: sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==} + engines: {node: 4.x || >=6.0.0} + + '@supabase/postgrest-js@1.19.2': + resolution: {integrity: sha512-MXRbk4wpwhWl9IN6rIY1mR8uZCCG4MZAEji942ve6nMwIqnBgBnZhZlON6zTTs6fgveMnoCILpZv1+K91jN+ow==} + + '@supabase/realtime-js@2.11.2': + resolution: {integrity: sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==} + + '@supabase/storage-js@2.7.1': + resolution: {integrity: sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==} + + '@supabase/supabase-js@2.49.1': + resolution: {integrity: sha512-lKaptKQB5/juEF5+jzmBeZlz69MdHZuxf+0f50NwhL+IE//m4ZnOeWlsKRjjsM0fVayZiQKqLvYdBn0RLkhGiQ==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/jsonwebtoken@9.0.9': + resolution: {integrity: sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@20.17.23': + resolution: {integrity: sha512-8PCGZ1ZJbEZuYNTMqywO+Sj4vSKjSjT6Ua+6RFOYlEvIvKQABPtrNkoVSLSKDb4obYcMhspVKmsw8Cm10NFRUg==} + + '@types/phoenix@1.6.6': + resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==} + + '@types/ws@8.18.0': + resolution: {integrity: sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==} + + '@typescript-eslint/eslint-plugin@7.18.0': + resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@7.18.0': + resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@7.18.0': + resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/type-utils@7.18.0': + resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@7.18.0': + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/typescript-estree@7.18.0': + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@7.18.0': + resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + + '@typescript-eslint/visitor-keys@7.18.0': + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitest/expect@1.6.1': + resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==} + + '@vitest/runner@1.6.1': + resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==} + + '@vitest/snapshot@1.6.1': + resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==} + + '@vitest/spy@1.6.1': + resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==} + + '@vitest/utils@1.6.1': + resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + bullmq@5.41.7: + resolution: {integrity: sha512-eZbKJSx15bflfzKRiR+dKeLTr/M/YKb4cIp73OdU79PEMHQ6aEFUtbG6R+f0KvLLznI/O01G581U2Eqli6S2ew==} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chai@4.5.0: + resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hono@4.7.4: + resolution: {integrity: sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg==} + engines: {node: '>=16.9.0'} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ioredis@5.6.0: + resolution: {integrity: sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==} + engines: {node: '>=12.22.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + local-pkg@0.5.1: + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} + engines: {node: '>=14'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + + luxon@3.5.0: + resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} + engines: {node: '>=12'} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.7.4: + resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + hasBin: true + + msgpackr@1.11.2: + resolution: {integrity: sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + + redis@4.7.0: + resolution: {integrity: sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.34.9: + resolution: {integrity: sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + + std-env@3.8.1: + resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@2.1.1: + resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinypool@0.8.4: + resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + engines: {node: '>=14.0.0'} + + tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.19.3: + resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + vite-node@1.6.1: + resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.14: + resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + 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 + + vitest@1.6.1: + resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.6.1 + '@vitest/ui': 1.6.1 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.18.1: + resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.1.1: + resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} + engines: {node: '>=12.20'} + +snapshots: + + '@clickhouse/client-common@0.2.10': {} + + '@clickhouse/client@0.2.10': + dependencies: + '@clickhouse/client-common': 0.2.10 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.25.0': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.25.0': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.25.0': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.25.0': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.25.0': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.25.0': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.0': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.25.0': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.25.0': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.25.0': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.25.0': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.25.0': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.25.0': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.25.0': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.25.0': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.25.0': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.25.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.0': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.25.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.0': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.25.0': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.25.0': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.25.0': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.25.0': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.25.0': + optional: true + + '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@hono/node-server@1.13.8(hono@4.7.4)': + dependencies: + hono: 4.7.4 + + '@hono/swagger-ui@0.5.1(hono@4.7.4)': + dependencies: + hono: 4.7.4 + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@ioredis/commands@1.2.0': {} + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@redis/bloom@1.2.0(@redis/client@1.6.0)': + dependencies: + '@redis/client': 1.6.0 + + '@redis/client@1.6.0': + dependencies: + cluster-key-slot: 1.1.2 + generic-pool: 3.9.0 + yallist: 4.0.0 + + '@redis/graph@1.1.1(@redis/client@1.6.0)': + dependencies: + '@redis/client': 1.6.0 + + '@redis/json@1.0.7(@redis/client@1.6.0)': + dependencies: + '@redis/client': 1.6.0 + + '@redis/search@1.2.0(@redis/client@1.6.0)': + dependencies: + '@redis/client': 1.6.0 + + '@redis/time-series@1.1.0(@redis/client@1.6.0)': + dependencies: + '@redis/client': 1.6.0 + + '@rollup/rollup-android-arm-eabi@4.34.9': + optional: true + + '@rollup/rollup-android-arm64@4.34.9': + optional: true + + '@rollup/rollup-darwin-arm64@4.34.9': + optional: true + + '@rollup/rollup-darwin-x64@4.34.9': + optional: true + + '@rollup/rollup-freebsd-arm64@4.34.9': + optional: true + + '@rollup/rollup-freebsd-x64@4.34.9': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.34.9': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.34.9': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-x64-musl@4.34.9': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.34.9': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.34.9': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.34.9': + optional: true + + '@sinclair/typebox@0.27.8': {} + + '@supabase/auth-js@2.68.0': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/functions-js@2.4.4': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/node-fetch@2.6.15': + dependencies: + whatwg-url: 5.0.0 + + '@supabase/postgrest-js@1.19.2': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/realtime-js@2.11.2': + dependencies: + '@supabase/node-fetch': 2.6.15 + '@types/phoenix': 1.6.6 + '@types/ws': 8.18.0 + ws: 8.18.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/storage-js@2.7.1': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/supabase-js@2.49.1': + dependencies: + '@supabase/auth-js': 2.68.0 + '@supabase/functions-js': 2.4.4 + '@supabase/node-fetch': 2.6.15 + '@supabase/postgrest-js': 1.19.2 + '@supabase/realtime-js': 2.11.2 + '@supabase/storage-js': 2.7.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@types/estree@1.0.6': {} + + '@types/jsonwebtoken@9.0.9': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 20.17.23 + + '@types/ms@2.1.0': {} + + '@types/node@20.17.23': + dependencies: + undici-types: 6.19.8 + + '@types/phoenix@1.6.6': {} + + '@types/ws@8.18.0': + dependencies: + '@types/node': 20.17.23 + + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1)(typescript@5.8.2)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.8.2) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.8.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 7.18.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.4.3(typescript@5.8.2) + optionalDependencies: + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2)': + dependencies: + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.4.0 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.8.2)': + dependencies: + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.8.2) + debug: 4.4.0 + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.8.2) + optionalDependencies: + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@7.18.0': {} + + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.8.2)': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.4.0 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 1.4.3(typescript@5.8.2) + optionalDependencies: + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.8.2)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.8.2) + eslint: 8.57.1 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.0': {} + + '@vitest/expect@1.6.1': + dependencies: + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 + chai: 4.5.0 + + '@vitest/runner@1.6.1': + dependencies: + '@vitest/utils': 1.6.1 + p-limit: 5.0.0 + pathe: 1.1.2 + + '@vitest/snapshot@1.6.1': + dependencies: + magic-string: 0.30.17 + pathe: 1.1.2 + pretty-format: 29.7.0 + + '@vitest/spy@1.6.1': + dependencies: + tinyspy: 2.2.1 + + '@vitest/utils@1.6.1': + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.1 + + acorn@8.14.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + assertion-error@1.1.0: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer-equal-constant-time@1.0.1: {} + + bullmq@5.41.7: + dependencies: + cron-parser: 4.9.0 + ioredis: 5.6.0 + msgpackr: 1.11.2 + node-abort-controller: 3.1.1 + semver: 7.7.1 + tslib: 2.8.1 + uuid: 9.0.1 + transitivePeerDependencies: + - supports-color + + cac@6.7.14: {} + + callsites@3.1.0: {} + + chai@4.5.0: + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.4 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.1.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + check-error@1.0.3: + dependencies: + get-func-name: 2.0.2 + + cluster-key-slot@1.1.2: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + cron-parser@4.9.0: + dependencies: + luxon: 3.5.0 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + deep-eql@4.1.4: + dependencies: + type-detect: 4.1.0 + + deep-is@0.1.4: {} + + denque@2.1.0: {} + + detect-libc@2.0.3: + optional: true + + diff-sequences@29.6.3: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dotenv@16.4.7: {} + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + esbuild@0.21.5: + 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 + + esbuild@0.25.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 + + escape-string-regexp@4.0.0: {} + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 3.4.3 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + + esutils@2.0.3: {} + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.3: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + generic-pool@3.9.0: {} + + get-func-name@2.0.2: {} + + get-stream@8.0.1: {} + + get-tsconfig@4.10.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + hono@4.7.4: {} + + human-signals@5.0.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ioredis@5.6.0: + dependencies: + '@ioredis/commands': 1.2.0 + cluster-key-slot: 1.1.2 + debug: 4.4.0 + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-stream@3.0.0: {} + + isexe@2.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.1 + + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + local-pkg@0.5.1: + dependencies: + mlly: 1.7.4 + pkg-types: 1.3.1 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.defaults@4.2.0: {} + + lodash.includes@4.3.0: {} + + lodash.isarguments@3.1.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.merge@4.6.2: {} + + lodash.once@4.1.1: {} + + loupe@2.3.7: + dependencies: + get-func-name: 2.0.2 + + luxon@3.5.0: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-fn@4.0.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + mlly@1.7.4: + dependencies: + acorn: 8.14.1 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.5.4 + + ms@2.1.3: {} + + msgpackr-extract@3.0.3: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + optional: true + + msgpackr@1.11.2: + optionalDependencies: + msgpackr-extract: 3.0.3 + + nanoid@3.3.8: {} + + natural-compare@1.4.0: {} + + node-abort-controller@3.1.1: {} + + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.0.3 + optional: true + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@5.0.0: + dependencies: + yocto-queue: 1.1.1 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-type@4.0.0: {} + + pathe@1.1.2: {} + + pathe@2.0.3: {} + + pathval@1.1.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.7.4 + pathe: 2.0.3 + + postcss@8.5.3: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-is@18.3.1: {} + + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + + redis@4.7.0: + dependencies: + '@redis/bloom': 1.2.0(@redis/client@1.6.0) + '@redis/client': 1.6.0 + '@redis/graph': 1.1.1(@redis/client@1.6.0) + '@redis/json': 1.0.7(@redis/client@1.6.0) + '@redis/search': 1.2.0(@redis/client@1.6.0) + '@redis/time-series': 1.1.0(@redis/client@1.6.0) + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.34.9: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.34.9 + '@rollup/rollup-android-arm64': 4.34.9 + '@rollup/rollup-darwin-arm64': 4.34.9 + '@rollup/rollup-darwin-x64': 4.34.9 + '@rollup/rollup-freebsd-arm64': 4.34.9 + '@rollup/rollup-freebsd-x64': 4.34.9 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.9 + '@rollup/rollup-linux-arm-musleabihf': 4.34.9 + '@rollup/rollup-linux-arm64-gnu': 4.34.9 + '@rollup/rollup-linux-arm64-musl': 4.34.9 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.9 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.9 + '@rollup/rollup-linux-riscv64-gnu': 4.34.9 + '@rollup/rollup-linux-s390x-gnu': 4.34.9 + '@rollup/rollup-linux-x64-gnu': 4.34.9 + '@rollup/rollup-linux-x64-musl': 4.34.9 + '@rollup/rollup-win32-arm64-msvc': 4.34.9 + '@rollup/rollup-win32-ia32-msvc': 4.34.9 + '@rollup/rollup-win32-x64-msvc': 4.34.9 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.2.1: {} + + semver@7.7.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + standard-as-callback@2.1.0: {} + + std-env@3.8.1: {} + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-final-newline@3.0.0: {} + + strip-json-comments@3.1.1: {} + + strip-literal@2.1.1: + dependencies: + js-tokens: 9.0.1 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + text-table@0.2.0: {} + + tinybench@2.9.0: {} + + tinypool@0.8.4: {} + + tinyspy@2.2.1: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@0.0.3: {} + + ts-api-utils@1.4.3(typescript@5.8.2): + dependencies: + typescript: 5.8.2 + + tslib@2.8.1: {} + + tsx@4.19.3: + dependencies: + esbuild: 0.25.0 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.1.0: {} + + type-fest@0.20.2: {} + + typescript@5.8.2: {} + + ufo@1.5.4: {} + + undici-types@6.19.8: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + uuid@9.0.1: {} + + vite-node@1.6.1(@types/node@20.17.23): + dependencies: + cac: 6.7.14 + debug: 4.4.0 + pathe: 1.1.2 + picocolors: 1.1.1 + vite: 5.4.14(@types/node@20.17.23) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.14(@types/node@20.17.23): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.34.9 + optionalDependencies: + '@types/node': 20.17.23 + fsevents: 2.3.3 + + vitest@1.6.1(@types/node@20.17.23): + dependencies: + '@vitest/expect': 1.6.1 + '@vitest/runner': 1.6.1 + '@vitest/snapshot': 1.6.1 + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 + acorn-walk: 8.3.4 + chai: 4.5.0 + debug: 4.4.0 + execa: 8.0.1 + local-pkg: 0.5.1 + magic-string: 0.30.17 + pathe: 1.1.2 + picocolors: 1.1.1 + std-env: 3.8.1 + strip-literal: 2.1.1 + tinybench: 2.9.0 + tinypool: 0.8.4 + vite: 5.4.14(@types/node@20.17.23) + vite-node: 1.6.1(@types/node@20.17.23) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.17.23 + transitivePeerDependencies: + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrappy@1.0.2: {} + + ws@8.18.1: {} + + yallist@4.0.0: {} + + yocto-queue@0.1.0: {} + + yocto-queue@1.1.1: {} diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts new file mode 100644 index 0000000..2369e86 --- /dev/null +++ b/backend/src/config/index.ts @@ -0,0 +1,55 @@ +import dotenv from 'dotenv'; +import { join } from 'path'; + +// Load environment variables from .env file +dotenv.config({ path: join(__dirname, '../../.env') }); + +export const config = { + port: process.env.PORT || 4000, + + // Supabase configuration + supabase: { + url: process.env.SUPABASE_URL || '', + key: process.env.SUPABASE_KEY || '', + anonKey: process.env.SUPABASE_ANON_KEY || '', + }, + + // Redis configuration + redis: { + host: process.env.REDIS_HOST || 'localhost', + port: parseInt(process.env.REDIS_PORT || '6379', 10), + password: process.env.REDIS_PASSWORD || '', + }, + + // ClickHouse configuration + clickhouse: { + host: process.env.CLICKHOUSE_HOST || 'localhost', + port: process.env.CLICKHOUSE_PORT || '8123', + user: process.env.CLICKHOUSE_USER || 'admin', + password: process.env.CLICKHOUSE_PASSWORD || 'your_secure_password', + database: process.env.CLICKHOUSE_DATABASE || 'promote', + }, + + // BullMQ configuration + bull: { + redis: { + host: process.env.BULL_REDIS_HOST || 'localhost', + port: parseInt(process.env.BULL_REDIS_PORT || '6379', 10), + password: process.env.BULL_REDIS_PASSWORD || '', + }, + }, + + // JWT configuration + jwt: { + secret: process.env.JWT_SECRET || 'your-secret-key', + expiresIn: process.env.JWT_EXPIRES_IN || '7d', + }, + + // Domain configuration + domain: process.env.DOMAIN || 'upj.to', + + // Enabled routes + enabledRoutes: process.env.ENABLED_ROUTES || 'all', +}; + +export default config; \ No newline at end of file diff --git a/backend/src/controllers/commentsController.ts b/backend/src/controllers/commentsController.ts new file mode 100644 index 0000000..2548e83 --- /dev/null +++ b/backend/src/controllers/commentsController.ts @@ -0,0 +1,120 @@ +import { Context } from 'hono'; +import supabase from '../utils/supabase'; + +export const getComments = async (c: Context) => { + try { + const { post_id, limit = '10', offset = '0' } = c.req.query(); + + let query; + + if (post_id) { + // 获取特定帖子的评论 + query = supabase.rpc('get_comments_for_post', { post_id_param: post_id }); + } else { + // 获取所有评论 + query = supabase.rpc('get_comments_with_posts'); + } + + // 应用分页 + query = query.range(Number(offset), Number(offset) + Number(limit) - 1); + + const { data: comments, error, count } = await query; + + if (error) { + return c.json({ error: error.message }, 500); + } + + return c.json({ + comments, + count, + limit: Number(limit), + offset: Number(offset) + }); + } catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; + +export const createComment = async (c: Context) => { + try { + const { post_id, content } = await c.req.json(); + const user_id = c.get('user')?.id; + + if (!user_id) { + return c.json({ error: 'Unauthorized' }, 401); + } + + const { data: comment, error } = await supabase + .from('comments') + .insert({ + post_id, + content, + user_id + }) + .select(` + comment_id, + content, + sentiment_score, + created_at, + updated_at, + post_id, + user_id + `) + .single(); + + if (error) { + return c.json({ error: error.message }, 500); + } + + // 获取用户信息 + const { data: userProfile, error: userError } = await supabase + .from('user_profiles') + .select('id, full_name, avatar_url') + .eq('id', user_id) + .single(); + + if (!userError && userProfile) { + comment.user_profile = userProfile; + } + + return c.json(comment, 201); + } catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; + +export const deleteComment = async (c: Context) => { + try { + const { comment_id } = c.req.param(); + const user_id = c.get('user')?.id; + + if (!user_id) { + return c.json({ error: 'Unauthorized' }, 401); + } + + // Check if the comment belongs to the user + const { data: comment, error: fetchError } = await supabase + .from('comments') + .select() + .eq('comment_id', comment_id) + .eq('user_id', user_id) + .single(); + + if (fetchError || !comment) { + return c.json({ error: 'Comment not found or unauthorized' }, 404); + } + + const { error: deleteError } = await supabase + .from('comments') + .delete() + .eq('comment_id', comment_id); + + if (deleteError) { + return c.json({ error: deleteError.message }, 500); + } + + return c.body(null, 204); + } catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; \ No newline at end of file diff --git a/backend/src/controllers/influencersController.ts b/backend/src/controllers/influencersController.ts new file mode 100644 index 0000000..57ae8d0 --- /dev/null +++ b/backend/src/controllers/influencersController.ts @@ -0,0 +1,136 @@ +import { Context } from 'hono'; +import supabase from '../utils/supabase'; + +export const getInfluencers = async (c: Context) => { + try { + const { + platform, + limit = '10', + offset = '0', + min_followers, + max_followers, + sort_by = 'followers_count', + sort_order = 'desc' + } = c.req.query(); + + let query = supabase + .from('influencers') + .select(` + influencer_id, + name, + platform, + profile_url, + followers_count, + video_count, + platform_count, + created_at, + updated_at + `); + + // Apply filters + if (platform) { + query = query.eq('platform', platform); + } + if (min_followers) { + query = query.gte('followers_count', Number(min_followers)); + } + if (max_followers) { + query = query.lte('followers_count', Number(max_followers)); + } + + // Apply sorting + if (sort_by && ['followers_count', 'video_count', 'created_at'].includes(sort_by)) { + query = query.order(sort_by, { ascending: sort_order === 'asc' }); + } + + // Apply pagination + query = query.range(Number(offset), Number(offset) + Number(limit) - 1); + + const { data: influencers, error, count } = await query; + + if (error) { + return c.json({ error: error.message }, 500); + } + + return c.json({ + influencers, + count, + limit: Number(limit), + offset: Number(offset) + }); + } catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; + +export const getInfluencerById = async (c: Context) => { + try { + const { influencer_id } = c.req.param(); + + const { data: influencer, error } = await supabase + .from('influencers') + .select(` + influencer_id, + name, + platform, + profile_url, + followers_count, + video_count, + platform_count, + created_at, + updated_at, + posts ( + post_id, + title, + description, + published_at + ) + `) + .eq('influencer_id', influencer_id) + .single(); + + if (error) { + return c.json({ error: 'Influencer not found' }, 404); + } + + return c.json(influencer); + } catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; + +export const getInfluencerStats = async (c: Context) => { + try { + const { platform } = c.req.query(); + + let query = supabase + .from('influencers') + .select('platform, followers_count, video_count'); + + if (platform) { + query = query.eq('platform', platform); + } + + const { data: stats, error } = await query; + + if (error) { + return c.json({ error: error.message }, 500); + } + + const aggregatedStats = { + total_influencers: stats.length, + total_followers: stats.reduce((sum: number, item: any) => sum + (item.followers_count || 0), 0), + total_videos: stats.reduce((sum: number, item: any) => sum + (item.video_count || 0), 0), + average_followers: Math.round( + stats.reduce((sum: number, item: any) => sum + (item.followers_count || 0), 0) / (stats.length || 1) + ), + average_videos: Math.round( + stats.reduce((sum: number, item: any) => sum + (item.video_count || 0), 0) / (stats.length || 1) + ) + }; + + return c.json(aggregatedStats); + } catch (error) { + return c.json({ error: 'Internal server error' }, 500); + } +}; \ No newline at end of file diff --git a/backend/src/index.ts b/backend/src/index.ts new file mode 100644 index 0000000..811fc6c --- /dev/null +++ b/backend/src/index.ts @@ -0,0 +1,172 @@ +import { serve } from '@hono/node-server'; +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { logger } from 'hono/logger'; +import config from './config'; +import authRouter from './routes/auth'; +import analyticsRouter from './routes/analytics'; +import communityRouter from './routes/community'; +import postsRouter from './routes/posts'; +import projectCommentsRouter from './routes/projectComments'; +import commentsRouter from './routes/comments'; +import influencersRouter from './routes/influencers'; +import { connectRedis } from './utils/redis'; +import { initClickHouse } from './utils/clickhouse'; +import { initWorkers } from './utils/queue'; +import { initDatabase, createSampleData, checkDatabaseConnection } from './utils/initDatabase'; +import { createSwaggerUI } from './swagger'; + +// Create Hono app +const app = new Hono(); + +// Middleware +app.use('*', logger()); +app.use('*', cors({ + origin: '*', + allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowHeaders: ['Content-Type', 'Authorization'], + exposeHeaders: ['Content-Length'], + maxAge: 86400, +})); + +// Health check route +app.get('/', (c) => { + return c.json({ + status: 'ok', + message: 'Promote API is running', + version: '1.0.0', + }); +}); + +// 数据库初始化路由 +app.post('/api/admin/init-db', async (c) => { + try { + const result = await initDatabase(); + return c.json({ + success: result, + message: result ? 'Database initialized successfully' : 'Database initialization failed' + }); + } catch (error) { + console.error('Error initializing database:', error); + return c.json({ + success: false, + message: 'Error initializing database', + error: error instanceof Error ? error.message : String(error) + }, 500); + } +}); + +// 创建测试数据路由 +app.post('/api/admin/create-sample-data', async (c) => { + try { + const result = await createSampleData(); + return c.json({ + success: result, + message: result ? 'Sample data created successfully' : 'Sample data creation failed' + }); + } catch (error) { + console.error('Error creating sample data:', error); + return c.json({ + success: false, + message: 'Error creating sample data', + error: error instanceof Error ? error.message : String(error) + }, 500); + } +}); + +// Routes +app.route('/api/auth', authRouter); +app.route('/api/analytics', analyticsRouter); +app.route('/api/community', communityRouter); +app.route('/api/posts', postsRouter); +app.route('/api/project-comments', projectCommentsRouter); +app.route('/api/comments', commentsRouter); +app.route('/api/influencers', influencersRouter); + +// Swagger UI +const swaggerApp = createSwaggerUI(); +app.route('', swaggerApp); + +// Initialize services and start server +const startServer = async () => { + try { + // Connect to Redis + try { + await connectRedis(); + console.log('Connected to Redis'); + } catch (error) { + console.error('Failed to connect to Redis:', error); + console.log('Continuing with mock Redis client...'); + } + + // Initialize ClickHouse + try { + await initClickHouse(); + console.log('ClickHouse initialized'); + } catch (error) { + console.error('Failed to initialize ClickHouse:', error); + console.log('Continuing with limited analytics functionality...'); + } + + // 检查数据库连接,但不自动初始化或修改数据库 + try { + await checkDatabaseConnection(); + } catch (error) { + console.error('Database connection check failed:', error); + console.log('Some features may not work correctly if database is not properly set up'); + } + + console.log('NOTICE: Database will NOT be automatically initialized on startup'); + console.log('Use /api/admin/init-db endpoint to manually initialize the database if needed'); + + // Initialize BullMQ workers + let workers; + try { + workers = initWorkers(); + console.log('BullMQ workers initialized'); + } catch (error) { + console.error('Failed to initialize BullMQ workers:', error); + console.log('Background processing will not be available...'); + workers = { analyticsWorker: null, notificationsWorker: null }; + } + + // Start server + const port = Number(config.port); + console.log(`Server starting on port ${port}...`); + + serve({ + fetch: app.fetch, + port, + }); + + console.log(`Server running at http://localhost:${port}`); + console.log(`Swagger UI available at http://localhost:${port}/swagger`); + console.log(`Initialize database at http://localhost:${port}/api/admin/init-db (POST)`); + console.log(`Create sample data at http://localhost:${port}/api/admin/create-sample-data (POST)`); + + // Handle graceful shutdown + const shutdown = async () => { + console.log('Shutting down server...'); + + // Close workers if they exist + if (workers.analyticsWorker) { + await workers.analyticsWorker.close(); + } + + if (workers.notificationsWorker) { + await workers.notificationsWorker.close(); + } + + process.exit(0); + }; + + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); + } catch (error) { + console.error('Failed to start server:', error); + process.exit(1); + } +}; + +// Start the server +startServer(); \ No newline at end of file diff --git a/backend/src/middlewares/auth.ts b/backend/src/middlewares/auth.ts new file mode 100644 index 0000000..de789ed --- /dev/null +++ b/backend/src/middlewares/auth.ts @@ -0,0 +1,101 @@ +import { Context, Next } from 'hono'; +import jwt from 'jsonwebtoken'; +import config from '../config'; +import supabase from '../utils/supabase'; + +// Interface for JWT payload +interface JwtPayload { + sub: string; + email: string; + iat: number; + exp: number; +} + +// Middleware to verify JWT token +export const authMiddleware = async (c: Context, next: Next) => { + try { + // Get authorization header + const authHeader = c.req.header('Authorization'); + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return c.json({ error: 'Unauthorized: No token provided' }, 401); + } + + // Extract token + const token = authHeader.split(' ')[1]; + + try { + // 验证 JWT token + const decoded = jwt.verify(token, config.jwt.secret) as JwtPayload; + + // 特殊处理 Swagger 测试 token + if (decoded.sub === 'swagger-test-user' && decoded.email === 'swagger@test.com') { + // 为 Swagger 测试设置一个模拟用户 + c.set('user', { + id: 'swagger-test-user', + email: 'swagger@test.com', + name: 'Swagger Test User' + }); + + // 继续到下一个中间件或路由处理器 + await next(); + return; + } + + // 设置用户信息到上下文 + c.set('user', { + id: decoded.sub, + email: decoded.email + }); + + // 继续到下一个中间件或路由处理器 + await next(); + } catch (jwtError) { + if (jwtError instanceof jwt.JsonWebTokenError) { + return c.json({ error: 'Unauthorized: Invalid token' }, 401); + } + + if (jwtError instanceof jwt.TokenExpiredError) { + return c.json({ error: 'Unauthorized: Token expired' }, 401); + } + + throw jwtError; + } + } catch (error) { + console.error('Auth middleware error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}; + +// Generate JWT token +export const generateToken = (userId: string, email: string): string => { + const secret = config.jwt.secret; + const expiresIn = config.jwt.expiresIn; + + return jwt.sign( + { + sub: userId, + email, + }, + secret, + { + expiresIn, + } + ); +}; + +// Verify Supabase token +export const verifySupabaseToken = async (token: string) => { + try { + const { data, error } = await supabase.auth.getUser(token); + + if (error || !data.user) { + return null; + } + + return data.user; + } catch (error) { + console.error('Supabase token verification error:', error); + return null; + } +}; \ No newline at end of file diff --git a/backend/src/routes/analytics.ts b/backend/src/routes/analytics.ts new file mode 100644 index 0000000..67509a7 --- /dev/null +++ b/backend/src/routes/analytics.ts @@ -0,0 +1,522 @@ +import { Hono } from 'hono'; +import { authMiddleware } from '../middlewares/auth'; +import clickhouse from '../utils/clickhouse'; +import { addAnalyticsJob } from '../utils/queue'; +import { getRedisClient } from '../utils/redis'; +import supabase from '../utils/supabase'; + +// Define user type +interface User { + id: string; + email: string; + name?: string; +} + +// Extend Hono's Context type +declare module 'hono' { + interface ContextVariableMap { + user: User; + } +} + +const analyticsRouter = new Hono(); + +// Apply auth middleware to all routes +analyticsRouter.use('*', authMiddleware); + +// Track a view event +analyticsRouter.post('/view', async (c) => { + try { + const { content_id } = await c.req.json(); + const user = c.get('user'); + + if (!content_id) { + return c.json({ error: 'Content ID is required' }, 400); + } + + // Get IP and user agent + const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || '0.0.0.0'; + const userAgent = c.req.header('user-agent') || 'unknown'; + + // Insert view event into ClickHouse + await clickhouse.query({ + query: ` + INSERT INTO promote.view_events (user_id, content_id, ip, user_agent) + VALUES (?, ?, ?, ?) + `, + values: [ + user.id, + content_id, + ip, + userAgent + ] + }); + + // Queue analytics processing job + await addAnalyticsJob('process_views', { + user_id: user.id, + content_id, + timestamp: new Date().toISOString() + }); + + // Increment view count in Redis cache + const redis = await getRedisClient(); + await redis.incr(`views:${content_id}`); + + return c.json({ message: 'View tracked successfully' }); + } catch (error) { + console.error('View tracking error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// Track a like event +analyticsRouter.post('/like', async (c) => { + try { + const { content_id, action } = await c.req.json(); + const user = c.get('user'); + + if (!content_id || !action) { + return c.json({ error: 'Content ID and action are required' }, 400); + } + + if (action !== 'like' && action !== 'unlike') { + return c.json({ error: 'Action must be either "like" or "unlike"' }, 400); + } + + // Insert like event into ClickHouse + await clickhouse.query({ + query: ` + INSERT INTO promote.like_events (user_id, content_id, action) + VALUES (?, ?, ?) + `, + values: [ + user.id, + content_id, + action === 'like' ? 1 : 2 + ] + }); + + // Queue analytics processing job + await addAnalyticsJob('process_likes', { + user_id: user.id, + content_id, + action, + timestamp: new Date().toISOString() + }); + + // Update like count in Redis cache + const redis = await getRedisClient(); + const likeKey = `likes:${content_id}`; + if (action === 'like') { + await redis.incr(likeKey); + } else { + await redis.decr(likeKey); + } + + return c.json({ message: `${action} tracked successfully` }); + } catch (error) { + console.error('Like tracking error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// Track a follow event +analyticsRouter.post('/follow', async (c) => { + try { + const { followed_id, action } = await c.req.json(); + const user = c.get('user'); + + if (!followed_id || !action) { + return c.json({ error: 'Followed ID and action are required' }, 400); + } + + if (action !== 'follow' && action !== 'unfollow') { + return c.json({ error: 'Action must be either "follow" or "unfollow"' }, 400); + } + + // Insert follower event into ClickHouse + await clickhouse.query({ + query: ` + INSERT INTO promote.follower_events (follower_id, followed_id, action) + VALUES (?, ?, ?) + `, + values: [ + user.id, + followed_id, + action === 'follow' ? 1 : 2 + ] + }); + + // Queue analytics processing job + await addAnalyticsJob('process_followers', { + follower_id: user.id, + followed_id, + action, + timestamp: new Date().toISOString() + }); + + // Update follower count in Redis cache + const redis = await getRedisClient(); + const followerKey = `followers:${followed_id}`; + if (action === 'follow') { + await redis.incr(followerKey); + } else { + await redis.decr(followerKey); + } + + return c.json({ message: `${action} tracked successfully` }); + } catch (error) { + console.error('Follow tracking error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// Get analytics for a content +analyticsRouter.get('/content/:id', async (c) => { + try { + const contentId = c.req.param('id'); + + // Get counts from Redis cache + const redis = await getRedisClient(); + const [views, likes] = await Promise.all([ + redis.get(`views:${contentId}`), + redis.get(`likes:${contentId}`) + ]); + + return c.json({ + content_id: contentId, + views: parseInt(views || '0'), + likes: parseInt(likes || '0') + }); + } catch (error) { + console.error('Content analytics error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// Get analytics for a user +analyticsRouter.get('/user/:id', async (c) => { + try { + const userId = c.req.param('id'); + + // Get follower count from Redis cache + const redis = await getRedisClient(); + const followers = await redis.get(`followers:${userId}`); + + // Get content view and like counts from ClickHouse + const viewsResult = await clickhouse.query({ + query: ` + SELECT content_id, COUNT(*) as view_count + FROM promote.view_events + WHERE user_id = ? + GROUP BY content_id + `, + values: [userId] + }); + + const likesResult = await clickhouse.query({ + query: ` + SELECT content_id, SUM(CASE WHEN action = 1 THEN 1 ELSE -1 END) as like_count + FROM promote.like_events + WHERE user_id = ? + GROUP BY content_id + `, + values: [userId] + }); + + // Extract data from results + const viewsData = 'rows' in viewsResult ? viewsResult.rows : []; + const likesData = 'rows' in likesResult ? likesResult.rows : []; + + return c.json({ + user_id: userId, + followers: parseInt(followers || '0'), + content_analytics: { + views: viewsData, + likes: likesData + } + }); + } catch (error) { + console.error('User analytics error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 社群分析相关路由 + +// 获取项目的顶级影响者 +analyticsRouter.get('/project/:id/top-influencers', async (c) => { + try { + const projectId = c.req.param('id'); + + // 从ClickHouse查询项目的顶级影响者 + const result = await clickhouse.query({ + query: ` + SELECT + influencer_id, + SUM(metric_value) AS total_views + FROM events + WHERE + project_id = ? AND + event_type = 'post_view_change' + GROUP BY influencer_id + ORDER BY total_views DESC + LIMIT 10 + `, + values: [projectId] + }); + + // 提取数据 + const influencerData = 'rows' in result ? result.rows : []; + + // 如果有数据,从Supabase获取影响者详细信息 + if (influencerData.length > 0) { + const influencerIds = influencerData.map((item: any) => item.influencer_id); + + const { data: influencerDetails, error } = await supabase + .from('influencers') + .select('influencer_id, name, platform, followers_count, video_count') + .in('influencer_id', influencerIds); + + if (error) { + console.error('Error fetching influencer details:', error); + return c.json({ error: 'Error fetching influencer details' }, 500); + } + + // 合并数据 + const enrichedData = influencerData.map((item: any) => { + const details = influencerDetails?.find( + (detail) => detail.influencer_id === item.influencer_id + ) || {}; + + return { + ...item, + ...details + }; + }); + + return c.json(enrichedData); + } + + return c.json(influencerData); + } catch (error) { + console.error('Error fetching top influencers:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取影响者的粉丝变化趋势(过去6个月) +analyticsRouter.get('/influencer/:id/follower-trend', async (c) => { + try { + const influencerId = c.req.param('id'); + + // 从ClickHouse查询影响者的粉丝变化趋势 + const result = await clickhouse.query({ + query: ` + SELECT + toStartOfMonth(timestamp) AS month, + SUM(metric_value) AS follower_change + FROM events + WHERE + influencer_id = ? AND + event_type = 'follower_change' AND + timestamp >= subtractMonths(now(), 6) + GROUP BY month + ORDER BY month ASC + `, + values: [influencerId] + }); + + // 提取数据 + const trendData = 'rows' in result ? result.rows : []; + + return c.json({ + influencer_id: influencerId, + follower_trend: trendData + }); + } catch (error) { + console.error('Error fetching follower trend:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取帖子的点赞变化(过去30天) +analyticsRouter.get('/post/:id/like-trend', async (c) => { + try { + const postId = c.req.param('id'); + + // 从ClickHouse查询帖子的点赞变化 + const result = await clickhouse.query({ + query: ` + SELECT + toDate(timestamp) AS day, + SUM(metric_value) AS like_change + FROM events + WHERE + post_id = ? AND + event_type = 'post_like_change' AND + timestamp >= subtractDays(now(), 30) + GROUP BY day + ORDER BY day ASC + `, + values: [postId] + }); + + // 提取数据 + const trendData = 'rows' in result ? result.rows : []; + + return c.json({ + post_id: postId, + like_trend: trendData + }); + } catch (error) { + console.error('Error fetching like trend:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取影响者详细信息 +analyticsRouter.get('/influencer/:id/details', async (c) => { + try { + const influencerId = c.req.param('id'); + + // 从Supabase获取影响者详细信息 + const { data, error } = await supabase + .from('influencers') + .select('influencer_id, name, platform, profile_url, external_id, followers_count, video_count, platform_count, created_at') + .eq('influencer_id', influencerId) + .single(); + + if (error) { + console.error('Error fetching influencer details:', error); + return c.json({ error: 'Error fetching influencer details' }, 500); + } + + if (!data) { + return c.json({ error: 'Influencer not found' }, 404); + } + + return c.json(data); + } catch (error) { + console.error('Error fetching influencer details:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取影响者的帖子列表 +analyticsRouter.get('/influencer/:id/posts', async (c) => { + try { + const influencerId = c.req.param('id'); + + // 从Supabase获取影响者的帖子列表 + const { data, error } = await supabase + .from('posts') + .select('post_id, influencer_id, platform, post_url, title, description, published_at, created_at') + .eq('influencer_id', influencerId) + .order('published_at', { ascending: false }); + + if (error) { + console.error('Error fetching influencer posts:', error); + return c.json({ error: 'Error fetching influencer posts' }, 500); + } + + return c.json(data || []); + } catch (error) { + console.error('Error fetching influencer posts:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取帖子的评论列表 +analyticsRouter.get('/post/:id/comments', async (c) => { + try { + const postId = c.req.param('id'); + + // 从Supabase获取帖子的评论列表 + const { data, error } = await supabase + .from('comments') + .select('comment_id, post_id, user_id, content, sentiment_score, created_at') + .eq('post_id', postId) + .order('created_at', { ascending: false }); + + if (error) { + console.error('Error fetching post comments:', error); + return c.json({ error: 'Error fetching post comments' }, 500); + } + + return c.json(data || []); + } catch (error) { + console.error('Error fetching post comments:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取项目的平台分布 +analyticsRouter.get('/project/:id/platform-distribution', async (c) => { + try { + const projectId = c.req.param('id'); + + // 从ClickHouse查询项目的平台分布 + const result = await clickhouse.query({ + query: ` + SELECT + platform, + COUNT(DISTINCT influencer_id) AS influencer_count + FROM events + WHERE project_id = ? + GROUP BY platform + ORDER BY influencer_count DESC + `, + values: [projectId] + }); + + // 提取数据 + const distributionData = 'rows' in result ? result.rows : []; + + return c.json({ + project_id: projectId, + platform_distribution: distributionData + }); + } catch (error) { + console.error('Error fetching platform distribution:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取项目的互动类型分布 +analyticsRouter.get('/project/:id/interaction-types', async (c) => { + try { + const projectId = c.req.param('id'); + + // 从ClickHouse查询项目的互动类型分布 + const result = await clickhouse.query({ + query: ` + SELECT + event_type, + COUNT(*) AS event_count, + SUM(metric_value) AS total_value + FROM events + WHERE + project_id = ? AND + event_type IN ('click', 'comment', 'share') + GROUP BY event_type + ORDER BY event_count DESC + `, + values: [projectId] + }); + + // 提取数据 + const interactionData = 'rows' in result ? result.rows : []; + + return c.json({ + project_id: projectId, + interaction_types: interactionData + }); + } catch (error) { + console.error('Error fetching interaction types:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +export default analyticsRouter; \ No newline at end of file diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts new file mode 100644 index 0000000..56314a4 --- /dev/null +++ b/backend/src/routes/auth.ts @@ -0,0 +1,159 @@ +import { Hono } from 'hono'; +import { generateToken, verifySupabaseToken } from '../middlewares/auth'; +import supabase from '../utils/supabase'; +import jwt from 'jsonwebtoken'; + +const authRouter = new Hono(); + +// Register a new user +authRouter.post('/register', async (c) => { + try { + const { email, password, name } = await c.req.json(); + + // Validate input + if (!email || !password || !name) { + return c.json({ error: 'Email, password, and name are required' }, 400); + } + + // Register user with Supabase + const { data: authData, error: authError } = await supabase.auth.signUp({ + email, + password, + }); + + if (authError) { + return c.json({ error: authError.message }, 400); + } + + if (!authData.user) { + return c.json({ error: 'Failed to create user' }, 500); + } + + // Create user profile in the database + const { error: profileError } = await supabase + .from('users') + .insert({ + id: authData.user.id, + email: authData.user.email, + name, + created_at: new Date().toISOString(), + }); + + if (profileError) { + // Attempt to clean up the auth user if profile creation fails + await supabase.auth.admin.deleteUser(authData.user.id); + return c.json({ error: profileError.message }, 500); + } + + // Generate JWT token + const token = generateToken(authData.user.id, authData.user.email!); + + return c.json({ + message: 'User registered successfully', + user: { + id: authData.user.id, + email: authData.user.email, + name, + }, + token, + }, 201); + } catch (error) { + console.error('Registration error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// Login user +authRouter.post('/login', async (c) => { + try { + const { email, password } = await c.req.json(); + + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password + }); + + if (error) { + return c.json({ error: error.message }, 400); + } + + // 使用与 authMiddleware 一致的方式创建 JWT + const token = generateToken(data.user.id, data.user.email || ''); + + // 只返回必要的用户信息和令牌 + return c.json({ + success: true, + token, + user: { + id: data.user.id, + email: data.user.email + } + }); + } catch (error) { + console.error(error); + return c.json({ error: 'Server error' }, 500); + } +}); + +// Verify token +authRouter.get('/verify', async (c) => { + try { + const token = c.req.header('Authorization')?.split(' ')[1]; + + if (!token) { + return c.json({ error: 'No token provided' }, 401); + } + + const user = await verifySupabaseToken(token); + + if (!user) { + return c.json({ error: 'Invalid token' }, 401); + } + + return c.json({ + message: 'Token is valid', + user: { + id: user.id, + email: user.email, + }, + }); + } catch (error) { + console.error('Token verification error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// Refresh token +authRouter.post('/refresh-token', async (c) => { + try { + const token = c.req.header('Authorization')?.split(' ')[1]; + + if (!token) { + return c.json({ error: 'No token provided' }, 401); + } + + // 验证当前token + const user = await verifySupabaseToken(token); + + if (!user) { + return c.json({ error: 'Invalid token' }, 401); + } + + // 生成新token + const newToken = generateToken(user.id, user.email || ''); + + return c.json({ + message: 'Token refreshed successfully', + token: newToken, + user: { + id: user.id, + email: user.email, + }, + }); + } catch (error) { + console.error('Token refresh error:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +export default authRouter; \ No newline at end of file diff --git a/backend/src/routes/comments.ts b/backend/src/routes/comments.ts new file mode 100644 index 0000000..1973ac1 --- /dev/null +++ b/backend/src/routes/comments.ts @@ -0,0 +1,14 @@ +import { Hono } from 'hono'; +import { getComments, createComment, deleteComment } from '../controllers/commentsController'; +import { authMiddleware } from '../middlewares/auth'; + +const commentsRouter = new Hono(); + +// Public routes +commentsRouter.get('/', getComments); + +// Protected routes +commentsRouter.post('/', authMiddleware, createComment); +commentsRouter.delete('/:comment_id', authMiddleware, deleteComment); + +export default commentsRouter; \ No newline at end of file diff --git a/backend/src/routes/community.ts b/backend/src/routes/community.ts new file mode 100644 index 0000000..37a4ea3 --- /dev/null +++ b/backend/src/routes/community.ts @@ -0,0 +1,770 @@ +import { Hono } from 'hono'; +import { authMiddleware } from '../middlewares/auth'; +import clickhouse from '../utils/clickhouse'; +import supabase from '../utils/supabase'; + +// Define user type +interface User { + id: string; + email: string; + name?: string; +} + +// Extend Hono's Context type +declare module 'hono' { + interface ContextVariableMap { + user: User; + } +} + +const communityRouter = new Hono(); + +// Apply auth middleware to all routes +communityRouter.use('*', authMiddleware); + +// 创建新项目 +communityRouter.post('/projects', async (c) => { + try { + const { name, description, start_date, end_date } = await c.req.json(); + const user = c.get('user'); + + if (!name) { + return c.json({ error: 'Project name is required' }, 400); + } + + // 在Supabase中创建项目 + const { data, error } = await supabase + .from('projects') + .insert({ + name, + description, + start_date, + end_date, + created_by: user.id + }) + .select() + .single(); + + if (error) { + console.error('Error creating project:', error); + return c.json({ error: 'Failed to create project' }, 500); + } + + return c.json({ + message: 'Project created successfully', + project: data + }, 201); + } catch (error) { + console.error('Error creating project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取项目列表 +communityRouter.get('/projects', async (c) => { + try { + const user = c.get('user'); + + // 从Supabase获取项目列表 + const { data, error } = await supabase + .from('projects') + .select('*') + .eq('created_by', user.id) + .order('created_at', { ascending: false }); + + if (error) { + console.error('Error fetching projects:', error); + return c.json({ error: 'Failed to fetch projects' }, 500); + } + + return c.json(data || []); + } catch (error) { + console.error('Error fetching projects:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取项目详情 +communityRouter.get('/projects/:id', async (c) => { + try { + const projectId = c.req.param('id'); + + // 从Supabase获取项目详情 + const { data, error } = await supabase + .from('projects') + .select('*') + .eq('id', projectId) + .single(); + + if (error) { + console.error('Error fetching project:', error); + return c.json({ error: 'Failed to fetch project' }, 500); + } + + if (!data) { + return c.json({ error: 'Project not found' }, 404); + } + + return c.json(data); + } catch (error) { + console.error('Error fetching project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 更新项目 +communityRouter.put('/projects/:id', async (c) => { + try { + const projectId = c.req.param('id'); + const { name, description, start_date, end_date, status } = await c.req.json(); + const user = c.get('user'); + + // 检查项目是否存在并属于当前用户 + const { data: existingProject, error: fetchError } = await supabase + .from('projects') + .select('*') + .eq('id', projectId) + .eq('created_by', user.id) + .single(); + + if (fetchError || !existingProject) { + return c.json({ error: 'Project not found or you do not have permission to update it' }, 404); + } + + // 更新项目 + const { data, error } = await supabase + .from('projects') + .update({ + name, + description, + start_date, + end_date, + status, + updated_at: new Date().toISOString() + }) + .eq('id', projectId) + .select() + .single(); + + if (error) { + console.error('Error updating project:', error); + return c.json({ error: 'Failed to update project' }, 500); + } + + return c.json({ + message: 'Project updated successfully', + project: data + }); + } catch (error) { + console.error('Error updating project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 删除项目 +communityRouter.delete('/projects/:id', async (c) => { + try { + const projectId = c.req.param('id'); + const user = c.get('user'); + + // 检查项目是否存在并属于当前用户 + const { data: existingProject, error: fetchError } = await supabase + .from('projects') + .select('*') + .eq('id', projectId) + .eq('created_by', user.id) + .single(); + + if (fetchError || !existingProject) { + return c.json({ error: 'Project not found or you do not have permission to delete it' }, 404); + } + + // 删除项目 + const { error } = await supabase + .from('projects') + .delete() + .eq('id', projectId); + + if (error) { + console.error('Error deleting project:', error); + return c.json({ error: 'Failed to delete project' }, 500); + } + + return c.json({ + message: 'Project deleted successfully' + }); + } catch (error) { + console.error('Error deleting project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 添加影响者到项目 +communityRouter.post('/projects/:id/influencers', async (c) => { + try { + const projectId = c.req.param('id'); + const { influencer_id, platform, external_id, name, profile_url } = await c.req.json(); + const user = c.get('user'); + + // 检查项目是否存在并属于当前用户 + const { data: existingProject, error: fetchError } = await supabase + .from('projects') + .select('*') + .eq('id', projectId) + .eq('created_by', user.id) + .single(); + + if (fetchError || !existingProject) { + return c.json({ error: 'Project not found or you do not have permission to update it' }, 404); + } + + // 检查影响者是否已存在 + let influencerData; + + if (influencer_id) { + // 如果提供了影响者ID,检查是否存在 + const { data, error } = await supabase + .from('influencers') + .select('*') + .eq('influencer_id', influencer_id) + .single(); + + if (!error && data) { + influencerData = data; + } + } else if (external_id && platform) { + // 如果提供了外部ID和平台,检查是否存在 + const { data, error } = await supabase + .from('influencers') + .select('*') + .eq('external_id', external_id) + .eq('platform', platform) + .single(); + + if (!error && data) { + influencerData = data; + } + } + + // 如果影响者不存在,创建新的影响者 + if (!influencerData) { + if (!name || !platform) { + return c.json({ error: 'Name and platform are required for new influencers' }, 400); + } + + const { data, error } = await supabase + .from('influencers') + .insert({ + name, + platform, + external_id, + profile_url + }) + .select() + .single(); + + if (error) { + console.error('Error creating influencer:', error); + return c.json({ error: 'Failed to create influencer' }, 500); + } + + influencerData = data; + } + + // 将影响者添加到项目 + const { data: projectInfluencer, error } = await supabase + .from('project_influencers') + .insert({ + project_id: projectId, + influencer_id: influencerData.influencer_id + }) + .select() + .single(); + + if (error) { + console.error('Error adding influencer to project:', error); + return c.json({ error: 'Failed to add influencer to project' }, 500); + } + + return c.json({ + message: 'Influencer added to project successfully', + project_influencer: projectInfluencer, + influencer: influencerData + }, 201); + } catch (error) { + console.error('Error adding influencer to project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取项目的影响者列表 +communityRouter.get('/projects/:id/influencers', async (c) => { + try { + const projectId = c.req.param('id'); + + // 从Supabase获取项目的影响者列表 + const { data, error } = await supabase + .from('project_influencers') + .select(` + project_id, + influencers ( + influencer_id, + name, + platform, + profile_url, + external_id, + followers_count, + video_count + ) + `) + .eq('project_id', projectId); + + if (error) { + console.error('Error fetching project influencers:', error); + return c.json({ error: 'Failed to fetch project influencers' }, 500); + } + + // 格式化数据 + const influencers = data?.map(item => item.influencers) || []; + + return c.json(influencers); + } catch (error) { + console.error('Error fetching project influencers:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 从项目中移除影响者 +communityRouter.delete('/projects/:projectId/influencers/:influencerId', async (c) => { + try { + const projectId = c.req.param('projectId'); + const influencerId = c.req.param('influencerId'); + const user = c.get('user'); + + // 检查项目是否存在并属于当前用户 + const { data: existingProject, error: fetchError } = await supabase + .from('projects') + .select('*') + .eq('id', projectId) + .eq('created_by', user.id) + .single(); + + if (fetchError || !existingProject) { + return c.json({ error: 'Project not found or you do not have permission to update it' }, 404); + } + + // 从项目中移除影响者 + const { error } = await supabase + .from('project_influencers') + .delete() + .eq('project_id', projectId) + .eq('influencer_id', influencerId); + + if (error) { + console.error('Error removing influencer from project:', error); + return c.json({ error: 'Failed to remove influencer from project' }, 500); + } + + return c.json({ + message: 'Influencer removed from project successfully' + }); + } catch (error) { + console.error('Error removing influencer from project:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 添加事件数据 +communityRouter.post('/events', async (c) => { + try { + const { + project_id, + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + } = await c.req.json(); + + if (!project_id || !influencer_id || !platform || !event_type || metric_value === undefined) { + return c.json({ + error: 'Project ID, influencer ID, platform, event type, and metric value are required' + }, 400); + } + + // 验证事件类型 + const validEventTypes = [ + 'follower_change', + 'post_like_change', + 'post_view_change', + 'click', + 'comment', + 'share' + ]; + + if (!validEventTypes.includes(event_type)) { + return c.json({ + error: `Invalid event type. Must be one of: ${validEventTypes.join(', ')}` + }, 400); + } + + // 验证平台 + const validPlatforms = ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook']; + + if (!validPlatforms.includes(platform)) { + return c.json({ + error: `Invalid platform. Must be one of: ${validPlatforms.join(', ')}` + }, 400); + } + + // 将事件数据插入ClickHouse + await clickhouse.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, ?, ?, ?, ?) + `, + values: [ + project_id, + influencer_id, + post_id || null, + platform, + event_type, + metric_value, + event_metadata ? JSON.stringify(event_metadata) : '{}' + ] + }); + + return c.json({ + message: 'Event data added successfully' + }, 201); + } catch (error) { + console.error('Error adding event data:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 批量添加事件数据 +communityRouter.post('/events/batch', async (c) => { + try { + const { events } = await c.req.json(); + + if (!Array.isArray(events) || events.length === 0) { + return c.json({ error: 'Events array is required and must not be empty' }, 400); + } + + // 验证事件类型和平台 + const validEventTypes = [ + 'follower_change', + 'post_like_change', + 'post_view_change', + 'click', + 'comment', + 'share' + ]; + + const validPlatforms = ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook']; + + // 验证每个事件 + for (const event of events) { + const { + project_id, + influencer_id, + platform, + event_type, + metric_value + } = event; + + if (!project_id || !influencer_id || !platform || !event_type || metric_value === undefined) { + return c.json({ + error: 'Project ID, influencer ID, platform, event type, and metric value are required for all events' + }, 400); + } + + if (!validEventTypes.includes(event_type)) { + return c.json({ + error: `Invalid event type: ${event_type}. Must be one of: ${validEventTypes.join(', ')}` + }, 400); + } + + if (!validPlatforms.includes(platform)) { + return c.json({ + error: `Invalid platform: ${platform}. Must be one of: ${validPlatforms.join(', ')}` + }, 400); + } + } + + // 准备批量插入数据 + const values = events.map(event => `( + '${event.project_id}', + '${event.influencer_id}', + ${event.post_id ? `'${event.post_id}'` : 'NULL'}, + '${event.platform}', + '${event.event_type}', + ${event.metric_value}, + '${event.event_metadata ? JSON.stringify(event.event_metadata) : '{}'}' + )`).join(','); + + // 批量插入事件数据 + await clickhouse.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES ${values} + ` + }); + + return c.json({ + message: `${events.length} events added successfully` + }, 201); + } catch (error) { + console.error('Error adding batch event data:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 添加帖子 +communityRouter.post('/posts', async (c) => { + try { + const { + influencer_id, + platform, + post_url, + title, + description, + published_at + } = await c.req.json(); + + if (!influencer_id || !platform || !post_url) { + return c.json({ + error: 'Influencer ID, platform, and post URL are required' + }, 400); + } + + // 验证平台 + const validPlatforms = ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook']; + + if (!validPlatforms.includes(platform)) { + return c.json({ + error: `Invalid platform. Must be one of: ${validPlatforms.join(', ')}` + }, 400); + } + + // 检查帖子是否已存在 + const { data: existingPost, error: checkError } = await supabase + .from('posts') + .select('*') + .eq('post_url', post_url) + .single(); + + if (!checkError && existingPost) { + return c.json({ + error: 'Post with this URL already exists', + post: existingPost + }, 409); + } + + // 创建新帖子 + const { data, error } = await supabase + .from('posts') + .insert({ + influencer_id, + platform, + post_url, + title, + description, + published_at: published_at || new Date().toISOString() + }) + .select() + .single(); + + if (error) { + console.error('Error creating post:', error); + return c.json({ error: 'Failed to create post' }, 500); + } + + return c.json({ + message: 'Post created successfully', + post: data + }, 201); + } catch (error) { + console.error('Error creating post:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 添加评论 +communityRouter.post('/comments', async (c) => { + try { + const { + post_id, + user_id, + content, + sentiment_score + } = await c.req.json(); + + if (!post_id || !content) { + return c.json({ + error: 'Post ID and content are required' + }, 400); + } + + // 创建新评论 + const { data, error } = await supabase + .from('comments') + .insert({ + post_id, + user_id: user_id || c.get('user').id, + content, + sentiment_score: sentiment_score || 0 + }) + .select() + .single(); + + if (error) { + console.error('Error creating comment:', error); + return c.json({ error: 'Failed to create comment' }, 500); + } + + return c.json({ + message: 'Comment created successfully', + comment: data + }, 201); + } catch (error) { + console.error('Error creating comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取项目的事件统计 +communityRouter.get('/projects/:id/event-stats', async (c) => { + try { + const projectId = c.req.param('id'); + + // 从ClickHouse查询项目的事件统计 + const result = await clickhouse.query({ + query: ` + SELECT + event_type, + COUNT(*) AS event_count, + SUM(metric_value) AS total_value + FROM events + WHERE project_id = ? + GROUP BY event_type + ORDER BY event_count DESC + `, + values: [projectId] + }); + + // 提取数据 + const statsData = 'rows' in result ? result.rows : []; + + return c.json({ + project_id: projectId, + event_stats: statsData + }); + } catch (error) { + console.error('Error fetching event stats:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取项目的时间趋势 +communityRouter.get('/projects/:id/time-trend', async (c) => { + try { + const projectId = c.req.param('id'); + const { event_type, interval = 'day', days = '30' } = c.req.query(); + + if (!event_type) { + return c.json({ error: 'Event type is required' }, 400); + } + + // 验证事件类型 + const validEventTypes = [ + 'follower_change', + 'post_like_change', + 'post_view_change', + 'click', + 'comment', + 'share' + ]; + + if (!validEventTypes.includes(event_type)) { + return c.json({ + error: `Invalid event type. Must be one of: ${validEventTypes.join(', ')}` + }, 400); + } + + // 验证时间间隔 + const validIntervals = ['hour', 'day', 'week', 'month']; + + if (!validIntervals.includes(interval)) { + return c.json({ + error: `Invalid interval. Must be one of: ${validIntervals.join(', ')}` + }, 400); + } + + // 构建时间间隔函数 + let timeFunction; + switch (interval) { + case 'hour': + timeFunction = 'toStartOfHour'; + break; + case 'day': + timeFunction = 'toDate'; + break; + case 'week': + timeFunction = 'toStartOfWeek'; + break; + case 'month': + timeFunction = 'toStartOfMonth'; + break; + } + + // 从ClickHouse查询项目的时间趋势 + const result = await clickhouse.query({ + query: ` + SELECT + ${timeFunction}(timestamp) AS time_period, + SUM(metric_value) AS value + FROM events + WHERE + project_id = ? AND + event_type = ? AND + timestamp >= subtractDays(now(), ?) + GROUP BY time_period + ORDER BY time_period ASC + `, + values: [projectId, event_type, parseInt(days)] + }); + + // 提取数据 + const trendData = 'rows' in result ? result.rows : []; + + return c.json({ + project_id: projectId, + event_type, + interval, + days: parseInt(days), + trend: trendData + }); + } catch (error) { + console.error('Error fetching time trend:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +export default communityRouter; \ No newline at end of file diff --git a/backend/src/routes/influencers.ts b/backend/src/routes/influencers.ts new file mode 100644 index 0000000..6396ce5 --- /dev/null +++ b/backend/src/routes/influencers.ts @@ -0,0 +1,11 @@ +import { Hono } from 'hono'; +import { getInfluencers, getInfluencerById, getInfluencerStats } from '../controllers/influencersController'; + +const influencersRouter = new Hono(); + +// Public routes +influencersRouter.get('/', getInfluencers); +influencersRouter.get('/stats', getInfluencerStats); +influencersRouter.get('/:influencer_id', getInfluencerById); + +export default influencersRouter; \ No newline at end of file diff --git a/backend/src/routes/posts.ts b/backend/src/routes/posts.ts new file mode 100644 index 0000000..7972860 --- /dev/null +++ b/backend/src/routes/posts.ts @@ -0,0 +1,686 @@ +import { Hono } from 'hono'; +import { authMiddleware } from '../middlewares/auth'; +import supabase from '../utils/supabase'; +import clickhouse from '../utils/clickhouse'; +import { getRedisClient } from '../utils/redis'; + +// Define user type +interface User { + id: string; + email: string; + name?: string; +} + +// Define stats type +interface PostStats { + post_id: string; + views: number | null; + likes: number | null; +} + +// Extend Hono's Context type +declare module 'hono' { + interface ContextVariableMap { + user: User; + } +} + +const postsRouter = new Hono(); + +// Apply auth middleware to most routes +postsRouter.use('*', authMiddleware); + +// 创建新帖子 +postsRouter.post('/', async (c) => { + try { + const { + influencer_id, + platform, + post_url, + title, + description, + published_at + } = await c.req.json(); + + if (!influencer_id || !platform || !post_url) { + return c.json({ + error: 'influencer_id, platform, and post_url are required' + }, 400); + } + + // 验证平台 + const validPlatforms = ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook']; + if (!validPlatforms.includes(platform)) { + return c.json({ + error: `Invalid platform. Must be one of: ${validPlatforms.join(', ')}` + }, 400); + } + + // 检查帖子URL是否已存在 + const { data: existingPost, error: checkError } = await supabase + .from('posts') + .select('*') + .eq('post_url', post_url) + .single(); + + if (!checkError && existingPost) { + return c.json({ + error: 'Post with this URL already exists', + post: existingPost + }, 409); + } + + // 创建新帖子 + const { data: post, error } = await supabase + .from('posts') + .insert({ + influencer_id, + platform, + post_url, + title, + description, + published_at: published_at || new Date().toISOString() + }) + .select() + .single(); + + if (error) { + console.error('Error creating post:', error); + return c.json({ error: 'Failed to create post' }, 500); + } + + return c.json({ + message: 'Post created successfully', + post + }, 201); + } catch (error) { + console.error('Error creating post:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取帖子列表 +postsRouter.get('/', async (c) => { + try { + const { + influencer_id, + platform, + limit = '20', + offset = '0', + sort = 'published_at', + order = 'desc' + } = c.req.query(); + + // 构建查询 + let query = supabase.from('posts').select(` + *, + influencer:influencers(name, platform, profile_url, followers_count) + `); + + // 添加过滤条件 + if (influencer_id) { + query = query.eq('influencer_id', influencer_id); + } + + if (platform) { + query = query.eq('platform', platform); + } + + // 添加排序和分页 + query = query.order(sort, { ascending: order === 'asc' }); + query = query.range(parseInt(offset), parseInt(offset) + parseInt(limit) - 1); + + // 执行查询 + const { data, error, count } = await query; + + if (error) { + console.error('Error fetching posts:', error); + return c.json({ error: 'Failed to fetch posts' }, 500); + } + + // 获取帖子的统计数据 + if (data && data.length > 0) { + const postIds = data.map(post => post.post_id); + + // 尝试从缓存获取数据 + const redis = await getRedisClient(); + const cachedStats: PostStats[] = await Promise.all( + postIds.map(async (postId) => { + const [views, likes] = await Promise.all([ + redis.get(`post:views:${postId}`), + redis.get(`post:likes:${postId}`) + ]); + + return { + post_id: postId, + views: views ? parseInt(views) : null, + likes: likes ? parseInt(likes) : null + }; + }) + ); + + // 找出缓存中没有的帖子ID + const missingIds = postIds.filter(id => { + const stat = cachedStats.find(s => s.post_id === id); + return stat?.views === null || stat?.likes === null; + }); + + // 如果有缺失的统计数据,从ClickHouse获取 + if (missingIds.length > 0) { + try { + // 查询帖子的观看数 + const viewsResult = await clickhouse.query({ + query: ` + SELECT + post_id, + SUM(metric_value) AS views + FROM events + WHERE + post_id IN (${missingIds.map(id => `'${id}'`).join(',')}) AND + event_type = 'post_view_change' + GROUP BY post_id + ` + }); + + // 查询帖子的点赞数 + const likesResult = await clickhouse.query({ + query: ` + SELECT + post_id, + SUM(metric_value) AS likes + FROM events + WHERE + post_id IN (${missingIds.map(id => `'${id}'`).join(',')}) AND + event_type = 'post_like_change' + GROUP BY post_id + ` + }); + + // 处理结果 + const viewsData = 'rows' in viewsResult ? viewsResult.rows : []; + const likesData = 'rows' in likesResult ? likesResult.rows : []; + + // 更新缓存并填充统计数据 + for (const viewStat of viewsData) { + if (viewStat && typeof viewStat === 'object' && 'post_id' in viewStat && 'views' in viewStat) { + // 更新缓存 + await redis.set(`post:views:${viewStat.post_id}`, String(viewStat.views)); + + // 更新缓存统计数据 + const cacheStat = cachedStats.find(s => s.post_id === viewStat.post_id); + if (cacheStat) { + cacheStat.views = Number(viewStat.views); + } + } + } + + for (const likeStat of likesData) { + if (likeStat && typeof likeStat === 'object' && 'post_id' in likeStat && 'likes' in likeStat) { + // 更新缓存 + await redis.set(`post:likes:${likeStat.post_id}`, String(likeStat.likes)); + + // 更新缓存统计数据 + const cacheStat = cachedStats.find(s => s.post_id === likeStat.post_id); + if (cacheStat) { + cacheStat.likes = Number(likeStat.likes); + } + } + } + } catch (chError) { + console.error('Error fetching stats from ClickHouse:', chError); + } + } + + // 合并统计数据到帖子数据 + data.forEach(post => { + const stats = cachedStats.find(s => s.post_id === post.post_id); + post.stats = { + views: stats?.views || 0, + likes: stats?.likes || 0 + }; + }); + } + + return c.json({ + posts: data || [], + total: count || 0, + limit: parseInt(limit), + offset: parseInt(offset) + }); + } catch (error) { + console.error('Error fetching posts:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取单个帖子详情 +postsRouter.get('/:id', async (c) => { + try { + const postId = c.req.param('id'); + + // 获取帖子详情 + const { data: post, error } = await supabase + .from('posts') + .select(` + *, + influencer:influencers(name, platform, profile_url, followers_count) + `) + .eq('post_id', postId) + .single(); + + if (error) { + console.error('Error fetching post:', error); + return c.json({ error: 'Failed to fetch post' }, 500); + } + + if (!post) { + return c.json({ error: 'Post not found' }, 404); + } + + // 获取帖子统计数据 + try { + // 先尝试从Redis缓存获取 + const redis = await getRedisClient(); + const [cachedViews, cachedLikes] = await Promise.all([ + redis.get(`post:views:${postId}`), + redis.get(`post:likes:${postId}`) + ]); + + // 如果缓存中有数据,直接使用 + if (cachedViews !== null && cachedLikes !== null) { + post.stats = { + views: parseInt(cachedViews), + likes: parseInt(cachedLikes) + }; + } else { + // 如果缓存中没有,从ClickHouse获取 + // 查询帖子的观看数 + const viewsResult = await clickhouse.query({ + query: ` + SELECT SUM(metric_value) AS views + FROM events + WHERE + post_id = ? AND + event_type = 'post_view_change' + `, + values: [postId] + }); + + // 查询帖子的点赞数 + const likesResult = await clickhouse.query({ + query: ` + SELECT SUM(metric_value) AS likes + FROM events + WHERE + post_id = ? AND + event_type = 'post_like_change' + `, + values: [postId] + }); + + // 处理结果 + let viewsData = 0; + if ('rows' in viewsResult && viewsResult.rows.length > 0 && viewsResult.rows[0] && typeof viewsResult.rows[0] === 'object' && 'views' in viewsResult.rows[0]) { + viewsData = Number(viewsResult.rows[0].views) || 0; + } + + let likesData = 0; + if ('rows' in likesResult && likesResult.rows.length > 0 && likesResult.rows[0] && typeof likesResult.rows[0] === 'object' && 'likes' in likesResult.rows[0]) { + likesData = Number(likesResult.rows[0].likes) || 0; + } + + // 更新缓存 + await redis.set(`post:views:${postId}`, String(viewsData)); + await redis.set(`post:likes:${postId}`, String(likesData)); + + // 添加统计数据 + post.stats = { + views: viewsData, + likes: likesData + }; + } + + // 获取互动时间线 + const timelineResult = await clickhouse.query({ + query: ` + SELECT + toDate(timestamp) as date, + event_type, + SUM(metric_value) as value + FROM events + WHERE + post_id = ? AND + event_type IN ('post_view_change', 'post_like_change') + GROUP BY date, event_type + ORDER BY date ASC + `, + values: [postId] + }); + + const timelineData = 'rows' in timelineResult ? timelineResult.rows : []; + + // 添加时间线数据 + post.timeline = timelineData; + + // 获取评论数量 + const { count } = await supabase + .from('comments') + .select('*', { count: 'exact', head: true }) + .eq('post_id', postId); + + post.comment_count = count || 0; + } catch (statsError) { + console.error('Error fetching post stats:', statsError); + // 继续返回帖子数据,但没有统计信息 + post.stats = { views: 0, likes: 0 }; + post.timeline = []; + post.comment_count = 0; + } + + return c.json(post); + } catch (error) { + console.error('Error fetching post:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 更新帖子 +postsRouter.put('/:id', async (c) => { + try { + const postId = c.req.param('id'); + const user = c.get('user'); + const { title, description } = await c.req.json(); + + // 先检查帖子是否存在 + const { data: existingPost, error: fetchError } = await supabase + .from('posts') + .select('*') + .eq('post_id', postId) + .single(); + + if (fetchError || !existingPost) { + return c.json({ error: 'Post not found' }, 404); + } + + // 更新帖子 + const { data: updatedPost, error } = await supabase + .from('posts') + .update({ + title, + description, + updated_at: new Date().toISOString() + }) + .eq('post_id', postId) + .select() + .single(); + + if (error) { + console.error('Error updating post:', error); + return c.json({ error: 'Failed to update post' }, 500); + } + + return c.json({ + message: 'Post updated successfully', + post: updatedPost + }); + } catch (error) { + console.error('Error updating post:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 删除帖子 +postsRouter.delete('/:id', async (c) => { + try { + const postId = c.req.param('id'); + const user = c.get('user'); + + // 删除帖子 + const { error } = await supabase + .from('posts') + .delete() + .eq('post_id', postId); + + if (error) { + console.error('Error deleting post:', error); + return c.json({ error: 'Failed to delete post' }, 500); + } + + // 清除缓存 + try { + const redis = await getRedisClient(); + await Promise.all([ + redis.del(`post:views:${postId}`), + redis.del(`post:likes:${postId}`) + ]); + } catch (cacheError) { + console.error('Error clearing cache:', cacheError); + } + + return c.json({ + message: 'Post deleted successfully' + }); + } catch (error) { + console.error('Error deleting post:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取帖子的评论 +postsRouter.get('/:id/comments', async (c) => { + try { + const postId = c.req.param('id'); + const { limit = '20', offset = '0' } = c.req.query(); + + // 获取评论 + const { data: comments, error, count } = await supabase + .from('comments') + .select('*', { count: 'exact' }) + .eq('post_id', postId) + .order('created_at', { ascending: false }) + .range(parseInt(offset), parseInt(offset) + parseInt(limit) - 1); + + if (error) { + console.error('Error fetching comments:', error); + return c.json({ error: 'Failed to fetch comments' }, 500); + } + + // 如果有评论,获取用户信息 + if (comments && comments.length > 0) { + const userIds = [...new Set(comments.map(comment => comment.user_id))]; + + // 获取用户信息 + const { data: userProfiles, error: userError } = await supabase + .from('user_profiles') + .select('id, full_name, avatar_url') + .in('id', userIds); + + if (!userError && userProfiles) { + // 将用户信息添加到评论中 + comments.forEach(comment => { + const userProfile = userProfiles.find(profile => profile.id === comment.user_id); + comment.user_profile = userProfile || null; + }); + } else { + console.error('Error fetching user profiles:', userError); + } + } + + return c.json({ + comments: comments || [], + total: count || 0, + limit: parseInt(limit), + offset: parseInt(offset) + }); + } catch (error) { + console.error('Error fetching comments:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 添加评论到帖子 +postsRouter.post('/:id/comments', async (c) => { + try { + const postId = c.req.param('id'); + const user = c.get('user'); + const { content, sentiment_score } = await c.req.json(); + + if (!content) { + return c.json({ error: 'Comment content is required' }, 400); + } + + // 创建评论 + const { data: comment, error } = await supabase + .from('comments') + .insert({ + post_id: postId, + user_id: user.id, + content, + sentiment_score: sentiment_score || 0 + }) + .select() + .single(); + + if (error) { + console.error('Error creating comment:', error); + return c.json({ error: 'Failed to create comment' }, 500); + } + + // 尝试记录评论事件到ClickHouse + try { + // 获取帖子信息 + const { data: post } = await supabase + .from('posts') + .select('influencer_id, platform') + .eq('post_id', postId) + .single(); + + if (post) { + await clickhouse.query({ + query: ` + INSERT INTO events ( + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, 'comment', ?, ?) + `, + values: [ + post.influencer_id, + postId, + post.platform, + 1, + JSON.stringify({ + comment_id: comment.comment_id, + user_id: user.id, + sentiment_score: sentiment_score || 0 + }) + ] + }); + } + } catch (eventError) { + console.error('Error recording comment event:', eventError); + // 不影响主流程,继续返回评论数据 + } + + return c.json({ + message: 'Comment added successfully', + comment + }, 201); + } catch (error) { + console.error('Error adding comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 更新评论 +postsRouter.put('/comments/:id', async (c) => { + try { + const commentId = c.req.param('id'); + const user = c.get('user'); + const { content, sentiment_score } = await c.req.json(); + + // 先检查评论是否存在且属于当前用户 + const { data: existingComment, error: fetchError } = await supabase + .from('comments') + .select('*') + .eq('comment_id', commentId) + .eq('user_id', user.id) + .single(); + + if (fetchError || !existingComment) { + return c.json({ + error: 'Comment not found or you do not have permission to update it' + }, 404); + } + + // 更新评论 + const { data: updatedComment, error } = await supabase + .from('comments') + .update({ + content, + sentiment_score: sentiment_score !== undefined ? sentiment_score : existingComment.sentiment_score, + updated_at: new Date().toISOString() + }) + .eq('comment_id', commentId) + .select() + .single(); + + if (error) { + console.error('Error updating comment:', error); + return c.json({ error: 'Failed to update comment' }, 500); + } + + return c.json({ + message: 'Comment updated successfully', + comment: updatedComment + }); + } catch (error) { + console.error('Error updating comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 删除评论 +postsRouter.delete('/comments/:id', async (c) => { + try { + const commentId = c.req.param('id'); + const user = c.get('user'); + + // 先检查评论是否存在且属于当前用户 + const { data: existingComment, error: fetchError } = await supabase + .from('comments') + .select('*') + .eq('comment_id', commentId) + .eq('user_id', user.id) + .single(); + + if (fetchError || !existingComment) { + return c.json({ + error: 'Comment not found or you do not have permission to delete it' + }, 404); + } + + // 删除评论 + const { error } = await supabase + .from('comments') + .delete() + .eq('comment_id', commentId); + + if (error) { + console.error('Error deleting comment:', error); + return c.json({ error: 'Failed to delete comment' }, 500); + } + + return c.json({ + message: 'Comment deleted successfully' + }); + } catch (error) { + console.error('Error deleting comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +export default postsRouter; \ No newline at end of file diff --git a/backend/src/routes/projectComments.ts b/backend/src/routes/projectComments.ts new file mode 100644 index 0000000..6420dd0 --- /dev/null +++ b/backend/src/routes/projectComments.ts @@ -0,0 +1,489 @@ +import { Hono } from 'hono'; +import { authMiddleware } from '../middlewares/auth'; +import supabase from '../utils/supabase'; +import clickhouse from '../utils/clickhouse'; + +// Define user type +interface User { + id: string; + email: string; + name?: string; +} + +// Define comment type +interface ProjectComment { + comment_id: string; + project_id: string; + user_id: string; + content: string; + sentiment_score: number; + status: string; + is_pinned: boolean; + parent_id: string | null; + created_at: string; + updated_at: string; + user: { id: string; email: string }[]; + reply_count?: number; +} + +// Define project type +interface Project { + id: string; + name: string; + created_by?: string; +} + +// Define reply count type +interface ReplyCount { + parent_id: string; + count: number; +} + +// Extend Hono's Context type +declare module 'hono' { + interface ContextVariableMap { + user: User; + } +} + +const projectCommentsRouter = new Hono(); + +// Apply auth middleware to all routes +projectCommentsRouter.use('*', authMiddleware); + +// 获取项目的评论列表 +projectCommentsRouter.get('/projects/:id/comments', async (c) => { + try { + const projectId = c.req.param('id'); + const { limit = '20', offset = '0', parent_id = null } = c.req.query(); + + // 检查项目是否存在 + const { data: project, error: projectError } = await supabase + .from('projects') + .select('id, name') + .eq('id', projectId) + .single(); + + if (projectError) { + console.error('Error fetching project:', projectError); + return c.json({ error: 'Project not found' }, 404); + } + + // 构建评论查询 + let commentsQuery = supabase + .from('project_comments') + .select(` + comment_id, + project_id, + user_id, + content, + sentiment_score, + status, + is_pinned, + parent_id, + created_at, + updated_at, + user:user_id(id, email) + `, { count: 'exact' }); + + // 过滤条件 + commentsQuery = commentsQuery.eq('project_id', projectId); + + // 如果指定了父评论ID,则获取子评论 + if (parent_id) { + commentsQuery = commentsQuery.eq('parent_id', parent_id); + } else { + // 否则获取顶级评论(没有父评论的评论) + commentsQuery = commentsQuery.is('parent_id', null); + } + + // 排序和分页 + const isPinned = parent_id ? false : true; // 只有顶级评论才考虑置顶 + if (isPinned) { + commentsQuery = commentsQuery.order('is_pinned', { ascending: false }); + } + commentsQuery = commentsQuery.order('created_at', { ascending: false }); + commentsQuery = commentsQuery.range( + parseInt(offset), + parseInt(offset) + parseInt(limit) - 1 + ); + + // 执行查询 + const { data: comments, error: commentsError, count } = await commentsQuery; + + if (commentsError) { + console.error('Error fetching project comments:', commentsError); + return c.json({ error: 'Failed to fetch project comments' }, 500); + } + + // 获取每个顶级评论的回复数量 + if (comments && !parent_id) { + const commentIds = comments.map(comment => comment.comment_id); + + if (commentIds.length > 0) { + // 手动构建SQL查询来计算每个父评论的回复数量 + const { data: replyCounts, error: replyCountError } = await supabase + .rpc('get_reply_counts_for_comments', { parent_ids: commentIds }); + + if (!replyCountError && replyCounts) { + // 将回复数量添加到评论中 + for (const comment of comments) { + const replyCountItem = replyCounts.find((r: ReplyCount) => r.parent_id === comment.comment_id); + comment.reply_count = replyCountItem ? replyCountItem.count : 0; + } + } + } + } + + return c.json({ + project, + comments: comments || [], + total: count || 0, + limit: parseInt(limit), + offset: parseInt(offset) + }); + } catch (error) { + console.error('Error fetching project comments:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 添加评论到项目 +projectCommentsRouter.post('/projects/:id/comments', async (c) => { + try { + const projectId = c.req.param('id'); + const user = c.get('user'); + const { content, sentiment_score = 0, parent_id = null } = await c.req.json(); + + if (!content) { + return c.json({ error: 'Comment content is required' }, 400); + } + + // 检查项目是否存在 + const { data: project, error: projectError } = await supabase + .from('projects') + .select('id') + .eq('id', projectId) + .single(); + + if (projectError) { + console.error('Error fetching project:', projectError); + return c.json({ error: 'Project not found' }, 404); + } + + // 如果指定了父评论ID,检查父评论是否存在 + if (parent_id) { + const { data: parentComment, error: parentError } = await supabase + .from('project_comments') + .select('comment_id') + .eq('comment_id', parent_id) + .eq('project_id', projectId) + .single(); + + if (parentError || !parentComment) { + return c.json({ error: 'Parent comment not found' }, 404); + } + } + + // 创建评论 + const { data: comment, error: commentError } = await supabase + .from('project_comments') + .insert({ + project_id: projectId, + user_id: user.id, + content, + sentiment_score, + parent_id + }) + .select() + .single(); + + if (commentError) { + console.error('Error creating project comment:', commentError); + return c.json({ error: 'Failed to create comment' }, 500); + } + + // 记录评论事件到ClickHouse + try { + await clickhouse.query({ + query: ` + INSERT INTO events ( + project_id, + event_type, + metric_value, + event_metadata + ) VALUES (?, 'project_comment', ?, ?) + `, + values: [ + projectId, + 1, + JSON.stringify({ + comment_id: comment.comment_id, + user_id: user.id, + parent_id: parent_id || null, + content: content.substring(0, 100), // 只存储部分内容以减小数据量 + sentiment_score: sentiment_score + }) + ] + }); + } catch (chError) { + console.error('Error recording project comment event:', chError); + // 继续执行,不中断主流程 + } + + return c.json({ + message: 'Comment added successfully', + comment + }, 201); + } catch (error) { + console.error('Error adding project comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 更新项目评论 +projectCommentsRouter.put('/comments/:id', async (c) => { + try { + const commentId = c.req.param('id'); + const user = c.get('user'); + const { content, sentiment_score, is_pinned } = await c.req.json(); + + // 检查评论是否存在且属于当前用户或用户是项目拥有者 + const { data: comment, error: fetchError } = await supabase + .from('project_comments') + .select(` + comment_id, + project_id, + user_id, + projects!inner(created_by) + `) + .eq('comment_id', commentId) + .single(); + + if (fetchError || !comment) { + return c.json({ error: 'Comment not found' }, 404); + } + + // 确保我们能够安全地访问projects中的created_by字段 + const projectOwner = comment.projects && + Array.isArray(comment.projects) && + comment.projects.length > 0 ? + comment.projects[0].created_by : null; + + // 检查用户是否有权限更新评论 + const isCommentOwner = comment.user_id === user.id; + const isProjectOwner = projectOwner === user.id; + + if (!isCommentOwner && !isProjectOwner) { + return c.json({ + error: 'You do not have permission to update this comment' + }, 403); + } + + // 准备更新数据 + const updateData: Record = {}; + + // 评论创建者可以更新内容和情感分数 + if (isCommentOwner) { + if (content !== undefined) { + updateData.content = content; + } + if (sentiment_score !== undefined) { + updateData.sentiment_score = sentiment_score; + } + } + + // 项目所有者可以更新状态和置顶 + if (isProjectOwner) { + if (is_pinned !== undefined) { + updateData.is_pinned = is_pinned; + } + } + + // 更新时间 + updateData.updated_at = new Date().toISOString(); + + // 如果没有内容要更新,返回错误 + if (Object.keys(updateData).length === 1) { // 只有updated_at + return c.json({ error: 'No valid fields to update' }, 400); + } + + // 更新评论 + const { data: updatedComment, error } = await supabase + .from('project_comments') + .update(updateData) + .eq('comment_id', commentId) + .select() + .single(); + + if (error) { + console.error('Error updating project comment:', error); + return c.json({ error: 'Failed to update comment' }, 500); + } + + return c.json({ + message: 'Comment updated successfully', + comment: updatedComment + }); + } catch (error) { + console.error('Error updating project comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 删除项目评论 +projectCommentsRouter.delete('/comments/:id', async (c) => { + try { + const commentId = c.req.param('id'); + const user = c.get('user'); + + // 检查评论是否存在且属于当前用户或用户是项目拥有者 + const { data: comment, error: fetchError } = await supabase + .from('project_comments') + .select(` + comment_id, + project_id, + user_id, + projects!inner(created_by) + `) + .eq('comment_id', commentId) + .single(); + + if (fetchError || !comment) { + return c.json({ error: 'Comment not found' }, 404); + } + + // 确保我们能够安全地访问projects中的created_by字段 + const projectOwner = comment.projects && + Array.isArray(comment.projects) && + comment.projects.length > 0 ? + comment.projects[0].created_by : null; + + // 检查用户是否有权限删除评论 + const isCommentOwner = comment.user_id === user.id; + const isProjectOwner = projectOwner === user.id; + + if (!isCommentOwner && !isProjectOwner) { + return c.json({ + error: 'You do not have permission to delete this comment' + }, 403); + } + + // 删除评论 + const { error } = await supabase + .from('project_comments') + .delete() + .eq('comment_id', commentId); + + if (error) { + console.error('Error deleting project comment:', error); + return c.json({ error: 'Failed to delete comment' }, 500); + } + + return c.json({ + message: 'Comment deleted successfully' + }); + } catch (error) { + console.error('Error deleting project comment:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// 获取项目评论的统计信息 +projectCommentsRouter.get('/projects/:id/comments/stats', async (c) => { + try { + const projectId = c.req.param('id'); + + // 检查项目是否存在 + const { data: project, error: projectError } = await supabase + .from('projects') + .select('id, name') + .eq('id', projectId) + .single(); + + if (projectError) { + console.error('Error fetching project:', projectError); + return c.json({ error: 'Project not found' }, 404); + } + + // 从Supabase获取评论总数 + const { count } = await supabase + .from('project_comments') + .select('*', { count: 'exact', head: true }) + .eq('project_id', projectId); + + // 从Supabase获取情感分析统计 + const { data: sentimentStats } = await supabase + .from('project_comments') + .select('sentiment_score') + .eq('project_id', projectId); + + let averageSentiment = 0; + let positiveCount = 0; + let neutralCount = 0; + let negativeCount = 0; + + if (sentimentStats && sentimentStats.length > 0) { + // 计算平均情感分数 + const totalSentiment = sentimentStats.reduce((acc, curr) => acc + (curr.sentiment_score || 0), 0); + averageSentiment = totalSentiment / sentimentStats.length; + + // 分类情感分数 + sentimentStats.forEach(stat => { + const score = stat.sentiment_score || 0; + if (score > 0.3) { + positiveCount++; + } else if (score < -0.3) { + negativeCount++; + } else { + neutralCount++; + } + }); + } + + // 从ClickHouse获取评论时间趋势 + type TrendItem = { date: string; comment_count: number }; + let timeTrend: TrendItem[] = []; + + try { + const result = await clickhouse.query({ + query: ` + SELECT + toDate(timestamp) as date, + count() as comment_count + FROM events + WHERE + project_id = ? AND + event_type = 'project_comment' AND + timestamp >= subtractDays(now(), 30) + GROUP BY date + ORDER BY date ASC + `, + values: [projectId] + }); + + timeTrend = 'rows' in result ? result.rows as TrendItem[] : []; + } catch (chError) { + console.error('Error fetching comment time trend:', chError); + // 继续执行,返回空趋势数据 + } + + return c.json({ + project_id: projectId, + project_name: project.name, + total_comments: count || 0, + sentiment: { + average: averageSentiment, + positive: positiveCount, + neutral: neutralCount, + negative: negativeCount + }, + time_trend: timeTrend + }); + } catch (error) { + console.error('Error fetching project comment stats:', error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +export default projectCommentsRouter; \ No newline at end of file diff --git a/backend/src/swagger/index.ts b/backend/src/swagger/index.ts new file mode 100644 index 0000000..ee6db8a --- /dev/null +++ b/backend/src/swagger/index.ts @@ -0,0 +1,1866 @@ +import { swaggerUI } from '@hono/swagger-ui' +import { Hono } from 'hono' +import jwt from 'jsonwebtoken' +import config from '../config' + +// 创建 OpenAPI 规范 +export const openAPISpec = { + openapi: '3.0.0', + info: { + title: 'Promote API', + version: '1.0.0', + description: 'API documentation for the Promote platform', + }, + servers: [ + { + url: 'http://localhost:4000', + description: 'Local development server', + }, + ], + paths: { + '/': { + get: { + summary: 'Health check', + description: 'Returns the API status', + responses: { + '200': { + description: 'API is running', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string', example: 'ok' }, + message: { type: 'string', example: 'Promote API is running' }, + version: { type: 'string', example: '1.0.0' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/auth/register': { + post: { + summary: 'Register a new user', + description: 'Creates a new user account', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['email', 'password', 'name'], + properties: { + email: { type: 'string', format: 'email', example: 'user@example.com' }, + password: { type: 'string', format: 'password', example: 'securepassword' }, + name: { type: 'string', example: 'John Doe' }, + }, + }, + }, + }, + }, + responses: { + '201': { + description: 'User registered successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'User registered successfully' }, + user: { + type: 'object', + properties: { + id: { type: 'string', example: '123e4567-e89b-12d3-a456-426614174000' }, + email: { type: 'string', example: 'user@example.com' }, + name: { type: 'string', example: 'John Doe' }, + }, + }, + token: { type: 'string', example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Email, password, and name are required' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/auth/login': { + post: { + summary: 'Login user', + description: 'Authenticates a user and returns a JWT token', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['email', 'password'], + properties: { + email: { type: 'string', format: 'email', example: 'vitalitymailg@gmail.com' }, + password: { type: 'string', format: 'password', example: 'password123' }, + }, + }, + examples: { + demoUser: { + summary: '示例用户', + value: { + email: 'vitalitymailg@gmail.com', + password: 'password123' + } + } + } + }, + }, + }, + responses: { + '200': { + description: 'Login successful', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'Login successful' }, + user: { + type: 'object', + properties: { + id: { type: 'string', example: '123e4567-e89b-12d3-a456-426614174000' }, + email: { type: 'string', example: 'vitalitymailg@gmail.com' }, + name: { type: 'string', example: 'Vitality User' }, + }, + }, + token: { type: 'string', example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Invalid credentials' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/auth/verify': { + get: { + summary: 'Verify token', + description: 'Verifies a JWT token', + security: [ + { + bearerAuth: [], + }, + ], + responses: { + '200': { + description: 'Token is valid', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'Token is valid' }, + user: { + type: 'object', + properties: { + id: { type: 'string', example: '123e4567-e89b-12d3-a456-426614174000' }, + email: { type: 'string', example: 'vitalitymailg@gmail.com' }, + }, + }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Invalid token' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/analytics/view': { + post: { + summary: 'Track view event', + description: 'Records a view event for content', + security: [ + { + bearerAuth: [], + }, + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['content_id'], + properties: { + content_id: { type: 'string', example: 'content-123' }, + }, + }, + }, + }, + }, + responses: { + '200': { + description: 'View tracked successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'View tracked successfully' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Content ID is required' }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Unauthorized: No token provided' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/analytics/like': { + post: { + summary: 'Track like event', + description: 'Records a like or unlike event for content', + security: [ + { + bearerAuth: [], + }, + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['content_id', 'action'], + properties: { + content_id: { type: 'string', example: 'content-123' }, + action: { type: 'string', enum: ['like', 'unlike'], example: 'like' }, + }, + }, + }, + }, + }, + responses: { + '200': { + description: 'Like/unlike tracked successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'like tracked successfully' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Content ID and action are required' }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Unauthorized: No token provided' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/analytics/follow': { + post: { + summary: 'Track follow event', + description: 'Records a follow or unfollow event for a user', + security: [ + { + bearerAuth: [], + }, + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['followed_id', 'action'], + properties: { + followed_id: { type: 'string', example: 'user-123' }, + action: { type: 'string', enum: ['follow', 'unfollow'], example: 'follow' }, + }, + }, + }, + }, + }, + responses: { + '200': { + description: 'Follow/unfollow tracked successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string', example: 'follow tracked successfully' }, + }, + }, + }, + }, + }, + '400': { + description: 'Bad request', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Followed ID and action are required' }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Unauthorized: No token provided' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/analytics/content/{id}': { + get: { + summary: 'Get content analytics', + description: 'Returns analytics data for a specific content', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'string', + }, + description: 'Content ID', + example: 'content-123', + }, + ], + responses: { + '200': { + description: 'Content analytics data', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + content_id: { type: 'string', example: 'content-123' }, + views: { type: 'integer', example: 1250 }, + likes: { type: 'integer', example: 87 }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Unauthorized: No token provided' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/analytics/user/{id}': { + get: { + summary: 'Get user analytics', + description: 'Returns analytics data for a specific user', + security: [ + { + bearerAuth: [], + }, + ], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'string', + }, + description: 'User ID', + example: 'user-123', + }, + ], + responses: { + '200': { + description: 'User analytics data', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + user_id: { type: 'string', example: 'user-123' }, + followers: { type: 'integer', example: 542 }, + content_analytics: { + type: 'object', + properties: { + views: { + type: 'array', + items: { + type: 'object', + properties: { + content_id: { type: 'string', example: 'content-123' }, + view_count: { type: 'integer', example: 1250 }, + }, + }, + }, + likes: { + type: 'array', + items: { + type: 'object', + properties: { + content_id: { type: 'string', example: 'content-123' }, + like_count: { type: 'integer', example: 87 }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + '401': { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Unauthorized: No token provided' }, + }, + }, + }, + }, + }, + '500': { + description: 'Internal server error', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string', example: 'Internal server error' }, + }, + }, + }, + }, + }, + }, + }, + }, + '/api/posts': { + get: { + summary: '获取帖子列表', + description: '返回分页的帖子列表,支持过滤和排序', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'influencer_id', + in: 'query', + description: '按影响者ID过滤', + schema: { type: 'string', format: 'uuid' }, + required: false + }, + { + name: 'platform', + in: 'query', + description: '按平台过滤', + schema: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + }, + required: false + }, + { + name: 'limit', + in: 'query', + description: '每页返回的记录数', + schema: { type: 'integer', default: 20 }, + required: false + }, + { + name: 'offset', + in: 'query', + description: '分页偏移量', + schema: { type: 'integer', default: 0 }, + required: false + }, + { + name: 'sort', + in: 'query', + description: '排序字段', + schema: { type: 'string', default: 'published_at' }, + required: false + }, + { + name: 'order', + in: 'query', + description: '排序方向', + schema: { + type: 'string', + enum: ['asc', 'desc'], + default: 'desc' + }, + required: false + } + ], + responses: { + '200': { + description: '成功获取帖子列表', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + posts: { + type: 'array', + items: { + $ref: '#/components/schemas/Post' + } + }, + total: { type: 'integer' }, + limit: { type: 'integer' }, + offset: { type: 'integer' } + } + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + }, + post: { + summary: '创建新帖子', + description: '创建一个新的帖子', + security: [{ bearerAuth: [] }], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['influencer_id', 'platform', 'post_url'], + properties: { + influencer_id: { type: 'string', format: 'uuid' }, + platform: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + }, + post_url: { type: 'string', format: 'uri' }, + title: { type: 'string' }, + description: { type: 'string' }, + published_at: { type: 'string', format: 'date-time' } + } + } + } + } + }, + responses: { + '201': { + description: '帖子创建成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + post: { + $ref: '#/components/schemas/Post' + } + } + } + } + } + }, + '400': { + description: '请求参数错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '409': { + description: '帖子URL已存在', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + error: { type: 'string' }, + post: { + $ref: '#/components/schemas/Post' + } + } + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + } + }, + '/api/posts/{id}': { + get: { + summary: '获取单个帖子详情', + description: '返回指定ID的帖子详情,包括统计数据和时间线', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '帖子ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + responses: { + '200': { + description: '成功获取帖子详情', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/PostDetail' + } + } + } + }, + '404': { + description: '帖子不存在', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + }, + put: { + summary: '更新帖子', + description: '更新指定ID的帖子信息', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '帖子ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + title: { type: 'string' }, + description: { type: 'string' } + } + } + } + } + }, + responses: { + '200': { + description: '帖子更新成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + post: { + $ref: '#/components/schemas/Post' + } + } + } + } + } + }, + '404': { + description: '帖子不存在', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + }, + delete: { + summary: '删除帖子', + description: '删除指定ID的帖子', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '帖子ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + responses: { + '200': { + description: '帖子删除成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' } + } + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + } + }, + '/api/posts/{id}/comments': { + get: { + summary: '获取帖子评论', + description: '返回指定帖子的评论列表', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '帖子ID', + required: true, + schema: { type: 'string', format: 'uuid' } + }, + { + name: 'limit', + in: 'query', + description: '每页返回的记录数', + schema: { type: 'integer', default: 20 }, + required: false + }, + { + name: 'offset', + in: 'query', + description: '分页偏移量', + schema: { type: 'integer', default: 0 }, + required: false + } + ], + responses: { + '200': { + description: '成功获取评论列表', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + comments: { + type: 'array', + items: { + $ref: '#/components/schemas/Comment' + } + }, + total: { type: 'integer' }, + limit: { type: 'integer' }, + offset: { type: 'integer' } + } + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + }, + post: { + summary: '添加评论', + description: '为指定帖子添加新评论', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '帖子ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['content'], + properties: { + content: { type: 'string' }, + sentiment_score: { type: 'number' } + } + } + } + } + }, + responses: { + '201': { + description: '评论添加成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + comment: { + $ref: '#/components/schemas/Comment' + } + } + } + } + } + }, + '400': { + description: '请求参数错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + } + }, + '/api/posts/comments/{id}': { + put: { + summary: '更新评论', + description: '更新指定ID的评论', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '评论ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + content: { type: 'string' }, + sentiment_score: { type: 'number' } + } + } + } + } + }, + responses: { + '200': { + description: '评论更新成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + comment: { + $ref: '#/components/schemas/Comment' + } + } + } + } + } + }, + '404': { + description: '评论不存在或无权限', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + }, + delete: { + summary: '删除评论', + description: '删除指定ID的评论', + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + description: '评论ID', + required: true, + schema: { type: 'string', format: 'uuid' } + } + ], + responses: { + '200': { + description: '评论删除成功', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' } + } + } + } + } + }, + '404': { + description: '评论不存在或无权限', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + }, + '500': { + description: '服务器错误', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Error' + } + } + } + } + } + } + }, + '/api/comments': { + get: { + tags: ['Comments'], + summary: 'Get comments', + description: 'Retrieve a list of comments with optional filtering by post_id', + parameters: [ + { + name: 'post_id', + in: 'query', + description: 'Filter comments by post ID', + required: false, + schema: { + type: 'string', + format: 'uuid' + } + }, + { + name: 'limit', + in: 'query', + description: 'Number of comments to return', + required: false, + schema: { + type: 'integer', + default: 10 + } + }, + { + name: 'offset', + in: 'query', + description: 'Number of comments to skip', + required: false, + schema: { + type: 'integer', + default: 0 + } + } + ], + responses: { + '200': { + description: 'List of comments', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + comments: { + type: 'array', + items: { + $ref: '#/components/schemas/Comment' + } + }, + count: { + type: 'integer' + }, + limit: { + type: 'integer' + }, + offset: { + type: 'integer' + } + } + } + } + } + } + } + }, + post: { + tags: ['Comments'], + summary: 'Create a comment', + description: 'Create a new comment on a post', + security: [ + { + bearerAuth: [] + } + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['post_id', 'content'], + properties: { + post_id: { + type: 'string', + format: 'uuid' + }, + content: { + type: 'string' + } + } + } + } + } + }, + responses: { + '201': { + description: 'Comment created successfully', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Comment' + } + } + } + }, + '401': { + description: 'Unauthorized' + } + } + } + }, + '/api/comments/{comment_id}': { + delete: { + tags: ['Comments'], + summary: 'Delete a comment', + description: 'Delete a comment by ID (only for comment owner)', + security: [ + { + bearerAuth: [] + } + ], + parameters: [ + { + name: 'comment_id', + in: 'path', + description: 'Comment ID to delete', + required: true, + schema: { + type: 'string', + format: 'uuid' + } + } + ], + responses: { + '204': { + description: 'Comment deleted successfully' + }, + '401': { + description: 'Unauthorized' + }, + '404': { + description: 'Comment not found or unauthorized' + } + } + } + }, + '/api/influencers': { + get: { + tags: ['Influencers'], + summary: 'Get influencers', + description: 'Retrieve a list of influencers with optional filtering and sorting', + parameters: [ + { + name: 'platform', + in: 'query', + description: 'Filter by platform', + required: false, + schema: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + } + }, + { + name: 'min_followers', + in: 'query', + description: 'Minimum number of followers', + required: false, + schema: { + type: 'integer' + } + }, + { + name: 'max_followers', + in: 'query', + description: 'Maximum number of followers', + required: false, + schema: { + type: 'integer' + } + }, + { + name: 'sort_by', + in: 'query', + description: 'Field to sort by', + required: false, + schema: { + type: 'string', + enum: ['followers_count', 'video_count', 'created_at'], + default: 'followers_count' + } + }, + { + name: 'sort_order', + in: 'query', + description: 'Sort order', + required: false, + schema: { + type: 'string', + enum: ['asc', 'desc'], + default: 'desc' + } + }, + { + name: 'limit', + in: 'query', + description: 'Number of influencers to return', + required: false, + schema: { + type: 'integer', + default: 10 + } + }, + { + name: 'offset', + in: 'query', + description: 'Number of influencers to skip', + required: false, + schema: { + type: 'integer', + default: 0 + } + } + ], + responses: { + '200': { + description: 'List of influencers', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + influencers: { + type: 'array', + items: { + $ref: '#/components/schemas/Influencer' + } + }, + count: { + type: 'integer' + }, + limit: { + type: 'integer' + }, + offset: { + type: 'integer' + } + } + } + } + } + } + } + } + }, + '/api/influencers/stats': { + get: { + tags: ['Influencers'], + summary: 'Get influencer statistics', + description: 'Retrieve aggregated statistics about influencers', + parameters: [ + { + name: 'platform', + in: 'query', + description: 'Filter statistics by platform', + required: false, + schema: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + } + } + ], + responses: { + '200': { + description: 'Influencer statistics', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + total_influencers: { + type: 'integer' + }, + total_followers: { + type: 'integer' + }, + total_videos: { + type: 'integer' + }, + average_followers: { + type: 'integer' + }, + average_videos: { + type: 'integer' + } + } + } + } + } + } + } + } + }, + '/api/influencers/{influencer_id}': { + get: { + tags: ['Influencers'], + summary: 'Get influencer by ID', + description: 'Retrieve detailed information about a specific influencer', + parameters: [ + { + name: 'influencer_id', + in: 'path', + description: 'Influencer ID', + required: true, + schema: { + type: 'string', + format: 'uuid' + } + } + ], + responses: { + '200': { + description: 'Influencer details', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/InfluencerWithPosts' + } + } + } + }, + '404': { + description: 'Influencer not found' + } + } + } + } + }, + components: { + schemas: { + Post: { + type: 'object', + properties: { + post_id: { type: 'string', format: 'uuid' }, + influencer_id: { type: 'string', format: 'uuid' }, + platform: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + }, + post_url: { type: 'string', format: 'uri' }, + title: { type: 'string' }, + description: { type: 'string' }, + published_at: { type: 'string', format: 'date-time' }, + created_at: { type: 'string', format: 'date-time' }, + updated_at: { type: 'string', format: 'date-time' }, + influencer: { + type: 'object', + properties: { + name: { type: 'string' }, + platform: { type: 'string' }, + profile_url: { type: 'string' }, + followers_count: { type: 'integer' } + } + }, + stats: { + type: 'object', + properties: { + views: { type: 'integer' }, + likes: { type: 'integer' } + } + } + } + }, + PostDetail: { + type: 'object', + properties: { + post_id: { type: 'string', format: 'uuid' }, + influencer_id: { type: 'string', format: 'uuid' }, + platform: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + }, + post_url: { type: 'string', format: 'uri' }, + title: { type: 'string' }, + description: { type: 'string' }, + published_at: { type: 'string', format: 'date-time' }, + created_at: { type: 'string', format: 'date-time' }, + updated_at: { type: 'string', format: 'date-time' }, + influencer: { + type: 'object', + properties: { + name: { type: 'string' }, + platform: { type: 'string' }, + profile_url: { type: 'string' }, + followers_count: { type: 'integer' } + } + }, + stats: { + type: 'object', + properties: { + views: { type: 'integer' }, + likes: { type: 'integer' } + } + }, + timeline: { + type: 'array', + items: { + type: 'object', + properties: { + date: { type: 'string', format: 'date' }, + event_type: { type: 'string' }, + value: { type: 'integer' } + } + } + }, + comment_count: { type: 'integer' } + } + }, + Comment: { + type: 'object', + properties: { + comment_id: { + type: 'string', + format: 'uuid' + }, + content: { + type: 'string' + }, + sentiment_score: { + type: 'number', + description: '情感分析分数,范围从 -1 到 1' + }, + created_at: { + type: 'string', + format: 'date-time' + }, + updated_at: { + type: 'string', + format: 'date-time' + }, + post_id: { + type: 'string', + format: 'uuid' + }, + user_id: { + type: 'string', + format: 'uuid' + }, + user_profile: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + }, + full_name: { + type: 'string' + }, + avatar_url: { + type: 'string', + format: 'uri' + } + } + }, + post: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + }, + title: { + type: 'string' + }, + platform: { + type: 'string', + enum: ['facebook', 'threads', 'instagram', 'linkedin', 'xiaohongshu', 'youtube'] + }, + content_type: { + type: 'string', + enum: ['post', 'reel', 'video', 'short'] + }, + influencer: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + }, + name: { + type: 'string' + }, + type: { + type: 'string', + enum: ['user', 'kol', 'official'] + } + } + } + } + }, + status: { + type: 'string', + enum: ['pending', 'approved', 'rejected'] + }, + reply_status: { + type: 'string', + enum: ['none', 'draft', 'sent'] + }, + language: { + type: 'string', + enum: ['zh-TW', 'zh-CN', 'en'] + } + } + }, + Influencer: { + type: 'object', + properties: { + influencer_id: { + type: 'string', + format: 'uuid' + }, + name: { + type: 'string' + }, + platform: { + type: 'string', + enum: ['youtube', 'instagram', 'tiktok', 'twitter', 'facebook'] + }, + profile_url: { + type: 'string' + }, + followers_count: { + type: 'integer' + }, + video_count: { + type: 'integer' + }, + platform_count: { + type: 'integer' + }, + created_at: { + type: 'string', + format: 'date-time' + }, + updated_at: { + type: 'string', + format: 'date-time' + } + } + }, + InfluencerWithPosts: { + allOf: [ + { + $ref: '#/components/schemas/Influencer' + }, + { + type: 'object', + properties: { + posts: { + type: 'array', + items: { + type: 'object', + properties: { + post_id: { + type: 'string', + format: 'uuid' + }, + title: { + type: 'string' + }, + description: { + type: 'string' + }, + published_at: { + type: 'string', + format: 'date-time' + } + } + } + } + } + } + ] + }, + Error: { + type: 'object', + properties: { + error: { type: 'string' } + } + } + }, + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + description: '登录后获取的 token 可直接在此处使用。在 Authorize 按钮中输入 "Bearer your-token" 或直接输入 token(不带 Bearer 前缀)。' + }, + }, + }, +} + +// 创建 Swagger UI 路由 +export const createSwaggerUI = () => { + const app = new Hono() + + // 设置 Swagger UI 路由 + app.get('/swagger', swaggerUI({ + url: '/api/swagger.json', + })) + + // 提供 OpenAPI 规范的 JSON 端点 + app.get('/api/swagger.json', (c) => { + return c.json(openAPISpec) + }) + + // 添加临时的 token 生成端点,仅用于 Swagger 测试 + app.get('/api/swagger/token', async (c) => { + try { + // 创建一个临时 token,与 authMiddleware 中的验证方式一致 + const token = jwt.sign( + { + sub: 'swagger-test-user', + email: 'swagger@test.com', + }, + config.jwt.secret, + { + expiresIn: '1h', + } + ); + + return c.json({ + message: '此 token 仅用于 Swagger UI 测试', + token, + usage: '在 Authorize 对话框中输入: Bearer [token]' + }); + } catch (error) { + console.error('Error generating swagger token:', error); + return c.json({ error: 'Failed to generate token' }, 500); + } + }); + + return app +} \ No newline at end of file diff --git a/backend/src/utils/clickhouse.ts b/backend/src/utils/clickhouse.ts new file mode 100644 index 0000000..fce5f32 --- /dev/null +++ b/backend/src/utils/clickhouse.ts @@ -0,0 +1,86 @@ +import { createClient } from '@clickhouse/client'; +import config from '../config'; + +// Create ClickHouse client with error handling +const createClickHouseClient = () => { + try { + return createClient({ + host: `http://${config.clickhouse.host}:${config.clickhouse.port}`, + username: config.clickhouse.user, + password: config.clickhouse.password, + database: config.clickhouse.database, + }); + } catch (error) { + console.error('Error creating ClickHouse client:', error); + // Return a mock client for development that logs operations instead of executing them + return { + query: async ({ query, values }: { query: string; values?: any[] }) => { + console.log('ClickHouse query (mock):', query, values); + return { rows: [] }; + }, + close: async () => { + console.log('ClickHouse connection closed (mock)'); + } + }; + } +}; + +const clickhouse = createClickHouseClient(); + +// Initialize ClickHouse database and tables +export const initClickHouse = async () => { + try { + // Create database if not exists + await clickhouse.query({ + query: `CREATE DATABASE IF NOT EXISTS ${config.clickhouse.database}`, + }); + + // Create tables for tracking events + await clickhouse.query({ + query: ` + CREATE TABLE IF NOT EXISTS ${config.clickhouse.database}.view_events ( + user_id String, + content_id String, + timestamp DateTime DEFAULT now(), + ip String, + user_agent String + ) ENGINE = MergeTree() + PARTITION BY toYYYYMM(timestamp) + ORDER BY (user_id, content_id, timestamp) + `, + }); + + await clickhouse.query({ + query: ` + CREATE TABLE IF NOT EXISTS ${config.clickhouse.database}.like_events ( + user_id String, + content_id String, + timestamp DateTime DEFAULT now(), + action Enum('like' = 1, 'unlike' = 2) + ) ENGINE = MergeTree() + PARTITION BY toYYYYMM(timestamp) + ORDER BY (user_id, content_id, timestamp) + `, + }); + + await clickhouse.query({ + query: ` + CREATE TABLE IF NOT EXISTS ${config.clickhouse.database}.follower_events ( + follower_id String, + followed_id String, + timestamp DateTime DEFAULT now(), + action Enum('follow' = 1, 'unfollow' = 2) + ) ENGINE = MergeTree() + PARTITION BY toYYYYMM(timestamp) + ORDER BY (follower_id, followed_id, timestamp) + `, + }); + + console.log('ClickHouse database and tables initialized'); + } catch (error) { + console.error('Error initializing ClickHouse:', error); + console.log('Continuing with limited functionality...'); + } +}; + +export default clickhouse; \ No newline at end of file diff --git a/backend/src/utils/initDatabase.ts b/backend/src/utils/initDatabase.ts new file mode 100644 index 0000000..68a59f6 --- /dev/null +++ b/backend/src/utils/initDatabase.ts @@ -0,0 +1,538 @@ +import supabase from './supabase'; +import clickhouse from './clickhouse'; +import fs from 'fs/promises'; +import path from 'path'; + +/** + * 初始化 Supabase (PostgreSQL) 数据库表 + */ +export const initSupabaseTables = async () => { + try { + console.log('开始初始化 Supabase 数据表...'); + + // 创建用户扩展表 + await supabase.rpc('create_user_profiles_if_not_exists'); + + // 创建项目表 + await supabase.rpc('create_projects_table_if_not_exists'); + + // 创建网红(影响者)表 + await supabase.rpc('create_influencers_table_if_not_exists'); + + // 创建项目-网红关联表 + await supabase.rpc('create_project_influencers_table_if_not_exists'); + + // 创建帖子表 + await supabase.rpc('create_posts_table_if_not_exists'); + + // 创建评论表 + await supabase.rpc('create_comments_table_if_not_exists'); + + // 创建项目评论表 + await supabase.rpc('create_project_comments_table_if_not_exists'); + + console.log('Supabase 数据表初始化完成'); + return true; + } catch (error) { + console.error('初始化 Supabase 数据表失败:', error); + return false; + } +}; + +/** + * 初始化 ClickHouse 数据库表 + */ +export const initClickHouseTables = async () => { + try { + console.log('开始初始化 ClickHouse 数据表...'); + + // 创建事件表 + await clickhouse.query({ + query: ` + CREATE TABLE IF NOT EXISTS events ( + event_id UUID DEFAULT generateUUIDv4(), + project_id UUID, + influencer_id UUID, + post_id UUID NULL, + platform String, + event_type Enum( + 'follower_change' = 1, + 'post_like_change' = 2, + 'post_view_change' = 3, + 'click' = 4, + 'comment' = 5, + 'share' = 6, + 'project_comment' = 7 + ), + metric_value Int64, + event_metadata String, + timestamp DateTime DEFAULT now() + ) ENGINE = MergeTree() + PARTITION BY toYYYYMM(timestamp) + ORDER BY (platform, influencer_id, post_id, event_type, timestamp) + ` + }); + + // 创建统计视图 - 按天统计 + await clickhouse.query({ + query: ` + CREATE MATERIALIZED VIEW IF NOT EXISTS daily_stats + ENGINE = SummingMergeTree() + PARTITION BY toYYYYMM(date) + ORDER BY (date, platform, influencer_id, event_type) + AS SELECT + toDate(timestamp) AS date, + platform, + influencer_id, + event_type, + SUM(metric_value) AS total_value, + COUNT(*) AS event_count + FROM events + GROUP BY date, platform, influencer_id, event_type + ` + }); + + // 创建统计视图 - 按月统计 + await clickhouse.query({ + query: ` + CREATE MATERIALIZED VIEW IF NOT EXISTS monthly_stats + ENGINE = SummingMergeTree() + ORDER BY (month, platform, influencer_id, event_type) + AS SELECT + toStartOfMonth(timestamp) AS month, + platform, + influencer_id, + event_type, + SUM(metric_value) AS total_value, + COUNT(*) AS event_count + FROM events + GROUP BY month, platform, influencer_id, event_type + ` + }); + + // 创建帖子互动统计视图 + await clickhouse.query({ + query: ` + CREATE MATERIALIZED VIEW IF NOT EXISTS post_interaction_stats + ENGINE = SummingMergeTree() + ORDER BY (post_id, event_type, date) + AS SELECT + post_id, + event_type, + toDate(timestamp) AS date, + SUM(metric_value) AS value, + COUNT(*) AS count + FROM events + WHERE post_id IS NOT NULL + GROUP BY post_id, event_type, date + ` + }); + + // 创建项目互动统计视图 + await clickhouse.query({ + query: ` + CREATE MATERIALIZED VIEW IF NOT EXISTS project_interaction_stats + ENGINE = SummingMergeTree() + ORDER BY (project_id, event_type, date) + AS SELECT + project_id, + event_type, + toDate(timestamp) AS date, + SUM(metric_value) AS value, + COUNT(*) AS count + FROM events + WHERE project_id IS NOT NULL AND event_type = 'project_comment' + GROUP BY project_id, event_type, date + ` + }); + + console.log('ClickHouse 数据表初始化完成'); + return true; + } catch (error) { + console.error('初始化 ClickHouse 数据表失败:', error); + return false; + } +}; + +/** + * 初始化 Supabase 存储函数 + */ +export const initSupabaseFunctions = async () => { + try { + console.log('开始初始化 Supabase 存储过程...'); + + // 创建用户简档表的存储过程 + await supabase.rpc('create_function_create_user_profiles_if_not_exists'); + + // 创建项目表的存储过程 + await supabase.rpc('create_function_create_projects_table_if_not_exists'); + + // 创建网红表的存储过程 + await supabase.rpc('create_function_create_influencers_table_if_not_exists'); + + // 创建项目-网红关联表的存储过程 + await supabase.rpc('create_function_create_project_influencers_table_if_not_exists'); + + // 创建帖子表的存储过程 + await supabase.rpc('create_function_create_posts_table_if_not_exists'); + + // 创建评论表的存储过程 + await supabase.rpc('create_function_create_comments_table_if_not_exists'); + + // 创建项目评论表的存储过程 + await supabase.rpc('create_function_create_project_comments_table_if_not_exists'); + + // 创建评论相关的SQL函数 + console.log('创建评论相关的SQL函数...'); + const commentsSQL = await fs.readFile( + path.join(__dirname, 'supabase-comments-functions.sql'), + 'utf8' + ); + + // 使用Supabase执行SQL + const { error: commentsFunctionsError } = await supabase.rpc( + 'pgclient_execute', + { query: commentsSQL } + ); + + if (commentsFunctionsError) { + console.error('创建评论SQL函数失败:', commentsFunctionsError); + } else { + console.log('评论SQL函数创建成功'); + } + + console.log('Supabase 存储过程初始化完成'); + return true; + } catch (error) { + console.error('初始化 Supabase 存储过程失败:', error); + return false; + } +}; + +/** + * 创建测试数据 + */ +export const createSampleData = async () => { + try { + console.log('开始创建测试数据...'); + + // 创建测试用户 + const { data: user, error: userError } = await supabase.auth.admin.createUser({ + email: 'test@example.com', + password: 'password123', + user_metadata: { + full_name: '测试用户' + } + }); + + if (userError) { + console.error('创建测试用户失败:', userError); + return false; + } + + // 创建测试项目 + const { data: project, error: projectError } = await supabase + .from('projects') + .insert({ + name: '测试营销活动', + description: '这是一个测试营销活动', + created_by: user.user.id + }) + .select() + .single(); + + if (projectError) { + console.error('创建测试项目失败:', projectError); + return false; + } + + // 创建项目评论 + await supabase + .from('project_comments') + .insert([ + { + project_id: project.id, + user_id: user.user.id, + content: '这是对项目的一条测试评论', + sentiment_score: 0.8 + }, + { + project_id: project.id, + user_id: user.user.id, + content: '这个项目很有前景', + sentiment_score: 0.9 + }, + { + project_id: project.id, + user_id: user.user.id, + content: '需要关注这个项目的进展', + sentiment_score: 0.7 + } + ]); + + // 创建测试网红 + const platforms = ['youtube', 'instagram', 'tiktok']; + const influencers = []; + + for (let i = 1; i <= 10; i++) { + const platform = platforms[Math.floor(Math.random() * platforms.length)]; + + const { data: influencer, error: influencerError } = await supabase + .from('influencers') + .insert({ + name: `测试网红 ${i}`, + platform, + profile_url: `https://${platform}.com/user${i}`, + external_id: `user_${platform}_${i}`, + followers_count: Math.floor(Math.random() * 1000000) + 1000, + video_count: Math.floor(Math.random() * 500) + 10 + }) + .select() + .single(); + + if (influencerError) { + console.error(`创建测试网红 ${i} 失败:`, influencerError); + continue; + } + + influencers.push(influencer); + + // 将网红添加到项目 + await supabase + .from('project_influencers') + .insert({ + project_id: project.id, + influencer_id: influencer.influencer_id + }); + + // 为每个网红创建 3-5 个帖子 + const postCount = Math.floor(Math.random() * 3) + 3; + + for (let j = 1; j <= postCount; j++) { + const { data: post, error: postError } = await supabase + .from('posts') + .insert({ + influencer_id: influencer.influencer_id, + platform, + post_url: `https://${platform}.com/user${i}/post${j}`, + title: `测试帖子 ${j} - 由 ${influencer.name} 发布`, + description: `这是一个测试帖子的描述 ${j}`, + published_at: new Date(Date.now() - Math.floor(Math.random() * 30) * 24 * 60 * 60 * 1000).toISOString() + }) + .select() + .single(); + + if (postError) { + console.error(`创建测试帖子 ${j} 失败:`, postError); + continue; + } + + // 为每个帖子创建 2-10 个评论 + const commentCount = Math.floor(Math.random() * 9) + 2; + + for (let k = 1; k <= commentCount; k++) { + await supabase + .from('comments') + .insert({ + post_id: post.post_id, + user_id: user.user.id, + content: `这是对帖子 ${post.title} 的测试评论 ${k}`, + sentiment_score: (Math.random() * 2 - 1) // -1 到 1 之间的随机数 + }); + } + + // 创建 ClickHouse 事件数据 + // 粉丝变化事件 + await clickhouse.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, 'follower_change', ?, ?) + `, + values: [ + project.id, + influencer.influencer_id, + platform, + Math.floor(Math.random() * 1000) - 200, // -200 到 800 之间的随机数 + JSON.stringify({ source: 'api_crawler' }) + ] + }); + + // 帖子点赞变化事件 + await clickhouse.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, ?, 'post_like_change', ?, ?) + `, + values: [ + project.id, + influencer.influencer_id, + post.post_id, + platform, + Math.floor(Math.random() * 500) + 10, // 10 到 510 之间的随机数 + JSON.stringify({ source: 'api_crawler' }) + ] + }); + + // 帖子观看数变化事件 + await clickhouse.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, ?, 'post_view_change', ?, ?) + `, + values: [ + project.id, + influencer.influencer_id, + post.post_id, + platform, + Math.floor(Math.random() * 5000) + 100, // 100 到 5100 之间的随机数 + JSON.stringify({ source: 'api_crawler' }) + ] + }); + + // 互动事件 + const interactionTypes = ['click', 'comment', 'share']; + const interactionType = interactionTypes[Math.floor(Math.random() * interactionTypes.length)]; + + await clickhouse.query({ + query: ` + INSERT INTO events ( + project_id, + influencer_id, + post_id, + platform, + event_type, + metric_value, + event_metadata + ) VALUES (?, ?, ?, ?, ?, ?, ?) + `, + values: [ + project.id, + influencer.influencer_id, + post.post_id, + platform, + interactionType, + 1, + JSON.stringify({ + ip: '192.168.1.' + Math.floor(Math.random() * 255), + user_agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + }) + ] + }); + } + } + + // 创建项目评论事件 + for (let i = 1; i <= 5; i++) { + await clickhouse.query({ + query: ` + INSERT INTO events ( + project_id, + event_type, + metric_value, + event_metadata + ) VALUES (?, 'project_comment', ?, ?) + `, + values: [ + project.id, + 1, + JSON.stringify({ + user_id: user.user.id, + timestamp: new Date().toISOString(), + comment: `项目评论事件 ${i}` + }) + ] + }); + } + + console.log('测试数据创建完成'); + return true; + } catch (error) { + console.error('创建测试数据失败:', error); + return false; + } +}; + +/** + * 检查数据库连接 + */ +export const checkDatabaseConnection = async () => { + try { + console.log('检查数据库连接...'); + + // 检查 Supabase 连接 + try { + // 仅检查连接是否正常,不执行实际查询 + const { data, error } = await supabase.auth.getSession(); + if (error) { + console.error('Supabase 连接测试失败:', error); + return false; + } + console.log('Supabase 连接正常'); + } catch (supabaseError) { + console.error('Supabase 连接测试失败:', supabaseError); + return false; + } + + // 检查 ClickHouse 连接 + try { + // 使用简单查询代替ping方法 + const result = await clickhouse.query({ query: 'SELECT 1' }); + console.log('ClickHouse 连接正常'); + } catch (error) { + console.error('ClickHouse 连接测试失败:', error); + return false; + } + + console.log('数据库连接检查完成,所有连接均正常'); + return true; + } catch (error) { + console.error('数据库连接检查失败:', error); + return false; + } +}; + +/** + * 初始化数据库 - 此函数现在仅作为手动初始化的入口点 + * 只有通过管理API明确调用时才会执行实际的初始化 + */ +export const initDatabase = async () => { + try { + console.log('开始数据库初始化...'); + console.log('警告: 此操作将修改数据库结构,请确保您知道自己在做什么'); + + // 初始化 Supabase 函数 + await initSupabaseFunctions(); + + // 初始化 Supabase 表 + await initSupabaseTables(); + + // 初始化 ClickHouse 表 + await initClickHouseTables(); + + console.log('数据库初始化完成'); + return true; + } catch (error) { + console.error('数据库初始化失败:', error); + return false; + } +}; \ No newline at end of file diff --git a/backend/src/utils/queue.ts b/backend/src/utils/queue.ts new file mode 100644 index 0000000..cb97a44 --- /dev/null +++ b/backend/src/utils/queue.ts @@ -0,0 +1,189 @@ +import { Queue, Worker, Job } from 'bullmq'; +import config from '../config'; + +// Define queue names +export const QUEUE_NAMES = { + ANALYTICS: 'analytics', + NOTIFICATIONS: 'notifications', +}; + +// Define job data types +interface AnalyticsJobData { + type: 'process_views' | 'process_likes' | 'process_followers'; + data: Record; +} + +interface NotificationJobData { + type: 'new_follower' | 'new_like'; + data: Record; +} + +// Create Redis connection options +const redisOptions = { + host: config.bull.redis.host, + port: config.bull.redis.port, + password: config.bull.redis.password, +}; + +// Create queues with error handling +let analyticsQueue: Queue; +let notificationsQueue: Queue; + +try { + analyticsQueue = new Queue(QUEUE_NAMES.ANALYTICS, { + connection: redisOptions, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + delay: 1000, + }, + }, + }); + + notificationsQueue = new Queue(QUEUE_NAMES.NOTIFICATIONS, { + connection: redisOptions, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + delay: 1000, + }, + }, + }); +} catch (error) { + console.error('Error initializing BullMQ queues:', error); + // Create mock queues for development + analyticsQueue = { + add: async (name: string, data: AnalyticsJobData) => { + console.log(`Mock analytics job added: ${name}`, data); + return { id: 'mock-job-id' } as any; + }, + close: async () => console.log('Mock analytics queue closed'), + } as any; + + notificationsQueue = { + add: async (name: string, data: NotificationJobData) => { + console.log(`Mock notification job added: ${name}`, data); + return { id: 'mock-job-id' } as any; + }, + close: async () => console.log('Mock notifications queue closed'), + } as any; +} + +// Initialize workers +export const initWorkers = () => { + try { + // Analytics worker + const analyticsWorker = new Worker( + QUEUE_NAMES.ANALYTICS, + async (job: Job) => { + console.log(`Processing analytics job ${job.id}`); + const { type, data } = job.data; + + switch (type) { + case 'process_views': + // Process view analytics + console.log('Processing view analytics', data); + break; + case 'process_likes': + // Process like analytics + console.log('Processing like analytics', data); + break; + case 'process_followers': + // Process follower analytics + console.log('Processing follower analytics', data); + break; + default: + console.log(`Unknown analytics job type: ${type as string}`); + } + }, + { connection: redisOptions } + ); + + // Notifications worker + const notificationsWorker = new Worker( + QUEUE_NAMES.NOTIFICATIONS, + async (job: Job) => { + console.log(`Processing notification job ${job.id}`); + const { type, data } = job.data; + + switch (type) { + case 'new_follower': + // Send new follower notification + console.log('Sending new follower notification', data); + break; + case 'new_like': + // Send new like notification + console.log('Sending new like notification', data); + break; + default: + console.log(`Unknown notification job type: ${type as string}`); + } + }, + { connection: redisOptions } + ); + + // Handle worker events + analyticsWorker.on('completed', (job: Job) => { + console.log(`Analytics job ${job.id} completed`); + }); + + analyticsWorker.on('failed', (job: Job | undefined, err: Error) => { + console.error(`Analytics job ${job?.id} failed with error ${err.message}`); + }); + + notificationsWorker.on('completed', (job: Job) => { + console.log(`Notification job ${job.id} completed`); + }); + + notificationsWorker.on('failed', (job: Job | undefined, err: Error) => { + console.error(`Notification job ${job?.id} failed with error ${err.message}`); + }); + + return { + analyticsWorker, + notificationsWorker, + }; + } catch (error) { + console.error('Error initializing BullMQ workers:', error); + // Return mock workers + return { + analyticsWorker: { + close: async () => console.log('Mock analytics worker closed'), + }, + notificationsWorker: { + close: async () => console.log('Mock notifications worker closed'), + }, + }; + } +}; + +// Helper function to add jobs to queues +export const addAnalyticsJob = async ( + type: AnalyticsJobData['type'], + data: Record, + options = {} +) => { + try { + return await analyticsQueue.add(type, { type, data } as AnalyticsJobData, options); + } catch (error) { + console.error('Error adding analytics job:', error); + console.log('Job details:', { type, data }); + return null; + } +}; + +export const addNotificationJob = async ( + type: NotificationJobData['type'], + data: Record, + options = {} +) => { + try { + return await notificationsQueue.add(type, { type, data } as NotificationJobData, options); + } catch (error) { + console.error('Error adding notification job:', error); + console.log('Job details:', { type, data }); + return null; + } +}; \ No newline at end of file diff --git a/backend/src/utils/redis.ts b/backend/src/utils/redis.ts new file mode 100644 index 0000000..e87c701 --- /dev/null +++ b/backend/src/utils/redis.ts @@ -0,0 +1,80 @@ +import { createClient } from 'redis'; +import config from '../config'; + +// Create Redis client +const redisClient = createClient({ + url: `redis://${config.redis.password ? `${config.redis.password}@` : ''}${config.redis.host}:${config.redis.port}`, +}); + +// Handle Redis connection errors +redisClient.on('error', (err) => { + console.error('Redis Client Error:', err); +}); + +// Create a mock Redis client for development when real connection fails +const createMockRedisClient = () => { + const store = new Map(); + + return { + isOpen: true, + connect: async () => console.log('Mock Redis client connected'), + get: async (key: string) => store.get(key) || null, + set: async (key: string, value: string) => { + store.set(key, value); + return 'OK'; + }, + incr: async (key: string) => { + const current = parseInt(store.get(key) || '0', 10); + const newValue = current + 1; + store.set(key, newValue.toString()); + return newValue; + }, + decr: async (key: string) => { + const current = parseInt(store.get(key) || '0', 10); + const newValue = Math.max(0, current - 1); + store.set(key, newValue.toString()); + return newValue; + }, + quit: async () => console.log('Mock Redis client disconnected'), + }; +}; + +// Connect to Redis +let mockRedisClient: ReturnType | null = null; + +const connectRedis = async () => { + try { + if (!redisClient.isOpen) { + await redisClient.connect(); + console.log('Redis client connected'); + } + return redisClient; + } catch (error) { + console.error('Failed to connect to Redis:', error); + console.log('Using mock Redis client for development...'); + + if (!mockRedisClient) { + mockRedisClient = createMockRedisClient(); + } + + return mockRedisClient; + } +}; + +// Export the appropriate client +const getRedisClient = async () => { + try { + if (redisClient.isOpen) { + return redisClient; + } + return await connectRedis(); + } catch (error) { + if (!mockRedisClient) { + mockRedisClient = createMockRedisClient(); + } + return mockRedisClient; + } +}; + +export { redisClient, connectRedis, getRedisClient }; +export default redisClient; \ No newline at end of file diff --git a/backend/src/utils/supabase-comments-functions.sql b/backend/src/utils/supabase-comments-functions.sql new file mode 100644 index 0000000..27910a7 --- /dev/null +++ b/backend/src/utils/supabase-comments-functions.sql @@ -0,0 +1,97 @@ +-- 创建获取所有评论的函数 +CREATE +OR REPLACE FUNCTION public .get_comments_with_posts() RETURNS TABLE ( + comment_id UUID, + content TEXT, + sentiment_score FLOAT, + created_at TIMESTAMP WITH TIME ZONE, + updated_at TIMESTAMP WITH TIME ZONE, + post_id UUID, + user_id UUID, + user_profile JSONB, + post JSONB +) LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN + RETURN QUERY + SELECT + c .comment_id, + c .content, + c .sentiment_score, + c .created_at, + c .updated_at, + c .post_id, + c .user_id, + jsonb_build_object( + 'id', + up.id, + 'full_name', + up.full_name, + 'avatar_url', + up.avatar_url + ) AS user_profile, + jsonb_build_object( + 'post_id', + p.post_id, + 'title', + p.title, + 'description', + p.description, + 'platform', + p.platform, + 'post_url', + p.post_url, + 'published_at', + p.published_at, + 'influencer_id', + p.influencer_id + ) AS post + FROM + public .comments c + LEFT JOIN public .user_profiles up ON c .user_id = up.id + LEFT JOIN public .posts p ON c .post_id = p.post_id + ORDER BY + c .created_at DESC; + +END; + +$$; + +-- 创建获取特定帖子评论的函数 +CREATE +OR REPLACE FUNCTION public .get_comments_for_post(post_id_param UUID) RETURNS TABLE ( + comment_id UUID, + content TEXT, + sentiment_score FLOAT, + created_at TIMESTAMP WITH TIME ZONE, + updated_at TIMESTAMP WITH TIME ZONE, + post_id UUID, + user_id UUID, + user_profile JSONB +) LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN + RETURN QUERY + SELECT + c .comment_id, + c .content, + c .sentiment_score, + c .created_at, + c .updated_at, + c .post_id, + c .user_id, + jsonb_build_object( + 'id', + up.id, + 'full_name', + up.full_name, + 'avatar_url', + up.avatar_url + ) AS user_profile + FROM + public .comments c + LEFT JOIN public .user_profiles up ON c .user_id = up.id + WHERE + c .post_id = post_id_param + ORDER BY + c .created_at DESC; + +END; + +$$; \ No newline at end of file diff --git a/backend/src/utils/supabase-functions.sql b/backend/src/utils/supabase-functions.sql new file mode 100644 index 0000000..517e5db --- /dev/null +++ b/backend/src/utils/supabase-functions.sql @@ -0,0 +1,738 @@ +-- 为系统创建所需的存储过程和表 +-- 创建用户简档表的函数 +CREATE +OR REPLACE FUNCTION create_function_create_user_profiles_if_not_exists() RETURNS void LANGUAGE plpgsql AS $$ BEGIN + EXECUTE $FUNC$ CREATE + OR REPLACE FUNCTION create_user_profiles_if_not_exists() RETURNS void LANGUAGE plpgsql AS $INNER$ BEGIN + -- 检查用户简档表是否存在 + IF NOT EXISTS ( + SELECT + FROM + pg_tables + WHERE + schemaname = 'public' + AND tablename = 'user_profiles' + ) THEN -- 创建用户简档表 + CREATE TABLE public .user_profiles ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON + DELETE + CASCADE, + full_name TEXT, + avatar_url TEXT, + website TEXT, + company TEXT, + role TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() + ); + +-- 添加 RLS 策略 +ALTER TABLE + public .user_profiles ENABLE ROW LEVEL SECURITY; + +-- 创建只有自己可以更新自己简档的策略 +CREATE POLICY "Users can view all profiles" ON public .user_profiles FOR +SELECT + USING (true); + +CREATE POLICY "Users can update own profile" ON public .user_profiles FOR +UPDATE + USING (auth.uid() = id); + +CREATE POLICY "Users can insert own profile" ON public .user_profiles FOR +INSERT + WITH CHECK (auth.uid() = id); + +-- 创建新用户时自动创建简档的触发器 +CREATE +OR REPLACE FUNCTION public .handle_new_user() RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER AS $TRIGGER$ BEGIN + INSERT INTO + public .user_profiles (id, full_name, avatar_url) + VALUES + ( + NEW .id, + NEW .raw_user_meta_data ->> 'full_name', + NEW .raw_user_meta_data ->> 'avatar_url' + ); + +RETURN NEW; + +END; + +$TRIGGER$; + +-- 创建触发器 +DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; + +CREATE TRIGGER on_auth_user_created AFTER +INSERT + ON auth.users FOR EACH ROW EXECUTE FUNCTION public .handle_new_user(); + +RAISE NOTICE 'Created user_profiles table with RLS policies and triggers'; + +ELSE RAISE NOTICE 'user_profiles table already exists'; + +END IF; + +END; + +$INNER$; + +$FUNC$; + +RAISE NOTICE 'Created function create_user_profiles_if_not_exists()'; + +END; + +$$; + +-- 创建项目表的函数 +CREATE +OR REPLACE FUNCTION create_function_create_projects_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $$ BEGIN + EXECUTE $FUNC$ CREATE + OR REPLACE FUNCTION create_projects_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $INNER$ BEGIN + -- 检查项目表是否存在 + IF NOT EXISTS ( + SELECT + FROM + pg_tables + WHERE + schemaname = 'public' + AND tablename = 'projects' + ) THEN -- 创建项目表 + CREATE TABLE public .projects ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + description TEXT, + status TEXT DEFAULT 'active' CHECK (status IN ('active', 'archived', 'completed')), + start_date TIMESTAMP WITH TIME ZONE, + end_date TIMESTAMP WITH TIME ZONE, + created_by UUID REFERENCES auth.users(id) ON + DELETE + SET + NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() + ); + +-- 添加 RLS 策略 +ALTER TABLE + public .projects ENABLE ROW LEVEL SECURITY; + +-- 创建只有创建者可以管理项目的策略 +CREATE POLICY "Users can view own projects" ON public .projects FOR +SELECT + USING (auth.uid() = created_by); + +CREATE POLICY "Users can insert own projects" ON public .projects FOR +INSERT + WITH CHECK (auth.uid() = created_by); + +CREATE POLICY "Users can update own projects" ON public .projects FOR +UPDATE + USING (auth.uid() = created_by); + +CREATE POLICY "Users can delete own projects" ON public .projects FOR +DELETE + USING (auth.uid() = created_by); + +-- 创建更新时间的触发器 +CREATE +OR REPLACE FUNCTION public .update_project_updated_at() RETURNS TRIGGER LANGUAGE plpgsql AS $TRIGGER$ BEGIN + NEW .updated_at = now(); + +RETURN NEW; + +END; + +$TRIGGER$; + +-- 创建触发器 +DROP TRIGGER IF EXISTS on_project_updated ON public .projects; + +CREATE TRIGGER on_project_updated BEFORE +UPDATE + ON public .projects FOR EACH ROW EXECUTE FUNCTION public .update_project_updated_at(); + +RAISE NOTICE 'Created projects table with RLS policies and triggers'; + +ELSE RAISE NOTICE 'projects table already exists'; + +END IF; + +END; + +$INNER$; + +$FUNC$; + +RAISE NOTICE 'Created function create_projects_table_if_not_exists()'; + +END; + +$$; + +-- 创建网红(影响者)表的函数 +CREATE +OR REPLACE FUNCTION create_function_create_influencers_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $$ BEGIN + EXECUTE $FUNC$ CREATE + OR REPLACE FUNCTION create_influencers_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $INNER$ BEGIN + -- 检查网红表是否存在 + IF NOT EXISTS ( + SELECT + FROM + pg_tables + WHERE + schemaname = 'public' + AND tablename = 'influencers' + ) THEN -- 创建网红表 + CREATE TABLE public .influencers ( + influencer_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + platform TEXT CHECK ( + platform IN ( + 'youtube', + 'instagram', + 'tiktok', + 'twitter', + 'facebook' + ) + ), + profile_url TEXT, + external_id TEXT UNIQUE, + followers_count INT DEFAULT 0, + video_count INT DEFAULT 0, + platform_count INT DEFAULT 1, + created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() + ); + +-- 添加 RLS 策略 +ALTER TABLE + public .influencers ENABLE ROW LEVEL SECURITY; + +-- 创建所有认证用户可以查看网红的策略 +CREATE POLICY "Authenticated users can view influencers" ON public .influencers FOR +SELECT + USING (auth.role() = 'authenticated'); + +-- 创建有权限的用户可以更新网红的策略 +CREATE POLICY "Authenticated users can insert influencers" ON public .influencers FOR +INSERT + WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update influencers" ON public .influencers FOR +UPDATE + USING (auth.role() = 'authenticated'); + +-- 创建更新时间的触发器 +CREATE +OR REPLACE FUNCTION public .update_influencer_updated_at() RETURNS TRIGGER LANGUAGE plpgsql AS $TRIGGER$ BEGIN + NEW .updated_at = now(); + +RETURN NEW; + +END; + +$TRIGGER$; + +-- 创建触发器 +DROP TRIGGER IF EXISTS on_influencer_updated ON public .influencers; + +CREATE TRIGGER on_influencer_updated BEFORE +UPDATE + ON public .influencers FOR EACH ROW EXECUTE FUNCTION public .update_influencer_updated_at(); + +RAISE NOTICE 'Created influencers table with RLS policies and triggers'; + +ELSE RAISE NOTICE 'influencers table already exists'; + +END IF; + +END; + +$INNER$; + +$FUNC$; + +RAISE NOTICE 'Created function create_influencers_table_if_not_exists()'; + +END; + +$$; + +-- 创建项目-网红关联表的函数 +CREATE +OR REPLACE FUNCTION create_function_create_project_influencers_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $$ BEGIN + EXECUTE $FUNC$ CREATE + OR REPLACE FUNCTION create_project_influencers_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $INNER$ BEGIN + -- 检查项目-网红关联表是否存在 + IF NOT EXISTS ( + SELECT + FROM + pg_tables + WHERE + schemaname = 'public' + AND tablename = 'project_influencers' + ) THEN -- 创建项目-网红关联表 + CREATE TABLE public .project_influencers ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + project_id UUID REFERENCES public .projects(id) ON + DELETE + CASCADE, + influencer_id UUID REFERENCES public .influencers(influencer_id) ON + DELETE + CASCADE, + status TEXT DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'completed')), + created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + UNIQUE (project_id, influencer_id) + ); + +-- 添加索引 +CREATE INDEX idx_project_influencers_project_id ON public .project_influencers(project_id); + +CREATE INDEX idx_project_influencers_influencer_id ON public .project_influencers(influencer_id); + +-- 添加 RLS 策略 +ALTER TABLE + public .project_influencers ENABLE ROW LEVEL SECURITY; + +-- 创建只有项目创建者可以管理项目-网红关联的策略 +CREATE POLICY "Users can view project influencers" ON public .project_influencers FOR +SELECT + USING ( + EXISTS ( + SELECT + 1 + FROM + public .projects + WHERE + id = project_id + AND created_by = auth.uid() + ) + ); + +CREATE POLICY "Users can insert project influencers" ON public .project_influencers FOR +INSERT + WITH CHECK ( + EXISTS ( + SELECT + 1 + FROM + public .projects + WHERE + id = project_id + AND created_by = auth.uid() + ) + ); + +CREATE POLICY "Users can update project influencers" ON public .project_influencers FOR +UPDATE + USING ( + EXISTS ( + SELECT + 1 + FROM + public .projects + WHERE + id = project_id + AND created_by = auth.uid() + ) + ); + +CREATE POLICY "Users can delete project influencers" ON public .project_influencers FOR +DELETE + USING ( + EXISTS ( + SELECT + 1 + FROM + public .projects + WHERE + id = project_id + AND created_by = auth.uid() + ) + ); + +-- 创建更新时间的触发器 +CREATE +OR REPLACE FUNCTION public .update_project_influencer_updated_at() RETURNS TRIGGER LANGUAGE plpgsql AS $TRIGGER$ BEGIN + NEW .updated_at = now(); + +RETURN NEW; + +END; + +$TRIGGER$; + +-- 创建触发器 +DROP TRIGGER IF EXISTS on_project_influencer_updated ON public .project_influencers; + +CREATE TRIGGER on_project_influencer_updated BEFORE +UPDATE + ON public .project_influencers FOR EACH ROW EXECUTE FUNCTION public .update_project_influencer_updated_at(); + +RAISE NOTICE 'Created project_influencers table with RLS policies and triggers'; + +ELSE RAISE NOTICE 'project_influencers table already exists'; + +END IF; + +END; + +$INNER$; + +$FUNC$; + +RAISE NOTICE 'Created function create_project_influencers_table_if_not_exists()'; + +END; + +$$; + +-- 创建帖子表的函数 +CREATE +OR REPLACE FUNCTION create_function_create_posts_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $$ BEGIN + EXECUTE $FUNC$ CREATE + OR REPLACE FUNCTION create_posts_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $INNER$ BEGIN + -- 检查帖子表是否存在 + IF NOT EXISTS ( + SELECT + FROM + pg_tables + WHERE + schemaname = 'public' + AND tablename = 'posts' + ) THEN -- 创建帖子表 + CREATE TABLE public .posts ( + post_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + influencer_id UUID REFERENCES public .influencers(influencer_id) ON + DELETE + CASCADE, + platform TEXT CHECK ( + platform IN ( + 'youtube', + 'instagram', + 'tiktok', + 'twitter', + 'facebook' + ) + ), + post_url TEXT UNIQUE NOT NULL, + title TEXT, + description TEXT, + published_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() + ); + +-- 添加索引 +CREATE INDEX idx_posts_influencer_id ON public .posts(influencer_id); + +CREATE INDEX idx_posts_platform ON public .posts(platform); + +CREATE INDEX idx_posts_published_at ON public .posts(published_at); + +-- 添加 RLS 策略 +ALTER TABLE + public .posts ENABLE ROW LEVEL SECURITY; + +-- 创建所有认证用户可以查看帖子的策略 +CREATE POLICY "Authenticated users can view posts" ON public .posts FOR +SELECT + USING (auth.role() = 'authenticated'); + +-- 创建有权限的用户可以更新帖子的策略 +CREATE POLICY "Authenticated users can insert posts" ON public .posts FOR +INSERT + WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update posts" ON public .posts FOR +UPDATE + USING (auth.role() = 'authenticated'); + +-- 创建更新时间的触发器 +CREATE +OR REPLACE FUNCTION public .update_post_updated_at() RETURNS TRIGGER LANGUAGE plpgsql AS $TRIGGER$ BEGIN + NEW .updated_at = now(); + +RETURN NEW; + +END; + +$TRIGGER$; + +-- 创建触发器 +DROP TRIGGER IF EXISTS on_post_updated ON public .posts; + +CREATE TRIGGER on_post_updated BEFORE +UPDATE + ON public .posts FOR EACH ROW EXECUTE FUNCTION public .update_post_updated_at(); + +RAISE NOTICE 'Created posts table with RLS policies and triggers'; + +ELSE RAISE NOTICE 'posts table already exists'; + +END IF; + +END; + +$INNER$; + +$FUNC$; + +RAISE NOTICE 'Created function create_posts_table_if_not_exists()'; + +END; + +$$; + +-- 创建评论表的函数 +CREATE +OR REPLACE FUNCTION create_function_create_comments_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $$ BEGIN + EXECUTE $FUNC$ CREATE + OR REPLACE FUNCTION create_comments_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $INNER$ BEGIN + -- 检查评论表是否存在 + IF NOT EXISTS ( + SELECT + FROM + pg_tables + WHERE + schemaname = 'public' + AND tablename = 'comments' + ) THEN -- 创建评论表 + CREATE TABLE public .comments ( + comment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + post_id UUID REFERENCES public .posts(post_id) ON + DELETE + CASCADE, + user_id UUID REFERENCES auth.users(id) ON + DELETE + SET + NULL, + content TEXT NOT NULL, + sentiment_score FLOAT, + status TEXT DEFAULT 'approved' CHECK (status IN ('approved', 'pending', 'rejected')), + created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() + ); + +-- 添加索引 +CREATE INDEX idx_comments_post_id ON public .comments(post_id); + +CREATE INDEX idx_comments_user_id ON public .comments(user_id); + +CREATE INDEX idx_comments_created_at ON public .comments(created_at); + +-- 添加 RLS 策略 +ALTER TABLE + public .comments ENABLE ROW LEVEL SECURITY; + +-- 创建所有认证用户可以查看评论的策略 +CREATE POLICY "Authenticated users can view comments" ON public .comments FOR +SELECT + USING (auth.role() = 'authenticated'); + +-- 创建有权限的用户可以添加评论的策略 +CREATE POLICY "Authenticated users can insert comments" ON public .comments FOR +INSERT + WITH CHECK (auth.role() = 'authenticated'); + +-- 创建只有评论作者可以更新评论的策略 +CREATE POLICY "Users can update own comments" ON public .comments FOR +UPDATE + USING (auth.uid() = user_id); + +-- 创建只有评论作者可以删除评论的策略 +CREATE POLICY "Users can delete own comments" ON public .comments FOR +DELETE + USING (auth.uid() = user_id); + +-- 创建更新时间的触发器 +CREATE +OR REPLACE FUNCTION public .update_comment_updated_at() RETURNS TRIGGER LANGUAGE plpgsql AS $TRIGGER$ BEGIN + NEW .updated_at = now(); + +RETURN NEW; + +END; + +$TRIGGER$; + +-- 创建触发器 +DROP TRIGGER IF EXISTS on_comment_updated ON public .comments; + +CREATE TRIGGER on_comment_updated BEFORE +UPDATE + ON public .comments FOR EACH ROW EXECUTE FUNCTION public .update_comment_updated_at(); + +RAISE NOTICE 'Created comments table with RLS policies and triggers'; + +ELSE RAISE NOTICE 'comments table already exists'; + +END IF; + +END; + +$INNER$; + +$FUNC$; + +RAISE NOTICE 'Created function create_comments_table_if_not_exists()'; + +END; + +$$; + +-- 创建项目评论表的函数 +CREATE +OR REPLACE FUNCTION create_function_create_project_comments_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $$ BEGIN + EXECUTE $FUNC$ CREATE + OR REPLACE FUNCTION create_project_comments_table_if_not_exists() RETURNS void LANGUAGE plpgsql AS $INNER$ BEGIN + -- 检查项目评论表是否存在 + IF NOT EXISTS ( + SELECT + FROM + pg_tables + WHERE + schemaname = 'public' + AND tablename = 'project_comments' + ) THEN -- 创建项目评论表 + CREATE TABLE public .project_comments ( + comment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + project_id UUID REFERENCES public .projects(id) ON + DELETE + CASCADE, + user_id UUID REFERENCES auth.users(id) ON + DELETE + SET + NULL, + content TEXT NOT NULL, + sentiment_score FLOAT, + status TEXT DEFAULT 'approved' CHECK (status IN ('approved', 'pending', 'rejected')), + is_pinned BOOLEAN DEFAULT false, + parent_id UUID REFERENCES public .project_comments(comment_id) ON + DELETE + SET + NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() + ); + +-- 添加索引 +CREATE INDEX idx_project_comments_project_id ON public .project_comments(project_id); + +CREATE INDEX idx_project_comments_user_id ON public .project_comments(user_id); + +CREATE INDEX idx_project_comments_parent_id ON public .project_comments(parent_id); + +CREATE INDEX idx_project_comments_created_at ON public .project_comments(created_at); + +-- 添加 RLS 策略 +ALTER TABLE + public .project_comments ENABLE ROW LEVEL SECURITY; + +-- 创建项目评论可见性策略 +CREATE POLICY "Project members can view project comments" ON public .project_comments FOR +SELECT + USING ( + EXISTS ( + SELECT + 1 + FROM + public .projects + WHERE + id = project_id + AND ( + created_by = auth.uid() + OR EXISTS ( + SELECT + 1 + FROM + public .project_influencers pi + JOIN public .influencers i ON pi.influencer_id = i.influencer_id + WHERE + pi.project_id = project_id + ) + ) + ) + ); + +-- 创建认证用户可以添加评论的策略 +CREATE POLICY "Authenticated users can insert project comments" ON public .project_comments FOR +INSERT + WITH CHECK (auth.role() = 'authenticated'); + +-- 创建只有评论作者可以更新评论的策略 +CREATE POLICY "Users can update own project comments" ON public .project_comments FOR +UPDATE + USING (auth.uid() = user_id); + +-- 创建项目所有者和评论作者可以删除评论的策略 +CREATE POLICY "Project owner and comment creator can delete project comments" ON public .project_comments FOR +DELETE + USING ( + auth.uid() = user_id + OR EXISTS ( + SELECT + 1 + FROM + public .projects + WHERE + id = project_id + AND created_by = auth.uid() + ) + ); + +-- 创建更新时间的触发器 +CREATE +OR REPLACE FUNCTION public .update_project_comment_updated_at() RETURNS TRIGGER LANGUAGE plpgsql AS $TRIGGER$ BEGIN + NEW .updated_at = now(); + +RETURN NEW; + +END; + +$TRIGGER$; + +-- 创建触发器 +DROP TRIGGER IF EXISTS on_project_comment_updated ON public .project_comments; + +CREATE TRIGGER on_project_comment_updated BEFORE +UPDATE + ON public .project_comments FOR EACH ROW EXECUTE FUNCTION public .update_project_comment_updated_at(); + +-- 创建获取评论回复数量的函数 +CREATE +OR REPLACE FUNCTION public .get_reply_counts_for_comments(parent_ids UUID [ ]) RETURNS TABLE (parent_id UUID, count BIGINT) LANGUAGE sql SECURITY DEFINER AS $$ +SELECT + parent_id, + COUNT(*) as count +FROM + public .project_comments +WHERE + parent_id IS NOT NULL + AND parent_id = ANY(parent_ids) +GROUP BY + parent_id; + +$$; + +RAISE NOTICE 'Created project_comments table with RLS policies and triggers'; + +ELSE RAISE NOTICE 'project_comments table already exists'; + +END IF; + +END; + +$INNER$; + +$FUNC$; + +RAISE NOTICE 'Created function create_project_comments_table_if_not_exists()'; + +END; + +$$; \ No newline at end of file diff --git a/backend/src/utils/supabase.ts b/backend/src/utils/supabase.ts new file mode 100644 index 0000000..6445cf5 --- /dev/null +++ b/backend/src/utils/supabase.ts @@ -0,0 +1,19 @@ +import { createClient } from '@supabase/supabase-js'; +import config from '../config'; + +// Validate Supabase URL +const validateSupabaseUrl = (url: string): string => { + if (!url || !url.startsWith('http')) { + console.warn('Invalid Supabase URL provided. Using a placeholder for development.'); + return 'https://example.supabase.co'; + } + return url; +}; + +// Create a single supabase client for interacting with your database +const supabase = createClient( + validateSupabaseUrl(config.supabase.url), + config.supabase.key || 'dummy-key' +); + +export default supabase; \ No newline at end of file diff --git a/backend/start-server.sh b/backend/start-server.sh new file mode 100755 index 0000000..7f57204 --- /dev/null +++ b/backend/start-server.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# 获取配置的端口,默认为4000 +PORT=$(grep "PORT=" .env | cut -d= -f2 || echo "4000") +echo "Service configured to use port $PORT" + +# 查找并停止使用该端口的进程 +PID=$(lsof -ti :$PORT) +if [ ! -z "$PID" ]; then + echo "Stopping process $PID using port $PORT..." + kill -9 $PID + sleep 1 +fi + +# 停止可能正在运行的服务器 +echo "Stopping any running server..." +pkill -f "node dist/index.js" || true +pkill -f "tsx watch src/index.ts" || true + +# 构建项目 +echo "Building project..." +npm run build + +# 启动服务器 +echo "Starting server..." +npm start \ No newline at end of file diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..935398c --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "outDir": "dist", + "rootDir": "src", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + }, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/docker/clickhouse/docker-compose.yml b/docker/clickhouse/docker-compose.yml new file mode 100644 index 0000000..4329d8a --- /dev/null +++ b/docker/clickhouse/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3.7' + +services: + clickhouse: + image: clickhouse/clickhouse-server:latest + container_name: clickhouse-server + restart: always + ports: + - "8123:8123" # HTTP 接口(DBeaver/HTTP 客户端使用) + - "9000:9000" # 原生 TCP 协议(clickhouse-client 使用) + - "9004:9004" # MySQL 协议(可选) + volumes: + - ./data:/var/lib/clickhouse # 数据持久化 + - /etc/localtime:/etc/localtime:ro # 同步时区[8](@ref) + environment: + - CLICKHOUSE_USER=admin + - CLICKHOUSE_PASSWORD=your_secure_password + - TZ=Asia/Shanghai # 时区设置 + ulimits: + nofile: + soft: 262144 + hard: 262144 # 高并发连接优化[3,6](@ref) + networks: + - clickhouse-net + +networks: + clickhouse-net: + driver: bridge \ No newline at end of file diff --git a/extension/.bolt/config.json b/extension/.bolt/config.json new file mode 100644 index 0000000..6b6787d --- /dev/null +++ b/extension/.bolt/config.json @@ -0,0 +1,3 @@ +{ + "template": "bolt-vite-react-ts" +} diff --git a/extension/.bolt/prompt b/extension/.bolt/prompt new file mode 100644 index 0000000..d0c0a8f --- /dev/null +++ b/extension/.bolt/prompt @@ -0,0 +1,8 @@ +For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production. + +By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them. + +Use icons from lucide-react for logos. + +Use stock photos from unsplash where appropriate, only valid URLs you know exist. Do not download the images, only link to them in image tags. + diff --git a/extension/.gitignore b/extension/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/extension/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/extension/background.js b/extension/background.js new file mode 100644 index 0000000..b25df12 --- /dev/null +++ b/extension/background.js @@ -0,0 +1,7 @@ +// Open the side panel when the extension icon is clicked +chrome.action.onClicked.addListener((tab) => { + chrome.sidePanel.open({ tabId: tab.id }); +}); + +// Set the side panel as open by default for all pages +chrome.sidePanel.setOptions({ enabled: true }); \ No newline at end of file diff --git a/extension/content.js b/extension/content.js new file mode 100644 index 0000000..eb7fdcb --- /dev/null +++ b/extension/content.js @@ -0,0 +1,243 @@ +// Function to extract comments from the page +function extractComments() { + const comments = []; + let platform = detectPlatform(); + + // Different extraction strategies based on the platform + if (platform === 'facebook') { + extractFacebookComments(comments); + } else if (platform === 'youtube') { + extractYoutubeComments(comments); + } else if (platform === 'twitter') { + extractTwitterComments(comments); + } else if (platform === 'instagram') { + extractInstagramComments(comments); + } else if (platform === 'linkedin') { + extractLinkedinComments(comments); + } else { + // Generic extraction for other platforms + extractGenericComments(comments); + } + + return comments; +} + +// Detect the current platform +function detectPlatform() { + const url = window.location.hostname; + + if (url.includes('facebook.com')) return 'facebook'; + if (url.includes('youtube.com')) return 'youtube'; + if (url.includes('twitter.com') || url.includes('x.com')) return 'twitter'; + if (url.includes('instagram.com')) return 'instagram'; + if (url.includes('linkedin.com')) return 'linkedin'; + + return 'other'; +} + +// Platform-specific extraction functions +function extractFacebookComments(comments) { + // Facebook comment selectors + const commentElements = document.querySelectorAll('[aria-label="Comment"]'); + + commentElements.forEach((element, index) => { + try { + const authorElement = element.querySelector('a'); + const contentElement = element.querySelector('[data-ad-comet-preview="message"]'); + const timestampElement = element.querySelector('a[href*="comment_id"]'); + const likesElement = element.querySelector('[aria-label*="reactions"]'); + + if (contentElement) { + comments.push({ + id: `fb-comment-${index}`, + author: authorElement ? authorElement.textContent : 'Facebook User', + content: contentElement.textContent, + timestamp: timestampElement ? timestampElement.textContent : 'Recently', + likes: likesElement ? parseInt(likesElement.textContent) || 0 : 0, + platform: 'facebook' + }); + } + } catch (error) { + console.error('Error extracting Facebook comment:', error); + } + }); +} + +function extractYoutubeComments(comments) { + // YouTube comment selectors + const commentElements = document.querySelectorAll('ytd-comment-thread-renderer'); + + commentElements.forEach((element, index) => { + try { + const authorElement = element.querySelector('#author-text'); + const contentElement = element.querySelector('#content-text'); + const timestampElement = element.querySelector('.published-time-text'); + const likesElement = element.querySelector('#vote-count-middle'); + + if (contentElement) { + comments.push({ + id: `yt-comment-${index}`, + author: authorElement ? authorElement.textContent.trim() : 'YouTube User', + content: contentElement.textContent.trim(), + timestamp: timestampElement ? timestampElement.textContent.trim() : 'Recently', + likes: likesElement ? parseInt(likesElement.textContent) || 0 : 0, + platform: 'youtube' + }); + } + } catch (error) { + console.error('Error extracting YouTube comment:', error); + } + }); +} + +function extractTwitterComments(comments) { + // Twitter/X comment selectors + const commentElements = document.querySelectorAll('[data-testid="tweet"]'); + + commentElements.forEach((element, index) => { + try { + const authorElement = element.querySelector('[data-testid="User-Name"]'); + const contentElement = element.querySelector('[data-testid="tweetText"]'); + const timestampElement = element.querySelector('time'); + const likesElement = element.querySelector('[data-testid="like"]'); + + if (contentElement) { + comments.push({ + id: `twitter-comment-${index}`, + author: authorElement ? authorElement.textContent.split('·')[0].trim() : 'Twitter User', + content: contentElement.textContent.trim(), + timestamp: timestampElement ? timestampElement.getAttribute('datetime') : 'Recently', + likes: likesElement ? parseInt(likesElement.textContent) || 0 : 0, + platform: 'twitter' + }); + } + } catch (error) { + console.error('Error extracting Twitter comment:', error); + } + }); +} + +function extractInstagramComments(comments) { + // Instagram comment selectors + const commentElements = document.querySelectorAll('ul > li > div > div > div:nth-child(2)'); + + commentElements.forEach((element, index) => { + try { + const authorElement = element.querySelector('h3'); + const contentElement = element.querySelector('span'); + + if (contentElement && authorElement) { + comments.push({ + id: `ig-comment-${index}`, + author: authorElement.textContent.trim(), + content: contentElement.textContent.trim(), + timestamp: 'Recently', // Instagram doesn't easily show timestamps + likes: 0, // Instagram doesn't easily show like counts + platform: 'instagram' + }); + } + } catch (error) { + console.error('Error extracting Instagram comment:', error); + } + }); +} + +function extractLinkedinComments(comments) { + // LinkedIn comment selectors + const commentElements = document.querySelectorAll('.comments-comment-item'); + + commentElements.forEach((element, index) => { + try { + const authorElement = element.querySelector('.comments-post-meta__name-text'); + const contentElement = element.querySelector('.comments-comment-item__main-content'); + const timestampElement = element.querySelector('.comments-comment-item__timestamp'); + + if (contentElement) { + comments.push({ + id: `linkedin-comment-${index}`, + author: authorElement ? authorElement.textContent.trim() : 'LinkedIn User', + content: contentElement.textContent.trim(), + timestamp: timestampElement ? timestampElement.textContent.trim() : 'Recently', + likes: 0, // LinkedIn doesn't easily show like counts + platform: 'linkedin' + }); + } + } catch (error) { + console.error('Error extracting LinkedIn comment:', error); + } + }); +} + +function extractGenericComments(comments) { + // Generic comment selectors that might work across different platforms + const possibleCommentSelectors = [ + '.comment', + '[class*="comment"]', + '[id*="comment"]', + '.review', + '[class*="review"]', + '[class*="post"]', + '[class*="message"]' + ]; + + for (const selector of possibleCommentSelectors) { + const elements = document.querySelectorAll(selector); + + if (elements.length > 0) { + elements.forEach((element, index) => { + // Try to find text content that looks like a comment + const textContent = element.textContent.trim(); + + if (textContent.length > 10 && textContent.length < 1000) { + comments.push({ + id: `generic-comment-${index}`, + author: 'User', + content: textContent, + timestamp: 'Recently', + likes: 0, + platform: 'other' + }); + } + }); + + // If we found comments with this selector, no need to try others + if (comments.length > 0) break; + } + } +} + +// Listen for messages from the sidebar +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'GET_COMMENTS') { + const comments = extractComments(); + + // Limit the number of comments based on settings + chrome.storage.sync.get(['maxComments'], (result) => { + const maxComments = result.maxComments || 50; + const limitedComments = comments.slice(0, maxComments); + + // Send the comments back to the sidebar + chrome.runtime.sendMessage({ + type: 'COMMENTS_CAPTURED', + comments: limitedComments + }); + }); + } + + return true; +}); + +// Initial extraction when the content script loads +setTimeout(() => { + const comments = extractComments(); + + chrome.storage.sync.get(['maxComments'], (result) => { + const maxComments = result.maxComments || 50; + const limitedComments = comments.slice(0, maxComments); + + chrome.runtime.sendMessage({ + type: 'COMMENTS_CAPTURED', + comments: limitedComments + }); + }); +}, 1000); \ No newline at end of file diff --git a/extension/eslint.config.js b/extension/eslint.config.js new file mode 100644 index 0000000..82c2e20 --- /dev/null +++ b/extension/eslint.config.js @@ -0,0 +1,28 @@ +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 }, + ], + }, + } +); diff --git a/extension/icons/icon128.png b/extension/icons/icon128.png new file mode 100644 index 0000000..c201946 --- /dev/null +++ b/extension/icons/icon128.png @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extension/icons/icon16.png b/extension/icons/icon16.png new file mode 100644 index 0000000..c201946 --- /dev/null +++ b/extension/icons/icon16.png @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extension/icons/icon48.png b/extension/icons/icon48.png new file mode 100644 index 0000000..c201946 --- /dev/null +++ b/extension/icons/icon48.png @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extension/index.html b/extension/index.html new file mode 100644 index 0000000..e4b78ea --- /dev/null +++ b/extension/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/extension/manifest.json b/extension/manifest.json new file mode 100644 index 0000000..075651e --- /dev/null +++ b/extension/manifest.json @@ -0,0 +1,41 @@ +{ + "manifest_version": 3, + "name": "Social Media Comment Assistant", + "version": "1.0.0", + "description": "A sidebar extension that captures comments, analyzes them, and suggests replies", + "action": { + "default_title": "Comment Assistant", + "default_icon": { + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + } + }, + "icons": { + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + }, + "permissions": [ + "activeTab", + "scripting", + "storage", + "sidePanel" + ], + "host_permissions": [ + "" + ], + "side_panel": { + "default_path": "sidebar.html" + }, + "background": { + "service_worker": "background.js", + "type": "module" + }, + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ] +} \ No newline at end of file diff --git a/extension/package-lock.json b/extension/package-lock.json new file mode 100644 index 0000000..c9c735b --- /dev/null +++ b/extension/package-lock.json @@ -0,0 +1,4517 @@ +{ + "name": "social-media-comment-assistant", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "social-media-comment-assistant", + "version": "1.0.0", + "dependencies": { + "lucide-react": "^0.344.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@crxjs/vite-plugin": "^2.0.0-beta.23", + "@eslint/js": "^9.9.1", + "@types/chrome": "^0.0.260", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.18", + "eslint": "^9.9.1", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.11", + "globals": "^15.9.0", + "postcss": "^8.4.35", + "tailwindcss": "^3.4.1", + "typescript": "^5.5.3", + "typescript-eslint": "^8.3.0", + "vite": "^5.4.2" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.25.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.7.tgz", + "integrity": "sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.7.tgz", + "integrity": "sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helpers": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7", + "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.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", + "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", + "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.7.tgz", + "integrity": "sha512-JD9MUnLbPL0WdVK8AWC7F7tTG2OS6u/AKKnsK+NdRhUiVdnzyR1S3kKQCaRLOiaULvUiqK6Z4JQE635VgtCFeg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.7.tgz", + "integrity": "sha512-S/JXG/KrbIY06iyJPKfxr0qRxnhNOdkNXYBl/rmwgDd72cQLH9tEGkDm/yJPGvcSIUoikzfjMios9i+xT/uv9w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", + "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@crxjs/vite-plugin": { + "version": "2.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@crxjs/vite-plugin/-/vite-plugin-2.0.0-beta.32.tgz", + "integrity": "sha512-FnEZFrmi4zWG+qPzz658riLIN6TTSOq/M8uEBcENUKoV/UOVUaPrTBjN3aTNOd+tMB7PlqYknKHZYddz/plRdQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^4.1.2", + "@webcomponents/custom-elements": "^1.5.0", + "acorn-walk": "^8.2.0", + "cheerio": "^1.0.0-rc.10", + "convert-source-map": "^1.7.0", + "debug": "^4.3.3", + "es-module-lexer": "^0.10.0", + "fast-glob": "^3.2.11", + "fs-extra": "^10.0.1", + "jsesc": "^3.0.2", + "magic-string": "^0.30.12", + "picocolors": "^1.0.0", + "react-refresh": "^0.13.0", + "rollup": "2.79.2", + "rxjs": "7.5.7" + } + }, + "node_modules/@crxjs/vite-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/@crxjs/vite-plugin/node_modules/react-refresh": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.13.0.tgz", + "integrity": "sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@crxjs/vite-plugin/node_modules/rollup": { + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", + "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", + "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", + "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", + "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", + "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.0", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "dev": true, + "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, + "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.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "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, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/chrome": { + "version": "0.0.260", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.260.tgz", + "integrity": "sha512-lX6QpgfsZRTDpNcCJ+3vzfFnFXq9bScFRTlfhbK5oecSAjamsno+ejFTCbNtc5O/TPnVK9Tja/PyecvWQe0F2w==", + "dev": true, + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/filesystem": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", + "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", + "dev": true, + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", + "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", + "dev": true + }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", + "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", + "integrity": "sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/type-utils": "8.8.1", + "@typescript-eslint/utils": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", + "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", + "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.1.tgz", + "integrity": "sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/utils": "8.8.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", + "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", + "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.1.tgz", + "integrity": "sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", + "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz", + "integrity": "sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/@webcomponents/custom-elements": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@webcomponents/custom-elements/-/custom-elements-1.6.0.tgz", + "integrity": "sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "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" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001667", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", + "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", + "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" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "dev": true, + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "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 + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.33", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.33.tgz", + "integrity": "sha512-+cYTcFB1QqD4j4LegwLfpCNxifb6dDFUAwk6RsLusCwIaZI6or2f+q8rs5tTB2YC53HhOlIbEaqHMAAC8IOIwA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.10.5.tgz", + "integrity": "sha512-+7IwY/kiGAacQfY+YBhKMvEmyAJnw5grTUgjG85Pe7vcUI/6b7pZjZG8nQ7+48YhzEAEqrEgD2dCz/JIK+AYvw==", + "dev": true + }, + "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, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", + "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.6.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.12.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.1.0-rc-fb9a90fa48-20240614", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-fb9a90fa48-20240614.tgz", + "integrity": "sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.12.tgz", + "integrity": "sha512-9neVjoGv20FwYtCP6CB1dzR1vr57ZDNOXst21wd2xJ/cTlM2xLq0GWVlSNTdMn/4BtP6cHYBMCSp1wFBJ9jBsg==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "dev": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "15.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", + "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "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==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "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, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "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==", + "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, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.344.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.344.0.tgz", + "integrity": "sha512-6YyBnn91GB45VuVT96bYCOKElbJzUHqp65vX8cDcu55MQL9T969v4dhGClpljamuI/+KMO9P6w9Acq1CVQGvIQ==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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 + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "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==", + "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==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", + "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", + "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.8.1.tgz", + "integrity": "sha512-R0dsXFt6t4SAFjUSKFjMh4pXDtq04SsFKCVGDP3ZOzNP7itF0jBcZYU4fMsZr4y7O7V7Nc751dDeESbe4PbQMQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.8.1", + "@typescript-eslint/parser": "8.8.1", + "@typescript-eslint/utils": "8.8.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/undici": { + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", + "dev": true, + "engines": { + "node": ">=18.17" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "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" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "dev": true, + "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/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/extension/package.json b/extension/package.json new file mode 100644 index 0000000..d51dfdc --- /dev/null +++ b/extension/package.json @@ -0,0 +1,36 @@ +{ + "name": "social-media-comment-assistant", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "lucide-react": "^0.344.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@crxjs/vite-plugin": "^2.0.0-beta.23", + "@eslint/js": "^9.9.1", + "@types/chrome": "^0.0.260", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.18", + "eslint": "^9.9.1", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.11", + "globals": "^15.9.0", + "postcss": "^8.4.35", + "tailwindcss": "^3.4.1", + "typescript": "^5.5.3", + "typescript-eslint": "^8.3.0", + "vite": "^5.4.2" + }, + "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" +} diff --git a/extension/pnpm-lock.yaml b/extension/pnpm-lock.yaml new file mode 100644 index 0000000..e713d96 --- /dev/null +++ b/extension/pnpm-lock.yaml @@ -0,0 +1,2887 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + lucide-react: + specifier: ^0.344.0 + version: 0.344.0(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@crxjs/vite-plugin': + specifier: ^2.0.0-beta.23 + version: 2.0.0-beta.32 + '@eslint/js': + specifier: ^9.9.1 + version: 9.21.0 + '@types/chrome': + specifier: ^0.0.260 + version: 0.0.260 + '@types/react': + specifier: ^18.3.5 + version: 18.3.18 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.5(@types/react@18.3.18) + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.3.4(vite@5.4.14) + autoprefixer: + specifier: ^10.4.18 + version: 10.4.20(postcss@8.5.3) + eslint: + specifier: ^9.9.1 + version: 9.21.0(jiti@1.21.7) + eslint-plugin-react-hooks: + specifier: ^5.1.0-rc.0 + version: 5.2.0(eslint@9.21.0(jiti@1.21.7)) + eslint-plugin-react-refresh: + specifier: ^0.4.11 + version: 0.4.19(eslint@9.21.0(jiti@1.21.7)) + globals: + specifier: ^15.9.0 + version: 15.15.0 + postcss: + specifier: ^8.4.35 + version: 8.5.3 + tailwindcss: + specifier: ^3.4.1 + version: 3.4.17 + typescript: + specifier: ^5.5.3 + version: 5.8.2 + typescript-eslint: + specifier: ^8.3.0 + version: 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + vite: + specifier: ^5.4.2 + version: 5.4.14 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.9': + resolution: {integrity: sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.9': + resolution: {integrity: sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.26.5': + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.9': + resolution: {integrity: sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.9': + resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.26.9': + resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.26.9': + resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.9': + resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} + engines: {node: '>=6.9.0'} + + '@crxjs/vite-plugin@2.0.0-beta.32': + resolution: {integrity: sha512-FnEZFrmi4zWG+qPzz658riLIN6TTSOq/M8uEBcENUKoV/UOVUaPrTBjN3aTNOd+tMB7PlqYknKHZYddz/plRdQ==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.19.2': + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.12.0': + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.0': + resolution: {integrity: sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.21.0': + resolution: {integrity: sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.7': + resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + engines: {node: '>=18.18'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + + '@rollup/rollup-android-arm-eabi@4.34.9': + resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.34.9': + resolution: {integrity: sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.34.9': + resolution: {integrity: sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.34.9': + resolution: {integrity: sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.34.9': + resolution: {integrity: sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.34.9': + resolution: {integrity: sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': + resolution: {integrity: sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.34.9': + resolution: {integrity: sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.34.9': + resolution: {integrity: sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.34.9': + resolution: {integrity: sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': + resolution: {integrity: sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': + resolution: {integrity: sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.34.9': + resolution: {integrity: sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-s390x-gnu@4.34.9': + resolution: {integrity: sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.34.9': + resolution: {integrity: sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.34.9': + resolution: {integrity: sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-win32-arm64-msvc@4.34.9': + resolution: {integrity: sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.34.9': + resolution: {integrity: sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.34.9': + resolution: {integrity: sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==} + cpu: [x64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/chrome@0.0.260': + resolution: {integrity: sha512-lX6QpgfsZRTDpNcCJ+3vzfFnFXq9bScFRTlfhbK5oecSAjamsno+ejFTCbNtc5O/TPnVK9Tja/PyecvWQe0F2w==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/filesystem@0.0.36': + resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} + + '@types/filewriter@0.0.33': + resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==} + + '@types/har-format@1.2.16': + resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/prop-types@15.7.14': + resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} + + '@types/react-dom@18.3.5': + resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.18': + resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + + '@typescript-eslint/eslint-plugin@8.26.0': + resolution: {integrity: sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/parser@8.26.0': + resolution: {integrity: sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.26.0': + resolution: {integrity: sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.26.0': + resolution: {integrity: sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/types@8.26.0': + resolution: {integrity: sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.26.0': + resolution: {integrity: sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.26.0': + resolution: {integrity: sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.26.0': + resolution: {integrity: sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + + '@webcomponents/custom-elements@1.6.0': + resolution: {integrity: sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001702: + resolution: {integrity: sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + + cheerio@1.0.0: + resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==} + engines: {node: '>=18.17'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + + css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.112: + resolution: {integrity: sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encoding-sniffer@0.2.0: + resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + es-module-lexer@0.10.5: + resolution: {integrity: sha512-+7IwY/kiGAacQfY+YBhKMvEmyAJnw5grTUgjG85Pe7vcUI/6b7pZjZG8nQ7+48YhzEAEqrEgD2dCz/JIK+AYvw==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.19: + resolution: {integrity: sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.21.0: + resolution: {integrity: sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + htmlparser2@9.1.0: + resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.344.0: + resolution: {integrity: sha512-6YyBnn91GB45VuVT96bYCOKElbJzUHqp65vX8cDcu55MQL9T969v4dhGClpljamuI/+KMO9P6w9Acq1CVQGvIQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + + parse5-parser-stream@7.1.2: + resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-refresh@0.13.0: + resolution: {integrity: sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==} + engines: {node: '>=0.10.0'} + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@2.79.2: + resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==} + engines: {node: '>=10.0.0'} + hasBin: true + + rollup@4.34.9: + resolution: {integrity: sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.5.7: + resolution: {integrity: sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.17: + resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} + engines: {node: '>=14.0.0'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@2.0.1: + resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.26.0: + resolution: {integrity: sha512-PtVz9nAnuNJuAVeUFvwztjuUgSnJInODAUx47VDwWPXzd5vismPOtPtt83tzNXyOjVQbPRp786D6WFW/M2koIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici@6.21.1: + resolution: {integrity: sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==} + engines: {node: '>=18.17'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@5.4.14: + resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + 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 + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.7.0: + resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} + engines: {node: '>= 14'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.8': {} + + '@babel/core@7.26.9': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.9 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) + '@babel/helpers': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.26.9': + dependencies: + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.26.5': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.9)': + dependencies: + '@babel/core': 7.26.9 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.26.9': + dependencies: + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 + + '@babel/parser@7.26.9': + dependencies: + '@babel/types': 7.26.9 + + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.9)': + dependencies: + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.9)': + dependencies: + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/template@7.26.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + + '@babel/traverse@7.26.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.9': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@crxjs/vite-plugin@2.0.0-beta.32': + dependencies: + '@rollup/pluginutils': 4.2.1 + '@webcomponents/custom-elements': 1.6.0 + acorn-walk: 8.3.4 + cheerio: 1.0.0 + convert-source-map: 1.9.0 + debug: 4.4.0 + es-module-lexer: 0.10.5 + fast-glob: 3.3.3 + fs-extra: 10.1.0 + jsesc: 3.1.0 + magic-string: 0.30.17 + picocolors: 1.1.1 + react-refresh: 0.13.0 + rollup: 2.79.2 + rxjs: 7.5.7 + transitivePeerDependencies: + - supports-color + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.4.1(eslint@9.21.0(jiti@1.21.7))': + dependencies: + eslint: 9.21.0(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.19.2': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.12.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.0': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.21.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.7': + dependencies: + '@eslint/core': 0.12.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/pluginutils@4.2.1': + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + + '@rollup/rollup-android-arm-eabi@4.34.9': + optional: true + + '@rollup/rollup-android-arm64@4.34.9': + optional: true + + '@rollup/rollup-darwin-arm64@4.34.9': + optional: true + + '@rollup/rollup-darwin-x64@4.34.9': + optional: true + + '@rollup/rollup-freebsd-arm64@4.34.9': + optional: true + + '@rollup/rollup-freebsd-x64@4.34.9': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.34.9': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.34.9': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-x64-musl@4.34.9': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.34.9': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.34.9': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.34.9': + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.26.9 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.26.9 + + '@types/chrome@0.0.260': + dependencies: + '@types/filesystem': 0.0.36 + '@types/har-format': 1.2.16 + + '@types/estree@1.0.6': {} + + '@types/filesystem@0.0.36': + dependencies: + '@types/filewriter': 0.0.33 + + '@types/filewriter@0.0.33': {} + + '@types/har-format@1.2.16': {} + + '@types/json-schema@7.0.15': {} + + '@types/prop-types@15.7.14': {} + + '@types/react-dom@18.3.5(@types/react@18.3.18)': + dependencies: + '@types/react': 18.3.18 + + '@types/react@18.3.18': + dependencies: + '@types/prop-types': 15.7.14 + csstype: 3.1.3 + + '@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2))(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + '@typescript-eslint/scope-manager': 8.26.0 + '@typescript-eslint/type-utils': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + '@typescript-eslint/utils': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.26.0 + eslint: 9.21.0(jiti@1.21.7) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.0.1(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.26.0 + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.26.0 + debug: 4.4.0 + eslint: 9.21.0(jiti@1.21.7) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.26.0': + dependencies: + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/visitor-keys': 8.26.0 + + '@typescript-eslint/type-utils@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2)': + dependencies: + '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2) + '@typescript-eslint/utils': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + debug: 4.4.0 + eslint: 9.21.0(jiti@1.21.7) + ts-api-utils: 2.0.1(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.26.0': {} + + '@typescript-eslint/typescript-estree@8.26.0(typescript@5.8.2)': + dependencies: + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/visitor-keys': 8.26.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.0.1(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.26.0 + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2) + eslint: 9.21.0(jiti@1.21.7) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.26.0': + dependencies: + '@typescript-eslint/types': 8.26.0 + eslint-visitor-keys: 4.2.0 + + '@vitejs/plugin-react@4.3.4(vite@5.4.14)': + dependencies: + '@babel/core': 7.26.9 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.14 + transitivePeerDependencies: + - supports-color + + '@webcomponents/custom-elements@1.6.0': {} + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.1 + + acorn@8.14.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + autoprefixer@10.4.20(postcss@8.5.3): + dependencies: + browserslist: 4.24.4 + caniuse-lite: 1.0.30001702 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001702 + electron-to-chromium: 1.5.112 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001702: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + cheerio-select@2.1.0: + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + + cheerio@1.0.0: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + encoding-sniffer: 0.2.0 + htmlparser2: 9.1.0 + parse5: 7.2.1 + parse5-htmlparser2-tree-adapter: 7.1.0 + parse5-parser-stream: 7.1.2 + undici: 6.21.1 + whatwg-mimetype: 4.0.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-select@5.1.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-what@6.1.0: {} + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.112: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encoding-sniffer@0.2.0: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding: 3.1.1 + + entities@4.5.0: {} + + es-module-lexer@0.10.5: {} + + esbuild@0.21.5: + 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 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.21.0(jiti@1.21.7)): + dependencies: + eslint: 9.21.0(jiti@1.21.7) + + eslint-plugin-react-refresh@0.4.19(eslint@9.21.0(jiti@1.21.7)): + dependencies: + eslint: 9.21.0(jiti@1.21.7) + + eslint-scope@8.2.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.21.0(jiti@1.21.7): + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@1.21.7)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.19.2 + '@eslint/core': 0.12.0 + '@eslint/eslintrc': 3.3.0 + '@eslint/js': 9.21.0 + '@eslint/plugin-kit': 0.2.7 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fraction.js@4.3.7: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globals@15.15.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + htmlparser2@9.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.7: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.344.0(react@18.3.1): + dependencies: + react: 18.3.1 + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.8: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.19: {} + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse5-htmlparser2-tree-adapter@7.1.0: + dependencies: + domhandler: 5.0.3 + parse5: 7.2.1 + + parse5-parser-stream@7.1.2: + dependencies: + parse5: 7.2.1 + + parse5@7.2.1: + dependencies: + entities: 4.5.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + pify@2.3.0: {} + + pirates@4.0.6: {} + + postcss-import@15.1.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.10 + + postcss-js@4.0.1(postcss@8.5.3): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.3 + + postcss-load-config@4.0.2(postcss@8.5.3): + dependencies: + lilconfig: 3.1.3 + yaml: 2.7.0 + optionalDependencies: + postcss: 8.5.3 + + postcss-nested@6.2.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.3: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-refresh@0.13.0: {} + + react-refresh@0.14.2: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + resolve-from@4.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rollup@2.79.2: + optionalDependencies: + fsevents: 2.3.3 + + rollup@4.34.9: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.34.9 + '@rollup/rollup-android-arm64': 4.34.9 + '@rollup/rollup-darwin-arm64': 4.34.9 + '@rollup/rollup-darwin-x64': 4.34.9 + '@rollup/rollup-freebsd-arm64': 4.34.9 + '@rollup/rollup-freebsd-x64': 4.34.9 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.9 + '@rollup/rollup-linux-arm-musleabihf': 4.34.9 + '@rollup/rollup-linux-arm64-gnu': 4.34.9 + '@rollup/rollup-linux-arm64-musl': 4.34.9 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.9 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.9 + '@rollup/rollup-linux-riscv64-gnu': 4.34.9 + '@rollup/rollup-linux-s390x-gnu': 4.34.9 + '@rollup/rollup-linux-x64-gnu': 4.34.9 + '@rollup/rollup-linux-x64-musl': 4.34.9 + '@rollup/rollup-win32-arm64-msvc': 4.34.9 + '@rollup/rollup-win32-ia32-msvc': 4.34.9 + '@rollup/rollup-win32-x64-msvc': 4.34.9 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.5.7: + dependencies: + tslib: 2.8.1 + + safer-buffer@2.1.2: {} + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + semver@7.7.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-json-comments@3.1.1: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.17: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.3 + postcss-import: 15.1.0(postcss@8.5.3) + postcss-js: 4.0.1(postcss@8.5.3) + postcss-load-config: 4.0.2(postcss@8.5.3) + postcss-nested: 6.2.0(postcss@8.5.3) + postcss-selector-parser: 6.1.2 + resolve: 1.22.10 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.0.1(typescript@5.8.2): + dependencies: + typescript: 5.8.2 + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2): + dependencies: + '@typescript-eslint/eslint-plugin': 8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2))(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + '@typescript-eslint/parser': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + '@typescript-eslint/utils': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + eslint: 9.21.0(jiti@1.21.7) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + typescript@5.8.2: {} + + undici@6.21.1: {} + + universalify@2.0.1: {} + + update-browserslist-db@1.1.3(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite@5.4.14: + dependencies: + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.34.9 + optionalDependencies: + fsevents: 2.3.3 + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + yallist@3.1.1: {} + + yaml@2.7.0: {} + + yocto-queue@0.1.0: {} diff --git a/extension/postcss.config.js b/extension/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/extension/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/extension/sidebar.html b/extension/sidebar.html new file mode 100644 index 0000000..d8ddccd --- /dev/null +++ b/extension/sidebar.html @@ -0,0 +1,12 @@ + + + + + + Comment Assistant + + +
+ + + \ No newline at end of file diff --git a/extension/src/App.tsx b/extension/src/App.tsx new file mode 100644 index 0000000..c61e562 --- /dev/null +++ b/extension/src/App.tsx @@ -0,0 +1,239 @@ +import React, { useState, useEffect } from 'react'; +import { MessageSquare, BarChart2, Send, RefreshCw, Settings as SettingsIcon, AlertCircle } from 'lucide-react'; +import CommentList from './sidebar/components/CommentList'; +import Analytics from './sidebar/components/Analytics'; +import ReplyGenerator from './sidebar/components/ReplyGenerator'; +import Settings from './sidebar/components/Settings'; +import { Comment } from './types'; +import mockComments from './mockData'; + +function App() { + const [activeTab, setActiveTab] = useState<'comments' | 'analytics' | 'reply' | 'settings'>('comments'); + const [comments, setComments] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [selectedComment, setSelectedComment] = useState(null); + const [mockDelay, setMockDelay] = useState(1000); + const [error, setError] = useState(null); + + useEffect(() => { + // Simulate loading comments with a delay + setError(null); + const timer = setTimeout(() => { + try { + setComments(mockComments); + setIsLoading(false); + } catch (err) { + setError('Error loading comments: ' + (err instanceof Error ? err.message : String(err))); + setIsLoading(false); + } + }, mockDelay); + + return () => clearTimeout(timer); + }, [mockDelay]); + + const refreshComments = () => { + setIsLoading(true); + setError(null); + setTimeout(() => { + try { + setComments(mockComments); + setIsLoading(false); + } catch (err) { + setError('Error refreshing comments: ' + (err instanceof Error ? err.message : String(err))); + setIsLoading(false); + } + }, mockDelay); + }; + + const handleSelectComment = (comment: Comment) => { + setSelectedComment(comment); + setActiveTab('reply'); + }; + + return ( +
+
+
+

社群留言助手 - 開發模式

+
+
+ 模擬延遲: + +
+ +
+
+
+ + {error && ( +
+
+ + {error} +
+
+ )} + +
+ {/* Sidebar Preview */} +
+ {/* Header */} +
+

+ + 社群留言助手 +

+

自動捕獲留言並產生回覆建議

+
+ + {/* Content */} +
+ {activeTab === 'comments' && ( + + )} + {activeTab === 'analytics' && ( + + )} + {activeTab === 'reply' && ( + setActiveTab('comments')} + /> + )} + {activeTab === 'settings' && ( + + )} +
+ + {/* Navigation */} + +
+ + {/* Development Info */} +
+
+

開發資訊

+ +
+

當前狀態

+
+
+

當前頁面

+

{activeTab}

+
+
+

留言數量

+

{comments.length}

+
+
+

載入狀態

+

{isLoading ? '載入中' : '已載入'}

+
+
+

選中的留言

+

{selectedComment ? `ID: ${selectedComment.id}` : '無'}

+
+
+
+ +
+

開發指南

+
+

這是一個開發環境,用於測試 Chrome 擴展的功能。

+
    +
  • 左側顯示的是擴展的側邊欄界面預覽
  • +
  • 可以調整模擬延遲來測試不同的載入狀態
  • +
  • 點擊刷新按鈕可以重新載入模擬數據
  • +
  • 所有功能都使用模擬數據,不會實際抓取網頁留言
  • +
+
+
+ +
+

構建與測試

+
+
+

構建擴展:

+ npm run build +
+
+

載入擴展:

+
    +
  1. 打開 Chrome 瀏覽器,進入擴展管理頁面 (chrome://extensions/)
  2. +
  3. 開啟開發者模式
  4. +
  5. 點擊「載入已解壓的擴展」
  6. +
  7. 選擇項目的 dist 目錄
  8. +
+
+
+

測試擴展:

+
    +
  1. 在任意網頁點擊擴展圖標
  2. +
  3. 側邊欄將會打開,顯示留言助手界面
  4. +
  5. 如果沒有自動打開,可以右鍵點擊擴展圖標,選擇「打開側邊欄」
  6. +
+
+
+
+
+
+
+ +
+

社群留言助手 - 開發模式 © 2025

+
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/extension/src/ErrorBoundary.tsx b/extension/src/ErrorBoundary.tsx new file mode 100644 index 0000000..ecbb32e --- /dev/null +++ b/extension/src/ErrorBoundary.tsx @@ -0,0 +1,59 @@ +import React, { Component, ErrorInfo, ReactNode } from 'react'; +import { AlertCircle } from 'lucide-react'; + +interface Props { + children: ReactNode; + fallback?: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo): void { + console.error('Error caught by ErrorBoundary:', error, errorInfo); + } + + render(): ReactNode { + if (this.state.hasError) { + if (this.props.fallback) { + return this.props.fallback; + } + + return ( +
+
+ +
+

Something went wrong

+

+ {this.state.error?.message || 'An unexpected error occurred'} +

+ +
+
+
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; \ No newline at end of file diff --git a/extension/src/index.css b/extension/src/index.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/extension/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/extension/src/main.tsx b/extension/src/main.tsx new file mode 100644 index 0000000..2dccfc3 --- /dev/null +++ b/extension/src/main.tsx @@ -0,0 +1,55 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.tsx'; +import './index.css'; +import ErrorBoundary from './ErrorBoundary'; + +// Error boundary for the entire application +const renderApp = () => { + try { + const rootElement = document.getElementById('root'); + if (!rootElement) { + console.error('Root element not found'); + return; + } + + createRoot(rootElement).render( + + + + + + ); + } catch (error) { + console.error('Error rendering application:', error); + + // Render a fallback UI in case of error + const rootElement = document.getElementById('root'); + if (rootElement) { + rootElement.innerHTML = ` +
+

Application Error

+

Sorry, something went wrong while loading the application.

+

Error details: ${error instanceof Error ? error.message : String(error)}

+ +
+ `; + } + } +}; + +// Disable Vite's error overlay to prevent WebSocket connection attempts +window.addEventListener('error', (event) => { + event.preventDefault(); + console.error('Caught error:', event.error); + return true; +}); + +// Disable Vite's HMR client +if (import.meta.hot) { + import.meta.hot.decline(); +} + +renderApp(); \ No newline at end of file diff --git a/extension/src/mockData.ts b/extension/src/mockData.ts new file mode 100644 index 0000000..1294dce --- /dev/null +++ b/extension/src/mockData.ts @@ -0,0 +1,224 @@ +import { Comment } from './types'; + +const mockComments: Comment[] = [ + { + id: 'comment-1', + author: '王小明', + content: '這個產品真的很好用!我已經推薦給我的朋友們了。希望未來能有更多顏色選擇。', + timestamp: '2小時前', + likes: 24, + platform: 'facebook', + sentiment: 'positive', + keywords: ['好用', '推薦', '顏色'], + category: '產品讚美', + replies: [ + { + id: 'reply-1-1', + author: '品牌官方', + content: '謝謝您的支持!我們正在開發更多顏色,敬請期待!', + timestamp: '1小時前', + likes: 5, + platform: 'facebook', + sentiment: 'positive' + } + ] + }, + { + id: 'comment-2', + author: '林美玲', + content: '請問這個產品適合敏感肌膚使用嗎?我之前用類似的產品會過敏。', + timestamp: '3小時前', + likes: 7, + platform: 'facebook', + sentiment: 'neutral', + keywords: ['敏感肌膚', '過敏', '適合'], + category: '產品詢問' + }, + { + id: 'comment-3', + author: 'Jason Chen', + content: 'The quality is amazing! Worth every penny. Will definitely buy again.', + timestamp: '5小時前', + likes: 18, + platform: 'instagram', + sentiment: 'positive', + keywords: ['quality', 'worth', 'buy again'], + category: '產品讚美' + }, + { + id: 'comment-4', + author: '陳大華', + content: '收到產品了,包裝很精美,但是尺寸比我想像中小一點。總體來說還是很滿意的。', + timestamp: '昨天', + likes: 12, + platform: 'facebook', + sentiment: 'neutral', + keywords: ['包裝', '尺寸', '滿意'], + category: '產品評價' + }, + { + id: 'comment-5', + author: 'Sarah Wong', + content: '我有個問題,請問這個產品可以國際運送嗎?我現在在美國。', + timestamp: '昨天', + likes: 3, + platform: 'instagram', + sentiment: 'neutral', + keywords: ['國際運送', '美國'], + category: '物流詢問' + }, + { + id: 'comment-6', + author: '黃小琳', + content: '價格有點貴,但品質確實不錯。希望能有折扣活動。', + timestamp: '2天前', + likes: 9, + platform: 'facebook', + sentiment: 'neutral', + keywords: ['價格', '品質', '折扣'], + category: '價格評論' + }, + { + id: 'comment-7', + author: 'Mike Li', + content: 'Just received my order. The shipping was super fast! Great service.', + timestamp: '2天前', + likes: 15, + platform: 'twitter', + sentiment: 'positive', + keywords: ['shipping', 'fast', 'service'], + category: '物流評價' + }, + { + id: 'comment-8', + author: '張小菲', + content: '我的訂單顯示已發貨,但追蹤號碼似乎不正確。能幫我確認一下嗎?訂單號:TW20250615001', + timestamp: '3天前', + likes: 0, + platform: 'facebook', + sentiment: 'negative', + keywords: ['訂單', '追蹤號碼', '不正確'], + category: '物流問題' + }, + { + id: 'comment-9', + author: 'David Wang', + content: '這是我第三次購買了,每次都很滿意。客服也很棒!', + timestamp: '4天前', + likes: 27, + platform: 'youtube', + sentiment: 'positive', + keywords: ['購買', '滿意', '客服'], + category: '客戶體驗', + replies: [ + { + id: 'reply-9-1', + author: '品牌官方', + content: '感謝您的持續支持!我們非常重視每一位顧客的體驗。', + timestamp: '4天前', + likes: 8, + platform: 'youtube', + sentiment: 'positive' + }, + { + id: 'reply-9-2', + author: 'Lisa Chen', + content: '我也很喜歡他們的客服,總是很有耐心解答問題。', + timestamp: '3天前', + likes: 5, + platform: 'youtube', + sentiment: 'positive' + } + ] + }, + { + id: 'comment-10', + author: '李小明', + content: '產品收到了,但有一個小零件好像壞了。請問如何申請售後服務?', + timestamp: '5天前', + likes: 2, + platform: 'facebook', + sentiment: 'negative', + keywords: ['零件', '壞了', '售後服務'], + category: '產品問題' + }, + { + id: 'comment-11', + author: 'Emma Chang', + content: '我很喜歡你們的環保包裝!希望更多品牌能這樣做。', + timestamp: '1週前', + likes: 42, + platform: 'instagram', + sentiment: 'positive', + keywords: ['環保包裝', '喜歡'], + category: '包裝評價', + replies: [ + { + id: 'reply-11-1', + author: '品牌官方', + content: '謝謝您的支持!環保是我們的核心價值之一,我們會繼續努力做得更好。', + timestamp: '1週前', + likes: 12, + platform: 'instagram', + sentiment: 'positive' + } + ] + }, + { + id: 'comment-12', + author: '陳小華', + content: '請問有沒有實體店面可以試用產品?', + timestamp: '1週前', + likes: 5, + platform: 'facebook', + sentiment: 'neutral', + keywords: ['實體店面', '試用'], + category: '銷售詢問' + }, + { + id: 'comment-13', + author: 'Kevin Wu', + content: 'Great product but the app needs improvement. Sometimes it crashes when I try to connect to the device.', + timestamp: '1週前', + likes: 8, + platform: 'twitter', + sentiment: 'neutral', + keywords: ['product', 'app', 'crashes'], + category: '應用問題' + }, + { + id: 'comment-14', + author: '林小芳', + content: '我在官網看到的價格和這裡不一樣,為什麼?', + timestamp: '2週前', + likes: 3, + platform: 'youtube', + sentiment: 'negative', + keywords: ['價格', '官網', '不一樣'], + category: '價格問題' + }, + { + id: 'comment-15', + author: 'Sophia Lin', + content: '剛剛在朋友家看到這個產品,效果真的很驚人!請問現在有什麼促銷活動嗎?', + timestamp: '2週前', + likes: 19, + platform: 'facebook', + sentiment: 'positive', + keywords: ['效果', '驚人', '促銷活動'], + category: '產品讚美', + replies: [ + { + id: 'reply-15-1', + author: '品牌官方', + content: '您好!我們目前有限時折扣活動,購買任兩件產品即可享85折優惠。詳情請查看我們的官網。', + timestamp: '2週前', + likes: 4, + platform: 'facebook', + sentiment: 'positive' + } + ] + } +]; + +export default mockComments; \ No newline at end of file diff --git a/extension/src/sidebar/Sidebar.tsx b/extension/src/sidebar/Sidebar.tsx new file mode 100644 index 0000000..d088c41 --- /dev/null +++ b/extension/src/sidebar/Sidebar.tsx @@ -0,0 +1,196 @@ +import React, { useState, useEffect } from 'react'; +import { MessageSquare, BarChart2, Send, RefreshCw, Settings } from 'lucide-react'; +import CommentList from './components/CommentList'; +import Analytics from './components/Analytics'; +import ReplyGenerator from './components/ReplyGenerator'; +import Settings from './components/Settings'; +import { Comment } from '../types'; + +const Sidebar: React.FC = () => { + const [activeTab, setActiveTab] = useState<'comments' | 'analytics' | 'reply' | 'settings'>('comments'); + const [comments, setComments] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [selectedComment, setSelectedComment] = useState(null); + + useEffect(() => { + // Setup message listener with error handling + const messageListener = (message: any) => { + try { + if (message.type === 'COMMENTS_CAPTURED') { + setComments(message.comments); + setIsLoading(false); + } + } catch (error) { + console.error('Error processing message:', error); + setIsLoading(false); + } + }; + + // Register listener if we're in a Chrome extension environment + if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.onMessage) { + chrome.runtime.onMessage.addListener(messageListener); + } else { + // We're in development mode - simulate comments loading + console.log('Development mode: simulating comment loading'); + setTimeout(() => { + try { + // Import mock data dynamically to avoid issues + import('../mockData').then(module => { + setComments(module.default); + setIsLoading(false); + }).catch(error => { + console.error('Error loading mock data:', error); + setIsLoading(false); + }); + } catch (error) { + console.error('Error in development mode comment simulation:', error); + setIsLoading(false); + } + }, 1000); + } + + // Request comments from the current page if in extension environment + const requestComments = () => { + try { + if (typeof chrome !== 'undefined' && chrome.tabs && chrome.tabs.query) { + setIsLoading(true); + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + if (tabs[0]?.id) { + chrome.tabs.sendMessage(tabs[0].id, { type: 'GET_COMMENTS' }); + } + }); + } + } catch (error) { + console.error('Error requesting comments:', error); + setIsLoading(false); + } + }; + + if (typeof chrome !== 'undefined' && chrome.tabs) { + requestComments(); + } + + // Cleanup function + return () => { + if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.onMessage) { + chrome.runtime.onMessage.removeListener(messageListener); + } + }; + }, []); + + const refreshComments = () => { + try { + if (typeof chrome !== 'undefined' && chrome.tabs && chrome.tabs.query) { + setIsLoading(true); + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + if (tabs[0]?.id) { + chrome.tabs.sendMessage(tabs[0].id, { type: 'GET_COMMENTS' }); + } + }); + } else { + // Development mode - reload mock data + setIsLoading(true); + setTimeout(() => { + import('../mockData').then(module => { + setComments(module.default); + setIsLoading(false); + }).catch(error => { + console.error('Error reloading mock data:', error); + setIsLoading(false); + }); + }, 1000); + } + } catch (error) { + console.error('Error refreshing comments:', error); + setIsLoading(false); + } + }; + + const handleSelectComment = (comment: Comment) => { + setSelectedComment(comment); + setActiveTab('reply'); + }; + + return ( +
+ {/* Header */} +
+

+ + 社群留言助手 +

+

自動捕獲留言並產生回覆建議

+
+ + {/* Main Content */} +
+ {activeTab === 'comments' && ( + + )} + {activeTab === 'analytics' && ( + + )} + {activeTab === 'reply' && ( + setActiveTab('comments')} + /> + )} + {activeTab === 'settings' && ( + + )} +
+ + {/* Refresh Button */} +
+ +
+ + {/* Navigation */} + +
+ ); +}; + +export default Sidebar; \ No newline at end of file diff --git a/extension/src/sidebar/components/Analytics.tsx b/extension/src/sidebar/components/Analytics.tsx new file mode 100644 index 0000000..dc255dc --- /dev/null +++ b/extension/src/sidebar/components/Analytics.tsx @@ -0,0 +1,371 @@ +import React, { useMemo } from 'react'; +import { BarChart2, TrendingUp, Clock, ThumbsUp, MessageSquare, Smile, Meh, Frown, Tag } from 'lucide-react'; +import { Comment } from '../../types'; + +interface AnalyticsProps { + comments: Comment[]; +} + +const Analytics: React.FC = ({ comments }) => { + const stats = useMemo(() => { + // Total comments + const totalComments = comments.length; + + // Comments by platform + const platformCounts: Record = {}; + comments.forEach(comment => { + platformCounts[comment.platform] = (platformCounts[comment.platform] || 0) + 1; + }); + + // Average likes + const totalLikes = comments.reduce((sum, comment) => sum + comment.likes, 0); + const avgLikes = totalComments > 0 ? (totalLikes / totalComments).toFixed(1) : '0'; + + // Comments with replies + const commentsWithReplies = comments.filter(comment => + comment.replies && comment.replies.length > 0 + ).length; + + // Total replies + const totalReplies = comments.reduce((sum, comment) => + sum + (comment.replies?.length || 0), 0 + ); + + // Sentiment counts + const sentimentCounts = { + positive: comments.filter(c => c.sentiment === 'positive').length, + neutral: comments.filter(c => c.sentiment === 'neutral').length, + negative: comments.filter(c => c.sentiment === 'negative').length + }; + + // Keywords analysis + const keywordCounts: Record = {}; + comments.forEach(comment => { + if (comment.keywords) { + comment.keywords.forEach(keyword => { + keywordCounts[keyword] = (keywordCounts[keyword] || 0) + 1; + }); + } + }); + + const topKeywords = Object.entries(keywordCounts) + .sort((a, b) => b[1] - a[1]) + .slice(0, 10) + .map(([keyword, count]) => ({ keyword, count })); + + // Categories analysis + const categoryCounts: Record = {}; + comments.forEach(comment => { + if (comment.category) { + categoryCounts[comment.category] = (categoryCounts[comment.category] || 0) + 1; + } + }); + + const categories = Object.entries(categoryCounts) + .sort((a, b) => b[1] - a[1]) + .map(([category, count]) => ({ category, count })); + + // Most active platforms (sorted) + const sortedPlatforms = Object.entries(platformCounts) + .sort((a, b) => b[1] - a[1]) + .map(([platform, count]) => ({ platform, count })); + + return { + totalComments, + platformCounts, + avgLikes, + commentsWithReplies, + totalReplies, + sortedPlatforms, + sentimentCounts, + topKeywords, + categories + }; + }, [comments]); + + if (comments.length === 0) { + return ( +
+ +

沒有數據可顯示

+

+ 請先捕獲留言以查看數據分析。 +

+
+ ); + } + + return ( +
+

留言數據分析

+ + {/* Summary Cards */} +
+
+
+ + 總留言數 +
+

{stats.totalComments}

+
+ +
+
+ + 平均讚數 +
+

{stats.avgLikes}

+
+ +
+
+ + 互動率 +
+

+ {stats.totalComments > 0 + ? `${Math.round((stats.commentsWithReplies / stats.totalComments) * 100)}%` + : '0%'} +

+
+ +
+
+ + 總回覆數 +
+

{stats.totalReplies}

+
+
+ + {/* Sentiment Analysis */} +
+

情緒分析

+ + {/* Sentiment Bar */} +
+
+
+
+
+ +
+
+
+
+ + 正面 +
+ {Math.round((stats.sentimentCounts.positive / stats.totalComments) * 100)}% +
+

{stats.sentimentCounts.positive}

+
+ +
+
+
+ + 中性 +
+ {Math.round((stats.sentimentCounts.neutral / stats.totalComments) * 100)}% +
+

{stats.sentimentCounts.neutral}

+
+ +
+
+
+ + 負面 +
+ {Math.round((stats.sentimentCounts.negative / stats.totalComments) * 100)}% +
+

{stats.sentimentCounts.negative}

+
+
+
+ + {/* Keywords Analysis */} +
+

熱門關鍵詞

+ +
+ {stats.topKeywords.slice(0, 5).map(({ keyword, count }) => ( +
+
+
+
+
+ {keyword} + {count} +
+
+ ))} +
+ +
+ {stats.topKeywords.slice(5, 15).map(({ keyword, count }) => ( + + {keyword} + + ))} +
+
+ + {/* Categories Analysis */} +
+

留言類別分佈

+ +
+ {stats.categories.slice(0, 5).map(({ category, count }) => ( +
+
+
+
+
+ {category} + {count} ({Math.round((count / stats.totalComments) * 100)}%) +
+
+ ))} +
+
+ + {/* Platform Distribution */} +
+

平台分佈

+ +
+ {Object.entries(stats.platformCounts).map(([platform, count]) => ( +
+
+ {platform} + {count} ({Math.round((count / stats.totalComments) * 100)}%) +
+
+
+
+
+ ))} +
+
+ + {/* Top Comments */} +
+

熱門留言

+ + {comments + .sort((a, b) => b.likes - a.likes) + .slice(0, 3) + .map(comment => ( +
+
+ {comment.author} +
+
+ + {comment.likes} +
+ {comment.sentiment && ( +
+ {comment.sentiment === 'positive' && } + {comment.sentiment === 'neutral' && } + {comment.sentiment === 'negative' && } +
+ )} +
+
+

{comment.content}

+
+ {comment.timestamp} +
+ {comment.category && ( + + {comment.category} + + )} + + {comment.platform} + +
+
+
+ ))} +
+
+ ); +}; + +// Helper function to get platform-specific colors +function getPlatformColor(platform: string): string { + switch (platform) { + case 'facebook': + return 'bg-blue-600'; + case 'instagram': + return 'bg-pink-600'; + case 'twitter': + return 'bg-blue-400'; + case 'youtube': + return 'bg-red-600'; + case 'linkedin': + return 'bg-blue-800'; + default: + return 'bg-gray-600'; + } +} + +// Helper function to get platform-specific badge colors +function getPlatformBadgeColor(platform: string): string { + switch (platform) { + case 'facebook': + return 'bg-blue-100 text-blue-800'; + case 'instagram': + return 'bg-pink-100 text-pink-800'; + case 'twitter': + return 'bg-blue-100 text-blue-600'; + case 'youtube': + return 'bg-red-100 text-red-800'; + case 'linkedin': + return 'bg-blue-100 text-blue-900'; + default: + return 'bg-gray-100 text-gray-800'; + } +} + +// Helper function to get sentiment badge colors +function getSentimentBadgeColor(sentiment: string): string { + switch (sentiment) { + case 'positive': + return 'bg-green-100 text-green-800'; + case 'neutral': + return 'bg-gray-100 text-gray-800'; + case 'negative': + return 'bg-red-100 text-red-800'; + default: + return 'bg-gray-100 text-gray-800'; + } +} + +export default Analytics; \ No newline at end of file diff --git a/extension/src/sidebar/components/CommentList.tsx b/extension/src/sidebar/components/CommentList.tsx new file mode 100644 index 0000000..12c9778 --- /dev/null +++ b/extension/src/sidebar/components/CommentList.tsx @@ -0,0 +1,453 @@ +import React, { useState, useMemo } from 'react'; +import { MessageSquare, ThumbsUp, Clock, Filter, SortDesc, Search, X, ChevronDown, Smile, Frown, Meh, Tag } from 'lucide-react'; +import { Comment } from '../../types'; + +interface CommentListProps { + comments: Comment[]; + isLoading: boolean; + onSelectComment: (comment: Comment) => void; +} + +const CommentList: React.FC = ({ comments, isLoading, onSelectComment }) => { + const [searchTerm, setSearchTerm] = useState(''); + const [platformFilter, setPlatformFilter] = useState('all'); + const [sentimentFilter, setSentimentFilter] = useState<'all' | 'positive' | 'neutral' | 'negative'>('all'); + const [sortBy, setSortBy] = useState<'newest' | 'oldest' | 'likes' | 'replies'>('newest'); + const [showFilters, setShowFilters] = useState(false); + const [showAnalytics, setShowAnalytics] = useState(true); + + // Get unique platforms from comments + const platforms = useMemo(() => { + const platformSet = new Set(); + comments.forEach(comment => platformSet.add(comment.platform)); + return Array.from(platformSet); + }, [comments]); + + // Calculate sentiment statistics + const sentimentStats = useMemo(() => { + const stats = { + positive: 0, + neutral: 0, + negative: 0, + total: comments.length + }; + + comments.forEach(comment => { + if (comment.sentiment === 'positive') stats.positive++; + else if (comment.sentiment === 'neutral') stats.neutral++; + else if (comment.sentiment === 'negative') stats.negative++; + }); + + return stats; + }, [comments]); + + // Extract top keywords + const topKeywords = useMemo(() => { + const keywordCounts: Record = {}; + + comments.forEach(comment => { + if (comment.keywords) { + comment.keywords.forEach(keyword => { + keywordCounts[keyword] = (keywordCounts[keyword] || 0) + 1; + }); + } + }); + + return Object.entries(keywordCounts) + .sort((a, b) => b[1] - a[1]) + .slice(0, 10) + .map(([keyword, count]) => ({ keyword, count })); + }, [comments]); + + // Extract categories + const categories = useMemo(() => { + const categoryCounts: Record = {}; + + comments.forEach(comment => { + if (comment.category) { + categoryCounts[comment.category] = (categoryCounts[comment.category] || 0) + 1; + } + }); + + return Object.entries(categoryCounts) + .sort((a, b) => b[1] - a[1]) + .map(([category, count]) => ({ category, count })); + }, [comments]); + + // Filter and sort comments + const filteredAndSortedComments = useMemo(() => { + // First filter by search term, platform, and sentiment + let filtered = comments.filter(comment => { + const matchesSearch = searchTerm === '' || + comment.content.toLowerCase().includes(searchTerm.toLowerCase()) || + comment.author.toLowerCase().includes(searchTerm.toLowerCase()); + + const matchesPlatform = platformFilter === 'all' || comment.platform === platformFilter; + + const matchesSentiment = sentimentFilter === 'all' || comment.sentiment === sentimentFilter; + + return matchesSearch && matchesPlatform && matchesSentiment; + }); + + // Then sort + return filtered.sort((a, b) => { + switch (sortBy) { + case 'newest': + // Simple string comparison for timestamps (in a real app, parse dates properly) + return a.timestamp < b.timestamp ? 1 : -1; + case 'oldest': + return a.timestamp > b.timestamp ? 1 : -1; + case 'likes': + return b.likes - a.likes; + case 'replies': + return (b.replies?.length || 0) - (a.replies?.length || 0); + default: + return 0; + } + }); + }, [comments, searchTerm, platformFilter, sentimentFilter, sortBy]); + + if (isLoading) { + return ( +
+
+

正在捕獲留言...

+
+ ); + } + + if (comments.length === 0) { + return ( +
+ +

沒有找到留言

+

+ 此頁面上沒有檢測到留言,或者留言格式不被支持。 +

+
+ ); + } + + return ( +
+
+

留言列表

+
+ + + + {filteredAndSortedComments.length} / {comments.length} + +
+
+ + {/* Quick Analytics */} + {showAnalytics && ( +
+
+

情緒分析

+
+ 總留言: {sentimentStats.total} +
+
+ + {/* Sentiment Distribution */} +
+
+
+
+
+ +
+
+
+ 正面: {sentimentStats.positive} +
+
+
+ 中性: {sentimentStats.neutral} +
+
+
+ 負面: {sentimentStats.negative} +
+
+ + {/* Top Keywords */} +
+

熱門關鍵詞

+
+ {topKeywords.slice(0, 8).map(({ keyword, count }) => ( + + {keyword} + + ))} +
+
+ + {/* Top Categories */} +
+

主要類別

+
+ {categories.slice(0, 5).map(({ category, count }) => ( + + {category} + + ))} +
+
+
+ )} + + {/* Search and Filter Panel */} + {showFilters && ( +
+ {/* Search */} +
+
+ +
+ setSearchTerm(e.target.value)} + placeholder="搜尋留言或作者..." + className="w-full pl-9 pr-9 py-2 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" + /> + {searchTerm && ( + + )} +
+ +
+ {/* Platform Filter */} +
+ +
+ +
+ +
+
+
+ + {/* Sentiment Filter */} +
+ +
+ +
+ +
+
+
+ + {/* Sort By */} +
+ +
+ +
+ +
+
+
+
+ + {/* Filter Stats */} + {(searchTerm || platformFilter !== 'all' || sentimentFilter !== 'all') && ( +
+ + {filteredAndSortedComments.length === comments.length + ? '顯示全部留言' + : `顯示 ${filteredAndSortedComments.length} 個符合條件的留言`} + + +
+ )} +
+ )} + + {/* Comments List */} + {filteredAndSortedComments.length === 0 ? ( +
+

沒有符合條件的留言

+ +
+ ) : ( +
+ {filteredAndSortedComments.map((comment) => ( +
onSelectComment(comment)} + > +
+
{comment.author}
+
+
+ + {comment.timestamp} +
+ {comment.sentiment && ( +
+ {comment.sentiment === 'positive' && } + {comment.sentiment === 'neutral' && } + {comment.sentiment === 'negative' && } + {getSentimentLabel(comment.sentiment)} +
+ )} +
+
+ +

{comment.content}

+ +
+ {comment.keywords?.map(keyword => ( + + {keyword} + + ))} +
+ +
+
+ + {comment.likes} 讚 +
+ +
+ + {comment.replies?.length || 0} 回覆 +
+ +
+ {comment.category && ( + + {comment.category} + + )} + + {comment.platform} + +
+
+
+ ))} +
+ )} +
+ ); +}; + +// Helper function to get sentiment badge colors +function getSentimentBadgeColor(sentiment: string): string { + switch (sentiment) { + case 'positive': + return 'bg-green-100 text-green-800'; + case 'neutral': + return 'bg-gray-100 text-gray-800'; + case 'negative': + return 'bg-red-100 text-red-800'; + default: + return 'bg-gray-100 text-gray-800'; + } +} + +// Helper function to get sentiment labels +function getSentimentLabel(sentiment: string): string { + switch (sentiment) { + case 'positive': + return '正面'; + case 'neutral': + return '中性'; + case 'negative': + return '負面'; + default: + return '未知'; + } +} + +export default CommentList; \ No newline at end of file diff --git a/extension/src/sidebar/components/ReplyGenerator.tsx b/extension/src/sidebar/components/ReplyGenerator.tsx new file mode 100644 index 0000000..8eefd54 --- /dev/null +++ b/extension/src/sidebar/components/ReplyGenerator.tsx @@ -0,0 +1,363 @@ +import React, { useState, useEffect } from 'react'; +import { ArrowLeft, Send, Copy, Check, Zap, User, Smile, Meh, Frown, Tag, ThumbsUp } from 'lucide-react'; +import { Comment, ReplyTone, ReplyPersona } from '../../types'; + +interface ReplyGeneratorProps { + comment: Comment | null; + onBack: () => void; +} + +const ReplyGenerator: React.FC = ({ comment, onBack }) => { + const [selectedTone, setSelectedTone] = useState('friendly'); + const [selectedPersona, setSelectedPersona] = useState('brand'); + const [generatedReplies, setGeneratedReplies] = useState([]); + const [isGenerating, setIsGenerating] = useState(false); + const [copied, setCopied] = useState(false); + + const tones: ReplyTone[] = [ + { id: 'friendly', name: '友善', description: '溫暖親切的語氣' }, + { id: 'professional', name: '專業', description: '正式且專業的語氣' }, + { id: 'casual', name: '輕鬆', description: '隨意輕鬆的對話風格' }, + { id: 'enthusiastic', name: '熱情', description: '充滿活力與熱情' }, + { id: 'empathetic', name: '同理心', description: '表達理解與關懷' } + ]; + + const personas: ReplyPersona[] = [ + { id: 'brand', name: '品牌代表', description: '以品牌官方身份回覆' }, + { id: 'support', name: '客服人員', description: '以客服專員身份回覆' }, + { id: 'expert', name: '領域專家', description: '以專業人士身份回覆' }, + { id: 'friend', name: '朋友', description: '以朋友身份回覆' } + ]; + + useEffect(() => { + // In development mode, we don't have access to chrome.storage + // So we'll use a mock implementation + const loadDefaultSettings = () => { + try { + // Check if we're in a Chrome extension environment + if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) { + chrome.storage.sync.get(['defaultTone', 'defaultPersona'], (result) => { + if (result.defaultTone) setSelectedTone(result.defaultTone); + if (result.defaultPersona) setSelectedPersona(result.defaultPersona); + }); + } else { + // Mock storage for development environment + console.log('Using mock storage for development'); + // Use default values or load from localStorage if needed + const savedTone = localStorage.getItem('defaultTone'); + const savedPersona = localStorage.getItem('defaultPersona'); + + if (savedTone) setSelectedTone(savedTone); + if (savedPersona) setSelectedPersona(savedPersona); + } + } catch (error) { + console.error('Error loading settings:', error); + // Continue with default values + } + }; + + loadDefaultSettings(); + }, []); + + const generateReplies = () => { + if (!comment) return; + + setIsGenerating(true); + + // Simulate API call or processing delay + setTimeout(() => { + // Generate replies based on comment sentiment, category, and selected tone/persona + let mockReplies: string[] = []; + + // Base reply templates for different sentiments + if (comment.sentiment === 'positive') { + mockReplies = [ + `感謝您的正面評價!我們很高興您喜歡我們的產品/服務。您的支持是我們前進的動力。`, + `非常感謝您的讚美!我們團隊一直致力於提供最好的體驗,很開心能得到您的認可。`, + `謝謝您的美好評價!您的滿意是我們最大的成就,我們會繼續努力維持這樣的水準。` + ]; + } else if (comment.sentiment === 'negative') { + mockReplies = [ + `非常抱歉給您帶來不便。我們非常重視您的反饋,並會立即處理您提到的問題。請問可以提供更多細節,以便我們更好地解決?`, + `感謝您的坦誠反饋。我們對您的體驗感到遺憾,並承諾會改進。我們的團隊已經注意到這個問題,正在積極解決中。`, + `您的意見對我們非常寶貴。對於您遇到的困難,我們深表歉意。請放心,我們會認真對待每一條反饋,並努力改進我們的產品和服務。` + ]; + } else { + mockReplies = [ + `感謝您的留言!我們很樂意回答您的問題。請問還有什麼我們可以幫助您的嗎?`, + `謝謝您的關注!您提出的問題很有價值,我們會盡快為您提供所需的信息。`, + `感謝您的互動!我們非常重視您的每一個問題,並致力於提供最準確的回答。` + ]; + } + + // Customize based on category if available + if (comment.category) { + // Add category-specific content to the replies + mockReplies = mockReplies.map(reply => { + switch (comment.category) { + case '產品讚美': + return reply + ` 我們不斷努力改進產品,您的鼓勵給了我們很大的動力。`; + case '產品詢問': + return reply + ` 關於產品的具體信息,我們建議您查看官網的產品說明頁面,或直接聯繫我們的客服團隊。`; + case '產品問題': + return reply + ` 我們的售後團隊將會與您聯繫,協助解決產品問題。您也可以撥打客服熱線獲取即時幫助。`; + case '物流問題': + return reply + ` 我們會立即與物流部門核實您的訂單狀態,並盡快給您回覆。`; + case '價格問題': + return reply + ` 關於價格的疑問,我們的銷售團隊將為您提供最詳細的解答和最優惠的方案。`; + default: + return reply; + } + }); + } + + // Adjust tone based on selection + mockReplies = mockReplies.map(reply => { + switch (selectedTone) { + case 'professional': + return reply.replace(/感謝|謝謝/g, '非常感謝').replace(/!/g, '。'); + case 'casual': + return reply.replace(/我們/g, '我們團隊').replace(/。/g, '~'); + case 'enthusiastic': + return reply.replace(/!/g, '!!').replace(/謝謝/g, '非常感謝'); + case 'empathetic': + return reply.replace(/感謝/g, '真誠感謝').replace(/我們理解/g, '我們完全理解'); + default: + return reply; + } + }); + + // Adjust persona based on selection + mockReplies = mockReplies.map(reply => { + switch (selectedPersona) { + case 'support': + return `作為客服代表,${reply}`; + case 'expert': + return `以專業角度來看,${reply}`; + case 'friend': + return reply.replace(/我們/g, '我們').replace(/非常感謝/g, '超級感謝'); + default: + return reply; + } + }); + + setGeneratedReplies(mockReplies); + setIsGenerating(false); + }, 1500); + }; + + const copyToClipboard = (text: string) => { + try { + navigator.clipboard.writeText(text).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }).catch(err => { + console.error('Failed to copy text: ', err); + // Fallback method + const textArea = document.createElement('textarea'); + textArea.value = text; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + } catch (error) { + console.error('Copy to clipboard failed:', error); + } + }; + + if (!comment) { + return ( +
+ +

請先選擇一條留言

+

+ 從留言列表中選擇一條留言來生成回覆建議。 +

+ +
+ ); + } + + return ( +
+
+ +

生成回覆建議

+
+ + {/* Original Comment */} +
+
+
+ +
+
+
+
+
{comment.author}
+
{comment.platform} · {comment.timestamp}
+
+
+ {comment.sentiment && ( +
+ {comment.sentiment === 'positive' && } + {comment.sentiment === 'neutral' && } + {comment.sentiment === 'negative' && } + {getSentimentLabel(comment.sentiment)} +
+ )} +
+
+
+
+

{comment.content}

+ +
+ {comment.keywords?.map(keyword => ( + + {keyword} + + ))} +
+ +
+
+ + {comment.likes} 讚 +
+ + {comment.category && ( + + {comment.category} + + )} +
+
+ + {/* Tone Selection */} +
+ +
+ {tones.map(tone => ( + + ))} +
+
+ + {/* Persona Selection */} +
+ +
+ {personas.map(persona => ( + + ))} +
+
+ + {/* Generate Button */} + + + {/* Generated Replies */} + {generatedReplies.length > 0 && ( +
+

回覆建議

+
+ {generatedReplies.map((reply, index) => ( +
+

{reply}

+ +
+ ))} +
+
+ )} +
+ ); +}; + +// Helper function to get sentiment badge colors +function getSentimentBadgeColor(sentiment: string): string { + switch (sentiment) { + case 'positive': + return 'bg-green-100 text-green-800'; + case 'neutral': + return 'bg-gray-100 text-gray-800'; + case 'negative': + return 'bg-red-100 text-red-800'; + default: + return 'bg-gray-100 text-gray-800'; + } +} + +// Helper function to get sentiment labels +function getSentimentLabel(sentiment: string): string { + switch (sentiment) { + case 'positive': + return '正面'; + case 'neutral': + return '中性'; + case 'negative': + return '負面'; + default: + return '未知'; + } +} + +export default ReplyGenerator; \ No newline at end of file diff --git a/extension/src/sidebar/components/Settings.tsx b/extension/src/sidebar/components/Settings.tsx new file mode 100644 index 0000000..93cd405 --- /dev/null +++ b/extension/src/sidebar/components/Settings.tsx @@ -0,0 +1,239 @@ +import React, { useState, useEffect } from 'react'; +import { Save, Settings as SettingsIcon } from 'lucide-react'; +import { ReplyTone, ReplyPersona, SettingsData } from '../../types'; + +const Settings: React.FC = () => { + const [settings, setSettings] = useState({ + defaultTone: 'friendly', + defaultPersona: 'brand', + autoDetectPlatform: true, + language: 'zh-TW', + maxComments: 50 + }); + + const [isSaving, setIsSaving] = useState(false); + const [saveSuccess, setSaveSuccess] = useState(false); + + const tones: ReplyTone[] = [ + { id: 'friendly', name: '友善', description: '溫暖親切的語氣' }, + { id: 'professional', name: '專業', description: '正式且專業的語氣' }, + { id: 'casual', name: '輕鬆', description: '隨意輕鬆的對話風格' }, + { id: 'enthusiastic', name: '熱情', description: '充滿活力與熱情' }, + { id: 'empathetic', name: '同理心', description: '表達理解與關懷' } + ]; + + const personas: ReplyPersona[] = [ + { id: 'brand', name: '品牌代表', description: '以品牌官方身份回覆' }, + { id: 'support', name: '客服人員', description: '以客服專員身份回覆' }, + { id: 'expert', name: '領域專家', description: '以專業人士身份回覆' }, + { id: 'friend', name: '朋友', description: '以朋友身份回覆' } + ]; + + useEffect(() => { + // Load settings - with fallback for development environment + const loadSettings = () => { + try { + if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) { + // We're in a Chrome extension environment + chrome.storage.sync.get(['defaultTone', 'defaultPersona', 'autoDetectPlatform', 'language', 'maxComments'], (result) => { + setSettings(prev => ({ + ...prev, + ...result + })); + }); + } else { + // We're in development mode - use localStorage + console.log('Using localStorage for settings in development mode'); + const savedSettings = localStorage.getItem('commentAssistantSettings'); + if (savedSettings) { + try { + const parsedSettings = JSON.parse(savedSettings); + setSettings(prev => ({ + ...prev, + ...parsedSettings + })); + } catch (e) { + console.error('Error parsing saved settings:', e); + } + } + } + } catch (error) { + console.error('Error loading settings:', error); + } + }; + + loadSettings(); + }, []); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value, type } = e.target; + + if (type === 'checkbox') { + const checked = (e.target as HTMLInputElement).checked; + setSettings(prev => ({ + ...prev, + [name]: checked + })); + } else { + setSettings(prev => ({ + ...prev, + [name]: value + })); + } + }; + + const saveSettings = () => { + setIsSaving(true); + + try { + if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) { + // We're in a Chrome extension environment + chrome.storage.sync.set(settings, () => { + setIsSaving(false); + setSaveSuccess(true); + + setTimeout(() => { + setSaveSuccess(false); + }, 2000); + }); + } else { + // We're in development mode - use localStorage + localStorage.setItem('commentAssistantSettings', JSON.stringify(settings)); + + // Simulate async operation + setTimeout(() => { + setIsSaving(false); + setSaveSuccess(true); + + setTimeout(() => { + setSaveSuccess(false); + }, 2000); + }, 500); + } + } catch (error) { + console.error('Error saving settings:', error); + setIsSaving(false); + } + }; + + return ( +
+
+ +

設置

+
+ +
+

回覆設置

+ +
+ + +
+ +
+ + +
+
+ +
+

一般設置

+ +
+ +
+ +
+ + +
+ +
+ + +
+ 10 + 50 + 100 +
+
+
+ + +
+ ); +}; + +export default Settings; \ No newline at end of file diff --git a/extension/src/sidebar/main.tsx b/extension/src/sidebar/main.tsx new file mode 100644 index 0000000..98197ac --- /dev/null +++ b/extension/src/sidebar/main.tsx @@ -0,0 +1,55 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import Sidebar from './Sidebar'; +import '../index.css'; +import ErrorBoundary from '../ErrorBoundary'; + +// Error boundary for the sidebar +const renderSidebar = () => { + try { + const rootElement = document.getElementById('root'); + if (!rootElement) { + console.error('Root element not found'); + return; + } + + createRoot(rootElement).render( + + + + + + ); + } catch (error) { + console.error('Error rendering sidebar:', error); + + // Render a fallback UI in case of error + const rootElement = document.getElementById('root'); + if (rootElement) { + rootElement.innerHTML = ` +
+

Sidebar Error

+

Sorry, something went wrong while loading the sidebar.

+

Error details: ${error instanceof Error ? error.message : String(error)}

+ +
+ `; + } + } +}; + +// Disable Vite's error overlay to prevent WebSocket connection attempts +window.addEventListener('error', (event) => { + event.preventDefault(); + console.error('Caught error:', event.error); + return true; +}); + +// Disable Vite's HMR client +if (import.meta.hot) { + import.meta.hot.decline(); +} + +renderSidebar(); \ No newline at end of file diff --git a/extension/src/types.ts b/extension/src/types.ts new file mode 100644 index 0000000..87e3cd6 --- /dev/null +++ b/extension/src/types.ts @@ -0,0 +1,49 @@ +export interface Comment { + id: string; + author: string; + content: string; + timestamp: string; + likes: number; + replies?: Comment[]; + platform: 'facebook' | 'instagram' | 'twitter' | 'youtube' | 'linkedin' | 'other'; + sentiment?: 'positive' | 'neutral' | 'negative'; + keywords?: string[]; + category?: string; +} + +export interface ReplyTone { + id: string; + name: string; + description: string; +} + +export interface ReplyPersona { + id: string; + name: string; + description: string; +} + +export interface SettingsData { + defaultTone: string; + defaultPersona: string; + autoDetectPlatform: boolean; + language: 'zh-TW' | 'en-US'; + maxComments: number; +} + +export interface CommentFilter { + searchTerm: string; + platform: string; + sortBy: 'newest' | 'oldest' | 'likes' | 'replies'; + sentiment?: 'positive' | 'neutral' | 'negative' | 'all'; +} + +export interface CommentAnalytics { + sentimentCounts: { + positive: number; + neutral: number; + negative: number; + }; + topKeywords: Array<{keyword: string, count: number}>; + categories: Record; +} \ No newline at end of file diff --git a/extension/src/vite-env.d.ts b/extension/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/extension/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/extension/tailwind.config.js b/extension/tailwind.config.js new file mode 100644 index 0000000..d21f1cd --- /dev/null +++ b/extension/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/extension/tsconfig.app.json b/extension/tsconfig.app.json new file mode 100644 index 0000000..f0a2350 --- /dev/null +++ b/extension/tsconfig.app.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/extension/tsconfig.json b/extension/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/extension/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/extension/tsconfig.node.json b/extension/tsconfig.node.json new file mode 100644 index 0000000..0d3d714 --- /dev/null +++ b/extension/tsconfig.node.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/extension/vite.config.ts b/extension/vite.config.ts new file mode 100644 index 0000000..67cfcd8 --- /dev/null +++ b/extension/vite.config.ts @@ -0,0 +1,25 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { crx } from '@crxjs/vite-plugin'; +import manifest from './manifest.json'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + react(), + crx({ manifest }), + ], + optimizeDeps: { + exclude: ['lucide-react'], + }, + build: { + rollupOptions: { + input: { + sidebar: 'sidebar.html', + }, + }, + }, + server: { + hmr: false, // Completely disable HMR to prevent WebSocket connection attempts + }, +}); \ No newline at end of file diff --git a/web/.bolt/config.json b/web/.bolt/config.json new file mode 100644 index 0000000..6b6787d --- /dev/null +++ b/web/.bolt/config.json @@ -0,0 +1,3 @@ +{ + "template": "bolt-vite-react-ts" +} diff --git a/web/.bolt/prompt b/web/.bolt/prompt new file mode 100644 index 0000000..d0c0a8f --- /dev/null +++ b/web/.bolt/prompt @@ -0,0 +1,8 @@ +For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production. + +By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them. + +Use icons from lucide-react for logos. + +Use stock photos from unsplash where appropriate, only valid URLs you know exist. Do not download the images, only link to them in image tags. + diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/web/eslint.config.js b/web/eslint.config.js new file mode 100644 index 0000000..82c2e20 --- /dev/null +++ b/web/eslint.config.js @@ -0,0 +1,28 @@ +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 }, + ], + }, + } +); diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..92b0ee3 --- /dev/null +++ b/web/index.html @@ -0,0 +1,13 @@ + + + + + + + 社群留言管理系統 + + +
+ + + \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..2b23317 --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,4061 @@ +{ + "name": "social-media-comment-management", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "social-media-comment-management", + "version": "0.0.0", + "dependencies": { + "date-fns": "^3.3.1", + "lucide-react": "^0.344.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.9.1", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.18", + "eslint": "^9.9.1", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.11", + "globals": "^15.9.0", + "postcss": "^8.4.35", + "tailwindcss": "^3.4.1", + "typescript": "^5.5.3", + "typescript-eslint": "^8.3.0", + "vite": "^5.4.2" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.25.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.7.tgz", + "integrity": "sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.7.tgz", + "integrity": "sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helpers": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7", + "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.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", + "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", + "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.7.tgz", + "integrity": "sha512-JD9MUnLbPL0WdVK8AWC7F7tTG2OS6u/AKKnsK+NdRhUiVdnzyR1S3kKQCaRLOiaULvUiqK6Z4JQE635VgtCFeg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.7.tgz", + "integrity": "sha512-S/JXG/KrbIY06iyJPKfxr0qRxnhNOdkNXYBl/rmwgDd72cQLH9tEGkDm/yJPGvcSIUoikzfjMios9i+xT/uv9w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", + "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "to-fast-properties": "^2.0.0" + }, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", + "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", + "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", + "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", + "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", + "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.0", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "dev": true, + "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, + "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.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "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, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", + "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", + "integrity": "sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/type-utils": "8.8.1", + "@typescript-eslint/utils": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", + "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", + "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.1.tgz", + "integrity": "sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/utils": "8.8.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", + "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", + "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.1.tgz", + "integrity": "sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", + "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz", + "integrity": "sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "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" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001667", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", + "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", + "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" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "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 + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.33", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.33.tgz", + "integrity": "sha512-+cYTcFB1QqD4j4LegwLfpCNxifb6dDFUAwk6RsLusCwIaZI6or2f+q8rs5tTB2YC53HhOlIbEaqHMAAC8IOIwA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "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, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", + "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.6.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.12.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.1.0-rc-fb9a90fa48-20240614", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-fb9a90fa48-20240614.tgz", + "integrity": "sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.12.tgz", + "integrity": "sha512-9neVjoGv20FwYtCP6CB1dzR1vr57ZDNOXst21wd2xJ/cTlM2xLq0GWVlSNTdMn/4BtP6cHYBMCSp1wFBJ9jBsg==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "dev": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "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, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "15.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", + "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "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==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "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, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "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==", + "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, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.344.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.344.0.tgz", + "integrity": "sha512-6YyBnn91GB45VuVT96bYCOKElbJzUHqp65vX8cDcu55MQL9T969v4dhGClpljamuI/+KMO9P6w9Acq1CVQGvIQ==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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 + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "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==", + "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==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.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==", + "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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", + "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.8.1.tgz", + "integrity": "sha512-R0dsXFt6t4SAFjUSKFjMh4pXDtq04SsFKCVGDP3ZOzNP7itF0jBcZYU4fMsZr4y7O7V7Nc751dDeESbe4PbQMQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.8.1", + "@typescript-eslint/parser": "8.8.1", + "@typescript-eslint/utils": "8.8.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "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" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "dev": true, + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..99a07f3 --- /dev/null +++ b/web/package.json @@ -0,0 +1,39 @@ +{ + "name": "social-media-comment-management", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/icons": "^5.6.1", + "antd": "^5.24.3", + "axios": "^1.8.1", + "date-fns": "^3.6.0", + "lucide-react": "^0.344.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.3.0" + }, + "devDependencies": { + "@eslint/js": "^9.9.1", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.18", + "eslint": "^9.9.1", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.11", + "globals": "^15.9.0", + "postcss": "^8.4.35", + "tailwindcss": "^3.4.1", + "typescript": "^5.5.3", + "typescript-eslint": "^8.3.0", + "vite": "^5.4.2" + }, + "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml new file mode 100644 index 0000000..ddd2f58 --- /dev/null +++ b/web/pnpm-lock.yaml @@ -0,0 +1,3709 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ant-design/icons': + specifier: ^5.6.1 + version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + antd: + specifier: ^5.24.3 + version: 5.24.3(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + axios: + specifier: ^1.8.1 + version: 1.8.1 + date-fns: + specifier: ^3.6.0 + version: 3.6.0 + lucide-react: + specifier: ^0.344.0 + version: 0.344.0(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^7.3.0 + version: 7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + devDependencies: + '@eslint/js': + specifier: ^9.9.1 + version: 9.21.0 + '@types/react': + specifier: ^18.3.5 + version: 18.3.18 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.5(@types/react@18.3.18) + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.3.4(vite@5.4.14) + autoprefixer: + specifier: ^10.4.18 + version: 10.4.20(postcss@8.5.3) + eslint: + specifier: ^9.9.1 + version: 9.21.0(jiti@1.21.7) + eslint-plugin-react-hooks: + specifier: ^5.1.0-rc.0 + version: 5.2.0(eslint@9.21.0(jiti@1.21.7)) + eslint-plugin-react-refresh: + specifier: ^0.4.11 + version: 0.4.19(eslint@9.21.0(jiti@1.21.7)) + globals: + specifier: ^15.9.0 + version: 15.15.0 + postcss: + specifier: ^8.4.35 + version: 8.5.3 + tailwindcss: + specifier: ^3.4.1 + version: 3.4.17 + typescript: + specifier: ^5.5.3 + version: 5.8.2 + typescript-eslint: + specifier: ^8.3.0 + version: 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + vite: + specifier: ^5.4.2 + version: 5.4.14 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@ant-design/colors@7.2.0': + resolution: {integrity: sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==} + + '@ant-design/cssinjs-utils@1.1.3': + resolution: {integrity: sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@ant-design/cssinjs@1.23.0': + resolution: {integrity: sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@2.0.6': + resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==} + engines: {node: '>=8.x'} + + '@ant-design/icons-svg@4.4.2': + resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} + + '@ant-design/icons@5.6.1': + resolution: {integrity: sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/react-slick@1.1.2': + resolution: {integrity: sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==} + peerDependencies: + react: '>=16.9.0' + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.9': + resolution: {integrity: sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.9': + resolution: {integrity: sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.26.5': + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.9': + resolution: {integrity: sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.9': + resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.26.9': + resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.26.9': + resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.26.9': + resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.9': + resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} + engines: {node: '>=6.9.0'} + + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + + '@emotion/unitless@0.7.5': + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.19.2': + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.12.0': + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.0': + resolution: {integrity: sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.21.0': + resolution: {integrity: sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.7': + resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + engines: {node: '>=18.18'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rc-component/async-validator@5.0.4': + resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==} + engines: {node: '>=14.x'} + + '@rc-component/color-picker@2.0.1': + resolution: {integrity: sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/context@1.4.0': + resolution: {integrity: sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mini-decimal@1.1.0': + resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==} + engines: {node: '>=8.x'} + + '@rc-component/mutate-observer@1.1.0': + resolution: {integrity: sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/portal@1.1.2': + resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/qrcode@1.0.0': + resolution: {integrity: sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tour@1.15.1': + resolution: {integrity: sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/trigger@2.2.6': + resolution: {integrity: sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rollup/rollup-android-arm-eabi@4.34.9': + resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.34.9': + resolution: {integrity: sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.34.9': + resolution: {integrity: sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.34.9': + resolution: {integrity: sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.34.9': + resolution: {integrity: sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.34.9': + resolution: {integrity: sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': + resolution: {integrity: sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.34.9': + resolution: {integrity: sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.34.9': + resolution: {integrity: sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.34.9': + resolution: {integrity: sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': + resolution: {integrity: sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': + resolution: {integrity: sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.34.9': + resolution: {integrity: sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.34.9': + resolution: {integrity: sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.34.9': + resolution: {integrity: sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.34.9': + resolution: {integrity: sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.34.9': + resolution: {integrity: sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.34.9': + resolution: {integrity: sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.34.9': + resolution: {integrity: sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==} + cpu: [x64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/prop-types@15.7.14': + resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} + + '@types/react-dom@18.3.5': + resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.18': + resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + + '@typescript-eslint/eslint-plugin@8.26.0': + resolution: {integrity: sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/parser@8.26.0': + resolution: {integrity: sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.26.0': + resolution: {integrity: sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.26.0': + resolution: {integrity: sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/types@8.26.0': + resolution: {integrity: sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.26.0': + resolution: {integrity: sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.26.0': + resolution: {integrity: sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.26.0': + resolution: {integrity: sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + antd@5.24.3: + resolution: {integrity: sha512-H5fopyOVRAnegfwLuEdjhPR+l5z3/lo4aQyDsgIYhfmeBcRgN/XNkefVxzRHNuWHeYr9E9LbyxEQcMF91sy5lg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + axios@1.8.1: + resolution: {integrity: sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001702: + resolution: {integrity: sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.112: + resolution: {integrity: sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.19: + resolution: {integrity: sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.21.0: + resolution: {integrity: sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json2mq@0.2.0: + resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.344.0: + resolution: {integrity: sha512-6YyBnn91GB45VuVT96bYCOKElbJzUHqp65vX8cDcu55MQL9T969v4dhGClpljamuI/+KMO9P6w9Acq1CVQGvIQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + rc-cascader@3.33.1: + resolution: {integrity: sha512-Kyl4EJ7ZfCBuidmZVieegcbFw0RcU5bHHSbtEdmuLYd0fYHCAiYKZ6zon7fWAVyC6rWWOOib0XKdTSf7ElC9rg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-checkbox@3.5.0: + resolution: {integrity: sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-collapse@3.9.0: + resolution: {integrity: sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dialog@9.6.0: + resolution: {integrity: sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-drawer@7.2.0: + resolution: {integrity: sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dropdown@4.2.1: + resolution: {integrity: sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==} + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' + + rc-field-form@2.7.0: + resolution: {integrity: sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-image@7.11.0: + resolution: {integrity: sha512-aZkTEZXqeqfPZtnSdNUnKQA0N/3MbgR7nUnZ+/4MfSFWPFHZau4p5r5ShaI0KPEMnNjv4kijSCFq/9wtJpwykw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input-number@9.4.0: + resolution: {integrity: sha512-Tiy4DcXcFXAf9wDhN8aUAyMeCLHJUHA/VA/t7Hj8ZEx5ETvxG7MArDOSE6psbiSCo+vJPm4E3fGN710ITVn6GA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input@1.7.3: + resolution: {integrity: sha512-A5w4egJq8+4JzlQ55FfQjDnPvOaAbzwC3VLOAdOytyek3TboSOP9qxN+Gifup+shVXfvecBLBbWBpWxmk02SWQ==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-mentions@2.19.1: + resolution: {integrity: sha512-KK3bAc/bPFI993J3necmaMXD2reZTzytZdlTvkeBbp50IGH1BDPDvxLdHDUrpQx2b2TGaVJsn+86BvYa03kGqA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-menu@9.16.1: + resolution: {integrity: sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-motion@2.9.5: + resolution: {integrity: sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-notification@5.6.3: + resolution: {integrity: sha512-42szwnn8VYQoT6GnjO00i1iwqV9D1TTMvxObWsuLwgl0TsOokzhkYiufdtQBsJMFjJravS1hfDKVMHLKLcPE4g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-overflow@1.4.1: + resolution: {integrity: sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-pagination@5.1.0: + resolution: {integrity: sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-picker@4.11.3: + resolution: {integrity: sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==} + engines: {node: '>=8.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + rc-progress@4.0.0: + resolution: {integrity: sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-rate@2.13.1: + resolution: {integrity: sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-resize-observer@1.4.3: + resolution: {integrity: sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-segmented@2.7.0: + resolution: {integrity: sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-select@14.16.6: + resolution: {integrity: sha512-YPMtRPqfZWOm2XGTbx5/YVr1HT0vn//8QS77At0Gjb3Lv+Lbut0IORJPKLWu1hQ3u4GsA0SrDzs7nI8JG7Zmyg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-slider@11.1.8: + resolution: {integrity: sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-steps@6.0.1: + resolution: {integrity: sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-switch@4.1.0: + resolution: {integrity: sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-table@7.50.4: + resolution: {integrity: sha512-Y+YuncnQqoS5e7yHvfvlv8BmCvwDYDX/2VixTBEhkMDk9itS9aBINp4nhzXFKiBP/frG4w0pS9d9Rgisl0T1Bw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tabs@15.5.1: + resolution: {integrity: sha512-yiWivLAjEo5d1v2xlseB2dQocsOhkoVSfo1krS8v8r+02K+TBUjSjXIf7dgyVSxp6wRIPv5pMi5hanNUlQMgUA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-textarea@1.9.0: + resolution: {integrity: sha512-dQW/Bc/MriPBTugj2Kx9PMS5eXCCGn2cxoIaichjbNvOiARlaHdI99j4DTxLl/V8+PIfW06uFy7kjfUIDDKyxQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tooltip@6.4.0: + resolution: {integrity: sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tree-select@5.27.0: + resolution: {integrity: sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==} + peerDependencies: + react: '*' + react-dom: '*' + + rc-tree@5.13.1: + resolution: {integrity: sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-upload@4.8.1: + resolution: {integrity: sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-util@5.44.4: + resolution: {integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-virtual-list@3.18.4: + resolution: {integrity: sha512-qkurwgc4Je4xJaYe1DprDl2fwtfEZcuC4UhsJRiX2YZ6wSZAUPQXH/lIX+ZRtNEWmz3pzSBQ7NX3Csjp0wCtcg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react-router-dom@7.3.0: + resolution: {integrity: sha512-z7Q5FTiHGgQfEurX/FBinkOXhWREJIAB2RiU24lvcBa82PxUpwqvs/PAXb9lJyPjTs2jrl6UkLvCZVGJPeNuuQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.3.0: + resolution: {integrity: sha512-466f2W7HIWaNXTKM5nHTqNxLrHTyXybm7R0eBlVSt0k/u55tTCDO194OIx/NrYD4TS5SXKTNekXfT37kMKUjgw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.34.9: + resolution: {integrity: sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + string-convert@0.2.1: + resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.17: + resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} + engines: {node: '>=14.0.0'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + + ts-api-utils@2.0.1: + resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + turbo-stream@2.4.0: + resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.26.0: + resolution: {integrity: sha512-PtVz9nAnuNJuAVeUFvwztjuUgSnJInODAUx47VDwWPXzd5vismPOtPtt83tzNXyOjVQbPRp786D6WFW/M2koIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@5.4.14: + resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + 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 + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.7.0: + resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} + engines: {node: '>= 14'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@ant-design/colors@7.2.0': + dependencies: + '@ant-design/fast-color': 2.0.6 + + '@ant-design/cssinjs-utils@1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/cssinjs': 1.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@babel/runtime': 7.26.9 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@ant-design/cssinjs@1.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + classnames: 2.5.1 + csstype: 3.1.3 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + stylis: 4.3.6 + + '@ant-design/fast-color@2.0.6': + dependencies: + '@babel/runtime': 7.26.9 + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/colors': 7.2.0 + '@ant-design/icons-svg': 4.4.2 + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@ant-design/react-slick@1.1.2(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + json2mq: 0.2.0 + react: 18.3.1 + resize-observer-polyfill: 1.5.1 + throttle-debounce: 5.0.2 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.8': {} + + '@babel/core@7.26.9': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.9 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) + '@babel/helpers': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.26.9': + dependencies: + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.26.5': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.9)': + dependencies: + '@babel/core': 7.26.9 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.26.9': + dependencies: + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 + + '@babel/parser@7.26.9': + dependencies: + '@babel/types': 7.26.9 + + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.9)': + dependencies: + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.9)': + dependencies: + '@babel/core': 7.26.9 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/runtime@7.26.9': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.26.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + + '@babel/traverse@7.26.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.9': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@emotion/hash@0.8.0': {} + + '@emotion/unitless@0.7.5': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.4.1(eslint@9.21.0(jiti@1.21.7))': + dependencies: + eslint: 9.21.0(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.19.2': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.12.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.0': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.21.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.7': + dependencies: + '@eslint/core': 0.12.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rc-component/async-validator@5.0.4': + dependencies: + '@babel/runtime': 7.26.9 + + '@rc-component/color-picker@2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/fast-color': 2.0.6 + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/context@1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/mini-decimal@1.1.0': + dependencies: + '@babel/runtime': 7.26.9 + + '@rc-component/mutate-observer@1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/portal@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/qrcode@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/tour@1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/trigger@2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rollup/rollup-android-arm-eabi@4.34.9': + optional: true + + '@rollup/rollup-android-arm64@4.34.9': + optional: true + + '@rollup/rollup-darwin-arm64@4.34.9': + optional: true + + '@rollup/rollup-darwin-x64@4.34.9': + optional: true + + '@rollup/rollup-freebsd-arm64@4.34.9': + optional: true + + '@rollup/rollup-freebsd-x64@4.34.9': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.34.9': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.34.9': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-x64-musl@4.34.9': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.34.9': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.34.9': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.34.9': + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.26.9 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.26.9 + + '@types/cookie@0.6.0': {} + + '@types/estree@1.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/prop-types@15.7.14': {} + + '@types/react-dom@18.3.5(@types/react@18.3.18)': + dependencies: + '@types/react': 18.3.18 + + '@types/react@18.3.18': + dependencies: + '@types/prop-types': 15.7.14 + csstype: 3.1.3 + + '@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2))(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + '@typescript-eslint/scope-manager': 8.26.0 + '@typescript-eslint/type-utils': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + '@typescript-eslint/utils': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.26.0 + eslint: 9.21.0(jiti@1.21.7) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.0.1(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.26.0 + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.26.0 + debug: 4.4.0 + eslint: 9.21.0(jiti@1.21.7) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.26.0': + dependencies: + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/visitor-keys': 8.26.0 + + '@typescript-eslint/type-utils@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2)': + dependencies: + '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2) + '@typescript-eslint/utils': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + debug: 4.4.0 + eslint: 9.21.0(jiti@1.21.7) + ts-api-utils: 2.0.1(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.26.0': {} + + '@typescript-eslint/typescript-estree@8.26.0(typescript@5.8.2)': + dependencies: + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/visitor-keys': 8.26.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.0.1(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.26.0 + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2) + eslint: 9.21.0(jiti@1.21.7) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.26.0': + dependencies: + '@typescript-eslint/types': 8.26.0 + eslint-visitor-keys: 4.2.0 + + '@vitejs/plugin-react@4.3.4(vite@5.4.14)': + dependencies: + '@babel/core': 7.26.9 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.14 + transitivePeerDependencies: + - supports-color + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn@8.14.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + antd@5.24.3(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@ant-design/colors': 7.2.0 + '@ant-design/cssinjs': 1.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/cssinjs-utils': 1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/fast-color': 2.0.6 + '@ant-design/icons': 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/react-slick': 1.1.2(react@18.3.1) + '@babel/runtime': 7.26.9 + '@rc-component/color-picker': 2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/mutate-observer': 1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/qrcode': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/tour': 1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + copy-to-clipboard: 3.3.3 + dayjs: 1.11.13 + rc-cascader: 3.33.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-checkbox: 3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-collapse: 3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-drawer: 7.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-field-form: 2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-image: 7.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-input: 1.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-input-number: 9.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-mentions: 2.19.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-notification: 5.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-pagination: 5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-picker: 4.11.3(date-fns@3.6.0)(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-progress: 4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-rate: 2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-segmented: 2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-select: 14.16.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-slider: 11.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-steps: 6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-switch: 4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-table: 7.50.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tabs: 15.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-textarea: 1.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tooltip: 6.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree-select: 5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-upload: 4.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + asynckit@0.4.0: {} + + autoprefixer@10.4.20(postcss@8.5.3): + dependencies: + browserslist: 4.24.4 + caniuse-lite: 1.0.30001702 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + + axios@1.8.1: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001702 + electron-to-chromium: 1.5.112 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001702: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + classnames@2.5.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@4.1.1: {} + + compute-scroll-into-view@3.1.1: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.0.2: {} + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + date-fns@3.6.0: {} + + dayjs@1.11.13: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.112: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.21.5: + 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 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.21.0(jiti@1.21.7)): + dependencies: + eslint: 9.21.0(jiti@1.21.7) + + eslint-plugin-react-refresh@0.4.19(eslint@9.21.0(jiti@1.21.7)): + dependencies: + eslint: 9.21.0(jiti@1.21.7) + + eslint-scope@8.2.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.21.0(jiti@1.21.7): + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@1.21.7)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.19.2 + '@eslint/core': 0.12.0 + '@eslint/eslintrc': 3.3.0 + '@eslint/js': 9.21.0 + '@eslint/plugin-kit': 0.2.7 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + follow-redirects@1.15.9: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + + fraction.js@4.3.7: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globals@15.15.0: {} + + gopd@1.2.0: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.7: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json2mq@0.2.0: + dependencies: + string-convert: 0.2.1 + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.344.0(react@18.3.1): + dependencies: + react: 18.3.1 + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.8: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.19: {} + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + pify@2.3.0: {} + + pirates@4.0.6: {} + + postcss-import@15.1.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.10 + + postcss-js@4.0.1(postcss@8.5.3): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.3 + + postcss-load-config@4.0.2(postcss@8.5.3): + dependencies: + lilconfig: 3.1.3 + yaml: 2.7.0 + optionalDependencies: + postcss: 8.5.3 + + postcss-nested@6.2.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.3: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + rc-cascader@3.33.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-select: 14.16.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-checkbox@3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-collapse@3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-dialog@9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-drawer@7.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-dropdown@4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-field-form@2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/async-validator': 5.0.4 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-image@7.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-input-number@9.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/mini-decimal': 1.1.0 + classnames: 2.5.1 + rc-input: 1.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-input@1.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-mentions@2.19.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-input: 1.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-textarea: 1.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-menu@9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-motion@2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-notification@5.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-overflow@1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-pagination@5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-picker@4.11.3(date-fns@3.6.0)(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + date-fns: 3.6.0 + dayjs: 1.11.13 + + rc-progress@4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-rate@2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-resize-observer@1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + resize-observer-polyfill: 1.5.1 + + rc-segmented@2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-select@14.16.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-slider@11.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-steps@6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-switch@4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-table@7.50.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/context': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tabs@15.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-textarea@1.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-input: 1.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tooltip@6.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tree-select@5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-select: 14.16.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tree@5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-upload@4.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-util@5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + + rc-virtual-list@3.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.9 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-is@18.3.1: {} + + react-refresh@0.14.2: {} + + react-router-dom@7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + + react-router@7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@types/cookie': 0.6.0 + cookie: 1.0.2 + react: 18.3.1 + set-cookie-parser: 2.7.1 + turbo-stream: 2.4.0 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + regenerator-runtime@0.14.1: {} + + resize-observer-polyfill@1.5.1: {} + + resolve-from@4.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rollup@4.34.9: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.34.9 + '@rollup/rollup-android-arm64': 4.34.9 + '@rollup/rollup-darwin-arm64': 4.34.9 + '@rollup/rollup-darwin-x64': 4.34.9 + '@rollup/rollup-freebsd-arm64': 4.34.9 + '@rollup/rollup-freebsd-x64': 4.34.9 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.9 + '@rollup/rollup-linux-arm-musleabihf': 4.34.9 + '@rollup/rollup-linux-arm64-gnu': 4.34.9 + '@rollup/rollup-linux-arm64-musl': 4.34.9 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.9 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.9 + '@rollup/rollup-linux-riscv64-gnu': 4.34.9 + '@rollup/rollup-linux-s390x-gnu': 4.34.9 + '@rollup/rollup-linux-x64-gnu': 4.34.9 + '@rollup/rollup-linux-x64-musl': 4.34.9 + '@rollup/rollup-win32-arm64-msvc': 4.34.9 + '@rollup/rollup-win32-ia32-msvc': 4.34.9 + '@rollup/rollup-win32-x64-msvc': 4.34.9 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + + semver@6.3.1: {} + + semver@7.7.1: {} + + set-cookie-parser@2.7.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + string-convert@0.2.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-json-comments@3.1.1: {} + + stylis@4.3.6: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.17: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.3 + postcss-import: 15.1.0(postcss@8.5.3) + postcss-js: 4.0.1(postcss@8.5.3) + postcss-load-config: 4.0.2(postcss@8.5.3) + postcss-nested: 6.2.0(postcss@8.5.3) + postcss-selector-parser: 6.1.2 + resolve: 1.22.10 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + throttle-debounce@5.0.2: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toggle-selection@1.0.6: {} + + ts-api-utils@2.0.1(typescript@5.8.2): + dependencies: + typescript: 5.8.2 + + ts-interface-checker@0.1.13: {} + + turbo-stream@2.4.0: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2): + dependencies: + '@typescript-eslint/eslint-plugin': 8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2))(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + '@typescript-eslint/parser': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + '@typescript-eslint/utils': 8.26.0(eslint@9.21.0(jiti@1.21.7))(typescript@5.8.2) + eslint: 9.21.0(jiti@1.21.7) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + typescript@5.8.2: {} + + update-browserslist-db@1.1.3(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite@5.4.14: + dependencies: + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.34.9 + optionalDependencies: + fsevents: 2.3.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + yallist@3.1.1: {} + + yaml@2.7.0: {} + + yocto-queue@0.1.0: {} diff --git a/web/postcss.config.js b/web/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/web/src/App.tsx b/web/src/App.tsx new file mode 100644 index 0000000..c961594 --- /dev/null +++ b/web/src/App.tsx @@ -0,0 +1,114 @@ +import React from 'react'; +import { BrowserRouter as Router, Routes, Route, Navigate, useLocation } from 'react-router-dom'; +import { AuthProvider, useAuth, User } from './context/AuthContext'; +import Login from './components/Login'; +import Header from './components/Header'; +import Sidebar from './components/Sidebar'; +import CommentList from './components/CommentList'; +import PostList from './components/PostList'; +import Dashboard from './components/Dashboard'; +import Analytics from './components/Analytics'; +import ProtectedRoute from './components/ProtectedRoute'; + +const AppContent = () => { + const { isAuthenticated, login, loading } = useAuth(); + const [sidebarOpen, setSidebarOpen] = React.useState(false); + const [activePage, setActivePage] = React.useState('dashboard'); + const location = useLocation(); + + // 添加更多调试信息 + React.useEffect(() => { + console.log('AppContent - Auth state updated:', { isAuthenticated, loading, path: location.pathname }); + }, [isAuthenticated, loading, location.pathname]); + + // Update active page based on URL + React.useEffect(() => { + if (location.pathname === '/') { + setActivePage('dashboard'); + } else if (location.pathname === '/comments') { + setActivePage('comments'); + } else if (location.pathname === '/posts') { + setActivePage('posts'); + } else if (location.pathname === '/analytics') { + setActivePage('analytics'); + } + }, [location]); + + // Show loading spinner while checking authentication status + if (loading) { + console.log('AppContent - Still loading auth state...'); + return ( +
+
+
+ ); + } + + // Handle successful login + const handleLoginSuccess = (token: string, user: User) => { + console.log('AppContent - Login success, calling login function with:', { user }); + login(token, user); + }; + + // Render main app layout when authenticated + const renderAppLayout = () => { + return ( +
+ { + setActivePage(page); + setSidebarOpen(false); + }} + isOpen={sidebarOpen} + onClose={() => setSidebarOpen(false)} + /> +
+
setSidebarOpen(!sidebarOpen)} + /> + + } /> + } /> + } /> + } /> + } /> + +
+
+ ); + }; + + return ( + + : + + } + /> + + {renderAppLayout()} + + } + /> + + ); +}; + +function App() { + return ( + + + + + + ); +} + +export default App; \ No newline at end of file diff --git a/web/src/components/Analytics.tsx b/web/src/components/Analytics.tsx new file mode 100644 index 0000000..3f6c5d0 --- /dev/null +++ b/web/src/components/Analytics.tsx @@ -0,0 +1,815 @@ +import React, { useState, useEffect } from 'react'; +import { + BarChart2, + TrendingUp, + PieChart, + MessageSquare, + Facebook, + Twitter, + Instagram, + Linkedin, + BookOpen, + CheckCircle, + XCircle, + Clock, + ThumbsUp, + ThumbsDown, + Youtube, + Hash, + Users, + Heart, + Share2, + Eye, + ArrowRight, + ChevronDown, + Filter, + Download, + AlertTriangle +} from 'lucide-react'; +import axios from 'axios'; + +// Define interfaces for analytics data +interface AnalyticsData { + name: string; + value: number; + color?: string; +} + +interface TimelineData { + date: string; + comments: number; +} + +interface SentimentData { + positive: number; + neutral: number; + negative: number; +} + +interface Article { + id: string; + title: string; + views: number; + engagement: number; + platform: string; +} + +interface KOLData { + id: string; + name: string; + platform: string; + followers: number; + engagement: number; + posts: number; + sentiment: SentimentData; +} + +interface FunnelData { + stage: string; + count: number; + rate: number; +} + +const Analytics: React.FC = () => { + const [timeRange, setTimeRange] = useState('7days'); + const [selectedKOL, setSelectedKOL] = useState('all'); + const [selectedPlatform, setSelectedPlatform] = useState('all'); + const [platformData, setPlatformData] = useState([]); + const [timelineData, setTimelineData] = useState([]); + const [sentimentData, setSentimentData] = useState({ + positive: 0, + neutral: 0, + negative: 0 + }); + const [statusData, setStatusData] = useState([]); + const [popularArticles, setPopularArticles] = useState([]); + const [kolData, setKolData] = useState([]); + const [funnelData, setFunnelData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchAnalyticsData = async () => { + try { + setLoading(true); + // Fetch platform distribution + const platformResponse = await axios.get(`http://localhost:4000/api/analytics/platforms?timeRange=${timeRange}`); + setPlatformData(platformResponse.data || []); + + // Fetch timeline data + const timelineResponse = await axios.get(`http://localhost:4000/api/analytics/timeline?timeRange=${timeRange}`); + setTimelineData(timelineResponse.data || []); + + // Fetch sentiment data + const sentimentResponse = await axios.get(`http://localhost:4000/api/analytics/sentiment?timeRange=${timeRange}`); + setSentimentData(sentimentResponse.data || { positive: 0, neutral: 0, negative: 0 }); + + // Fetch status data + const statusResponse = await axios.get(`http://localhost:4000/api/analytics/status?timeRange=${timeRange}`); + setStatusData(statusResponse.data || []); + + // Fetch popular articles + const articlesResponse = await axios.get(`http://localhost:4000/api/analytics/popular-content?timeRange=${timeRange}`); + setPopularArticles(articlesResponse.data || []); + + // Fetch KOL data + const kolResponse = await axios.get(`http://localhost:4000/api/analytics/influencers?timeRange=${timeRange}`); + setKolData(kolResponse.data || []); + + // Fetch funnel data + const funnelResponse = await axios.get(`http://localhost:4000/api/analytics/conversion?timeRange=${timeRange}`); + setFunnelData(funnelResponse.data || []); + + setError(null); + } catch (err) { + console.error('Failed to fetch analytics data:', err); + setError('Failed to load analytics data. Please try again later.'); + } finally { + setLoading(false); + } + }; + + fetchAnalyticsData(); + }, [timeRange]); + + // 根據選擇的KOL和平台過濾數據 + const filteredKOLData = selectedKOL === 'all' + ? kolData + : kolData.filter(kol => kol.id === selectedKOL); + + const filteredEngagementData = selectedKOL === 'all' + ? kolData + : kolData.filter(item => item.id === selectedKOL); + + const getPlatformIcon = (platform: string) => { + switch (platform) { + case 'facebook': + return ; + case 'threads': + return ; + case 'instagram': + return ; + case 'linkedin': + return ; + case 'xiaohongshu': + return ; + case 'youtube': + return ; + default: + return null; + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case 'approved': + return ; + case 'rejected': + return ; + case 'pending': + return ; + default: + return null; + } + }; + + const getStatusName = (status: string) => { + switch (status) { + case 'approved': + return '已核准'; + case 'rejected': + return '已拒絕'; + case 'pending': + return '待審核'; + default: + return status; + } + }; + + const getPlatformColor = (platform: string) => { + switch (platform) { + case 'facebook': + return 'bg-blue-600'; + case 'threads': + return 'bg-black'; + case 'instagram': + return 'bg-pink-500'; + case 'linkedin': + return 'bg-blue-700'; + case 'xiaohongshu': + return 'bg-red-500'; + case 'youtube': + return 'bg-red-600'; + default: + return 'bg-gray-600'; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'approved': + return 'bg-green-600'; + case 'rejected': + return 'bg-red-600'; + case 'pending': + return 'bg-yellow-600'; + default: + return 'bg-gray-600'; + } + }; + + const getSentimentColor = (sentiment: string) => { + switch (sentiment) { + case 'positive': + return 'bg-green-500'; + case 'negative': + return 'bg-red-500'; + case 'neutral': + return 'bg-gray-500'; + case 'mixed': + return 'bg-yellow-500'; + default: + return 'bg-gray-500'; + } + }; + + const maxTimelineCount = Math.max(...timelineData.map(item => item.comments)); + + // 計算KOL表現排名 + const sortedKOLs = [...filteredKOLData].sort((a, b) => b.engagement - a.engagement); + + return ( +
+
+
+

數據分析

+
+
+ + + + + +
+ + +
+
+ + {/* KOL 表現概覽 */} +
+
+

KOL 表現概覽

+
+ 查看詳細報告 + +
+
+ +
+ + + + + + + + + + + + + + + {sortedKOLs.map((kol, index) => ( + + + + + + + + + + + ))} + +
+ KOL + + 平台 + + 貼文數 + + 總讚數 + + 總留言數 + + 互動率 + + 情緒指標 + + 官方互動 +
+
+
+ {kol.name} +
+
+
{kol.name}
+
{kol.followers} 粉絲
+
+ {index === 0 && ( + + Top KOL + + )} +
+
+
+ {kol.platforms.map(platform => ( +
+ {getPlatformIcon(platform)} +
+ ))} +
+
+ {kol.postCount} + +
+ + {kol.likeCount.toLocaleString()} +
+
+
+ + {kol.commentCount.toLocaleString()} +
+
+
+
{(kol.engagementRate * 100).toFixed(1)}%
+
0 ? 'text-green-500' : 'text-red-500'} flex items-center text-xs`}> + {kol.engagementTrend > 0 ? ( + <> + + +{kol.engagementTrend}% + + ) : ( + <> + + {kol.engagementTrend}% + + )} +
+
+
+
+
+
+
+ {kol.sentimentScore}% +
+
+ {kol.officialInteractions}次 +
+
+
+ + {/* 轉換漏斗 */} +
+

KOL 合作轉換漏斗

+
+
+ {funnelData.map((stage, index) => ( +
+
+ {stage.name}: {stage.count.toLocaleString()} +
+ {index < funnelData.length - 1 && ( +
+
+ + 轉換率: {((funnelData[index + 1].count / stage.count) * 100).toFixed(1)}% +
+
+ )} +
+ ))} +
+
+
+
+

平均轉換率

+

+ {((funnelData[funnelData.length - 1].count / funnelData[0].count) * 100).toFixed(1)}% +

+

從曝光到轉換的整體效率

+
+
+

最高轉換階段

+

互動 → 點擊

+

此階段轉換率高於平均值 15%

+
+
+

最低轉換階段

+

點擊 → 購買

+

此階段需要優化,低於平均值 23%

+
+
+
+ + {/* KOL 貼文表現 */} +
+

KOL 貼文表現

+
+ + + + + + + + + + + + + + + + {filteredEngagementData.map((post, index) => ( + + + + + + + + + + + + ))} + +
+ 貼文 + + KOL + + 平台 + + 發布日期 + + 觀看數 + + 讚數 + + 留言數 + + 分享數 + + 情緒指標 +
+
+
+ {post.title} +
+
{post.title}
+
+
+
+
+ k.id === post.kolId)?.avatar || ''} + alt={kolData.find(k => k.id === post.kolId)?.name || ''} + className="h-full w-full object-cover" + /> +
+
+ {kolData.find(k => k.id === post.kolId)?.name || ''} +
+
+
+
+ {getPlatformIcon(post.platform)} + + {post.platform === 'xiaohongshu' ? '小紅書' : post.platform} + +
+
+ {post.date} + +
+ + {post.views.toLocaleString()} +
+
+
+ + {post.likes.toLocaleString()} +
+
+
+ + {post.comments.toLocaleString()} +
+
+
+ + {post.shares.toLocaleString()} +
+
+
+
+
+
+ {post.sentimentScore}% +
+
+
+
+ + {/* 概覽卡片 */} +
+
+
+

留言總數

+ +
+

{platformData.reduce((sum, item) => sum + item.value, 0)}

+
+ + ↑ 12% 較上週 +
+
+ +
+
+

平均互動率

+ +
+

4.8%

+
+ + ↑ 0.5% 較上週 +
+
+ +
+
+

情感分析

+ +
+

{sentimentData.positive}% 正面

+
+ + ↑ 5% 較上週 +
+
+
+ + {/* 留言趨勢圖 */} +
+

留言趨勢

+
+
+ {timelineData.map((item, index) => ( +
+
+
+ {item.comments} +
+
+

{item.date}

+
+ ))} +
+
+
+ +
+ {/* 平台分佈 */} +
+

平台分佈

+
+ {platformData.map((item, index) => ( +
+
+
+ {getPlatformIcon(item.name)} + + {item.name === 'xiaohongshu' ? '小紅書' : item.name} + +
+
+ {item.value} 則留言 + {item.percentage}% +
+
+
+
+
+
+ ))} +
+
+ + {/* 審核狀態分佈 */} +
+

審核狀態分佈

+
+
+ {statusData.map((item, index) => { + // 計算每個扇形的起始角度和結束角度 + const startAngle = index === 0 ? 0 : statusData.slice(0, index).reduce((sum, i) => sum + i.percentage, 0) * 3.6; + const endAngle = startAngle + item.percentage * 3.6; + + return ( +
+ ); + })} +
+
+
+
+
+
+ {statusData.map((item, index) => ( +
+
+ {getStatusIcon(item.name)} + {getStatusName(item.name)} +
+
+ {item.value} 則留言 + {item.percentage}% +
+
+ ))} +
+
+
+ +
+ {/* 情感分析詳情 */} +
+

情感分析詳情

+
+
+
+
+
+
+
+

負面

+

{sentimentData.negative}%

+
+
+

中性

+

{sentimentData.neutral}%

+
+
+

正面

+

{sentimentData.positive}%

+
+
+
+ + {/* 熱門文章 */} +
+

熱門文章

+
+ {popularArticles.map((article: any, index: number) => ( +
+

{article.title}

+
+ {article.count} 則留言 +
+
+ 高互動 +
+
+
+ ))} +
+
+
+ + {/* 關鍵字雲 */} +
+

熱門關鍵字

+
+ 產品 + 推薦 + 價格 + 質感 + 效果 + 服務 + 美觀 + 環境 + 便宜 + 好用 + 設計 + 功能 +
+
+ + {/* 用戶互動時間分析 */} +
+

用戶互動時間分析

+
+ {Array.from({ length: 24 }).map((_, hour) => { + // 模擬不同時段的活躍度 + let height = '20%'; + if (hour >= 9 && hour <= 11) height = '60%'; + if (hour >= 12 && hour <= 14) height = '40%'; + if (hour >= 19 && hour <= 22) height = '80%'; + + return ( +
+
+ {hour} +
+ ); + })} +
+
+

時間 (24小時制)

+
+
+
+
+ ); +}; + +export default Analytics; \ No newline at end of file diff --git a/web/src/components/CommentList.tsx b/web/src/components/CommentList.tsx new file mode 100644 index 0000000..c562f2a --- /dev/null +++ b/web/src/components/CommentList.tsx @@ -0,0 +1,520 @@ +import React, { useState, useEffect } from 'react'; +import { useSearchParams, useNavigate } from 'react-router-dom'; +import { + Facebook, + MessageSquare, + Instagram, + Linkedin, + CheckCircle, + XCircle, + MoreHorizontal, + ExternalLink, + BookOpen, + ThumbsUp, + ThumbsDown, + Minus, + AlertTriangle, + User, + Award, + Briefcase, + Youtube, + Hash, + Filter, + ChevronDown, + ArrowLeft +} from 'lucide-react'; +import CommentPreview from './CommentPreview'; +import { commentsApi, postsApi } from '../utils/api'; + +// 定义后端返回的评论类型 +interface ApiComment { + comment_id: string; + content: string; + sentiment_score: number; + created_at: string; + updated_at: string; + post_id: string; + user_id: string; + user_profile?: { + id: string; + full_name: string; + avatar_url: string; + }; +} + +// 定义前端使用的评论类型 +interface FrontendComment { + id: string; + content: string; + author: string; + authorType: 'user' | 'kol' | 'official'; + platform: 'facebook' | 'threads' | 'instagram' | 'linkedin' | 'xiaohongshu' | 'youtube'; + contentType?: 'post' | 'reel' | 'video' | 'short'; + timestamp: string; + sentiment: string; + status: string; + replyStatus?: string; + language?: string; + articleTitle?: string; + postAuthor?: string; + postAuthorType?: string; + url?: string; +} + +interface CommentListProps { + postId?: string; // 可选的帖子 ID,如果提供则只获取该帖子的评论 +} + +interface PostData { + id: string; + title: string; + description?: string; + platform: string; + post_url?: string; +} + +const CommentList: React.FC = () => { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const postId = searchParams.get('post_id'); + + const [comments, setComments] = useState([]); + const [post, setPost] = useState(null); // Store post data + const [loading, setLoading] = useState(true); + const [selectedComment, setSelectedComment] = useState(null); + const [error, setError] = useState(null); + + // 过滤和分页状态 + const [platformFilter, setPlatformFilter] = useState('all'); + const [statusFilter, setStatusFilter] = useState('all'); + const [sentimentFilter, setSentimentFilter] = useState('all'); + const [replyStatusFilter, setReplyStatusFilter] = useState('all'); + const [languageFilter, setLanguageFilter] = useState('all'); + const [searchQuery, setSearchQuery] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [totalComments, setTotalComments] = useState(0); + const [showFilters, setShowFilters] = useState(false); + + // Fetch post data if postId is provided + useEffect(() => { + const fetchPostData = async () => { + if (postId) { + try { + const response = await postsApi.getPost(postId); + setPost(response.data); + } catch (err) { + console.error('Failed to fetch post data:', err); + } + } + }; + + fetchPostData(); + }, [postId]); + + // 获取评论数据 + useEffect(() => { + const fetchComments = async () => { + try { + setLoading(true); + + // Build query parameters + const params: Record = {}; + + if (postId) { + params.post_id = postId; + } + + if (platformFilter !== 'all') { + params.platform = platformFilter; + } + + if (statusFilter !== 'all') { + params.status = statusFilter; + } + + if (sentimentFilter !== 'all') { + params.sentiment = sentimentFilter; + } + + if (searchQuery) { + params.query = searchQuery; + } + + if (languageFilter !== 'all') { + params.language = languageFilter; + } + + // Add pagination + params.limit = pageSize; + params.offset = (currentPage - 1) * pageSize; + + const response = await commentsApi.getComments(params); + + // 处理返回的数据 + const apiComments: ApiComment[] = response.data.comments || []; + const total = response.data.total || apiComments.length; + + // 转换为前端格式 + const frontendComments: FrontendComment[] = apiComments.map(comment => { + // 确定情感 + let sentiment = 'neutral'; + if (comment.sentiment_score > 0.3) { + sentiment = 'positive'; + } else if (comment.sentiment_score < -0.3) { + sentiment = 'negative'; + } + + // 检测语言 + const language = detectLanguage(comment.content); + + return { + id: comment.comment_id, + content: comment.content, + author: comment.user_profile?.full_name || '匿名用户', + authorType: 'user', // 默认为普通用户 + platform: 'facebook', // 假设默认是 Facebook + timestamp: comment.created_at, + sentiment, + status: 'approved', // 假设默认已审核 + language, + // 其他可选字段可以根据 API 返回的数据动态添加 + }; + }); + + setComments(frontendComments); + setTotalComments(total); + setError(null); + } catch (err) { + console.error('Failed to fetch comments:', err); + setError('加载评论失败,请稍后再试'); + } finally { + setLoading(false); + } + }; + + fetchComments(); + }, [postId, platformFilter, statusFilter, sentimentFilter, searchQuery, languageFilter, currentPage, pageSize]); + + // 简单的语言检测 + const detectLanguage = (text: string): 'zh-TW' | 'zh-CN' | 'en' => { + const traditionalChineseRegex = /[一-龥]/; + const simplifiedChineseRegex = /[一-龥]/; + const englishRegex = /[a-zA-Z]/; + + if (englishRegex.test(text) && !traditionalChineseRegex.test(text) && !simplifiedChineseRegex.test(text)) { + return 'en'; + } else if (traditionalChineseRegex.test(text)) { + // 这里简化了繁体/简体的判断,实际实现应该更复杂 + return 'zh-TW'; + } else { + return 'zh-CN'; + } + }; + + // Function to go back to posts list + const handleBackToPosts = () => { + navigate('/posts'); + }; + + // 显示加载状态 + if (loading) { + return ( +
+
+
+
+
+
+
+
+
+ ); + } + + // 显示错误信息 + if (error) { + return ( +
+
+
+
+
+ +

{error}

+ +
+
+
+
+
+ ); + } + + return ( +
+
+
+
+ {postId && ( + + )} +

+ {post ? `${post.title} 的评论` : '所有评论'} +

+ {post && ( + + ({totalComments} 条评论) + + )} +
+ +
+
+ setSearchQuery(e.target.value)} + /> +
+ +
+
+ + {/* Mobile filters panel */} + {showFilters && ( +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ )} + + {/* Mobile comment list */} +
+
+ {comments.map((comment) => ( +
setSelectedComment(comment)} + > +
+
+
+ + Facebook +
+
+

{comment.content}

+
+
+ {comment.author} +
+ {comment.timestamp} +
+
+
+ ))} +
+
+ + {/* Desktop table */} +
+
+ + + + + + + + + + + + + + + {comments.map((comment) => ( + setSelectedComment(comment)} + > + + + + + + + + + + ))} + +
平台留言內容留言者時間語言情感回覆狀態操作
+
+
+ + + Facebook + +
+
+
+
+ {comment.content} +
+
+
+
{comment.author}
+
+
+
{comment.timestamp}
+
+ {comment.language === 'zh-TW' && ( + + 繁中 + + )} + {comment.language === 'zh-CN' && ( + + 简中 + + )} + {comment.language === 'en' && ( + + EN + + )} + + {comment.sentiment === 'positive' && ( + + + 正面 + + )} + {comment.sentiment === 'negative' && ( + + + 負面 + + )} + {comment.sentiment === 'neutral' && ( + + + 中性 + + )} + + {comment.replyStatus === 'sent' && ( + + + 已回覆 + + )} + {comment.replyStatus === 'draft' && ( + + + 草稿 + + )} + {comment.replyStatus === 'none' && ( + + + 未回覆 + + )} + + +
+
+
+
+ + {selectedComment && ( +
+ setSelectedComment(null)} /> +
+ )} +
+ ); +}; + +export default CommentList; \ No newline at end of file diff --git a/web/src/components/CommentPreview.tsx b/web/src/components/CommentPreview.tsx new file mode 100644 index 0000000..ec9ad40 --- /dev/null +++ b/web/src/components/CommentPreview.tsx @@ -0,0 +1,637 @@ +import React, { useState, useEffect } from 'react'; +import { Comment } from '../types'; +import { + X, + CheckCircle, + XCircle, + MessageSquare, + ExternalLink, + ThumbsUp, + ThumbsDown, + Minus, + AlertTriangle, + User, + Award, + Briefcase, + Send, + Edit, + RefreshCw, + Facebook, + Instagram, + Linkedin, + BookOpen, + Youtube, + Hash, + List, + Copy, + Save, + Lock +} from 'lucide-react'; +import { templatesApi } from '../utils/api'; + +interface ReplyTemplate { + id: string; + title: string; + content: string; + category: string; +} + +interface CommentPreviewProps { + comment: Comment; + onClose: () => void; +} + +const CommentPreview: React.FC = ({ comment, onClose }) => { + const [replyText, setReplyText] = useState(comment.aiReply || ''); + const [privateMessageText, setPrivateMessageText] = useState(''); + const [isEditing, setIsEditing] = useState(false); + const [isGeneratingReply, setIsGeneratingReply] = useState(false); + const [showTemplates, setShowTemplates] = useState(false); + const [activeMode, setActiveMode] = useState<'reply' | 'private'>('reply'); + const [templates, setTemplates] = useState([]); + const [loadingTemplates, setLoadingTemplates] = useState(false); + + // Fetch templates from API + useEffect(() => { + const fetchTemplates = async () => { + if (showTemplates) { + try { + setLoadingTemplates(true); + const response = await templatesApi.getTemplates(); + setTemplates(response.data.templates || []); + } catch (err) { + console.error('Failed to fetch reply templates:', err); + } finally { + setLoadingTemplates(false); + } + } + }; + + fetchTemplates(); + }, [showTemplates]); + + const getSentimentIcon = (sentiment: string) => { + switch (sentiment) { + case 'positive': + return ; + case 'negative': + return ; + case 'neutral': + return ; + case 'mixed': + return ; + default: + return null; + } + }; + + const getSentimentText = (sentiment: string) => { + switch (sentiment) { + case 'positive': + return '正面'; + case 'negative': + return '負面'; + case 'neutral': + return '中性'; + case 'mixed': + return '混合'; + default: + return ''; + } + }; + + const getAuthorTypeIcon = (authorType: string) => { + switch (authorType) { + case 'official': + return ; + case 'kol': + return ; + case 'user': + return ; + default: + return null; + } + }; + + const getAuthorTypeText = (authorType: string) => { + switch (authorType) { + case 'official': + return '官方'; + case 'kol': + return 'KOL'; + case 'user': + return '一般用戶'; + default: + return ''; + } + }; + + const getPlatformIcon = (platform: string) => { + switch (platform) { + case 'facebook': + return ; + case 'threads': + return ; + case 'instagram': + return ; + case 'linkedin': + return ; + case 'xiaohongshu': + return ; + case 'youtube': + return ; + default: + return null; + } + }; + + const getPlatformName = (platform: string) => { + switch (platform) { + case 'facebook': + return 'Facebook'; + case 'threads': + return 'Threads'; + case 'instagram': + return 'Instagram'; + case 'linkedin': + return 'LinkedIn'; + case 'xiaohongshu': + return '小红书'; + case 'youtube': + return 'YouTube'; + default: + return platform; + } + }; + + const getContentTypeText = (contentType?: string) => { + if (!contentType) return ''; + + switch (contentType) { + case 'reel': + return 'Reel'; + case 'post': + return 'Post'; + case 'video': + return 'Video'; + case 'short': + return 'Short'; + default: + return contentType; + } + }; + + const getLanguageText = (language?: string) => { + if (!language) return ''; + + switch (language) { + case 'zh-TW': + return '繁體中文'; + case 'zh-CN': + return '简体中文'; + case 'en': + return 'English'; + default: + return language; + } + }; + + const handleGenerateReply = () => { + setIsGeneratingReply(true); + // 模擬 AI 生成回覆的過程 + setTimeout(() => { + const greeting = comment.language === 'zh-CN' ? + `${comment.author}您好,感谢您的留言!我们非常重视您的反馈。` : + comment.language === 'en' ? + `Hello ${comment.author}, thank you for your comment! We greatly value your feedback.` : + `${comment.author}您好,感謝您的留言!我們非常重視您的反饋。`; + + let sentiment = ''; + if (comment.sentiment === 'positive') { + sentiment = comment.language === 'zh-CN' ? + '很高兴您对我们的产品有正面评价。' : + comment.language === 'en' ? + 'We\'re pleased to hear your positive feedback about our product.' : + '很高興您對我們的產品有正面評價。'; + } else if (comment.sentiment === 'negative') { + sentiment = comment.language === 'zh-CN' ? + '对于您提出的问题,我们深表歉意并会积极改进。' : + comment.language === 'en' ? + 'We sincerely apologize for the issues you\'ve raised and will actively work to improve.' : + '對於您提出的問題,我們深表歉意並會積極改進。'; + } else { + sentiment = comment.language === 'zh-CN' ? + '我们会认真考虑您的建议。' : + comment.language === 'en' ? + 'We will carefully consider your suggestions.' : + '我們會認真考慮您的建議。'; + } + + const closing = comment.language === 'zh-CN' ? + '我们的团队将进一步跟进这个问题,如有任何疑问,欢迎随时联系我们。' : + comment.language === 'en' ? + 'Our team will follow up on this matter further. If you have any questions, please feel free to contact us anytime.' : + '我們的團隊將進一步跟進這個問題,如有任何疑問,歡迎隨時聯繫我們。'; + + setReplyText(`${greeting} ${sentiment} ${closing}`); + setIsGeneratingReply(false); + }, 1500); + }; + + const handleGeneratePrivateMessage = () => { + setIsGeneratingReply(true); + // 模擬 AI 生成私訊的過程 + setTimeout(() => { + const greeting = comment.language === 'zh-CN' ? + `${comment.author}您好,我是客服团队的代表。` : + comment.language === 'en' ? + `Hello ${comment.author}, I'm a representative from our customer service team.` : + `${comment.author}您好,我是客服團隊的代表。`; + + const content = comment.language === 'zh-CN' ? + '感谢您在我们的平台上留言。为了更好地解决您的问题,我想私下与您沟通一些细节。' : + comment.language === 'en' ? + 'Thank you for your comment on our platform. To better address your concerns, I would like to discuss some details with you privately.' : + '感謝您在我們的平台上留言。為了更好地解決您的問題,我想私下與您溝通一些細節。'; + + const question = comment.language === 'zh-CN' ? + '方便提供您的联系方式吗?或者您可以直接联系我们的客服热线:0800-123-456。' : + comment.language === 'en' ? + 'Would it be convenient for you to provide your contact information? Alternatively, you can reach our customer service hotline at 0800-123-456.' : + '方便提供您的聯繫方式嗎?或者您可以直接聯繫我們的客服熱線:0800-123-456。'; + + setPrivateMessageText(`${greeting} ${content} ${question}`); + setIsGeneratingReply(false); + }, 1500); + }; + + const handleSendMessage = () => { + if (activeMode === 'reply') { + // 這裡會調用後端 API 來發送公開回覆 + alert(`公開回覆已發送至 ${getPlatformName(comment.platform)} 平台:\n\n${replyText}`); + } else { + // 這裡會調用後端 API 來發送私訊 + alert(`私訊已發送至 ${comment.author}:\n\n${privateMessageText}`); + } + }; + + const handleTemplateSelect = (template: ReplyTemplate) => { + if (activeMode === 'reply') { + setReplyText(template.content); + } else { + setPrivateMessageText(template.content); + } + setShowTemplates(false); + }; + + const handleSaveAsDraft = () => { + if (activeMode === 'reply') { + alert('公開回覆已儲存為草稿'); + } else { + alert('私訊已儲存為草稿'); + } + }; + + const handleCopyToClipboard = () => { + const textToCopy = activeMode === 'reply' ? replyText : privateMessageText; + navigator.clipboard.writeText(textToCopy); + alert('內容已複製到剪貼簿'); + }; + + return ( +
+
+

留言預覽

+ +
+ +
+
+

文章標題

+

{comment.articleTitle}

+
+
+ {getAuthorTypeIcon(comment.postAuthorType)} + {comment.postAuthor} +
+ {getAuthorTypeText(comment.postAuthorType)} +
+
+ +
+
+

留言內容

+
+ {getPlatformIcon(comment.platform)} + {getPlatformName(comment.platform)} + {comment.contentType && ( + ({getContentTypeText(comment.contentType)}) + )} +
+
+
+

{comment.content}

+
+
+ {getAuthorTypeIcon(comment.authorType)} + {comment.author} + {getAuthorTypeText(comment.authorType)} +
+ {comment.timestamp} +
+
+
+
+ {getSentimentIcon(comment.sentiment)} + {getSentimentText(comment.sentiment)} +
+ {getLanguageText(comment.language)} +
+
+ + {/* 回覆模式切換 */} +
+
+ + +
+
+ + {/* 回覆內容區域 */} + {activeMode === 'reply' ? ( +
+
+

公開回覆內容

+
+ + + +
+
+ + {/* Templates Popup for replies */} + {showTemplates && activeMode === 'reply' && ( +
+
+

選擇回覆模板

+ {loadingTemplates ? ( +
+
+
+ ) : ( +
+
+ {templates.map(template => ( +
handleTemplateSelect(template)} + > +

{template.title}

+

{template.content.substring(0, 60)}...

+
+ ))} +
+
+ )} +
+
+ )} + + {isEditing ? ( +