Spaces:
Sleeping
Sleeping
Gemini
commited on
Commit
·
256cef9
0
Parent(s):
VLA Data Generator - Complete TypeScript/React app with backend
Browse filesA 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 +14 -0
- .gitignore +24 -0
- App.tsx +248 -0
- Dockerfile +48 -0
- README.md +64 -0
- backend/package.json +19 -0
- backend/server.js +180 -0
- components/Icons.tsx +51 -0
- components/ResultsDisplay.tsx +157 -0
- components/TaskSegmentCard.tsx +281 -0
- components/VideoPlayer.tsx +51 -0
- components/VideoUploader.tsx +76 -0
- index.html +42 -0
- index.tsx +16 -0
- metadata.json +6 -0
- package-lock.json +2048 -0
- package.json +24 -0
- services/backendService.ts +104 -0
- services/geminiService.ts +248 -0
- services/prompts.ts +88 -0
- tsconfig.json +30 -0
- types.ts +22 -0
- utils/videoProcessor.ts +95 -0
- vite.config.ts +15 -0
.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 |
+
});
|