Gemini commited on
Commit
256cef9
·
0 Parent(s):

VLA Data Generator - Complete TypeScript/React app with backend

Browse files

A production-ready application for generating vision-language-action (VLA) training data from videos using Google's Gemini AI.

Features:
TypeScript/React frontend with modern UI
Express.js backend for secure API key handling
Google Gemini 1.5 Flash integration
Video frame extraction and analysis
Task segmentation and interaction detection
Docker deployment for Hugging Face Spaces
Responsive design with Tailwind CSS

Tech Stack: Node.js 20, TypeScript, React 19, Vite, Express.js, Google Generative AI API

Ready for production deployment on Hugging Face Spaces!

.dockerignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ npm-debug.log
3
+ .npm
4
+ .env.local
5
+ .env.development.local
6
+ .env.test.local
7
+ .env.production.local
8
+ .git
9
+ .gitignore
10
+ README.md
11
+ .vscode
12
+ .DS_Store
13
+ dist
14
+ coverage
.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
App.tsx ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useCallback, useRef } from 'react';
3
+ import { VlaData, TaskSegment, Interaction } from './types';
4
+ import { generateOverallGoal, generateTasksAndInteractions } from './services/backendService';
5
+ import { extractFramesFromVideo } from './utils/videoProcessor';
6
+ import { VideoUploader } from './components/VideoUploader';
7
+ import { VideoPlayer } from './components/VideoPlayer';
8
+ import { ResultsDisplay } from './components/ResultsDisplay';
9
+ import { WandSparkles } from './components/Icons';
10
+
11
+ // Type for the point to be highlighted on the video
12
+ type HighlightPoint = { x: number; y: number; isEditing: boolean } | null;
13
+ // Type for the coordinate picker callback function
14
+ type CoordinatePickerCallback = ((coords: { x: number; y: number }) => void) | null;
15
+
16
+
17
+ export default function App(): React.ReactNode {
18
+ const [videoFile, setVideoFile] = useState<File | null>(null);
19
+ const [videoSrc, setVideoSrc] = useState<string | null>(null);
20
+ const [videoDuration, setVideoDuration] = useState<number>(0);
21
+ const [vlaData, setVlaData] = useState<VlaData | null>(null);
22
+ const [isLoading, setIsLoading] = useState<boolean>(false);
23
+ const [loadingMessage, setLoadingMessage] = useState<string>('');
24
+ const [error, setError] = useState<string | null>(null);
25
+ const [totalFrames, setTotalFrames] = useState<number>(0);
26
+ const [highlightPoint, setHighlightPoint] = useState<HighlightPoint>(null);
27
+ const [coordinatePicker, setCoordinatePicker] = useState<CoordinatePickerCallback>(null);
28
+ const [usedFallback, setUsedFallback] = useState<boolean>(false);
29
+
30
+ const videoRef = useRef<HTMLVideoElement>(null);
31
+ const isGeneratingRef = useRef(false);
32
+
33
+ const handleVideoUpload = useCallback((file: File) => {
34
+ if (file.type.startsWith('video/')) {
35
+ setVideoFile(file);
36
+
37
+ if (videoSrc) {
38
+ URL.revokeObjectURL(videoSrc);
39
+ }
40
+
41
+ const url = URL.createObjectURL(file);
42
+ setVideoSrc(url);
43
+ setVlaData(null);
44
+ setError(null);
45
+ setUsedFallback(false);
46
+ setVideoDuration(0);
47
+ setTotalFrames(0);
48
+
49
+ const videoElement = document.createElement('video');
50
+ videoElement.preload = 'metadata';
51
+ videoElement.src = url;
52
+ videoElement.onloadedmetadata = () => {
53
+ setVideoDuration(videoElement.duration);
54
+ };
55
+ videoElement.onerror = () => {
56
+ setError("Could not read video metadata to get duration.");
57
+ };
58
+ } else {
59
+ setError('Please upload a valid video file.');
60
+ }
61
+ }, [videoSrc]);
62
+
63
+ const handleGenerate = useCallback(async () => {
64
+ if (!videoFile || isGeneratingRef.current) {
65
+ if (!videoFile) setError('No video file selected.');
66
+ return;
67
+ }
68
+
69
+ isGeneratingRef.current = true;
70
+ setIsLoading(true);
71
+ setError(null);
72
+ setVlaData(null);
73
+ setUsedFallback(false); // Reset on each generation
74
+
75
+ try {
76
+ const FRAMES_PER_SECOND = 2; // Extract 2 frames per second
77
+ const MAX_FRAMES_TOTAL = 360; // Cap at 360 frames (e.g., 3 minutes at 2fps) to manage memory/performance
78
+
79
+ let calculatedFrames = Math.ceil(videoDuration * FRAMES_PER_SECOND);
80
+ if (calculatedFrames > MAX_FRAMES_TOTAL) {
81
+ calculatedFrames = MAX_FRAMES_TOTAL;
82
+ }
83
+ if (calculatedFrames === 0 && videoDuration > 0) {
84
+ calculatedFrames = 1; // ensure at least one frame for very short videos
85
+ }
86
+ setTotalFrames(calculatedFrames);
87
+
88
+ // Step 1: Extract frames
89
+ setLoadingMessage(`Step 1/3: Extracting ${calculatedFrames} frames from video...`);
90
+ const frames = await extractFramesFromVideo(videoFile, calculatedFrames);
91
+
92
+ if (frames.length === 0) {
93
+ throw new Error("Could not extract any frames from the video. The file might be corrupted or in an unsupported format.");
94
+ }
95
+
96
+ // Step 2: Generate Overall Goal
97
+ setLoadingMessage('Step 2/3: Determining overall goal...');
98
+ const keyframes = [frames[0], frames[Math.floor(frames.length / 2)], frames[frames.length - 1]];
99
+ const overallGoal = await generateOverallGoal(keyframes, videoDuration);
100
+
101
+ const initialVlaData: VlaData = { overallGoal, tasks: [] };
102
+ setVlaData(initialVlaData);
103
+
104
+ // Step 3: Generate Task Segments and Interactions in one go
105
+ setLoadingMessage('Step 3/3: Analyzing tasks and interactions...');
106
+ const vlaData = await generateTasksAndInteractions(
107
+ frames,
108
+ overallGoal,
109
+ videoDuration,
110
+ totalFrames,
111
+ (current, total) => {
112
+ // Progress callback - you could update loading message here
113
+ console.log(`Progress: ${current}/${total}`);
114
+ }
115
+ );
116
+
117
+ setVlaData(vlaData);
118
+
119
+ } catch (err) {
120
+ console.error(err);
121
+ const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred.';
122
+ setError(`Failed to process video. ${errorMessage}`);
123
+ setVlaData(null); // Clear partial data on major failure
124
+ } finally {
125
+ setIsLoading(false);
126
+ setLoadingMessage('');
127
+ isGeneratingRef.current = false;
128
+ }
129
+ }, [videoFile, videoDuration]);
130
+
131
+ const handleDownload = useCallback(() => {
132
+ if (!vlaData || !videoFile) return;
133
+
134
+ const dataStr = JSON.stringify(vlaData, null, 2);
135
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
136
+ const dataUrl = URL.createObjectURL(dataBlob);
137
+
138
+ const link = document.createElement('a');
139
+ link.href = dataUrl;
140
+ const baseName = videoFile.name.substring(0, videoFile.name.lastIndexOf('.')) || videoFile.name;
141
+ link.download = `${baseName}_vla_data.json`;
142
+
143
+ document.body.appendChild(link);
144
+ link.click();
145
+ document.body.removeChild(link);
146
+ URL.revokeObjectURL(dataUrl);
147
+ }, [vlaData, videoFile]);
148
+
149
+ const handleSeekToTime = useCallback((time: number) => {
150
+ if (videoRef.current) {
151
+ videoRef.current.currentTime = time;
152
+ }
153
+ }, []);
154
+
155
+ const handleUpdateInteraction = useCallback((taskId: number, interactionIndex: number, updatedInteraction: Interaction) => {
156
+ setVlaData(currentData => {
157
+ if (!currentData) return null;
158
+
159
+ const newTasks = currentData.tasks.map(task => {
160
+ if (task.id === taskId) {
161
+ const newInteractions = [...task.interactions];
162
+ newInteractions[interactionIndex] = updatedInteraction;
163
+ return { ...task, interactions: newInteractions };
164
+ }
165
+ return task;
166
+ });
167
+
168
+ return { ...currentData, tasks: newTasks };
169
+ });
170
+ }, []);
171
+
172
+ const handleVideoClick = useCallback((coords: { x: number; y: number }) => {
173
+ if (coordinatePicker) {
174
+ coordinatePicker(coords);
175
+ }
176
+ }, [coordinatePicker]);
177
+
178
+ const handleHighlightPoint = useCallback((point: HighlightPoint) => {
179
+ // Prevent hover from overriding a sticky editing highlight
180
+ if (highlightPoint?.isEditing && !point?.isEditing) {
181
+ return;
182
+ }
183
+ setHighlightPoint(point);
184
+ }, [highlightPoint?.isEditing]);
185
+
186
+ const handleSetCoordinatePicker = useCallback((callback: CoordinatePickerCallback) => {
187
+ setCoordinatePicker(() => callback);
188
+ }, []);
189
+
190
+ return (
191
+ <div className="min-h-screen bg-slate-900 text-slate-200 font-sans">
192
+ <main className="grid grid-cols-1 lg:grid-cols-2 gap-6 p-4 md:p-8 max-w-screen-2xl mx-auto">
193
+ {/* Left Column: Video and Controls */}
194
+ <div className="flex flex-col gap-6 lg:h-[calc(100vh-4rem)]">
195
+ <header>
196
+ <h1 className="text-3xl md:text-4xl font-bold text-white tracking-tight">VLA Data Generator</h1>
197
+ <p className="text-slate-400 mt-2">Upload a screen recording to automatically generate structured data about user actions.</p>
198
+ </header>
199
+
200
+ <div
201
+ className={`bg-slate-800/50 rounded-2xl p-2 aspect-video flex-grow flex items-center justify-center ${coordinatePicker ? 'cursor-crosshair' : ''}`}
202
+ >
203
+ {videoSrc ? (
204
+ <VideoPlayer
205
+ src={videoSrc}
206
+ ref={videoRef}
207
+ highlightPoint={highlightPoint}
208
+ onVideoClick={handleVideoClick}
209
+ />
210
+ ) : (
211
+ <VideoUploader onVideoSelect={handleVideoUpload} />
212
+ )}
213
+ </div>
214
+
215
+ {videoFile && (
216
+ <button
217
+ onClick={handleGenerate}
218
+ disabled={isLoading || videoDuration === 0}
219
+ className="w-full flex items-center justify-center gap-3 bg-indigo-600 hover:bg-indigo-500 disabled:bg-indigo-800 disabled:text-slate-400 disabled:cursor-not-allowed text-white font-bold py-3 px-4 rounded-xl transition-all duration-300 text-lg shadow-lg shadow-indigo-900/50"
220
+ >
221
+ <WandSparkles className="w-6 h-6" />
222
+ {isLoading ? 'Generating...' : (videoDuration === 0 ? 'Reading Video...' : 'Generate Action Data')}
223
+ </button>
224
+ )}
225
+ </div>
226
+
227
+ {/* Right Column: Results */}
228
+ <div className="bg-slate-800 rounded-2xl lg:h-[calc(100vh-4rem)] flex flex-col">
229
+ <ResultsDisplay
230
+ vlaData={vlaData}
231
+ isLoading={isLoading}
232
+ loadingMessage={loadingMessage}
233
+ error={error}
234
+ hasVideo={!!videoFile}
235
+ videoDuration={videoDuration}
236
+ totalFrames={totalFrames}
237
+ usedFallback={usedFallback}
238
+ onDownload={handleDownload}
239
+ onSeekToTime={handleSeekToTime}
240
+ onUpdateInteraction={handleUpdateInteraction}
241
+ onHighlightPoint={handleHighlightPoint}
242
+ onSetCoordinatePicker={handleSetCoordinatePicker}
243
+ />
244
+ </div>
245
+ </main>
246
+ </div>
247
+ );
248
+ }
Dockerfile ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Node.js 20 LTS version (required by @google/generative-ai)
2
+ FROM node:20-alpine
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Copy package files for frontend
8
+ COPY package*.json ./
9
+
10
+ # Install frontend dependencies
11
+ RUN npm ci
12
+
13
+ # Copy frontend source code
14
+ COPY . .
15
+
16
+ # Build the frontend application
17
+ RUN npm run build
18
+
19
+ # Set up backend
20
+ WORKDIR /app/backend
21
+
22
+ # Copy backend package file
23
+ COPY backend/package.json ./
24
+
25
+ # Install backend dependencies
26
+ RUN npm install
27
+
28
+ # Copy backend source
29
+ COPY backend/server.js ./
30
+
31
+ # Create a startup script that runs the unified server
32
+ WORKDIR /app
33
+ RUN echo '#!/bin/sh' > /start.sh && \
34
+ echo 'echo "Starting VLA Data Generator..."' >> /start.sh && \
35
+ echo 'cd /app/backend && node server.js' >> /start.sh && \
36
+ chmod +x /start.sh
37
+
38
+ # Remove serve since backend handles everything
39
+ # RUN npm install -g serve
40
+
41
+ # Expose the port that Hugging Face Spaces expects
42
+ EXPOSE 7860
43
+
44
+ # Set environment variable for the port
45
+ ENV PORT=7860
46
+
47
+ # Command to run the application
48
+ CMD ["/start.sh"]
README.md ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: VLA Data Generator
3
+ emoji: 🎬
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ license: mit
10
+ short_description: Generate VLA training data from videos using AI
11
+ ---
12
+
13
+ # VLA Data Generator
14
+
15
+ A TypeScript/React application for generating vision-language-action (VLA) training data using Google's Gemini AI. This app allows users to upload videos and generate corresponding action sequences and descriptions for training VLA models.
16
+
17
+ ## Technology Stack
18
+
19
+ - **TypeScript** - Type-safe JavaScript development
20
+ - **React 19** - Modern React with latest features
21
+ - **Vite** - Fast build tool and development server
22
+ - **Google Gemini AI** - AI-powered video analysis and action generation
23
+
24
+ ## Run Locally
25
+
26
+ **Prerequisites:** Node.js (v20 or higher)
27
+
28
+ 1. Install dependencies:
29
+ ```bash
30
+ npm install
31
+ ```
32
+
33
+ 2. Set up environment variables:
34
+ Create a `.env.local` file and add your Gemini API key:
35
+ ```
36
+ GEMINI_API_KEY=your_gemini_api_key_here
37
+ ```
38
+
39
+ 3. Run the development server:
40
+ ```bash
41
+ npm run dev
42
+ ```
43
+
44
+ 4. Build for production:
45
+ ```bash
46
+ npm run build
47
+ ```
48
+
49
+ ## Deploy to Hugging Face Spaces
50
+
51
+ This application can be deployed to Hugging Face Spaces using Docker.
52
+
53
+ ### Using the Dockerfile
54
+
55
+ 1. Ensure your `GEMINI_API_KEY` is set as a secret in your Hugging Face Space settings
56
+ 2. The included Dockerfile will handle the build and deployment process
57
+ 3. The app will be accessible on port 7860 (Hugging Face Spaces default)
58
+
59
+ ### Manual Deployment Steps
60
+
61
+ 1. Fork or upload this repository to Hugging Face Spaces
62
+ 2. Select "Docker" as the SDK
63
+ 3. Add your `GEMINI_API_KEY` as a secret in the Space settings
64
+ 4. The Space will automatically build and deploy using the provided Dockerfile
backend/package.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "vla-backend",
3
+ "version": "1.0.0",
4
+ "description": "Backend server for VLA Data Generator",
5
+ "main": "server.js",
6
+ "scripts": {
7
+ "start": "node server.js",
8
+ "dev": "node server.js"
9
+ },
10
+ "dependencies": {
11
+ "express": "^4.18.2",
12
+ "cors": "^2.8.5",
13
+ "multer": "^1.4.5-lts.1",
14
+ "@google/generative-ai": "^0.21.0"
15
+ },
16
+ "engines": {
17
+ "node": ">=20.0.0"
18
+ }
19
+ }
backend/server.js ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const cors = require('cors');
3
+ const multer = require('multer');
4
+ const path = require('path');
5
+ const { GoogleGenerativeAI } = require('@google/generative-ai');
6
+
7
+ const app = express();
8
+ const PORT = process.env.PORT || 7860; // Use HF Spaces port
9
+
10
+ // Get API key from environment variables
11
+ const API_KEY = process.env.GEMINI_API_KEY || process.env.API_KEY;
12
+ if (!API_KEY) {
13
+ console.error('Error: GEMINI_API_KEY or API_KEY environment variable is required');
14
+ process.exit(1);
15
+ }
16
+
17
+ console.log('Initializing Google Generative AI...');
18
+ const genAI = new GoogleGenerativeAI(API_KEY);
19
+
20
+ // Middleware
21
+ app.use(cors());
22
+ app.use(express.json({ limit: '50mb' }));
23
+
24
+ // Serve static files from frontend build
25
+ app.use(express.static(path.join(__dirname, '../dist')));
26
+
27
+ // Configure multer for file uploads
28
+ const upload = multer({
29
+ storage: multer.memoryStorage(),
30
+ limits: {
31
+ fileSize: 50 * 1024 * 1024, // 50MB limit
32
+ }
33
+ });
34
+
35
+ // Health check endpoint
36
+ app.get('/health', (req, res) => {
37
+ res.json({ status: 'ok', message: 'VLA Backend Server is running' });
38
+ });
39
+
40
+ // Generate overall goal endpoint
41
+ app.post('/api/generate-goal', async (req, res) => {
42
+ try {
43
+ const { frames, videoDuration } = req.body;
44
+
45
+ if (!frames || !Array.isArray(frames)) {
46
+ return res.status(400).json({ error: 'Frames array is required' });
47
+ }
48
+
49
+ const model = genAI.getGenerativeModel({
50
+ model: "gemini-1.5-flash"
51
+ });
52
+
53
+ // Create the prompt (you'll need to move the prompt logic here)
54
+ const prompt = `Analyze these video frames and generate an overall goal for the user's actions.
55
+ Video duration: ${videoDuration} seconds
56
+ Frames: ${frames.length} total
57
+
58
+ Please provide a concise overall goal description of what the user is trying to accomplish in this video.`;
59
+
60
+ const result = await model.generateContent([
61
+ { text: prompt },
62
+ ...frames.map(frame => ({
63
+ inlineData: {
64
+ data: frame.split(',')[1], // Remove data URL prefix
65
+ mimeType: 'image/jpeg'
66
+ }
67
+ }))
68
+ ]);
69
+
70
+ const response = result.response;
71
+ const text = response.text();
72
+
73
+ res.json({ goal: text });
74
+ } catch (error) {
75
+ console.error('Error generating goal:', error);
76
+ res.status(500).json({ error: 'Failed to generate goal' });
77
+ }
78
+ });
79
+
80
+ // Generate tasks and interactions endpoint
81
+ app.post('/api/generate-tasks', async (req, res) => {
82
+ try {
83
+ const { frames, goal, videoDuration, totalFrames } = req.body;
84
+
85
+ if (!frames || !Array.isArray(frames) || !goal) {
86
+ return res.status(400).json({ error: 'Frames array and goal are required' });
87
+ }
88
+
89
+ const model = genAI.getGenerativeModel({
90
+ model: "gemini-1.5-flash"
91
+ });
92
+
93
+ // Create the prompt for tasks and interactions
94
+ const prompt = `Based on the overall goal: "${goal}"
95
+
96
+ Analyze these ${frames.length} video frames and generate detailed tasks and interactions.
97
+ Video duration: ${videoDuration} seconds
98
+ Total frames: ${totalFrames}
99
+
100
+ Please provide a JSON response with tasks and interactions following this structure:
101
+ {
102
+ "tasks": [
103
+ {
104
+ "task_id": "task_1",
105
+ "description": "Description of the task",
106
+ "start_frame": 0,
107
+ "end_frame": 10,
108
+ "interactions": [
109
+ {
110
+ "interaction_id": "interaction_1",
111
+ "type": "click|scroll|type|drag",
112
+ "description": "What action is being performed",
113
+ "frame_number": 5,
114
+ "coordinates": {"x": 100, "y": 200},
115
+ "target_element": "Description of UI element"
116
+ }
117
+ ]
118
+ }
119
+ ]
120
+ }`;
121
+
122
+ const result = await model.generateContent([
123
+ { text: prompt },
124
+ ...frames.map(frame => ({
125
+ inlineData: {
126
+ data: frame.split(',')[1],
127
+ mimeType: 'image/jpeg'
128
+ }
129
+ }))
130
+ ]);
131
+
132
+ const response = result.response;
133
+ const text = response.text();
134
+
135
+ // Try to parse JSON response
136
+ try {
137
+ const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/) || text.match(/\{[\s\S]*\}/);
138
+ if (jsonMatch) {
139
+ const jsonStr = jsonMatch[1] || jsonMatch[0];
140
+ const parsedData = JSON.parse(jsonStr);
141
+ res.json(parsedData);
142
+ } else {
143
+ // Fallback if JSON parsing fails
144
+ res.json({
145
+ tasks: [{
146
+ task_id: "task_1",
147
+ description: text,
148
+ start_frame: 0,
149
+ end_frame: frames.length - 1,
150
+ interactions: []
151
+ }]
152
+ });
153
+ }
154
+ } catch (parseError) {
155
+ console.error('JSON parsing error:', parseError);
156
+ res.json({
157
+ tasks: [{
158
+ task_id: "task_1",
159
+ description: text,
160
+ start_frame: 0,
161
+ end_frame: frames.length - 1,
162
+ interactions: []
163
+ }]
164
+ });
165
+ }
166
+ } catch (error) {
167
+ console.error('Error generating tasks:', error);
168
+ res.status(500).json({ error: 'Failed to generate tasks and interactions' });
169
+ }
170
+ });
171
+
172
+ // Serve frontend for all other routes
173
+ app.get('*', (req, res) => {
174
+ res.sendFile(path.join(__dirname, '../dist/index.html'));
175
+ });
176
+
177
+ app.listen(PORT, '0.0.0.0', () => {
178
+ console.log(`VLA Data Generator running on port ${PORT}`);
179
+ console.log(`API Key configured: ${API_KEY ? 'Yes' : 'No'}`);
180
+ });
components/Icons.tsx ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ type IconProps = React.SVGProps<SVGSVGElement>;
4
+
5
+ export const FileVideo: React.FC<IconProps> = (props) => (
6
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"></path><path d="m14 12-2.5 1.8L9 12l-2.5 1.8L4 12"></path><circle cx="12" cy="12" r="3"></circle></svg>
7
+ );
8
+
9
+ export const WandSparkles: React.FC<IconProps> = (props) => (
10
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}><path d="m5 3 2.5 5L12 3l2.5 5L19 3l-2.5 5L21 13l-5 2.5L21 18l-5 2.5L12 21l-2.5-5L5 21l2.5-5L3 13l5-2.5L3 8l2.5-5Z"></path></svg>
11
+ );
12
+
13
+ export const Loader: React.FC<IconProps> = (props) => (
14
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props} className={`animate-spin ${props.className || ''}`}><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>
15
+ );
16
+
17
+ export const AlertTriangle: React.FC<IconProps> = (props) => (
18
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}><path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>
19
+ );
20
+
21
+ export const MousePointerClick: React.FC<IconProps> = (props) => (
22
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}><path d="m9 9 5 12 1.8-5.2L21 14Z"></path><path d="M3 3l7.2 7.2"></path><path d="m7 11 2-2"></path><path d="M11 15 9.5 13.5"></path><path d="m14 18-1-1"></path></svg>
23
+ );
24
+
25
+ export const Keyboard: React.FC<IconProps> = (props) => (
26
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}><rect width="20" height="16" x="2" y="4" rx="2" ry="2"></rect><path d="M6 8h.01"></path><path d="M10 8h.01"></path><path d="M14 8h.01"></path><path d="M18 8h.01"></path><path d="M8 12h.01"></path><path d="M12 12h.01"></path><path d="M16 12h.01"></path><path d="M7 16h10"></path></svg>
27
+ );
28
+
29
+ export const ArrowDownUp: React.FC<IconProps> = (props) => (
30
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}><path d="m3 16 4 4 4-4"></path><path d="M7 20V4"></path><path d="m21 8-4-4-4 4"></path><path d="M17 4v16"></path></svg>
31
+ );
32
+
33
+ export const Type: React.FC<IconProps> = (props) => (
34
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}><polyline points="4 7 4 4 20 4 20 7"></polyline><line x1="9" x2="15" y1="20" y2="20"></line><line x1="12" x2="12" y1="4" y2="20"></line></svg>
35
+ );
36
+
37
+ export const Copy: React.FC<IconProps> = (props) => (
38
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg>
39
+ );
40
+
41
+ export const Check: React.FC<IconProps> = (props) => (
42
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}><path d="M20 6 9 17l-5-5"></path></svg>
43
+ );
44
+
45
+ export const Download: React.FC<IconProps> = (props) => (
46
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" x2="12" y1="15" y2="3"></line></svg>
47
+ );
48
+
49
+ export const Pencil: React.FC<IconProps> = (props) => (
50
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"></path><path d="m15 5 4 4"></path></svg>
51
+ );
components/ResultsDisplay.tsx ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+ import { VlaData, Interaction } from '../types';
4
+ import { TaskSegmentCard } from './TaskSegmentCard';
5
+ import { Loader, AlertTriangle, WandSparkles, Copy, Check, Download } from './Icons';
6
+
7
+ type HighlightPoint = { x: number; y: number; isEditing: boolean } | null;
8
+ type CoordinatePickerCallback = ((coords: { x: number; y: number }) => void) | null;
9
+
10
+ interface ResultsDisplayProps {
11
+ vlaData: VlaData | null;
12
+ isLoading: boolean;
13
+ loadingMessage: string;
14
+ error: string | null;
15
+ hasVideo: boolean;
16
+ videoDuration: number;
17
+ totalFrames: number;
18
+ usedFallback: boolean;
19
+ onDownload: () => void;
20
+ onSeekToTime: (time: number) => void;
21
+ onUpdateInteraction: (taskId: number, interactionIndex: number, updatedInteraction: Interaction) => void;
22
+ onHighlightPoint: (point: HighlightPoint) => void;
23
+ onSetCoordinatePicker: (callback: CoordinatePickerCallback) => void;
24
+ }
25
+
26
+ export const ResultsDisplay: React.FC<ResultsDisplayProps> = ({
27
+ vlaData,
28
+ isLoading,
29
+ loadingMessage,
30
+ error,
31
+ hasVideo,
32
+ videoDuration,
33
+ totalFrames,
34
+ usedFallback,
35
+ onDownload,
36
+ onSeekToTime,
37
+ onUpdateInteraction,
38
+ onHighlightPoint,
39
+ onSetCoordinatePicker
40
+ }) => {
41
+ const [hasCopied, setHasCopied] = useState(false);
42
+
43
+ useEffect(() => {
44
+ if(hasCopied) {
45
+ const timer = setTimeout(() => setHasCopied(false), 2000);
46
+ return () => clearTimeout(timer);
47
+ }
48
+ }, [hasCopied]);
49
+
50
+ const handleCopy = () => {
51
+ if(vlaData) {
52
+ navigator.clipboard.writeText(JSON.stringify(vlaData, null, 2));
53
+ setHasCopied(true);
54
+ }
55
+ };
56
+
57
+ const renderContent = () => {
58
+ if (isLoading) {
59
+ return (
60
+ <div className="flex flex-col items-center justify-center h-full text-center">
61
+ <Loader className="w-12 h-12 text-indigo-400" />
62
+ <p className="mt-4 text-lg font-semibold text-slate-300">Analyzing Video</p>
63
+ <p className="text-slate-400">{loadingMessage}</p>
64
+ </div>
65
+ );
66
+ }
67
+
68
+ if (error) {
69
+ return (
70
+ <div className="flex flex-col items-center justify-center h-full text-center p-4">
71
+ <AlertTriangle className="w-12 h-12 text-red-400" />
72
+ <p className="mt-4 text-lg font-semibold text-slate-200">Analysis Failed</p>
73
+ <p className="text-red-300 bg-red-900/50 p-3 rounded-lg mt-2 max-w-md">{error}</p>
74
+ </div>
75
+ );
76
+ }
77
+
78
+ if (vlaData) {
79
+ return (
80
+ <div className="p-4 md:p-6 space-y-6">
81
+ <div>
82
+ <h3 className="text-xs font-semibold uppercase text-slate-400 tracking-wider">Overall Goal</h3>
83
+ <p className="mt-2 text-lg text-slate-200 bg-slate-700/50 p-3 rounded-lg">{vlaData.overallGoal}</p>
84
+ </div>
85
+ <div>
86
+ <h3 className="text-xs font-semibold uppercase text-slate-400 tracking-wider">Sequential Tasks</h3>
87
+ <div className="space-y-4 mt-2">
88
+ {vlaData.tasks.map((task) => (
89
+ <TaskSegmentCard
90
+ key={task.id}
91
+ task={task}
92
+ videoDuration={videoDuration}
93
+ totalFrames={totalFrames}
94
+ onSeekToTime={onSeekToTime}
95
+ onUpdateInteraction={onUpdateInteraction}
96
+ onHighlightPoint={onHighlightPoint}
97
+ onSetCoordinatePicker={onSetCoordinatePicker}
98
+ />
99
+ ))}
100
+ </div>
101
+ </div>
102
+ </div>
103
+ );
104
+ }
105
+
106
+ return (
107
+ <div className="flex flex-col items-center justify-center h-full text-center p-4">
108
+ <WandSparkles className="w-16 h-16 text-slate-600" />
109
+ <h3 className="mt-4 text-xl font-bold text-slate-300">Analysis Results</h3>
110
+ <p className="text-slate-400 mt-1 max-w-xs">
111
+ {hasVideo ? 'Click "Generate Action Data" to begin the analysis.' : 'Upload a video to get started.'}
112
+ </p>
113
+ </div>
114
+ );
115
+ };
116
+
117
+ return (
118
+ <div className="flex-grow flex flex-col h-full overflow-hidden">
119
+ <div className="flex-shrink-0 flex justify-between items-center p-4 border-b border-slate-700">
120
+ <h2 className="text-xl font-bold text-white">Generated Data</h2>
121
+ {vlaData && (
122
+ <div className="flex items-center gap-2">
123
+ <button
124
+ onClick={handleCopy}
125
+ className="flex items-center gap-2 px-3 py-1.5 text-sm font-semibold bg-slate-700 hover:bg-slate-600 rounded-md transition-colors"
126
+ aria-label="Copy JSON to clipboard"
127
+ >
128
+ {hasCopied ? <Check className="w-4 h-4 text-green-400" /> : <Copy className="w-4 h-4" />}
129
+ {hasCopied ? 'Copied!' : 'Copy JSON'}
130
+ </button>
131
+ <button
132
+ onClick={onDownload}
133
+ className="flex items-center gap-2 px-3 py-1.5 text-sm font-semibold bg-slate-700 hover:bg-slate-600 rounded-md transition-colors"
134
+ aria-label="Download JSON data"
135
+ >
136
+ <Download className="w-4 h-4" />
137
+ Download
138
+ </button>
139
+ </div>
140
+ )}
141
+ </div>
142
+
143
+ {usedFallback && !isLoading && vlaData && (
144
+ <div className="flex-shrink-0 bg-yellow-900/50 border-b border-yellow-700/50 text-yellow-200 p-3 text-sm flex items-start gap-3" role="alert">
145
+ <AlertTriangle className="w-5 h-5 flex-shrink-0 text-yellow-400 mt-0.5" aria-hidden="true" />
146
+ <div>
147
+ <span className="font-semibold">Using Fallback Configuration.</span> Due to high demand, results were generated using a faster, alternate setting. Quality may vary slightly.
148
+ </div>
149
+ </div>
150
+ )}
151
+
152
+ <div className="flex-grow overflow-y-auto">
153
+ {renderContent()}
154
+ </div>
155
+ </div>
156
+ );
157
+ };
components/TaskSegmentCard.tsx ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+ import { TaskSegment, Interaction, InteractionType } from '../types';
4
+ import { MousePointerClick, Type, Pencil } from './Icons';
5
+
6
+ type HighlightPoint = { x: number; y: number; isEditing: boolean } | null;
7
+ type CoordinatePickerCallback = ((coords: { x: number; y: number }) => void) | null;
8
+
9
+ const interactionIcons: Record<InteractionType, React.ReactNode> = {
10
+ click: <MousePointerClick className="w-4 h-4 text-sky-400" />,
11
+ type: <Type className="w-4 h-4 text-lime-400" />,
12
+ };
13
+
14
+ interface TaskSegmentCardProps {
15
+ task: TaskSegment;
16
+ videoDuration: number;
17
+ totalFrames: number;
18
+ onSeekToTime?: (time: number) => void;
19
+ onUpdateInteraction: (taskId: number, interactionIndex: number, updatedInteraction: Interaction) => void;
20
+ onHighlightPoint: (point: HighlightPoint) => void;
21
+ onSetCoordinatePicker: (callback: CoordinatePickerCallback) => void;
22
+ }
23
+
24
+ const formatTime = (seconds: number) => {
25
+ if (isNaN(seconds) || seconds < 0) return '0.0s';
26
+ return `${seconds.toFixed(1)}s`;
27
+ };
28
+
29
+ const formatCoords = (x?: number, y?: number) => {
30
+ if (x === undefined || y === undefined) return '';
31
+ return `(x: ${x.toFixed(2)}, y: ${y.toFixed(2)})`;
32
+ }
33
+
34
+ export const TaskSegmentCard: React.FC<TaskSegmentCardProps> = ({ task, videoDuration, totalFrames, onSeekToTime, onUpdateInteraction, onHighlightPoint, onSetCoordinatePicker }) => {
35
+ const [editingIndex, setEditingIndex] = useState<number | null>(null);
36
+ const [editingInteractionType, setEditingInteractionType] = useState<InteractionType | null>(null);
37
+ const [editDetails, setEditDetails] = useState('');
38
+ const [editTime, setEditTime] = useState('');
39
+ const [editX, setEditX] = useState('');
40
+ const [editY, setEditY] = useState('');
41
+
42
+ // Live seek video when editing timestamp
43
+ useEffect(() => {
44
+ if (editingIndex !== null && onSeekToTime) {
45
+ const timeInSeconds = parseFloat(editTime);
46
+ if (!isNaN(timeInSeconds) && timeInSeconds >= 0 && timeInSeconds <= videoDuration) {
47
+ onSeekToTime(timeInSeconds);
48
+ }
49
+ }
50
+ }, [editTime, editingIndex, onSeekToTime, videoDuration]);
51
+
52
+ // Live update highlight marker when editing coordinates
53
+ useEffect(() => {
54
+ if (editingInteractionType === 'click' && editingIndex !== null) {
55
+ const x = parseFloat(editX);
56
+ const y = parseFloat(editY);
57
+ if (!isNaN(x) && !isNaN(y)) {
58
+ onHighlightPoint({ x, y, isEditing: true });
59
+ }
60
+ }
61
+ }, [editX, editY, editingInteractionType, editingIndex, onHighlightPoint]);
62
+
63
+ const calculateTime = (frameIndex: number): number | null => {
64
+ if (!videoDuration || !totalFrames || videoDuration === 0 || totalFrames === 0) return null;
65
+ return (frameIndex / totalFrames) * videoDuration;
66
+ };
67
+
68
+ const handleInteractionClick = (interaction: Interaction) => {
69
+ const time = calculateTime(interaction.frameIndex);
70
+ if (time !== null && onSeekToTime) {
71
+ onSeekToTime(time);
72
+ }
73
+ };
74
+
75
+ const handleEditClick = (interaction: Interaction, index: number) => {
76
+ setEditingIndex(index);
77
+ setEditingInteractionType(interaction.type);
78
+ setEditDetails(interaction.details);
79
+ const time = calculateTime(interaction.frameIndex);
80
+ setEditTime(time !== null ? time.toFixed(1) : '');
81
+
82
+ if (interaction.type === 'click') {
83
+ setEditX(interaction.x?.toFixed(4) || '0.5');
84
+ setEditY(interaction.y?.toFixed(4) || '0.5');
85
+ onSetCoordinatePicker((coords) => {
86
+ setEditX(coords.x.toFixed(4));
87
+ setEditY(coords.y.toFixed(4));
88
+ });
89
+ }
90
+ };
91
+
92
+ const handleCancelEdit = () => {
93
+ setEditingIndex(null);
94
+ setEditingInteractionType(null);
95
+ setEditDetails('');
96
+ setEditTime('');
97
+ setEditX('');
98
+ setEditY('');
99
+ onHighlightPoint(null);
100
+ onSetCoordinatePicker(null);
101
+ };
102
+
103
+ const handleSaveEdit = () => {
104
+ if (editingIndex === null) return;
105
+
106
+ const timeInSeconds = parseFloat(editTime);
107
+ if (isNaN(timeInSeconds) || !videoDuration || !totalFrames) {
108
+ console.error("Cannot save edit due to invalid time or video data.");
109
+ return;
110
+ }
111
+
112
+ const newFrameIndex = Math.round((timeInSeconds / videoDuration) * totalFrames);
113
+ const originalInteraction = task.interactions[editingIndex];
114
+
115
+ const updatedInteraction: Interaction = {
116
+ ...originalInteraction,
117
+ details: editDetails,
118
+ frameIndex: newFrameIndex,
119
+ };
120
+
121
+ if (updatedInteraction.type === 'click') {
122
+ updatedInteraction.x = parseFloat(editX) || 0;
123
+ updatedInteraction.y = parseFloat(editY) || 0;
124
+ }
125
+
126
+ onUpdateInteraction(task.id, editingIndex, updatedInteraction);
127
+ handleCancelEdit();
128
+ };
129
+
130
+ const handleInteractionMouseEnter = (interaction: Interaction) => {
131
+ if (interaction.type === 'click' && interaction.x !== undefined && interaction.y !== undefined) {
132
+ onHighlightPoint({ x: interaction.x, y: interaction.y, isEditing: false });
133
+ }
134
+ };
135
+
136
+ const handleInteractionMouseLeave = () => {
137
+ onHighlightPoint(null);
138
+ };
139
+
140
+ const segmentStartTime = formatTime(calculateTime(task.startFrame) ?? 0);
141
+ const segmentEndTime = formatTime(calculateTime(task.endFrame) ?? 0);
142
+
143
+ return (
144
+ <div className="bg-slate-900/70 border border-slate-700 rounded-xl p-4 transition-all hover:border-slate-600">
145
+ <div className="flex items-start gap-4">
146
+ <div className="flex-shrink-0 bg-slate-700 text-indigo-300 font-bold text-sm w-8 h-8 flex items-center justify-center rounded-full">
147
+ {task.id}
148
+ </div>
149
+ <div className="flex-grow">
150
+ <div className="flex justify-between items-start gap-2">
151
+ <p className="font-semibold text-slate-200 pr-2">{task.description}</p>
152
+ {(calculateTime(task.startFrame) !== null) && (
153
+ <span className="text-xs font-mono text-slate-400 bg-slate-800 px-2 py-1 rounded-md whitespace-nowrap">
154
+ {segmentStartTime} - {segmentEndTime}
155
+ </span>
156
+ )}
157
+ </div>
158
+
159
+ {task.interactions && task.interactions.length > 0 && (
160
+ <div className="mt-3 space-y-1">
161
+ {task.interactions.map((interaction, index) => {
162
+ if (editingIndex === index) {
163
+ // EDITING VIEW
164
+ return (
165
+ <div key={index} className="bg-slate-800 p-3 -mx-3 rounded-lg ring-2 ring-indigo-500">
166
+ <div className="flex items-start gap-3">
167
+ <div className="flex-shrink-0 pt-1">
168
+ {interactionIcons[interaction.type] || <div className="w-4 h-4" />}
169
+ </div>
170
+ <div className="flex-grow space-y-3">
171
+ <div>
172
+ <label className="text-xs text-slate-400">Description</label>
173
+ <input
174
+ type="text"
175
+ value={editDetails}
176
+ onChange={(e) => setEditDetails(e.target.value)}
177
+ className="w-full bg-slate-900 border border-slate-600 rounded-md px-2 py-1 text-sm text-slate-200 focus:ring-1 focus:ring-indigo-400 focus:border-indigo-400"
178
+ />
179
+ </div>
180
+ <div className="grid grid-cols-1 sm:grid-cols-3 gap-2">
181
+ <div>
182
+ <label className="text-xs text-slate-400">Timestamp (s)</label>
183
+ <input
184
+ type="number"
185
+ step="0.1"
186
+ value={editTime}
187
+ onChange={(e) => setEditTime(e.target.value)}
188
+ className="w-full bg-slate-900 border border-slate-600 rounded-md px-2 py-1 text-sm text-slate-200 focus:ring-1 focus:ring-indigo-400 focus:border-indigo-400"
189
+ />
190
+ </div>
191
+ {interaction.type === 'click' && (
192
+ <>
193
+ <div>
194
+ <label className="text-xs text-slate-400">Coord. X</label>
195
+ <input
196
+ type="number"
197
+ step="0.01"
198
+ value={editX}
199
+ onChange={(e) => setEditX(e.target.value)}
200
+ className="w-full bg-slate-900 border border-slate-600 rounded-md px-2 py-1 text-sm text-slate-200 focus:ring-1 focus:ring-indigo-400 focus:border-indigo-400"
201
+ />
202
+ </div>
203
+ <div>
204
+ <label className="text-xs text-slate-400">Coord. Y</label>
205
+ <input
206
+ type="number"
207
+ step="0.01"
208
+ value={editY}
209
+ onChange={(e) => setEditY(e.target.value)}
210
+ className="w-full bg-slate-900 border border-slate-600 rounded-md px-2 py-1 text-sm text-slate-200 focus:ring-1 focus:ring-indigo-400 focus:border-indigo-400"
211
+ />
212
+ </div>
213
+ </>
214
+ )}
215
+ </div>
216
+ {interaction.type === 'click' && (
217
+ <p className="text-xs text-slate-400 italic">Click on the video player to update coordinates.</p>
218
+ )}
219
+ </div>
220
+ </div>
221
+ <div className="flex justify-end items-center gap-2 mt-3">
222
+ <button onClick={handleCancelEdit} className="px-3 py-1 text-sm font-semibold text-slate-300 hover:bg-slate-700 rounded-md">Cancel</button>
223
+ <button onClick={handleSaveEdit} className="px-3 py-1 text-sm font-semibold text-white bg-indigo-600 hover:bg-indigo-500 rounded-md">Save</button>
224
+ </div>
225
+ </div>
226
+ );
227
+ }
228
+
229
+ // DISPLAY VIEW
230
+ const interactionTime = calculateTime(interaction.frameIndex);
231
+ const interactionCoords = formatCoords(interaction.x, interaction.y);
232
+ return (
233
+ <div
234
+ key={index}
235
+ onMouseEnter={() => handleInteractionMouseEnter(interaction)}
236
+ onMouseLeave={handleInteractionMouseLeave}
237
+ className="group flex items-start gap-3 p-1.5 -mx-1.5 rounded-md transition-colors hover:bg-slate-800/60"
238
+ >
239
+ <div
240
+ className="flex-shrink-0 pt-1 cursor-pointer"
241
+ onClick={() => handleInteractionClick(interaction)}
242
+ role="button"
243
+ tabIndex={0}
244
+ onKeyPress={(e) => (e.key === 'Enter' || e.key === ' ') && handleInteractionClick(interaction)}
245
+ >
246
+ {interactionIcons[interaction.type] || <div className="w-4 h-4" />}
247
+ </div>
248
+ <div
249
+ className="flex-grow text-sm text-slate-400 cursor-pointer"
250
+ onClick={() => handleInteractionClick(interaction)}
251
+ role="button"
252
+ >
253
+ <span>{interaction.details}</span>
254
+ {interaction.type === 'click' && interactionCoords && (
255
+ <span className="ml-2 font-mono text-xs text-cyan-400">{interactionCoords}</span>
256
+ )}
257
+ </div>
258
+ <div className="flex items-center gap-2">
259
+ {interactionTime !== null && (
260
+ <span className="text-xs font-mono text-slate-500 whitespace-nowrap hidden group-hover:inline">
261
+ (~{formatTime(interactionTime)})
262
+ </span>
263
+ )}
264
+ <button
265
+ onClick={() => handleEditClick(interaction, index)}
266
+ className="hidden group-hover:block p-1 text-slate-500 hover:text-slate-300 transition-colors"
267
+ aria-label="Edit interaction"
268
+ >
269
+ <Pencil className="w-3.5 h-3.5" />
270
+ </button>
271
+ </div>
272
+ </div>
273
+ );
274
+ })}
275
+ </div>
276
+ )}
277
+ </div>
278
+ </div>
279
+ </div>
280
+ );
281
+ };
components/VideoPlayer.tsx ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { forwardRef } from 'react';
2
+
3
+ interface VideoPlayerProps {
4
+ src: string;
5
+ highlightPoint: { x: number; y: number } | null;
6
+ onVideoClick: (coords: { x: number; y: number }) => void;
7
+ }
8
+
9
+ export const VideoPlayer = forwardRef<HTMLVideoElement, VideoPlayerProps>(
10
+ ({ src, highlightPoint, onVideoClick }, ref) => {
11
+
12
+ const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
13
+ const rect = e.currentTarget.getBoundingClientRect();
14
+ const x = (e.clientX - rect.left) / rect.width;
15
+ const y = (e.clientY - rect.top) / rect.height;
16
+ onVideoClick({ x, y });
17
+ };
18
+
19
+ return (
20
+ <div className="relative w-full h-full" onClick={handleOverlayClick}>
21
+ <video
22
+ ref={ref}
23
+ src={src}
24
+ controls
25
+ className="w-full h-full object-contain rounded-lg"
26
+ key={src} // Force re-render when src changes
27
+ >
28
+ Your browser does not support the video tag.
29
+ </video>
30
+ {highlightPoint && (
31
+ <div
32
+ className="absolute top-0 left-0 w-full h-full pointer-events-none"
33
+ >
34
+ <span
35
+ className="absolute w-4 h-4 -ml-2 -mt-2 bg-red-500/80 rounded-full ring-2 ring-white/80 shadow-lg"
36
+ style={{
37
+ left: `${highlightPoint.x * 100}%`,
38
+ top: `${highlightPoint.y * 100}%`,
39
+ transform: 'scale(1)',
40
+ transition: 'transform 0.1s ease-out',
41
+ }}
42
+ aria-hidden="true"
43
+ ></span>
44
+ </div>
45
+ )}
46
+ </div>
47
+ );
48
+ }
49
+ );
50
+
51
+ VideoPlayer.displayName = "VideoPlayer";
components/VideoUploader.tsx ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useCallback, useRef } from 'react';
3
+ import { FileVideo } from './Icons';
4
+
5
+ interface VideoUploaderProps {
6
+ onVideoSelect: (file: File) => void;
7
+ }
8
+
9
+ export const VideoUploader: React.FC<VideoUploaderProps> = ({ onVideoSelect }) => {
10
+ const [isDragging, setIsDragging] = useState(false);
11
+ const fileInputRef = useRef<HTMLInputElement>(null);
12
+
13
+ const handleDrag = useCallback((e: React.DragEvent<HTMLDivElement>) => {
14
+ e.preventDefault();
15
+ e.stopPropagation();
16
+ }, []);
17
+
18
+ const handleDragIn = useCallback((e: React.DragEvent<HTMLDivElement>) => {
19
+ e.preventDefault();
20
+ e.stopPropagation();
21
+ if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
22
+ setIsDragging(true);
23
+ }
24
+ }, []);
25
+
26
+ const handleDragOut = useCallback((e: React.DragEvent<HTMLDivElement>) => {
27
+ e.preventDefault();
28
+ e.stopPropagation();
29
+ setIsDragging(false);
30
+ }, []);
31
+
32
+ const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
33
+ e.preventDefault();
34
+ e.stopPropagation();
35
+ setIsDragging(false);
36
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
37
+ onVideoSelect(e.dataTransfer.files[0]);
38
+ e.dataTransfer.clearData();
39
+ }
40
+ }, [onVideoSelect]);
41
+
42
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
43
+ if (e.target.files && e.target.files.length > 0) {
44
+ onVideoSelect(e.target.files[0]);
45
+ }
46
+ };
47
+
48
+ const onButtonClick = () => {
49
+ fileInputRef.current?.click();
50
+ };
51
+
52
+ return (
53
+ <div
54
+ onDragEnter={handleDragIn}
55
+ onDragLeave={handleDragOut}
56
+ onDragOver={handleDrag}
57
+ onDrop={handleDrop}
58
+ onClick={onButtonClick}
59
+ className={`w-full h-full p-4 flex flex-col items-center justify-center border-4 border-dashed rounded-xl transition-all duration-300 cursor-pointer
60
+ ${isDragging ? 'border-indigo-500 bg-slate-700/50' : 'border-slate-600 hover:border-indigo-600 hover:bg-slate-700/30'}`}
61
+ >
62
+ <input
63
+ ref={fileInputRef}
64
+ type="file"
65
+ accept="video/*"
66
+ onChange={handleFileChange}
67
+ className="hidden"
68
+ />
69
+ <div className="text-center pointer-events-none">
70
+ <FileVideo className="w-16 h-16 mx-auto text-slate-500 mb-4" />
71
+ <p className="text-lg font-semibold text-slate-300">Drag & Drop Video File</p>
72
+ <p className="text-slate-400">or click to select a file</p>
73
+ </div>
74
+ </div>
75
+ );
76
+ };
index.html ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <title>VLA Data Generator</title>
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <style>
11
+ /* For a custom scrollbar to match the dark theme */
12
+ ::-webkit-scrollbar {
13
+ width: 8px;
14
+ height: 8px;
15
+ }
16
+ ::-webkit-scrollbar-track {
17
+ background: #1e293b; /* slate-800 */
18
+ }
19
+ ::-webkit-scrollbar-thumb {
20
+ background: #475569; /* slate-600 */
21
+ border-radius: 4px;
22
+ }
23
+ ::-webkit-scrollbar-thumb:hover {
24
+ background: #64748b; /* slate-500 */
25
+ }
26
+ </style>
27
+ <script type="importmap">
28
+ {
29
+ "imports": {
30
+ "react": "https://esm.sh/react@^19.1.0",
31
+ "react-dom/": "https://esm.sh/react-dom@^19.1.0/",
32
+ "react/": "https://esm.sh/react@^19.1.0/",
33
+ "@google/genai": "https://esm.sh/@google/genai@^1.8.0"
34
+ }
35
+ }
36
+ </script>
37
+ </head>
38
+ <body class="bg-slate-900">
39
+ <div id="root"></div>
40
+ <script type="module" src="/index.tsx"></script>
41
+ </body>
42
+ </html>
index.tsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+ import ReactDOM from 'react-dom/client';
4
+ import App from './App';
5
+
6
+ const rootElement = document.getElementById('root');
7
+ if (!rootElement) {
8
+ throw new Error("Could not find root element to mount to");
9
+ }
10
+
11
+ const root = ReactDOM.createRoot(rootElement);
12
+ root.render(
13
+ <React.StrictMode>
14
+ <App />
15
+ </React.StrictMode>
16
+ );
metadata.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "name": "VLA Data Generator",
3
+ "description": "An AI-powered tool to analyze screen recordings. It understands the user's goal, breaks down the video into sequential tasks, and identifies key actions like clicks and keypresses to generate structured data.",
4
+ "requestFramePermissions": [],
5
+ "prompt": ""
6
+ }
package-lock.json ADDED
@@ -0,0 +1,2048 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "vla-data-generator",
3
+ "version": "0.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "vla-data-generator",
9
+ "version": "0.0.0",
10
+ "dependencies": {
11
+ "@google/genai": "^1.8.0",
12
+ "react": "^19.1.0",
13
+ "react-dom": "^19.1.0"
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^22.14.0",
17
+ "@types/react": "^19.1.0",
18
+ "@types/react-dom": "^19.1.0",
19
+ "@vitejs/plugin-react": "^4.0.0",
20
+ "typescript": "~5.7.2",
21
+ "vite": "^6.2.0"
22
+ }
23
+ },
24
+ "node_modules/@ampproject/remapping": {
25
+ "version": "2.3.0",
26
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
27
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
28
+ "dev": true,
29
+ "license": "Apache-2.0",
30
+ "dependencies": {
31
+ "@jridgewell/gen-mapping": "^0.3.5",
32
+ "@jridgewell/trace-mapping": "^0.3.24"
33
+ },
34
+ "engines": {
35
+ "node": ">=6.0.0"
36
+ }
37
+ },
38
+ "node_modules/@babel/code-frame": {
39
+ "version": "7.27.1",
40
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
41
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
42
+ "dev": true,
43
+ "license": "MIT",
44
+ "dependencies": {
45
+ "@babel/helper-validator-identifier": "^7.27.1",
46
+ "js-tokens": "^4.0.0",
47
+ "picocolors": "^1.1.1"
48
+ },
49
+ "engines": {
50
+ "node": ">=6.9.0"
51
+ }
52
+ },
53
+ "node_modules/@babel/compat-data": {
54
+ "version": "7.28.0",
55
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
56
+ "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
57
+ "dev": true,
58
+ "license": "MIT",
59
+ "engines": {
60
+ "node": ">=6.9.0"
61
+ }
62
+ },
63
+ "node_modules/@babel/core": {
64
+ "version": "7.28.0",
65
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
66
+ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
67
+ "dev": true,
68
+ "license": "MIT",
69
+ "dependencies": {
70
+ "@ampproject/remapping": "^2.2.0",
71
+ "@babel/code-frame": "^7.27.1",
72
+ "@babel/generator": "^7.28.0",
73
+ "@babel/helper-compilation-targets": "^7.27.2",
74
+ "@babel/helper-module-transforms": "^7.27.3",
75
+ "@babel/helpers": "^7.27.6",
76
+ "@babel/parser": "^7.28.0",
77
+ "@babel/template": "^7.27.2",
78
+ "@babel/traverse": "^7.28.0",
79
+ "@babel/types": "^7.28.0",
80
+ "convert-source-map": "^2.0.0",
81
+ "debug": "^4.1.0",
82
+ "gensync": "^1.0.0-beta.2",
83
+ "json5": "^2.2.3",
84
+ "semver": "^6.3.1"
85
+ },
86
+ "engines": {
87
+ "node": ">=6.9.0"
88
+ },
89
+ "funding": {
90
+ "type": "opencollective",
91
+ "url": "https://opencollective.com/babel"
92
+ }
93
+ },
94
+ "node_modules/@babel/generator": {
95
+ "version": "7.28.0",
96
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
97
+ "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
98
+ "dev": true,
99
+ "license": "MIT",
100
+ "dependencies": {
101
+ "@babel/parser": "^7.28.0",
102
+ "@babel/types": "^7.28.0",
103
+ "@jridgewell/gen-mapping": "^0.3.12",
104
+ "@jridgewell/trace-mapping": "^0.3.28",
105
+ "jsesc": "^3.0.2"
106
+ },
107
+ "engines": {
108
+ "node": ">=6.9.0"
109
+ }
110
+ },
111
+ "node_modules/@babel/helper-compilation-targets": {
112
+ "version": "7.27.2",
113
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
114
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
115
+ "dev": true,
116
+ "license": "MIT",
117
+ "dependencies": {
118
+ "@babel/compat-data": "^7.27.2",
119
+ "@babel/helper-validator-option": "^7.27.1",
120
+ "browserslist": "^4.24.0",
121
+ "lru-cache": "^5.1.1",
122
+ "semver": "^6.3.1"
123
+ },
124
+ "engines": {
125
+ "node": ">=6.9.0"
126
+ }
127
+ },
128
+ "node_modules/@babel/helper-globals": {
129
+ "version": "7.28.0",
130
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
131
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
132
+ "dev": true,
133
+ "license": "MIT",
134
+ "engines": {
135
+ "node": ">=6.9.0"
136
+ }
137
+ },
138
+ "node_modules/@babel/helper-module-imports": {
139
+ "version": "7.27.1",
140
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
141
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
142
+ "dev": true,
143
+ "license": "MIT",
144
+ "dependencies": {
145
+ "@babel/traverse": "^7.27.1",
146
+ "@babel/types": "^7.27.1"
147
+ },
148
+ "engines": {
149
+ "node": ">=6.9.0"
150
+ }
151
+ },
152
+ "node_modules/@babel/helper-module-transforms": {
153
+ "version": "7.27.3",
154
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
155
+ "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
156
+ "dev": true,
157
+ "license": "MIT",
158
+ "dependencies": {
159
+ "@babel/helper-module-imports": "^7.27.1",
160
+ "@babel/helper-validator-identifier": "^7.27.1",
161
+ "@babel/traverse": "^7.27.3"
162
+ },
163
+ "engines": {
164
+ "node": ">=6.9.0"
165
+ },
166
+ "peerDependencies": {
167
+ "@babel/core": "^7.0.0"
168
+ }
169
+ },
170
+ "node_modules/@babel/helper-plugin-utils": {
171
+ "version": "7.27.1",
172
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
173
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
174
+ "dev": true,
175
+ "license": "MIT",
176
+ "engines": {
177
+ "node": ">=6.9.0"
178
+ }
179
+ },
180
+ "node_modules/@babel/helper-string-parser": {
181
+ "version": "7.27.1",
182
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
183
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
184
+ "dev": true,
185
+ "license": "MIT",
186
+ "engines": {
187
+ "node": ">=6.9.0"
188
+ }
189
+ },
190
+ "node_modules/@babel/helper-validator-identifier": {
191
+ "version": "7.27.1",
192
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
193
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
194
+ "dev": true,
195
+ "license": "MIT",
196
+ "engines": {
197
+ "node": ">=6.9.0"
198
+ }
199
+ },
200
+ "node_modules/@babel/helper-validator-option": {
201
+ "version": "7.27.1",
202
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
203
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
204
+ "dev": true,
205
+ "license": "MIT",
206
+ "engines": {
207
+ "node": ">=6.9.0"
208
+ }
209
+ },
210
+ "node_modules/@babel/helpers": {
211
+ "version": "7.27.6",
212
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
213
+ "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
214
+ "dev": true,
215
+ "license": "MIT",
216
+ "dependencies": {
217
+ "@babel/template": "^7.27.2",
218
+ "@babel/types": "^7.27.6"
219
+ },
220
+ "engines": {
221
+ "node": ">=6.9.0"
222
+ }
223
+ },
224
+ "node_modules/@babel/parser": {
225
+ "version": "7.28.0",
226
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
227
+ "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
228
+ "dev": true,
229
+ "license": "MIT",
230
+ "dependencies": {
231
+ "@babel/types": "^7.28.0"
232
+ },
233
+ "bin": {
234
+ "parser": "bin/babel-parser.js"
235
+ },
236
+ "engines": {
237
+ "node": ">=6.0.0"
238
+ }
239
+ },
240
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
241
+ "version": "7.27.1",
242
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
243
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
244
+ "dev": true,
245
+ "license": "MIT",
246
+ "dependencies": {
247
+ "@babel/helper-plugin-utils": "^7.27.1"
248
+ },
249
+ "engines": {
250
+ "node": ">=6.9.0"
251
+ },
252
+ "peerDependencies": {
253
+ "@babel/core": "^7.0.0-0"
254
+ }
255
+ },
256
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
257
+ "version": "7.27.1",
258
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
259
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
260
+ "dev": true,
261
+ "license": "MIT",
262
+ "dependencies": {
263
+ "@babel/helper-plugin-utils": "^7.27.1"
264
+ },
265
+ "engines": {
266
+ "node": ">=6.9.0"
267
+ },
268
+ "peerDependencies": {
269
+ "@babel/core": "^7.0.0-0"
270
+ }
271
+ },
272
+ "node_modules/@babel/template": {
273
+ "version": "7.27.2",
274
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
275
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
276
+ "dev": true,
277
+ "license": "MIT",
278
+ "dependencies": {
279
+ "@babel/code-frame": "^7.27.1",
280
+ "@babel/parser": "^7.27.2",
281
+ "@babel/types": "^7.27.1"
282
+ },
283
+ "engines": {
284
+ "node": ">=6.9.0"
285
+ }
286
+ },
287
+ "node_modules/@babel/traverse": {
288
+ "version": "7.28.0",
289
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
290
+ "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
291
+ "dev": true,
292
+ "license": "MIT",
293
+ "dependencies": {
294
+ "@babel/code-frame": "^7.27.1",
295
+ "@babel/generator": "^7.28.0",
296
+ "@babel/helper-globals": "^7.28.0",
297
+ "@babel/parser": "^7.28.0",
298
+ "@babel/template": "^7.27.2",
299
+ "@babel/types": "^7.28.0",
300
+ "debug": "^4.3.1"
301
+ },
302
+ "engines": {
303
+ "node": ">=6.9.0"
304
+ }
305
+ },
306
+ "node_modules/@babel/types": {
307
+ "version": "7.28.1",
308
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz",
309
+ "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==",
310
+ "dev": true,
311
+ "license": "MIT",
312
+ "dependencies": {
313
+ "@babel/helper-string-parser": "^7.27.1",
314
+ "@babel/helper-validator-identifier": "^7.27.1"
315
+ },
316
+ "engines": {
317
+ "node": ">=6.9.0"
318
+ }
319
+ },
320
+ "node_modules/@esbuild/aix-ppc64": {
321
+ "version": "0.25.7",
322
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.7.tgz",
323
+ "integrity": "sha512-uD0kKFHh6ETr8TqEtaAcV+dn/2qnYbH/+8wGEdY70Qf7l1l/jmBUbrmQqwiPKAQE6cOQ7dTj6Xr0HzQDGHyceQ==",
324
+ "cpu": [
325
+ "ppc64"
326
+ ],
327
+ "dev": true,
328
+ "license": "MIT",
329
+ "optional": true,
330
+ "os": [
331
+ "aix"
332
+ ],
333
+ "engines": {
334
+ "node": ">=18"
335
+ }
336
+ },
337
+ "node_modules/@esbuild/android-arm": {
338
+ "version": "0.25.7",
339
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.7.tgz",
340
+ "integrity": "sha512-Jhuet0g1k9rAJHrXGIh7sFknFuT4sfytYZpZpuZl7YKDhnPByVAm5oy2LEBmMbuYf3ejWVYCc2seX81Mk+madA==",
341
+ "cpu": [
342
+ "arm"
343
+ ],
344
+ "dev": true,
345
+ "license": "MIT",
346
+ "optional": true,
347
+ "os": [
348
+ "android"
349
+ ],
350
+ "engines": {
351
+ "node": ">=18"
352
+ }
353
+ },
354
+ "node_modules/@esbuild/android-arm64": {
355
+ "version": "0.25.7",
356
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.7.tgz",
357
+ "integrity": "sha512-p0ohDnwyIbAtztHTNUTzN5EGD/HJLs1bwysrOPgSdlIA6NDnReoVfoCyxG6W1d85jr2X80Uq5KHftyYgaK9LPQ==",
358
+ "cpu": [
359
+ "arm64"
360
+ ],
361
+ "dev": true,
362
+ "license": "MIT",
363
+ "optional": true,
364
+ "os": [
365
+ "android"
366
+ ],
367
+ "engines": {
368
+ "node": ">=18"
369
+ }
370
+ },
371
+ "node_modules/@esbuild/android-x64": {
372
+ "version": "0.25.7",
373
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.7.tgz",
374
+ "integrity": "sha512-mMxIJFlSgVK23HSsII3ZX9T2xKrBCDGyk0qiZnIW10LLFFtZLkFD6imZHu7gUo2wkNZwS9Yj3mOtZD3ZPcjCcw==",
375
+ "cpu": [
376
+ "x64"
377
+ ],
378
+ "dev": true,
379
+ "license": "MIT",
380
+ "optional": true,
381
+ "os": [
382
+ "android"
383
+ ],
384
+ "engines": {
385
+ "node": ">=18"
386
+ }
387
+ },
388
+ "node_modules/@esbuild/darwin-arm64": {
389
+ "version": "0.25.7",
390
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.7.tgz",
391
+ "integrity": "sha512-jyOFLGP2WwRwxM8F1VpP6gcdIJc8jq2CUrURbbTouJoRO7XCkU8GdnTDFIHdcifVBT45cJlOYsZ1kSlfbKjYUQ==",
392
+ "cpu": [
393
+ "arm64"
394
+ ],
395
+ "dev": true,
396
+ "license": "MIT",
397
+ "optional": true,
398
+ "os": [
399
+ "darwin"
400
+ ],
401
+ "engines": {
402
+ "node": ">=18"
403
+ }
404
+ },
405
+ "node_modules/@esbuild/darwin-x64": {
406
+ "version": "0.25.7",
407
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.7.tgz",
408
+ "integrity": "sha512-m9bVWqZCwQ1BthruifvG64hG03zzz9gE2r/vYAhztBna1/+qXiHyP9WgnyZqHgGeXoimJPhAmxfbeU+nMng6ZA==",
409
+ "cpu": [
410
+ "x64"
411
+ ],
412
+ "dev": true,
413
+ "license": "MIT",
414
+ "optional": true,
415
+ "os": [
416
+ "darwin"
417
+ ],
418
+ "engines": {
419
+ "node": ">=18"
420
+ }
421
+ },
422
+ "node_modules/@esbuild/freebsd-arm64": {
423
+ "version": "0.25.7",
424
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.7.tgz",
425
+ "integrity": "sha512-Bss7P4r6uhr3kDzRjPNEnTm/oIBdTPRNQuwaEFWT/uvt6A1YzK/yn5kcx5ZxZ9swOga7LqeYlu7bDIpDoS01bA==",
426
+ "cpu": [
427
+ "arm64"
428
+ ],
429
+ "dev": true,
430
+ "license": "MIT",
431
+ "optional": true,
432
+ "os": [
433
+ "freebsd"
434
+ ],
435
+ "engines": {
436
+ "node": ">=18"
437
+ }
438
+ },
439
+ "node_modules/@esbuild/freebsd-x64": {
440
+ "version": "0.25.7",
441
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.7.tgz",
442
+ "integrity": "sha512-S3BFyjW81LXG7Vqmr37ddbThrm3A84yE7ey/ERBlK9dIiaWgrjRlre3pbG7txh1Uaxz8N7wGGQXmC9zV+LIpBQ==",
443
+ "cpu": [
444
+ "x64"
445
+ ],
446
+ "dev": true,
447
+ "license": "MIT",
448
+ "optional": true,
449
+ "os": [
450
+ "freebsd"
451
+ ],
452
+ "engines": {
453
+ "node": ">=18"
454
+ }
455
+ },
456
+ "node_modules/@esbuild/linux-arm": {
457
+ "version": "0.25.7",
458
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.7.tgz",
459
+ "integrity": "sha512-JZMIci/1m5vfQuhKoFXogCKVYVfYQmoZJg8vSIMR4TUXbF+0aNlfXH3DGFEFMElT8hOTUF5hisdZhnrZO/bkDw==",
460
+ "cpu": [
461
+ "arm"
462
+ ],
463
+ "dev": true,
464
+ "license": "MIT",
465
+ "optional": true,
466
+ "os": [
467
+ "linux"
468
+ ],
469
+ "engines": {
470
+ "node": ">=18"
471
+ }
472
+ },
473
+ "node_modules/@esbuild/linux-arm64": {
474
+ "version": "0.25.7",
475
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.7.tgz",
476
+ "integrity": "sha512-HfQZQqrNOfS1Okn7PcsGUqHymL1cWGBslf78dGvtrj8q7cN3FkapFgNA4l/a5lXDwr7BqP2BSO6mz9UremNPbg==",
477
+ "cpu": [
478
+ "arm64"
479
+ ],
480
+ "dev": true,
481
+ "license": "MIT",
482
+ "optional": true,
483
+ "os": [
484
+ "linux"
485
+ ],
486
+ "engines": {
487
+ "node": ">=18"
488
+ }
489
+ },
490
+ "node_modules/@esbuild/linux-ia32": {
491
+ "version": "0.25.7",
492
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.7.tgz",
493
+ "integrity": "sha512-9Jex4uVpdeofiDxnwHRgen+j6398JlX4/6SCbbEFEXN7oMO2p0ueLN+e+9DdsdPLUdqns607HmzEFnxwr7+5wQ==",
494
+ "cpu": [
495
+ "ia32"
496
+ ],
497
+ "dev": true,
498
+ "license": "MIT",
499
+ "optional": true,
500
+ "os": [
501
+ "linux"
502
+ ],
503
+ "engines": {
504
+ "node": ">=18"
505
+ }
506
+ },
507
+ "node_modules/@esbuild/linux-loong64": {
508
+ "version": "0.25.7",
509
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.7.tgz",
510
+ "integrity": "sha512-TG1KJqjBlN9IHQjKVUYDB0/mUGgokfhhatlay8aZ/MSORMubEvj/J1CL8YGY4EBcln4z7rKFbsH+HeAv0d471w==",
511
+ "cpu": [
512
+ "loong64"
513
+ ],
514
+ "dev": true,
515
+ "license": "MIT",
516
+ "optional": true,
517
+ "os": [
518
+ "linux"
519
+ ],
520
+ "engines": {
521
+ "node": ">=18"
522
+ }
523
+ },
524
+ "node_modules/@esbuild/linux-mips64el": {
525
+ "version": "0.25.7",
526
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.7.tgz",
527
+ "integrity": "sha512-Ty9Hj/lx7ikTnhOfaP7ipEm/ICcBv94i/6/WDg0OZ3BPBHhChsUbQancoWYSO0WNkEiSW5Do4febTTy4x1qYQQ==",
528
+ "cpu": [
529
+ "mips64el"
530
+ ],
531
+ "dev": true,
532
+ "license": "MIT",
533
+ "optional": true,
534
+ "os": [
535
+ "linux"
536
+ ],
537
+ "engines": {
538
+ "node": ">=18"
539
+ }
540
+ },
541
+ "node_modules/@esbuild/linux-ppc64": {
542
+ "version": "0.25.7",
543
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.7.tgz",
544
+ "integrity": "sha512-MrOjirGQWGReJl3BNQ58BLhUBPpWABnKrnq8Q/vZWWwAB1wuLXOIxS2JQ1LT3+5T+3jfPh0tyf5CpbyQHqnWIQ==",
545
+ "cpu": [
546
+ "ppc64"
547
+ ],
548
+ "dev": true,
549
+ "license": "MIT",
550
+ "optional": true,
551
+ "os": [
552
+ "linux"
553
+ ],
554
+ "engines": {
555
+ "node": ">=18"
556
+ }
557
+ },
558
+ "node_modules/@esbuild/linux-riscv64": {
559
+ "version": "0.25.7",
560
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.7.tgz",
561
+ "integrity": "sha512-9pr23/pqzyqIZEZmQXnFyqp3vpa+KBk5TotfkzGMqpw089PGm0AIowkUppHB9derQzqniGn3wVXgck19+oqiOw==",
562
+ "cpu": [
563
+ "riscv64"
564
+ ],
565
+ "dev": true,
566
+ "license": "MIT",
567
+ "optional": true,
568
+ "os": [
569
+ "linux"
570
+ ],
571
+ "engines": {
572
+ "node": ">=18"
573
+ }
574
+ },
575
+ "node_modules/@esbuild/linux-s390x": {
576
+ "version": "0.25.7",
577
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.7.tgz",
578
+ "integrity": "sha512-4dP11UVGh9O6Y47m8YvW8eoA3r8qL2toVZUbBKyGta8j6zdw1cn9F/Rt59/Mhv0OgY68pHIMjGXWOUaykCnx+w==",
579
+ "cpu": [
580
+ "s390x"
581
+ ],
582
+ "dev": true,
583
+ "license": "MIT",
584
+ "optional": true,
585
+ "os": [
586
+ "linux"
587
+ ],
588
+ "engines": {
589
+ "node": ">=18"
590
+ }
591
+ },
592
+ "node_modules/@esbuild/linux-x64": {
593
+ "version": "0.25.7",
594
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.7.tgz",
595
+ "integrity": "sha512-ghJMAJTdw/0uhz7e7YnpdX1xVn7VqA0GrWrAO2qKMuqbvgHT2VZiBv1BQ//VcHsPir4wsL3P2oPggfKPzTKoCA==",
596
+ "cpu": [
597
+ "x64"
598
+ ],
599
+ "dev": true,
600
+ "license": "MIT",
601
+ "optional": true,
602
+ "os": [
603
+ "linux"
604
+ ],
605
+ "engines": {
606
+ "node": ">=18"
607
+ }
608
+ },
609
+ "node_modules/@esbuild/netbsd-arm64": {
610
+ "version": "0.25.7",
611
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.7.tgz",
612
+ "integrity": "sha512-bwXGEU4ua45+u5Ci/a55B85KWaDSRS8NPOHtxy2e3etDjbz23wlry37Ffzapz69JAGGc4089TBo+dGzydQmydg==",
613
+ "cpu": [
614
+ "arm64"
615
+ ],
616
+ "dev": true,
617
+ "license": "MIT",
618
+ "optional": true,
619
+ "os": [
620
+ "netbsd"
621
+ ],
622
+ "engines": {
623
+ "node": ">=18"
624
+ }
625
+ },
626
+ "node_modules/@esbuild/netbsd-x64": {
627
+ "version": "0.25.7",
628
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.7.tgz",
629
+ "integrity": "sha512-tUZRvLtgLE5OyN46sPSYlgmHoBS5bx2URSrgZdW1L1teWPYVmXh+QN/sKDqkzBo/IHGcKcHLKDhBeVVkO7teEA==",
630
+ "cpu": [
631
+ "x64"
632
+ ],
633
+ "dev": true,
634
+ "license": "MIT",
635
+ "optional": true,
636
+ "os": [
637
+ "netbsd"
638
+ ],
639
+ "engines": {
640
+ "node": ">=18"
641
+ }
642
+ },
643
+ "node_modules/@esbuild/openbsd-arm64": {
644
+ "version": "0.25.7",
645
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.7.tgz",
646
+ "integrity": "sha512-bTJ50aoC+WDlDGBReWYiObpYvQfMjBNlKztqoNUL0iUkYtwLkBQQeEsTq/I1KyjsKA5tyov6VZaPb8UdD6ci6Q==",
647
+ "cpu": [
648
+ "arm64"
649
+ ],
650
+ "dev": true,
651
+ "license": "MIT",
652
+ "optional": true,
653
+ "os": [
654
+ "openbsd"
655
+ ],
656
+ "engines": {
657
+ "node": ">=18"
658
+ }
659
+ },
660
+ "node_modules/@esbuild/openbsd-x64": {
661
+ "version": "0.25.7",
662
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.7.tgz",
663
+ "integrity": "sha512-TA9XfJrgzAipFUU895jd9j2SyDh9bbNkK2I0gHcvqb/o84UeQkBpi/XmYX3cO1q/9hZokdcDqQxIi6uLVrikxg==",
664
+ "cpu": [
665
+ "x64"
666
+ ],
667
+ "dev": true,
668
+ "license": "MIT",
669
+ "optional": true,
670
+ "os": [
671
+ "openbsd"
672
+ ],
673
+ "engines": {
674
+ "node": ">=18"
675
+ }
676
+ },
677
+ "node_modules/@esbuild/openharmony-arm64": {
678
+ "version": "0.25.7",
679
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.7.tgz",
680
+ "integrity": "sha512-5VTtExUrWwHHEUZ/N+rPlHDwVFQ5aME7vRJES8+iQ0xC/bMYckfJ0l2n3yGIfRoXcK/wq4oXSItZAz5wslTKGw==",
681
+ "cpu": [
682
+ "arm64"
683
+ ],
684
+ "dev": true,
685
+ "license": "MIT",
686
+ "optional": true,
687
+ "os": [
688
+ "openharmony"
689
+ ],
690
+ "engines": {
691
+ "node": ">=18"
692
+ }
693
+ },
694
+ "node_modules/@esbuild/sunos-x64": {
695
+ "version": "0.25.7",
696
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.7.tgz",
697
+ "integrity": "sha512-umkbn7KTxsexhv2vuuJmj9kggd4AEtL32KodkJgfhNOHMPtQ55RexsaSrMb+0+jp9XL4I4o2y91PZauVN4cH3A==",
698
+ "cpu": [
699
+ "x64"
700
+ ],
701
+ "dev": true,
702
+ "license": "MIT",
703
+ "optional": true,
704
+ "os": [
705
+ "sunos"
706
+ ],
707
+ "engines": {
708
+ "node": ">=18"
709
+ }
710
+ },
711
+ "node_modules/@esbuild/win32-arm64": {
712
+ "version": "0.25.7",
713
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.7.tgz",
714
+ "integrity": "sha512-j20JQGP/gz8QDgzl5No5Gr4F6hurAZvtkFxAKhiv2X49yi/ih8ECK4Y35YnjlMogSKJk931iNMcd35BtZ4ghfw==",
715
+ "cpu": [
716
+ "arm64"
717
+ ],
718
+ "dev": true,
719
+ "license": "MIT",
720
+ "optional": true,
721
+ "os": [
722
+ "win32"
723
+ ],
724
+ "engines": {
725
+ "node": ">=18"
726
+ }
727
+ },
728
+ "node_modules/@esbuild/win32-ia32": {
729
+ "version": "0.25.7",
730
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.7.tgz",
731
+ "integrity": "sha512-4qZ6NUfoiiKZfLAXRsvFkA0hoWVM+1y2bSHXHkpdLAs/+r0LgwqYohmfZCi985c6JWHhiXP30mgZawn/XrqAkQ==",
732
+ "cpu": [
733
+ "ia32"
734
+ ],
735
+ "dev": true,
736
+ "license": "MIT",
737
+ "optional": true,
738
+ "os": [
739
+ "win32"
740
+ ],
741
+ "engines": {
742
+ "node": ">=18"
743
+ }
744
+ },
745
+ "node_modules/@esbuild/win32-x64": {
746
+ "version": "0.25.7",
747
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.7.tgz",
748
+ "integrity": "sha512-FaPsAHTwm+1Gfvn37Eg3E5HIpfR3i6x1AIcla/MkqAIupD4BW3MrSeUqfoTzwwJhk3WE2/KqUn4/eenEJC76VA==",
749
+ "cpu": [
750
+ "x64"
751
+ ],
752
+ "dev": true,
753
+ "license": "MIT",
754
+ "optional": true,
755
+ "os": [
756
+ "win32"
757
+ ],
758
+ "engines": {
759
+ "node": ">=18"
760
+ }
761
+ },
762
+ "node_modules/@google/genai": {
763
+ "version": "1.10.0",
764
+ "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.10.0.tgz",
765
+ "integrity": "sha512-PR4tLuiIFMrpAiiCko2Z16ydikFsPF1c5TBfI64hlZcv3xBEApSCceLuDYu1pNMq2SkNh4r66J4AG+ZexBnMLw==",
766
+ "license": "Apache-2.0",
767
+ "dependencies": {
768
+ "google-auth-library": "^9.14.2",
769
+ "ws": "^8.18.0"
770
+ },
771
+ "engines": {
772
+ "node": ">=20.0.0"
773
+ },
774
+ "peerDependencies": {
775
+ "@modelcontextprotocol/sdk": "^1.11.0"
776
+ },
777
+ "peerDependenciesMeta": {
778
+ "@modelcontextprotocol/sdk": {
779
+ "optional": true
780
+ }
781
+ }
782
+ },
783
+ "node_modules/@jridgewell/gen-mapping": {
784
+ "version": "0.3.12",
785
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
786
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
787
+ "dev": true,
788
+ "license": "MIT",
789
+ "dependencies": {
790
+ "@jridgewell/sourcemap-codec": "^1.5.0",
791
+ "@jridgewell/trace-mapping": "^0.3.24"
792
+ }
793
+ },
794
+ "node_modules/@jridgewell/resolve-uri": {
795
+ "version": "3.1.2",
796
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
797
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
798
+ "dev": true,
799
+ "license": "MIT",
800
+ "engines": {
801
+ "node": ">=6.0.0"
802
+ }
803
+ },
804
+ "node_modules/@jridgewell/sourcemap-codec": {
805
+ "version": "1.5.4",
806
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
807
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
808
+ "dev": true,
809
+ "license": "MIT"
810
+ },
811
+ "node_modules/@jridgewell/trace-mapping": {
812
+ "version": "0.3.29",
813
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
814
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
815
+ "dev": true,
816
+ "license": "MIT",
817
+ "dependencies": {
818
+ "@jridgewell/resolve-uri": "^3.1.0",
819
+ "@jridgewell/sourcemap-codec": "^1.4.14"
820
+ }
821
+ },
822
+ "node_modules/@rolldown/pluginutils": {
823
+ "version": "1.0.0-beta.27",
824
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
825
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
826
+ "dev": true,
827
+ "license": "MIT"
828
+ },
829
+ "node_modules/@rollup/rollup-android-arm-eabi": {
830
+ "version": "4.45.1",
831
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz",
832
+ "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==",
833
+ "cpu": [
834
+ "arm"
835
+ ],
836
+ "dev": true,
837
+ "license": "MIT",
838
+ "optional": true,
839
+ "os": [
840
+ "android"
841
+ ]
842
+ },
843
+ "node_modules/@rollup/rollup-android-arm64": {
844
+ "version": "4.45.1",
845
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz",
846
+ "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==",
847
+ "cpu": [
848
+ "arm64"
849
+ ],
850
+ "dev": true,
851
+ "license": "MIT",
852
+ "optional": true,
853
+ "os": [
854
+ "android"
855
+ ]
856
+ },
857
+ "node_modules/@rollup/rollup-darwin-arm64": {
858
+ "version": "4.45.1",
859
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz",
860
+ "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==",
861
+ "cpu": [
862
+ "arm64"
863
+ ],
864
+ "dev": true,
865
+ "license": "MIT",
866
+ "optional": true,
867
+ "os": [
868
+ "darwin"
869
+ ]
870
+ },
871
+ "node_modules/@rollup/rollup-darwin-x64": {
872
+ "version": "4.45.1",
873
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz",
874
+ "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==",
875
+ "cpu": [
876
+ "x64"
877
+ ],
878
+ "dev": true,
879
+ "license": "MIT",
880
+ "optional": true,
881
+ "os": [
882
+ "darwin"
883
+ ]
884
+ },
885
+ "node_modules/@rollup/rollup-freebsd-arm64": {
886
+ "version": "4.45.1",
887
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz",
888
+ "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==",
889
+ "cpu": [
890
+ "arm64"
891
+ ],
892
+ "dev": true,
893
+ "license": "MIT",
894
+ "optional": true,
895
+ "os": [
896
+ "freebsd"
897
+ ]
898
+ },
899
+ "node_modules/@rollup/rollup-freebsd-x64": {
900
+ "version": "4.45.1",
901
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz",
902
+ "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==",
903
+ "cpu": [
904
+ "x64"
905
+ ],
906
+ "dev": true,
907
+ "license": "MIT",
908
+ "optional": true,
909
+ "os": [
910
+ "freebsd"
911
+ ]
912
+ },
913
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
914
+ "version": "4.45.1",
915
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz",
916
+ "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==",
917
+ "cpu": [
918
+ "arm"
919
+ ],
920
+ "dev": true,
921
+ "license": "MIT",
922
+ "optional": true,
923
+ "os": [
924
+ "linux"
925
+ ]
926
+ },
927
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
928
+ "version": "4.45.1",
929
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz",
930
+ "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==",
931
+ "cpu": [
932
+ "arm"
933
+ ],
934
+ "dev": true,
935
+ "license": "MIT",
936
+ "optional": true,
937
+ "os": [
938
+ "linux"
939
+ ]
940
+ },
941
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
942
+ "version": "4.45.1",
943
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz",
944
+ "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==",
945
+ "cpu": [
946
+ "arm64"
947
+ ],
948
+ "dev": true,
949
+ "license": "MIT",
950
+ "optional": true,
951
+ "os": [
952
+ "linux"
953
+ ]
954
+ },
955
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
956
+ "version": "4.45.1",
957
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz",
958
+ "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==",
959
+ "cpu": [
960
+ "arm64"
961
+ ],
962
+ "dev": true,
963
+ "license": "MIT",
964
+ "optional": true,
965
+ "os": [
966
+ "linux"
967
+ ]
968
+ },
969
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
970
+ "version": "4.45.1",
971
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz",
972
+ "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==",
973
+ "cpu": [
974
+ "loong64"
975
+ ],
976
+ "dev": true,
977
+ "license": "MIT",
978
+ "optional": true,
979
+ "os": [
980
+ "linux"
981
+ ]
982
+ },
983
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
984
+ "version": "4.45.1",
985
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz",
986
+ "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==",
987
+ "cpu": [
988
+ "ppc64"
989
+ ],
990
+ "dev": true,
991
+ "license": "MIT",
992
+ "optional": true,
993
+ "os": [
994
+ "linux"
995
+ ]
996
+ },
997
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
998
+ "version": "4.45.1",
999
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz",
1000
+ "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==",
1001
+ "cpu": [
1002
+ "riscv64"
1003
+ ],
1004
+ "dev": true,
1005
+ "license": "MIT",
1006
+ "optional": true,
1007
+ "os": [
1008
+ "linux"
1009
+ ]
1010
+ },
1011
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
1012
+ "version": "4.45.1",
1013
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz",
1014
+ "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==",
1015
+ "cpu": [
1016
+ "riscv64"
1017
+ ],
1018
+ "dev": true,
1019
+ "license": "MIT",
1020
+ "optional": true,
1021
+ "os": [
1022
+ "linux"
1023
+ ]
1024
+ },
1025
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
1026
+ "version": "4.45.1",
1027
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz",
1028
+ "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==",
1029
+ "cpu": [
1030
+ "s390x"
1031
+ ],
1032
+ "dev": true,
1033
+ "license": "MIT",
1034
+ "optional": true,
1035
+ "os": [
1036
+ "linux"
1037
+ ]
1038
+ },
1039
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
1040
+ "version": "4.45.1",
1041
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz",
1042
+ "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==",
1043
+ "cpu": [
1044
+ "x64"
1045
+ ],
1046
+ "dev": true,
1047
+ "license": "MIT",
1048
+ "optional": true,
1049
+ "os": [
1050
+ "linux"
1051
+ ]
1052
+ },
1053
+ "node_modules/@rollup/rollup-linux-x64-musl": {
1054
+ "version": "4.45.1",
1055
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz",
1056
+ "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==",
1057
+ "cpu": [
1058
+ "x64"
1059
+ ],
1060
+ "dev": true,
1061
+ "license": "MIT",
1062
+ "optional": true,
1063
+ "os": [
1064
+ "linux"
1065
+ ]
1066
+ },
1067
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
1068
+ "version": "4.45.1",
1069
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz",
1070
+ "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==",
1071
+ "cpu": [
1072
+ "arm64"
1073
+ ],
1074
+ "dev": true,
1075
+ "license": "MIT",
1076
+ "optional": true,
1077
+ "os": [
1078
+ "win32"
1079
+ ]
1080
+ },
1081
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
1082
+ "version": "4.45.1",
1083
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz",
1084
+ "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==",
1085
+ "cpu": [
1086
+ "ia32"
1087
+ ],
1088
+ "dev": true,
1089
+ "license": "MIT",
1090
+ "optional": true,
1091
+ "os": [
1092
+ "win32"
1093
+ ]
1094
+ },
1095
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
1096
+ "version": "4.45.1",
1097
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz",
1098
+ "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==",
1099
+ "cpu": [
1100
+ "x64"
1101
+ ],
1102
+ "dev": true,
1103
+ "license": "MIT",
1104
+ "optional": true,
1105
+ "os": [
1106
+ "win32"
1107
+ ]
1108
+ },
1109
+ "node_modules/@types/babel__core": {
1110
+ "version": "7.20.5",
1111
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
1112
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
1113
+ "dev": true,
1114
+ "license": "MIT",
1115
+ "dependencies": {
1116
+ "@babel/parser": "^7.20.7",
1117
+ "@babel/types": "^7.20.7",
1118
+ "@types/babel__generator": "*",
1119
+ "@types/babel__template": "*",
1120
+ "@types/babel__traverse": "*"
1121
+ }
1122
+ },
1123
+ "node_modules/@types/babel__generator": {
1124
+ "version": "7.27.0",
1125
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
1126
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
1127
+ "dev": true,
1128
+ "license": "MIT",
1129
+ "dependencies": {
1130
+ "@babel/types": "^7.0.0"
1131
+ }
1132
+ },
1133
+ "node_modules/@types/babel__template": {
1134
+ "version": "7.4.4",
1135
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
1136
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
1137
+ "dev": true,
1138
+ "license": "MIT",
1139
+ "dependencies": {
1140
+ "@babel/parser": "^7.1.0",
1141
+ "@babel/types": "^7.0.0"
1142
+ }
1143
+ },
1144
+ "node_modules/@types/babel__traverse": {
1145
+ "version": "7.20.7",
1146
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
1147
+ "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
1148
+ "dev": true,
1149
+ "license": "MIT",
1150
+ "dependencies": {
1151
+ "@babel/types": "^7.20.7"
1152
+ }
1153
+ },
1154
+ "node_modules/@types/estree": {
1155
+ "version": "1.0.8",
1156
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
1157
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
1158
+ "dev": true,
1159
+ "license": "MIT"
1160
+ },
1161
+ "node_modules/@types/node": {
1162
+ "version": "22.16.5",
1163
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.5.tgz",
1164
+ "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==",
1165
+ "dev": true,
1166
+ "license": "MIT",
1167
+ "dependencies": {
1168
+ "undici-types": "~6.21.0"
1169
+ }
1170
+ },
1171
+ "node_modules/@types/react": {
1172
+ "version": "19.1.8",
1173
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
1174
+ "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
1175
+ "dev": true,
1176
+ "license": "MIT",
1177
+ "dependencies": {
1178
+ "csstype": "^3.0.2"
1179
+ }
1180
+ },
1181
+ "node_modules/@types/react-dom": {
1182
+ "version": "19.1.6",
1183
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz",
1184
+ "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
1185
+ "dev": true,
1186
+ "license": "MIT",
1187
+ "peerDependencies": {
1188
+ "@types/react": "^19.0.0"
1189
+ }
1190
+ },
1191
+ "node_modules/@vitejs/plugin-react": {
1192
+ "version": "4.7.0",
1193
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
1194
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
1195
+ "dev": true,
1196
+ "license": "MIT",
1197
+ "dependencies": {
1198
+ "@babel/core": "^7.28.0",
1199
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
1200
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
1201
+ "@rolldown/pluginutils": "1.0.0-beta.27",
1202
+ "@types/babel__core": "^7.20.5",
1203
+ "react-refresh": "^0.17.0"
1204
+ },
1205
+ "engines": {
1206
+ "node": "^14.18.0 || >=16.0.0"
1207
+ },
1208
+ "peerDependencies": {
1209
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
1210
+ }
1211
+ },
1212
+ "node_modules/agent-base": {
1213
+ "version": "7.1.4",
1214
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
1215
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
1216
+ "license": "MIT",
1217
+ "engines": {
1218
+ "node": ">= 14"
1219
+ }
1220
+ },
1221
+ "node_modules/base64-js": {
1222
+ "version": "1.5.1",
1223
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
1224
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
1225
+ "funding": [
1226
+ {
1227
+ "type": "github",
1228
+ "url": "https://github.com/sponsors/feross"
1229
+ },
1230
+ {
1231
+ "type": "patreon",
1232
+ "url": "https://www.patreon.com/feross"
1233
+ },
1234
+ {
1235
+ "type": "consulting",
1236
+ "url": "https://feross.org/support"
1237
+ }
1238
+ ],
1239
+ "license": "MIT"
1240
+ },
1241
+ "node_modules/bignumber.js": {
1242
+ "version": "9.3.1",
1243
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
1244
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
1245
+ "license": "MIT",
1246
+ "engines": {
1247
+ "node": "*"
1248
+ }
1249
+ },
1250
+ "node_modules/browserslist": {
1251
+ "version": "4.25.1",
1252
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
1253
+ "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
1254
+ "dev": true,
1255
+ "funding": [
1256
+ {
1257
+ "type": "opencollective",
1258
+ "url": "https://opencollective.com/browserslist"
1259
+ },
1260
+ {
1261
+ "type": "tidelift",
1262
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1263
+ },
1264
+ {
1265
+ "type": "github",
1266
+ "url": "https://github.com/sponsors/ai"
1267
+ }
1268
+ ],
1269
+ "license": "MIT",
1270
+ "dependencies": {
1271
+ "caniuse-lite": "^1.0.30001726",
1272
+ "electron-to-chromium": "^1.5.173",
1273
+ "node-releases": "^2.0.19",
1274
+ "update-browserslist-db": "^1.1.3"
1275
+ },
1276
+ "bin": {
1277
+ "browserslist": "cli.js"
1278
+ },
1279
+ "engines": {
1280
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1281
+ }
1282
+ },
1283
+ "node_modules/buffer-equal-constant-time": {
1284
+ "version": "1.0.1",
1285
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
1286
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
1287
+ "license": "BSD-3-Clause"
1288
+ },
1289
+ "node_modules/caniuse-lite": {
1290
+ "version": "1.0.30001727",
1291
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
1292
+ "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
1293
+ "dev": true,
1294
+ "funding": [
1295
+ {
1296
+ "type": "opencollective",
1297
+ "url": "https://opencollective.com/browserslist"
1298
+ },
1299
+ {
1300
+ "type": "tidelift",
1301
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1302
+ },
1303
+ {
1304
+ "type": "github",
1305
+ "url": "https://github.com/sponsors/ai"
1306
+ }
1307
+ ],
1308
+ "license": "CC-BY-4.0"
1309
+ },
1310
+ "node_modules/convert-source-map": {
1311
+ "version": "2.0.0",
1312
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1313
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1314
+ "dev": true,
1315
+ "license": "MIT"
1316
+ },
1317
+ "node_modules/csstype": {
1318
+ "version": "3.1.3",
1319
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
1320
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
1321
+ "dev": true,
1322
+ "license": "MIT"
1323
+ },
1324
+ "node_modules/debug": {
1325
+ "version": "4.4.1",
1326
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
1327
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
1328
+ "license": "MIT",
1329
+ "dependencies": {
1330
+ "ms": "^2.1.3"
1331
+ },
1332
+ "engines": {
1333
+ "node": ">=6.0"
1334
+ },
1335
+ "peerDependenciesMeta": {
1336
+ "supports-color": {
1337
+ "optional": true
1338
+ }
1339
+ }
1340
+ },
1341
+ "node_modules/ecdsa-sig-formatter": {
1342
+ "version": "1.0.11",
1343
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
1344
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
1345
+ "license": "Apache-2.0",
1346
+ "dependencies": {
1347
+ "safe-buffer": "^5.0.1"
1348
+ }
1349
+ },
1350
+ "node_modules/electron-to-chromium": {
1351
+ "version": "1.5.187",
1352
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz",
1353
+ "integrity": "sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==",
1354
+ "dev": true,
1355
+ "license": "ISC"
1356
+ },
1357
+ "node_modules/esbuild": {
1358
+ "version": "0.25.7",
1359
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.7.tgz",
1360
+ "integrity": "sha512-daJB0q2dmTzo90L9NjRaohhRWrCzYxWNFTjEi72/h+p5DcY3yn4MacWfDakHmaBaDzDiuLJsCh0+6LK/iX+c+Q==",
1361
+ "dev": true,
1362
+ "hasInstallScript": true,
1363
+ "license": "MIT",
1364
+ "bin": {
1365
+ "esbuild": "bin/esbuild"
1366
+ },
1367
+ "engines": {
1368
+ "node": ">=18"
1369
+ },
1370
+ "optionalDependencies": {
1371
+ "@esbuild/aix-ppc64": "0.25.7",
1372
+ "@esbuild/android-arm": "0.25.7",
1373
+ "@esbuild/android-arm64": "0.25.7",
1374
+ "@esbuild/android-x64": "0.25.7",
1375
+ "@esbuild/darwin-arm64": "0.25.7",
1376
+ "@esbuild/darwin-x64": "0.25.7",
1377
+ "@esbuild/freebsd-arm64": "0.25.7",
1378
+ "@esbuild/freebsd-x64": "0.25.7",
1379
+ "@esbuild/linux-arm": "0.25.7",
1380
+ "@esbuild/linux-arm64": "0.25.7",
1381
+ "@esbuild/linux-ia32": "0.25.7",
1382
+ "@esbuild/linux-loong64": "0.25.7",
1383
+ "@esbuild/linux-mips64el": "0.25.7",
1384
+ "@esbuild/linux-ppc64": "0.25.7",
1385
+ "@esbuild/linux-riscv64": "0.25.7",
1386
+ "@esbuild/linux-s390x": "0.25.7",
1387
+ "@esbuild/linux-x64": "0.25.7",
1388
+ "@esbuild/netbsd-arm64": "0.25.7",
1389
+ "@esbuild/netbsd-x64": "0.25.7",
1390
+ "@esbuild/openbsd-arm64": "0.25.7",
1391
+ "@esbuild/openbsd-x64": "0.25.7",
1392
+ "@esbuild/openharmony-arm64": "0.25.7",
1393
+ "@esbuild/sunos-x64": "0.25.7",
1394
+ "@esbuild/win32-arm64": "0.25.7",
1395
+ "@esbuild/win32-ia32": "0.25.7",
1396
+ "@esbuild/win32-x64": "0.25.7"
1397
+ }
1398
+ },
1399
+ "node_modules/escalade": {
1400
+ "version": "3.2.0",
1401
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1402
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1403
+ "dev": true,
1404
+ "license": "MIT",
1405
+ "engines": {
1406
+ "node": ">=6"
1407
+ }
1408
+ },
1409
+ "node_modules/extend": {
1410
+ "version": "3.0.2",
1411
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
1412
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
1413
+ "license": "MIT"
1414
+ },
1415
+ "node_modules/fdir": {
1416
+ "version": "6.4.6",
1417
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
1418
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
1419
+ "dev": true,
1420
+ "license": "MIT",
1421
+ "peerDependencies": {
1422
+ "picomatch": "^3 || ^4"
1423
+ },
1424
+ "peerDependenciesMeta": {
1425
+ "picomatch": {
1426
+ "optional": true
1427
+ }
1428
+ }
1429
+ },
1430
+ "node_modules/fsevents": {
1431
+ "version": "2.3.3",
1432
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1433
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1434
+ "dev": true,
1435
+ "hasInstallScript": true,
1436
+ "license": "MIT",
1437
+ "optional": true,
1438
+ "os": [
1439
+ "darwin"
1440
+ ],
1441
+ "engines": {
1442
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1443
+ }
1444
+ },
1445
+ "node_modules/gaxios": {
1446
+ "version": "6.7.1",
1447
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
1448
+ "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
1449
+ "license": "Apache-2.0",
1450
+ "dependencies": {
1451
+ "extend": "^3.0.2",
1452
+ "https-proxy-agent": "^7.0.1",
1453
+ "is-stream": "^2.0.0",
1454
+ "node-fetch": "^2.6.9",
1455
+ "uuid": "^9.0.1"
1456
+ },
1457
+ "engines": {
1458
+ "node": ">=14"
1459
+ }
1460
+ },
1461
+ "node_modules/gcp-metadata": {
1462
+ "version": "6.1.1",
1463
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
1464
+ "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
1465
+ "license": "Apache-2.0",
1466
+ "dependencies": {
1467
+ "gaxios": "^6.1.1",
1468
+ "google-logging-utils": "^0.0.2",
1469
+ "json-bigint": "^1.0.0"
1470
+ },
1471
+ "engines": {
1472
+ "node": ">=14"
1473
+ }
1474
+ },
1475
+ "node_modules/gensync": {
1476
+ "version": "1.0.0-beta.2",
1477
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1478
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1479
+ "dev": true,
1480
+ "license": "MIT",
1481
+ "engines": {
1482
+ "node": ">=6.9.0"
1483
+ }
1484
+ },
1485
+ "node_modules/google-auth-library": {
1486
+ "version": "9.15.1",
1487
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
1488
+ "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
1489
+ "license": "Apache-2.0",
1490
+ "dependencies": {
1491
+ "base64-js": "^1.3.0",
1492
+ "ecdsa-sig-formatter": "^1.0.11",
1493
+ "gaxios": "^6.1.1",
1494
+ "gcp-metadata": "^6.1.0",
1495
+ "gtoken": "^7.0.0",
1496
+ "jws": "^4.0.0"
1497
+ },
1498
+ "engines": {
1499
+ "node": ">=14"
1500
+ }
1501
+ },
1502
+ "node_modules/google-logging-utils": {
1503
+ "version": "0.0.2",
1504
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
1505
+ "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
1506
+ "license": "Apache-2.0",
1507
+ "engines": {
1508
+ "node": ">=14"
1509
+ }
1510
+ },
1511
+ "node_modules/gtoken": {
1512
+ "version": "7.1.0",
1513
+ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
1514
+ "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
1515
+ "license": "MIT",
1516
+ "dependencies": {
1517
+ "gaxios": "^6.0.0",
1518
+ "jws": "^4.0.0"
1519
+ },
1520
+ "engines": {
1521
+ "node": ">=14.0.0"
1522
+ }
1523
+ },
1524
+ "node_modules/https-proxy-agent": {
1525
+ "version": "7.0.6",
1526
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
1527
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
1528
+ "license": "MIT",
1529
+ "dependencies": {
1530
+ "agent-base": "^7.1.2",
1531
+ "debug": "4"
1532
+ },
1533
+ "engines": {
1534
+ "node": ">= 14"
1535
+ }
1536
+ },
1537
+ "node_modules/is-stream": {
1538
+ "version": "2.0.1",
1539
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
1540
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
1541
+ "license": "MIT",
1542
+ "engines": {
1543
+ "node": ">=8"
1544
+ },
1545
+ "funding": {
1546
+ "url": "https://github.com/sponsors/sindresorhus"
1547
+ }
1548
+ },
1549
+ "node_modules/js-tokens": {
1550
+ "version": "4.0.0",
1551
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1552
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1553
+ "dev": true,
1554
+ "license": "MIT"
1555
+ },
1556
+ "node_modules/jsesc": {
1557
+ "version": "3.1.0",
1558
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
1559
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
1560
+ "dev": true,
1561
+ "license": "MIT",
1562
+ "bin": {
1563
+ "jsesc": "bin/jsesc"
1564
+ },
1565
+ "engines": {
1566
+ "node": ">=6"
1567
+ }
1568
+ },
1569
+ "node_modules/json-bigint": {
1570
+ "version": "1.0.0",
1571
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
1572
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
1573
+ "license": "MIT",
1574
+ "dependencies": {
1575
+ "bignumber.js": "^9.0.0"
1576
+ }
1577
+ },
1578
+ "node_modules/json5": {
1579
+ "version": "2.2.3",
1580
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
1581
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
1582
+ "dev": true,
1583
+ "license": "MIT",
1584
+ "bin": {
1585
+ "json5": "lib/cli.js"
1586
+ },
1587
+ "engines": {
1588
+ "node": ">=6"
1589
+ }
1590
+ },
1591
+ "node_modules/jwa": {
1592
+ "version": "2.0.1",
1593
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
1594
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
1595
+ "license": "MIT",
1596
+ "dependencies": {
1597
+ "buffer-equal-constant-time": "^1.0.1",
1598
+ "ecdsa-sig-formatter": "1.0.11",
1599
+ "safe-buffer": "^5.0.1"
1600
+ }
1601
+ },
1602
+ "node_modules/jws": {
1603
+ "version": "4.0.0",
1604
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
1605
+ "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
1606
+ "license": "MIT",
1607
+ "dependencies": {
1608
+ "jwa": "^2.0.0",
1609
+ "safe-buffer": "^5.0.1"
1610
+ }
1611
+ },
1612
+ "node_modules/lru-cache": {
1613
+ "version": "5.1.1",
1614
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
1615
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
1616
+ "dev": true,
1617
+ "license": "ISC",
1618
+ "dependencies": {
1619
+ "yallist": "^3.0.2"
1620
+ }
1621
+ },
1622
+ "node_modules/ms": {
1623
+ "version": "2.1.3",
1624
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1625
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1626
+ "license": "MIT"
1627
+ },
1628
+ "node_modules/nanoid": {
1629
+ "version": "3.3.11",
1630
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
1631
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
1632
+ "dev": true,
1633
+ "funding": [
1634
+ {
1635
+ "type": "github",
1636
+ "url": "https://github.com/sponsors/ai"
1637
+ }
1638
+ ],
1639
+ "license": "MIT",
1640
+ "bin": {
1641
+ "nanoid": "bin/nanoid.cjs"
1642
+ },
1643
+ "engines": {
1644
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1645
+ }
1646
+ },
1647
+ "node_modules/node-fetch": {
1648
+ "version": "2.7.0",
1649
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
1650
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
1651
+ "license": "MIT",
1652
+ "dependencies": {
1653
+ "whatwg-url": "^5.0.0"
1654
+ },
1655
+ "engines": {
1656
+ "node": "4.x || >=6.0.0"
1657
+ },
1658
+ "peerDependencies": {
1659
+ "encoding": "^0.1.0"
1660
+ },
1661
+ "peerDependenciesMeta": {
1662
+ "encoding": {
1663
+ "optional": true
1664
+ }
1665
+ }
1666
+ },
1667
+ "node_modules/node-releases": {
1668
+ "version": "2.0.19",
1669
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
1670
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
1671
+ "dev": true,
1672
+ "license": "MIT"
1673
+ },
1674
+ "node_modules/picocolors": {
1675
+ "version": "1.1.1",
1676
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1677
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1678
+ "dev": true,
1679
+ "license": "ISC"
1680
+ },
1681
+ "node_modules/picomatch": {
1682
+ "version": "4.0.3",
1683
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
1684
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
1685
+ "dev": true,
1686
+ "license": "MIT",
1687
+ "engines": {
1688
+ "node": ">=12"
1689
+ },
1690
+ "funding": {
1691
+ "url": "https://github.com/sponsors/jonschlinkert"
1692
+ }
1693
+ },
1694
+ "node_modules/postcss": {
1695
+ "version": "8.5.6",
1696
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
1697
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
1698
+ "dev": true,
1699
+ "funding": [
1700
+ {
1701
+ "type": "opencollective",
1702
+ "url": "https://opencollective.com/postcss/"
1703
+ },
1704
+ {
1705
+ "type": "tidelift",
1706
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1707
+ },
1708
+ {
1709
+ "type": "github",
1710
+ "url": "https://github.com/sponsors/ai"
1711
+ }
1712
+ ],
1713
+ "license": "MIT",
1714
+ "dependencies": {
1715
+ "nanoid": "^3.3.11",
1716
+ "picocolors": "^1.1.1",
1717
+ "source-map-js": "^1.2.1"
1718
+ },
1719
+ "engines": {
1720
+ "node": "^10 || ^12 || >=14"
1721
+ }
1722
+ },
1723
+ "node_modules/react": {
1724
+ "version": "19.1.0",
1725
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
1726
+ "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
1727
+ "license": "MIT",
1728
+ "engines": {
1729
+ "node": ">=0.10.0"
1730
+ }
1731
+ },
1732
+ "node_modules/react-dom": {
1733
+ "version": "19.1.0",
1734
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
1735
+ "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
1736
+ "license": "MIT",
1737
+ "dependencies": {
1738
+ "scheduler": "^0.26.0"
1739
+ },
1740
+ "peerDependencies": {
1741
+ "react": "^19.1.0"
1742
+ }
1743
+ },
1744
+ "node_modules/react-refresh": {
1745
+ "version": "0.17.0",
1746
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
1747
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
1748
+ "dev": true,
1749
+ "license": "MIT",
1750
+ "engines": {
1751
+ "node": ">=0.10.0"
1752
+ }
1753
+ },
1754
+ "node_modules/rollup": {
1755
+ "version": "4.45.1",
1756
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz",
1757
+ "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==",
1758
+ "dev": true,
1759
+ "license": "MIT",
1760
+ "dependencies": {
1761
+ "@types/estree": "1.0.8"
1762
+ },
1763
+ "bin": {
1764
+ "rollup": "dist/bin/rollup"
1765
+ },
1766
+ "engines": {
1767
+ "node": ">=18.0.0",
1768
+ "npm": ">=8.0.0"
1769
+ },
1770
+ "optionalDependencies": {
1771
+ "@rollup/rollup-android-arm-eabi": "4.45.1",
1772
+ "@rollup/rollup-android-arm64": "4.45.1",
1773
+ "@rollup/rollup-darwin-arm64": "4.45.1",
1774
+ "@rollup/rollup-darwin-x64": "4.45.1",
1775
+ "@rollup/rollup-freebsd-arm64": "4.45.1",
1776
+ "@rollup/rollup-freebsd-x64": "4.45.1",
1777
+ "@rollup/rollup-linux-arm-gnueabihf": "4.45.1",
1778
+ "@rollup/rollup-linux-arm-musleabihf": "4.45.1",
1779
+ "@rollup/rollup-linux-arm64-gnu": "4.45.1",
1780
+ "@rollup/rollup-linux-arm64-musl": "4.45.1",
1781
+ "@rollup/rollup-linux-loongarch64-gnu": "4.45.1",
1782
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1",
1783
+ "@rollup/rollup-linux-riscv64-gnu": "4.45.1",
1784
+ "@rollup/rollup-linux-riscv64-musl": "4.45.1",
1785
+ "@rollup/rollup-linux-s390x-gnu": "4.45.1",
1786
+ "@rollup/rollup-linux-x64-gnu": "4.45.1",
1787
+ "@rollup/rollup-linux-x64-musl": "4.45.1",
1788
+ "@rollup/rollup-win32-arm64-msvc": "4.45.1",
1789
+ "@rollup/rollup-win32-ia32-msvc": "4.45.1",
1790
+ "@rollup/rollup-win32-x64-msvc": "4.45.1",
1791
+ "fsevents": "~2.3.2"
1792
+ }
1793
+ },
1794
+ "node_modules/safe-buffer": {
1795
+ "version": "5.2.1",
1796
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1797
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1798
+ "funding": [
1799
+ {
1800
+ "type": "github",
1801
+ "url": "https://github.com/sponsors/feross"
1802
+ },
1803
+ {
1804
+ "type": "patreon",
1805
+ "url": "https://www.patreon.com/feross"
1806
+ },
1807
+ {
1808
+ "type": "consulting",
1809
+ "url": "https://feross.org/support"
1810
+ }
1811
+ ],
1812
+ "license": "MIT"
1813
+ },
1814
+ "node_modules/scheduler": {
1815
+ "version": "0.26.0",
1816
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
1817
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
1818
+ "license": "MIT"
1819
+ },
1820
+ "node_modules/semver": {
1821
+ "version": "6.3.1",
1822
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
1823
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
1824
+ "dev": true,
1825
+ "license": "ISC",
1826
+ "bin": {
1827
+ "semver": "bin/semver.js"
1828
+ }
1829
+ },
1830
+ "node_modules/source-map-js": {
1831
+ "version": "1.2.1",
1832
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1833
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1834
+ "dev": true,
1835
+ "license": "BSD-3-Clause",
1836
+ "engines": {
1837
+ "node": ">=0.10.0"
1838
+ }
1839
+ },
1840
+ "node_modules/tinyglobby": {
1841
+ "version": "0.2.14",
1842
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
1843
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
1844
+ "dev": true,
1845
+ "license": "MIT",
1846
+ "dependencies": {
1847
+ "fdir": "^6.4.4",
1848
+ "picomatch": "^4.0.2"
1849
+ },
1850
+ "engines": {
1851
+ "node": ">=12.0.0"
1852
+ },
1853
+ "funding": {
1854
+ "url": "https://github.com/sponsors/SuperchupuDev"
1855
+ }
1856
+ },
1857
+ "node_modules/tr46": {
1858
+ "version": "0.0.3",
1859
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
1860
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
1861
+ "license": "MIT"
1862
+ },
1863
+ "node_modules/typescript": {
1864
+ "version": "5.7.3",
1865
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
1866
+ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
1867
+ "dev": true,
1868
+ "license": "Apache-2.0",
1869
+ "bin": {
1870
+ "tsc": "bin/tsc",
1871
+ "tsserver": "bin/tsserver"
1872
+ },
1873
+ "engines": {
1874
+ "node": ">=14.17"
1875
+ }
1876
+ },
1877
+ "node_modules/undici-types": {
1878
+ "version": "6.21.0",
1879
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
1880
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
1881
+ "dev": true,
1882
+ "license": "MIT"
1883
+ },
1884
+ "node_modules/update-browserslist-db": {
1885
+ "version": "1.1.3",
1886
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
1887
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
1888
+ "dev": true,
1889
+ "funding": [
1890
+ {
1891
+ "type": "opencollective",
1892
+ "url": "https://opencollective.com/browserslist"
1893
+ },
1894
+ {
1895
+ "type": "tidelift",
1896
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1897
+ },
1898
+ {
1899
+ "type": "github",
1900
+ "url": "https://github.com/sponsors/ai"
1901
+ }
1902
+ ],
1903
+ "license": "MIT",
1904
+ "dependencies": {
1905
+ "escalade": "^3.2.0",
1906
+ "picocolors": "^1.1.1"
1907
+ },
1908
+ "bin": {
1909
+ "update-browserslist-db": "cli.js"
1910
+ },
1911
+ "peerDependencies": {
1912
+ "browserslist": ">= 4.21.0"
1913
+ }
1914
+ },
1915
+ "node_modules/uuid": {
1916
+ "version": "9.0.1",
1917
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
1918
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
1919
+ "funding": [
1920
+ "https://github.com/sponsors/broofa",
1921
+ "https://github.com/sponsors/ctavan"
1922
+ ],
1923
+ "license": "MIT",
1924
+ "bin": {
1925
+ "uuid": "dist/bin/uuid"
1926
+ }
1927
+ },
1928
+ "node_modules/vite": {
1929
+ "version": "6.3.5",
1930
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
1931
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
1932
+ "dev": true,
1933
+ "license": "MIT",
1934
+ "dependencies": {
1935
+ "esbuild": "^0.25.0",
1936
+ "fdir": "^6.4.4",
1937
+ "picomatch": "^4.0.2",
1938
+ "postcss": "^8.5.3",
1939
+ "rollup": "^4.34.9",
1940
+ "tinyglobby": "^0.2.13"
1941
+ },
1942
+ "bin": {
1943
+ "vite": "bin/vite.js"
1944
+ },
1945
+ "engines": {
1946
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
1947
+ },
1948
+ "funding": {
1949
+ "url": "https://github.com/vitejs/vite?sponsor=1"
1950
+ },
1951
+ "optionalDependencies": {
1952
+ "fsevents": "~2.3.3"
1953
+ },
1954
+ "peerDependencies": {
1955
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
1956
+ "jiti": ">=1.21.0",
1957
+ "less": "*",
1958
+ "lightningcss": "^1.21.0",
1959
+ "sass": "*",
1960
+ "sass-embedded": "*",
1961
+ "stylus": "*",
1962
+ "sugarss": "*",
1963
+ "terser": "^5.16.0",
1964
+ "tsx": "^4.8.1",
1965
+ "yaml": "^2.4.2"
1966
+ },
1967
+ "peerDependenciesMeta": {
1968
+ "@types/node": {
1969
+ "optional": true
1970
+ },
1971
+ "jiti": {
1972
+ "optional": true
1973
+ },
1974
+ "less": {
1975
+ "optional": true
1976
+ },
1977
+ "lightningcss": {
1978
+ "optional": true
1979
+ },
1980
+ "sass": {
1981
+ "optional": true
1982
+ },
1983
+ "sass-embedded": {
1984
+ "optional": true
1985
+ },
1986
+ "stylus": {
1987
+ "optional": true
1988
+ },
1989
+ "sugarss": {
1990
+ "optional": true
1991
+ },
1992
+ "terser": {
1993
+ "optional": true
1994
+ },
1995
+ "tsx": {
1996
+ "optional": true
1997
+ },
1998
+ "yaml": {
1999
+ "optional": true
2000
+ }
2001
+ }
2002
+ },
2003
+ "node_modules/webidl-conversions": {
2004
+ "version": "3.0.1",
2005
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
2006
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
2007
+ "license": "BSD-2-Clause"
2008
+ },
2009
+ "node_modules/whatwg-url": {
2010
+ "version": "5.0.0",
2011
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
2012
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
2013
+ "license": "MIT",
2014
+ "dependencies": {
2015
+ "tr46": "~0.0.3",
2016
+ "webidl-conversions": "^3.0.0"
2017
+ }
2018
+ },
2019
+ "node_modules/ws": {
2020
+ "version": "8.18.3",
2021
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
2022
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
2023
+ "license": "MIT",
2024
+ "engines": {
2025
+ "node": ">=10.0.0"
2026
+ },
2027
+ "peerDependencies": {
2028
+ "bufferutil": "^4.0.1",
2029
+ "utf-8-validate": ">=5.0.2"
2030
+ },
2031
+ "peerDependenciesMeta": {
2032
+ "bufferutil": {
2033
+ "optional": true
2034
+ },
2035
+ "utf-8-validate": {
2036
+ "optional": true
2037
+ }
2038
+ }
2039
+ },
2040
+ "node_modules/yallist": {
2041
+ "version": "3.1.1",
2042
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
2043
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
2044
+ "dev": true,
2045
+ "license": "ISC"
2046
+ }
2047
+ }
2048
+ }
package.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "vla-data-generator",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^19.1.0",
13
+ "react-dom": "^19.1.0",
14
+ "@google/genai": "^1.8.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^22.14.0",
18
+ "@types/react": "^19.1.0",
19
+ "@types/react-dom": "^19.1.0",
20
+ "@vitejs/plugin-react": "^4.0.0",
21
+ "typescript": "~5.7.2",
22
+ "vite": "^6.2.0"
23
+ }
24
+ }
services/backendService.ts ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { VlaData } from '../types';
2
+
3
+ // Backend API base URL - same server serves both frontend and API
4
+ const API_BASE_URL = '/api';
5
+
6
+ /**
7
+ * A wrapper for the backend API call that includes retry logic.
8
+ */
9
+ async function callBackendAPI(endpoint: string, data: any, retries = 3): Promise<any> {
10
+ for (let attempt = 1; attempt <= retries; attempt++) {
11
+ try {
12
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
13
+ method: 'POST',
14
+ headers: {
15
+ 'Content-Type': 'application/json',
16
+ },
17
+ body: JSON.stringify(data)
18
+ });
19
+
20
+ if (!response.ok) {
21
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
22
+ }
23
+
24
+ return await response.json();
25
+ } catch (error) {
26
+ console.error(`API call attempt ${attempt} failed:`, error);
27
+
28
+ if (attempt === retries) {
29
+ throw error;
30
+ }
31
+
32
+ // Wait before retry
33
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
34
+ }
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Generates an overall goal for the video based on analyzed frames.
40
+ */
41
+ export async function generateOverallGoal(frames: string[], videoDuration: number): Promise<string> {
42
+ try {
43
+ console.log(`Generating overall goal for ${frames.length} frames...`);
44
+
45
+ const response = await callBackendAPI('/generate-goal', {
46
+ frames,
47
+ videoDuration
48
+ });
49
+
50
+ return response.goal || 'Unable to determine overall goal';
51
+ } catch (error) {
52
+ console.error('Error generating overall goal:', error);
53
+ throw new Error('Failed to generate overall goal. Please check your connection and try again.');
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Generates tasks and interactions for the video.
59
+ */
60
+ export async function generateTasksAndInteractions(
61
+ frames: string[],
62
+ goal: string,
63
+ videoDuration: number,
64
+ totalFrames: number,
65
+ onProgress?: (current: number, total: number) => void
66
+ ): Promise<VlaData> {
67
+ try {
68
+ console.log(`Generating tasks and interactions for ${frames.length} frames...`);
69
+
70
+ if (onProgress) onProgress(0, 1);
71
+
72
+ const response = await callBackendAPI('/generate-tasks', {
73
+ frames,
74
+ goal,
75
+ videoDuration,
76
+ totalFrames
77
+ });
78
+
79
+ if (onProgress) onProgress(1, 1);
80
+
81
+ // Transform backend response to match frontend types
82
+ const vlaData: VlaData = {
83
+ overallGoal: goal,
84
+ tasks: response.tasks?.map((task: any, index: number) => ({
85
+ id: index + 1,
86
+ description: task.description || '',
87
+ startFrame: task.start_frame || 0,
88
+ endFrame: task.end_frame || frames.length - 1,
89
+ interactions: task.interactions?.map((interaction: any) => ({
90
+ type: (interaction.type === 'click' || interaction.type === 'type') ? interaction.type : 'click',
91
+ details: interaction.description || '',
92
+ frameIndex: interaction.frame_number || 0,
93
+ x: interaction.coordinates?.x ? interaction.coordinates.x / 1000 : undefined, // Convert to relative coords
94
+ y: interaction.coordinates?.y ? interaction.coordinates.y / 1000 : undefined
95
+ })) || []
96
+ })) || []
97
+ };
98
+
99
+ return vlaData;
100
+ } catch (error) {
101
+ console.error('Error generating tasks and interactions:', error);
102
+ throw new Error('Failed to generate tasks and interactions. Please check your connection and try again.');
103
+ }
104
+ }
services/geminiService.ts ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { GoogleGenAI, GenerateContentResponse } from "@google/genai";
3
+ import { VlaData, TaskSegment, Interaction } from '../types';
4
+ import { GET_OVERALL_GOAL_PROMPT, GET_TASKS_AND_INTERACTIONS_PROMPT } from './prompts';
5
+
6
+ // Get API key from environment variables - try both names
7
+ const getApiKey = () => {
8
+ // In development, try process.env (from Vite)
9
+ if (typeof process !== 'undefined' && process.env) {
10
+ return process.env.GEMINI_API_KEY || process.env.API_KEY || '';
11
+ }
12
+
13
+ // In production, try window.__ENV__ (injected at runtime)
14
+ if (typeof window !== 'undefined' && (window as any).__ENV__) {
15
+ return (window as any).__ENV__.GEMINI_API_KEY || (window as any).__ENV__.API_KEY || '';
16
+ }
17
+
18
+ // Fallback - prompt user for API key
19
+ const storedKey = localStorage.getItem('gemini_api_key');
20
+ if (storedKey) return storedKey;
21
+
22
+ const userKey = prompt('Please enter your Gemini API key:');
23
+ if (userKey) {
24
+ localStorage.setItem('gemini_api_key', userKey);
25
+ return userKey;
26
+ }
27
+
28
+ return '';
29
+ };
30
+
31
+ const apiKey = getApiKey();
32
+ if (!apiKey) {
33
+ throw new Error("API key not found. Please set GEMINI_API_KEY or API_KEY environment variable, or enter it when prompted.");
34
+ }
35
+
36
+ const ai = new GoogleGenAI({ apiKey });
37
+
38
+ const CHUNK_SIZE = 15;
39
+ const OVERLAP = 5;
40
+
41
+ /**
42
+ * A wrapper for the Gemini API call that includes retry and fallback logic.
43
+ * It first tries the primary model. If it encounters rate-limiting errors,
44
+ * it retries, and if that still fails, it falls back to a "lite" configuration.
45
+ */
46
+ async function callGeminiWithRetry(
47
+ params: Parameters<typeof ai.models.generateContent>[0],
48
+ maxRetries: number = 2, // Retries for primary model before fallback
49
+ initialDelay: number = 1000
50
+ ): Promise<{ response: GenerateContentResponse; usedFallback: boolean }> {
51
+ let lastRateLimitError: any;
52
+
53
+ for (let i = 0; i < maxRetries; i++) {
54
+ try {
55
+ const primaryParams = { ...params, model: 'gemini-2.5-flash-preview-04-17' };
56
+ const response = await ai.models.generateContent(primaryParams);
57
+ return { response, usedFallback: false };
58
+ } catch (error) {
59
+ const isRateLimitError = error?.toString().includes('429') || error?.toString().includes('RESOURCE_EXHAUSTED');
60
+ if (isRateLimitError) {
61
+ lastRateLimitError = error;
62
+ const delay = initialDelay * Math.pow(2, i);
63
+ console.warn(`Primary model failed with rate limit on attempt ${i + 1}. Retrying in ${delay}ms...`);
64
+ await new Promise(resolve => setTimeout(resolve, delay));
65
+ } else {
66
+ console.error("Gemini API call failed with a non-rate-limit error.", error);
67
+ throw error; // Fail fast on other errors
68
+ }
69
+ }
70
+ }
71
+
72
+ // If loop completes, all primary attempts were rate-limited.
73
+ console.warn(`All primary model attempts failed due to rate limits. Switching to fallback 'lite' configuration.`);
74
+ try {
75
+ const fallbackParams = {
76
+ ...params,
77
+ model: 'gemini-2.5-flash-preview-04-17',
78
+ config: {
79
+ ...(params.config || {}),
80
+ thinkingConfig: { thinkingBudget: 0 }
81
+ }
82
+ };
83
+ const response = await ai.models.generateContent(fallbackParams);
84
+ console.log("Successfully generated content with fallback configuration.");
85
+ return { response, usedFallback: true };
86
+ } catch (fallbackError) {
87
+ console.error("Fallback configuration also failed.", fallbackError);
88
+ // Throw the fallback error as it's the most recent.
89
+ throw fallbackError;
90
+ }
91
+ }
92
+
93
+
94
+ function parseJsonResponse<T>(response: GenerateContentResponse): T {
95
+ let jsonStr = response.text.trim();
96
+ const fenceRegex = /^```(\w*)?\s*\n?(.*?)\n?\s*```$/s;
97
+ const match = jsonStr.match(fenceRegex);
98
+ if (match && match[2]) {
99
+ jsonStr = match[2].trim();
100
+ }
101
+ try {
102
+ return JSON.parse(jsonStr) as T;
103
+ } catch (e) {
104
+ console.error("Failed to parse JSON response:", jsonStr);
105
+ throw new Error("AI response was not valid JSON.");
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Generates the overall goal by analyzing a few keyframes.
111
+ */
112
+ export async function generateOverallGoal(keyframes: string[]): Promise<{ goal: string, usedFallback: boolean }> {
113
+ const imageParts = keyframes.map(frame => ({
114
+ inlineData: { mimeType: 'image/jpeg', data: frame.split(',')[1] },
115
+ }));
116
+
117
+ const contents = [{ text: GET_OVERALL_GOAL_PROMPT }, ...imageParts];
118
+
119
+ try {
120
+ const { response, usedFallback } = await callGeminiWithRetry({
121
+ model: 'gemini-2.5-flash-preview-04-17',
122
+ contents: { parts: contents },
123
+ config: { responseMimeType: "application/json", temperature: 0.1 }
124
+ });
125
+ const parsed = parseJsonResponse<{ overallGoal: string }>(response);
126
+ if (!parsed.overallGoal) {
127
+ throw new Error("AI response for overall goal is missing the 'overallGoal' field.");
128
+ }
129
+ return { goal: parsed.overallGoal, usedFallback };
130
+ } catch (error) {
131
+ console.error("Error calling Gemini API for overall goal:", error);
132
+ throw new Error(`AI model failed to determine overall goal: ${error instanceof Error ? error.message : 'Unknown error'}`);
133
+ }
134
+ }
135
+
136
+
137
+ function mergeAndDeduplicateTasks(tasks: Omit<TaskSegment, 'id'>[]): TaskSegment[] {
138
+ if (tasks.length === 0) return [];
139
+
140
+ // Sort tasks by their start frame
141
+ const sortedTasks = tasks.sort((a, b) => a.startFrame - b.startFrame);
142
+
143
+ const mergedTasks: Omit<TaskSegment, 'id'>[] = [];
144
+ if (sortedTasks.length > 0) {
145
+ // Deep copy to avoid mutation issues when merging
146
+ mergedTasks.push(JSON.parse(JSON.stringify(sortedTasks[0])));
147
+ } else {
148
+ return [];
149
+ }
150
+
151
+
152
+ for (let i = 1; i < sortedTasks.length; i++) {
153
+ const currentTask = sortedTasks[i];
154
+ const lastMergedTask = mergedTasks[mergedTasks.length - 1];
155
+
156
+ const isSimilarDescription = currentTask.description.trim().toLowerCase() === lastMergedTask.description.trim().toLowerCase();
157
+ const isOverlapping = currentTask.startFrame < lastMergedTask.endFrame;
158
+
159
+ // If descriptions are identical and frames overlap, merge them.
160
+ if (isSimilarDescription && isOverlapping) {
161
+ lastMergedTask.endFrame = Math.max(lastMergedTask.endFrame, currentTask.endFrame);
162
+ // Merge interactions and deduplicate them
163
+ if (currentTask.interactions) {
164
+ lastMergedTask.interactions = lastMergedTask.interactions || [];
165
+ const existingInteractionKeys = new Set(
166
+ lastMergedTask.interactions.map(inter => `${inter.frameIndex}-${inter.type}`)
167
+ );
168
+
169
+ for (const newInteraction of currentTask.interactions) {
170
+ const newKey = `${newInteraction.frameIndex}-${newInteraction.type}`;
171
+ if (!existingInteractionKeys.has(newKey)) {
172
+ lastMergedTask.interactions.push(newInteraction);
173
+ existingInteractionKeys.add(newKey);
174
+ }
175
+ }
176
+ // Sort interactions by frame index after merging
177
+ lastMergedTask.interactions.sort((a,b) => a.frameIndex - b.frameIndex);
178
+ }
179
+ } else if (currentTask.startFrame >= lastMergedTask.endFrame) {
180
+ // Only add new tasks that start after or at the same time the previous one ends
181
+ mergedTasks.push(JSON.parse(JSON.stringify(currentTask)));
182
+ }
183
+ }
184
+
185
+ // Re-assign sequential IDs and ensure interactions array exists
186
+ return mergedTasks.map((task, index) => ({
187
+ ...task,
188
+ id: index + 1,
189
+ interactions: task.interactions || [],
190
+ }));
191
+ }
192
+
193
+
194
+ /**
195
+ * Generates task segments and their interactions by analyzing the video in overlapping chunks.
196
+ */
197
+ export async function generateTasksAndInteractions(frames: string[]): Promise<{ tasks: TaskSegment[], usedFallback: boolean }> {
198
+ const chunks: { frames: string[], startIndex: number }[] = [];
199
+ for (let i = 0; i < frames.length; i += CHUNK_SIZE - OVERLAP) {
200
+ const chunkFrames = frames.slice(i, i + CHUNK_SIZE);
201
+ if (chunkFrames.length > 0) {
202
+ chunks.push({ frames: chunkFrames, startIndex: i });
203
+ }
204
+ }
205
+
206
+ try {
207
+ let anyChunkUsedFallback = false;
208
+ const chunkPromises = chunks.map(async (chunk) => {
209
+ const imageParts = chunk.frames.map(frame => ({
210
+ inlineData: { mimeType: 'image/jpeg', data: frame.split(',')[1] },
211
+ }));
212
+ const endFrameIndex = chunk.startIndex + chunk.frames.length - 1;
213
+ const prompt = GET_TASKS_AND_INTERACTIONS_PROMPT(chunk.startIndex, endFrameIndex);
214
+ const contents = [{ text: prompt }, ...imageParts];
215
+
216
+ const { response, usedFallback } = await callGeminiWithRetry({
217
+ model: 'gemini-2.5-flash-preview-04-17',
218
+ contents: { parts: contents },
219
+ config: { responseMimeType: "application/json", temperature: 0.1 }
220
+ });
221
+
222
+ if (usedFallback) {
223
+ anyChunkUsedFallback = true;
224
+ }
225
+
226
+ // AI returns tasks with interactions included
227
+ const parsed = parseJsonResponse<Omit<TaskSegment, 'id'>[]>(response);
228
+ if (!Array.isArray(parsed)) {
229
+ console.warn("AI response for a chunk was not an array, skipping chunk.", parsed);
230
+ return [];
231
+ }
232
+ return parsed;
233
+ });
234
+
235
+ const resultsFromAllChunks = await Promise.all(chunkPromises);
236
+ const allTasks = resultsFromAllChunks.flat();
237
+
238
+ // Sort, merge, and de-duplicate tasks and their interactions
239
+ return {
240
+ tasks: mergeAndDeduplicateTasks(allTasks),
241
+ usedFallback: anyChunkUsedFallback
242
+ };
243
+
244
+ } catch (error) {
245
+ console.error("Error calling Gemini API for task and interaction generation:", error);
246
+ throw new Error(`AI model failed during analysis: ${error instanceof Error ? error.message : 'Unknown error'}`);
247
+ }
248
+ }
services/prompts.ts ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const GET_OVERALL_GOAL_PROMPT = `
2
+ You are an expert Vision-Language-Action (VLA) data generation model. Your task is to analyze a few keyframes from a screen recording to determine the user's single, high-level overall goal for the entire session.
3
+
4
+ Based on the provided frames, what is the user's primary objective?
5
+
6
+ Provide your response ONLY in the following JSON format. Do not add any extra commentary or markdown.
7
+
8
+ {
9
+ "overallGoal": "A string describing the user's main objective."
10
+ }
11
+ `;
12
+
13
+ export const GET_TASKS_AND_INTERACTIONS_PROMPT = (startFrame: number, endFrame: number): string => `
14
+ You are an expert Vision-Language-Action (VLA) data generation model. You are analyzing a segment of a screen recording. The frames provided are from global frame index ${startFrame} to ${endFrame}.
15
+
16
+ Your task is to identify distinct, chronological tasks AND the specific user interactions within each task.
17
+
18
+ **Overall Instructions:**
19
+ 1. **Identify Tasks:** A task is a continuous set of actions to achieve a small sub-goal. Create a new task only when the user's immediate goal changes significantly. A task should not be a single action, but a sequence of actions.
20
+ 2. **Identify Interactions:** Within each task, identify every key user interaction ('click' or 'type').
21
+ 3. **Crucially, if a user types into a field, there is almost always a 'click' interaction to select that field first. You MUST capture this preceding 'click' on the input field.**
22
+ 4. **Frame Indices:** All frame indices you return MUST be within the global range of ${startFrame} to ${endFrame}.
23
+ 5. **Accuracy is critical.** Do not guess. If you cannot see the text on an element, do not invent it.
24
+
25
+ **Task Definition:**
26
+ For each task, provide:
27
+ a. \`description\`: A concise, goal-oriented description (e.g., "Search for 'quarterly results'").
28
+ b. \`startFrame\` & \`endFrame\`: The global start and end frame indices for the task.
29
+ c. \`interactions\`: An array of all user interactions that occur during that task.
30
+
31
+ **Interaction Definition:**
32
+ For each interaction, provide:
33
+ a. \`type\`: Must be 'click' or 'type'.
34
+ b. \`details\`:
35
+ - For a 'click': The exact text of the element clicked. If no text, describe the icon (e.g., "Clicked the gear icon for settings.").
36
+ - For a 'type': The text that was typed and where (e.g., "Typed 'hello world' into the search bar.").
37
+ c. \`frameIndex\`: The global frame index where the interaction is most visible.
38
+ d. For 'click' interactions ONLY:
39
+ - \`x\`: Relative horizontal position (0.0 to 1.0).
40
+ - \`y\`: Relative vertical position (0.0 to 1.0).
41
+
42
+ **Output Format:**
43
+ Provide your response ONLY in a JSON array of task objects. Do not add any extra commentary or markdown.
44
+
45
+ Example Response:
46
+ [
47
+ {
48
+ "description": "Navigate to the settings page.",
49
+ "startFrame": ${startFrame + 1},
50
+ "endFrame": ${startFrame + 8},
51
+ "interactions": [
52
+ {
53
+ "type": "click",
54
+ "details": "Clicked the user profile avatar.",
55
+ "frameIndex": ${startFrame + 3},
56
+ "x": 0.95,
57
+ "y": 0.1
58
+ },
59
+ {
60
+ "type": "click",
61
+ "details": "Clicked the 'Settings' menu item.",
62
+ "frameIndex": ${startFrame + 7},
63
+ "x": 0.9,
64
+ "y": 0.25
65
+ }
66
+ ]
67
+ },
68
+ {
69
+ "description": "Search for a document.",
70
+ "startFrame": ${startFrame + 10},
71
+ "endFrame": ${startFrame + 15},
72
+ "interactions": [
73
+ {
74
+ "type": "click",
75
+ "details": "Clicked on the search bar with placeholder 'Search documents'.",
76
+ "frameIndex": ${startFrame + 11},
77
+ "x": 0.45,
78
+ "y": 0.15
79
+ },
80
+ {
81
+ "type": "type",
82
+ "details": "Typed 'product roadmap' into the search bar.",
83
+ "frameIndex": ${startFrame + 14}
84
+ }
85
+ ]
86
+ }
87
+ ]
88
+ `;
tsconfig.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "experimentalDecorators": true,
5
+ "useDefineForClassFields": false,
6
+ "module": "ESNext",
7
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "isolatedModules": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "allowJs": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "noFallthroughCasesInSwitch": true,
24
+ "noUncheckedSideEffectImports": true,
25
+
26
+ "paths": {
27
+ "@/*" : ["./*"]
28
+ }
29
+ }
30
+ }
types.ts ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export type InteractionType = 'click' | 'type';
2
+
3
+ export interface Interaction {
4
+ type: InteractionType;
5
+ details: string;
6
+ frameIndex: number; // The index of the frame where this interaction occurs
7
+ x?: number; // Relative X coordinate for clicks (0.0 to 1.0)
8
+ y?: number; // Relative Y coordinate for clicks (0.0 to 1.0)
9
+ }
10
+
11
+ export interface TaskSegment {
12
+ id: number;
13
+ description: string; // Should be goal-oriented
14
+ interactions: Interaction[];
15
+ startFrame: number; // The starting frame index for this task segment
16
+ endFrame: number; // The ending frame index for this task segment
17
+ }
18
+
19
+ export interface VlaData {
20
+ overallGoal: string;
21
+ tasks: TaskSegment[];
22
+ }
utils/videoProcessor.ts ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ export function extractFramesFromVideo(
3
+ videoFile: File,
4
+ maxFrames: number
5
+ ): Promise<string[]> {
6
+ return new Promise((resolve, reject) => {
7
+ const video = document.createElement('video');
8
+ const canvas = document.createElement('canvas');
9
+ const context = canvas.getContext('2d');
10
+ const frames: string[] = [];
11
+
12
+ if (!context) {
13
+ return reject(new Error('Could not create canvas context.'));
14
+ }
15
+
16
+ video.preload = 'metadata';
17
+ video.muted = true;
18
+ video.playsInline = true;
19
+ video.src = URL.createObjectURL(videoFile);
20
+
21
+ video.onloadedmetadata = () => {
22
+ canvas.width = video.videoWidth;
23
+ canvas.height = video.videoHeight;
24
+ const duration = video.duration;
25
+ if (duration === 0) {
26
+ URL.revokeObjectURL(video.src);
27
+ return reject(new Error("Video duration is 0, it might be a still image or invalid file."));
28
+ }
29
+
30
+ const interval = duration / maxFrames;
31
+ let currentTime = 0;
32
+ let framesExtracted = 0;
33
+
34
+ const extractFrame = () => {
35
+ if (framesExtracted >= maxFrames) {
36
+ URL.revokeObjectURL(video.src);
37
+ resolve(frames);
38
+ return;
39
+ }
40
+
41
+ video.currentTime = currentTime;
42
+ };
43
+
44
+ video.onseeked = () => {
45
+ context.drawImage(video, 0, 0, canvas.width, canvas.height);
46
+ // Use JPEG for smaller file size compared to PNG
47
+ const frameDataUrl = canvas.toDataURL('image/jpeg', 0.8);
48
+ frames.push(frameDataUrl);
49
+
50
+ framesExtracted++;
51
+ currentTime += interval;
52
+
53
+ // Ensure we don't seek beyond the video duration
54
+ if (currentTime <= duration) {
55
+ extractFrame();
56
+ } else {
57
+ URL.revokeObjectURL(video.src);
58
+ resolve(frames);
59
+ }
60
+ };
61
+
62
+ video.onerror = (e) => {
63
+ URL.revokeObjectURL(video.src);
64
+ let message = "An unknown video error occurred.";
65
+ if (video.error) {
66
+ switch(video.error.code) {
67
+ case video.error.MEDIA_ERR_ABORTED:
68
+ message = 'The video playback was aborted.';
69
+ break;
70
+ case video.error.MEDIA_ERR_NETWORK:
71
+ message = 'A network error caused the video download to fail.';
72
+ break;
73
+ case video.error.MEDIA_ERR_DECODE:
74
+ message = 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.';
75
+ break;
76
+ case video.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
77
+ message = 'The video could not be loaded, either because the server or network failed or because the format is not supported.';
78
+ break;
79
+ default:
80
+ message = 'An unknown error occurred.';
81
+ }
82
+ }
83
+ reject(new Error(message));
84
+ };
85
+
86
+ // Start the process
87
+ extractFrame();
88
+ };
89
+
90
+ video.onerror = () => {
91
+ URL.revokeObjectURL(video.src);
92
+ reject(new Error('Failed to load video metadata. The file may be invalid or unsupported.'));
93
+ };
94
+ });
95
+ }
vite.config.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig, loadEnv } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig(({ mode }) => {
5
+ // For development, load from .env files
6
+ const env = loadEnv(mode, '.', '');
7
+
8
+ return {
9
+ plugins: [react()],
10
+ // Don't embed API keys at build time - let them be read at runtime
11
+ define: {
12
+ 'process.env.NODE_ENV': JSON.stringify(mode)
13
+ }
14
+ };
15
+ });