Spaces:
				
			
			
	
			
			
					
		Running
		
			on 
			
			CPU Upgrade
	
	
	
			
			
	
	
	
	
		
		
					
		Running
		
			on 
			
			CPU Upgrade
	Commit 
							
							·
						
						889687b
	
1
								Parent(s):
							
							b6daf71
								
UI updates
Browse files- app.py +0 -12
- index.html +22 -26
    	
        app.py
    CHANGED
    
    | @@ -205,11 +205,8 @@ async def auth_login(request: Request, state: Optional[str] = None): | |
| 205 | 
             
            @app.post("/api/auth/exchange")
         | 
| 206 | 
             
            async def auth_exchange(request: Request, code: str, state: str, hf_oauth_state: Optional[str] = Cookie(None)):
         | 
| 207 | 
             
                """Exchange OAuth code for access token - called from callback page"""
         | 
| 208 | 
            -
                print(f"Exchange request - code: {code[:20] if code and len(code) >= 20 else code}..., state: {state}, cookie_state: {hf_oauth_state}")
         | 
| 209 | 
            -
             | 
| 210 | 
             
                # Validate state from cookie
         | 
| 211 | 
             
                if not hf_oauth_state or state != hf_oauth_state:
         | 
| 212 | 
            -
                    print(f"State mismatch! URL state: {state}, Cookie state: {hf_oauth_state}")
         | 
| 213 | 
             
                    raise HTTPException(status_code=400, detail="Invalid or expired OAuth state")
         | 
| 214 |  | 
| 215 | 
             
                origin = get_origin_from_request(request)
         | 
| @@ -246,7 +243,6 @@ async def auth_exchange(request: Request, code: str, state: str, hf_oauth_state: | |
| 246 | 
             
                    return response
         | 
| 247 |  | 
| 248 | 
             
                except Exception as e:
         | 
| 249 | 
            -
                    print(f"Token exchange error: {e}")
         | 
| 250 | 
             
                    response = JSONResponse(
         | 
| 251 | 
             
                        {"error": str(e)},
         | 
| 252 | 
             
                        status_code=400
         | 
| @@ -284,23 +280,18 @@ async def oauth_callback(request: Request): | |
| 284 | 
             
                            }
         | 
| 285 |  | 
| 286 | 
             
                            try {
         | 
| 287 | 
            -
                                console.log('Exchanging code for token...');
         | 
| 288 | 
             
                                // Exchange code for token
         | 
| 289 | 
             
                                const response = await fetch('/api/auth/exchange?code=' + code + '&state=' + state, {
         | 
| 290 | 
             
                                    method: 'POST',
         | 
| 291 | 
             
                                    credentials: 'same-origin'
         | 
| 292 | 
             
                                });
         | 
| 293 |  | 
| 294 | 
            -
                                console.log('Exchange response status:', response.status);
         | 
| 295 | 
            -
             | 
| 296 | 
             
                                if (!response.ok) {
         | 
| 297 | 
             
                                    const data = await response.json().catch(() => ({ detail: 'Unknown error' }));
         | 
| 298 | 
            -
                                    console.error('Exchange failed:', data);
         | 
| 299 | 
             
                                    throw new Error(data.detail || data.error || 'Failed to exchange code for token');
         | 
| 300 | 
             
                                }
         | 
| 301 |  | 
| 302 | 
             
                                const data = await response.json();
         | 
| 303 | 
            -
                                console.log('Token exchange successful');
         | 
| 304 |  | 
| 305 | 
             
                                // Store in localStorage
         | 
| 306 | 
             
                                const authState = {
         | 
| @@ -308,12 +299,10 @@ async def oauth_callback(request: Request): | |
| 308 | 
             
                                    user: { username: data.namespace }
         | 
| 309 | 
             
                                };
         | 
| 310 | 
             
                                localStorage.setItem('HF_AUTH_STATE', JSON.stringify(authState));
         | 
| 311 | 
            -
                                console.log('Token saved to localStorage, redirecting to /');
         | 
| 312 |  | 
| 313 | 
             
                                // Redirect back to home
         | 
| 314 | 
             
                                window.location.href = '/';
         | 
| 315 | 
             
                            } catch (err) {
         | 
| 316 | 
            -
                                console.error('OAuth error:', err);
         | 
| 317 | 
             
                                document.body.innerHTML = '<h2>Authentication failed</h2><p>' + err.message + '</p><p><a href="/">Return to app</a></p>';
         | 
| 318 | 
             
                            }
         | 
| 319 | 
             
                        })();
         | 
| @@ -445,7 +434,6 @@ async def websocket_video_gen(websocket: WebSocket, user_fal_key: Optional[str] | |
| 445 |  | 
| 446 | 
             
                # If user provided their own FAL key, use it (bypass limits)
         | 
| 447 | 
             
                if user_fal_key:
         | 
| 448 | 
            -
                    print(f"User {user_info['username']} using their own FAL key")
         | 
| 449 | 
             
                    fal_key_to_use = user_fal_key
         | 
| 450 | 
             
                else:
         | 
| 451 | 
             
                    # Check if user can start session with server FAL key
         | 
|  | |
| 205 | 
             
            @app.post("/api/auth/exchange")
         | 
| 206 | 
             
            async def auth_exchange(request: Request, code: str, state: str, hf_oauth_state: Optional[str] = Cookie(None)):
         | 
| 207 | 
             
                """Exchange OAuth code for access token - called from callback page"""
         | 
|  | |
|  | |
| 208 | 
             
                # Validate state from cookie
         | 
| 209 | 
             
                if not hf_oauth_state or state != hf_oauth_state:
         | 
|  | |
| 210 | 
             
                    raise HTTPException(status_code=400, detail="Invalid or expired OAuth state")
         | 
| 211 |  | 
| 212 | 
             
                origin = get_origin_from_request(request)
         | 
|  | |
| 243 | 
             
                    return response
         | 
| 244 |  | 
| 245 | 
             
                except Exception as e:
         | 
|  | |
| 246 | 
             
                    response = JSONResponse(
         | 
| 247 | 
             
                        {"error": str(e)},
         | 
| 248 | 
             
                        status_code=400
         | 
|  | |
| 280 | 
             
                            }
         | 
| 281 |  | 
| 282 | 
             
                            try {
         | 
|  | |
| 283 | 
             
                                // Exchange code for token
         | 
| 284 | 
             
                                const response = await fetch('/api/auth/exchange?code=' + code + '&state=' + state, {
         | 
| 285 | 
             
                                    method: 'POST',
         | 
| 286 | 
             
                                    credentials: 'same-origin'
         | 
| 287 | 
             
                                });
         | 
| 288 |  | 
|  | |
|  | |
| 289 | 
             
                                if (!response.ok) {
         | 
| 290 | 
             
                                    const data = await response.json().catch(() => ({ detail: 'Unknown error' }));
         | 
|  | |
| 291 | 
             
                                    throw new Error(data.detail || data.error || 'Failed to exchange code for token');
         | 
| 292 | 
             
                                }
         | 
| 293 |  | 
| 294 | 
             
                                const data = await response.json();
         | 
|  | |
| 295 |  | 
| 296 | 
             
                                // Store in localStorage
         | 
| 297 | 
             
                                const authState = {
         | 
|  | |
| 299 | 
             
                                    user: { username: data.namespace }
         | 
| 300 | 
             
                                };
         | 
| 301 | 
             
                                localStorage.setItem('HF_AUTH_STATE', JSON.stringify(authState));
         | 
|  | |
| 302 |  | 
| 303 | 
             
                                // Redirect back to home
         | 
| 304 | 
             
                                window.location.href = '/';
         | 
| 305 | 
             
                            } catch (err) {
         | 
|  | |
| 306 | 
             
                                document.body.innerHTML = '<h2>Authentication failed</h2><p>' + err.message + '</p><p><a href="/">Return to app</a></p>';
         | 
| 307 | 
             
                            }
         | 
| 308 | 
             
                        })();
         | 
|  | |
| 434 |  | 
| 435 | 
             
                # If user provided their own FAL key, use it (bypass limits)
         | 
| 436 | 
             
                if user_fal_key:
         | 
|  | |
| 437 | 
             
                    fal_key_to_use = user_fal_key
         | 
| 438 | 
             
                else:
         | 
| 439 | 
             
                    # Check if user can start session with server FAL key
         | 
    	
        index.html
    CHANGED
    
    | @@ -257,10 +257,16 @@ | |
| 257 | 
             
                    }
         | 
| 258 |  | 
| 259 | 
             
                    .video-grid.text-mode .output-panel {
         | 
| 260 | 
            -
                        max-width:  | 
| 261 | 
             
                        margin: 0 auto;
         | 
| 262 | 
             
                    }
         | 
| 263 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 264 | 
             
                    .panel {
         | 
| 265 | 
             
                        background: #fafaf9;
         | 
| 266 | 
             
                        border-radius: 8px;
         | 
| @@ -330,6 +336,9 @@ | |
| 330 | 
             
                        display: flex;
         | 
| 331 | 
             
                        gap: 10px;
         | 
| 332 | 
             
                        margin-bottom: 20px;
         | 
|  | |
|  | |
|  | |
| 333 | 
             
                    }
         | 
| 334 |  | 
| 335 | 
             
                    .mode-btn {
         | 
| @@ -632,6 +641,10 @@ | |
| 632 | 
             
                            flex-direction: column;
         | 
| 633 | 
             
                            gap: 15px;
         | 
| 634 | 
             
                        }
         | 
|  | |
|  | |
|  | |
|  | |
| 635 | 
             
                    }
         | 
| 636 | 
             
                </style>
         | 
| 637 | 
             
            </head>
         | 
| @@ -662,6 +675,13 @@ | |
| 662 | 
             
                            </div>
         | 
| 663 | 
             
                        </header>
         | 
| 664 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 665 | 
             
                        <!-- Video Grid: Webcam Left, Output Right -->
         | 
| 666 | 
             
                        <div id="videoGrid" class="video-grid">
         | 
| 667 | 
             
                            <!-- Input Panel (Webcam or Video) -->
         | 
| @@ -724,15 +744,6 @@ | |
| 724 |  | 
| 725 | 
             
                        <!-- Controls Panel -->
         | 
| 726 | 
             
                        <div class="panel">
         | 
| 727 | 
            -
                            <h2 style="margin-bottom: 20px;">Controls</h2>
         | 
| 728 | 
            -
             | 
| 729 | 
            -
                            <!-- Mode Selection -->
         | 
| 730 | 
            -
                            <div class="mode-toggle">
         | 
| 731 | 
            -
                                <button id="textModeBtn" class="mode-btn">Text-to-Video</button>
         | 
| 732 | 
            -
                                <button id="videoModeBtn" class="mode-btn">Video-to-Video</button>
         | 
| 733 | 
            -
                                <button id="webcamModeBtn" class="mode-btn active">Webcam-to-Video</button>
         | 
| 734 | 
            -
                            </div>
         | 
| 735 | 
            -
             | 
| 736 | 
             
                            <!-- Prompt (Main Setting) -->
         | 
| 737 | 
             
                            <div class="form-group">
         | 
| 738 | 
             
                                <label>Prompt <span style="color: #15803d; font-size: 0.75rem;">(updates sent every 2 seconds)</span></label>
         | 
| @@ -923,15 +934,12 @@ | |
| 923 | 
             
                        hasFalKey: false,
         | 
| 924 |  | 
| 925 | 
             
                        async init() {
         | 
| 926 | 
            -
                            console.log('Auth init - checking for saved token');
         | 
| 927 | 
            -
             | 
| 928 | 
             
                            // Check if user has FAL key
         | 
| 929 | 
             
                            this.hasFalKey = limitModal.hasFalKey();
         | 
| 930 |  | 
| 931 | 
             
                            // Try to restore saved auth from localStorage
         | 
| 932 | 
             
                            const saved = localStorage.getItem(AUTH_STORAGE_KEY);
         | 
| 933 | 
             
                            if (saved) {
         | 
| 934 | 
            -
                                console.log('Found saved auth, attempting to restore');
         | 
| 935 | 
             
                                try {
         | 
| 936 | 
             
                                    const authState = JSON.parse(saved);
         | 
| 937 | 
             
                                    await this.validateAndSetAuth(authState.token);
         | 
| @@ -940,13 +948,11 @@ | |
| 940 | 
             
                                    this.clearAuth();
         | 
| 941 | 
             
                                }
         | 
| 942 | 
             
                            } else {
         | 
| 943 | 
            -
                                console.log('No saved auth found, showing login strip');
         | 
| 944 | 
             
                                this.showLoginStrip();
         | 
| 945 | 
             
                            }
         | 
| 946 | 
             
                        },
         | 
| 947 |  | 
| 948 | 
             
                        async validateAndSetAuth(token) {
         | 
| 949 | 
            -
                            console.log('Validating token with /api/whoami');
         | 
| 950 | 
             
                            try {
         | 
| 951 | 
             
                                const response = await fetch('/api/whoami', {
         | 
| 952 | 
             
                                    headers: { 'Authorization': `Bearer ${token}` }
         | 
| @@ -957,7 +963,6 @@ | |
| 957 | 
             
                                }
         | 
| 958 |  | 
| 959 | 
             
                                const data = await response.json();
         | 
| 960 | 
            -
                                console.log('Token validated successfully:', data);
         | 
| 961 | 
             
                                this.token = token;
         | 
| 962 | 
             
                                this.user = data;
         | 
| 963 | 
             
                                this.canStart = data.can_start;
         | 
| @@ -977,8 +982,6 @@ | |
| 977 | 
             
                        },
         | 
| 978 |  | 
| 979 | 
             
                        loginWithOAuth() {
         | 
| 980 | 
            -
                            console.log('Starting OAuth flow - navigating to /api/auth/login');
         | 
| 981 | 
            -
                            // Full page navigation to login endpoint which handles state and redirects to HF OAuth
         | 
| 982 | 
             
                            window.location.href = '/api/auth/login';
         | 
| 983 | 
             
                        },
         | 
| 984 |  | 
| @@ -1297,7 +1300,6 @@ | |
| 1297 | 
             
                                this.showInfo('Webcam started successfully');
         | 
| 1298 | 
             
                            } catch (error) {
         | 
| 1299 | 
             
                                this.showError(`Failed to access webcam: ${error.message}`);
         | 
| 1300 | 
            -
                                console.error('Webcam error:', error);
         | 
| 1301 | 
             
                                // Switch back to text mode if webcam fails
         | 
| 1302 | 
             
                                this.setMode('text');
         | 
| 1303 | 
             
                            }
         | 
| @@ -1361,8 +1363,6 @@ | |
| 1361 | 
             
                                        this.showError('Failed to start session');
         | 
| 1362 | 
             
                                        return;
         | 
| 1363 | 
             
                                    }
         | 
| 1364 | 
            -
                                } else {
         | 
| 1365 | 
            -
                                    console.log('Using FAL API key - bypassing HF generation limits');
         | 
| 1366 | 
             
                                }
         | 
| 1367 |  | 
| 1368 | 
             
                                const prompt = document.getElementById('prompt').value.trim();
         | 
| @@ -1392,7 +1392,6 @@ | |
| 1392 |  | 
| 1393 | 
             
                                // Check if WebSocket is already open and ready
         | 
| 1394 | 
             
                                if (this.ws && this.ws.readyState === WebSocket.OPEN) {
         | 
| 1395 | 
            -
                                    console.log('Reusing existing WebSocket connection');
         | 
| 1396 | 
             
                                    this.showInfo('Starting new generation...');
         | 
| 1397 | 
             
                                    await this.sendInitialParams();
         | 
| 1398 |  | 
| @@ -1433,7 +1432,7 @@ | |
| 1433 | 
             
                                                    this.disconnect();
         | 
| 1434 | 
             
                                                }
         | 
| 1435 | 
             
                                            } catch (e) {
         | 
| 1436 | 
            -
                                                console. | 
| 1437 | 
             
                                            }
         | 
| 1438 | 
             
                                        } else if (event.data instanceof ArrayBuffer) {
         | 
| 1439 | 
             
                                            await this.displayFrame(event.data);
         | 
| @@ -1446,8 +1445,6 @@ | |
| 1446 | 
             
                                    };
         | 
| 1447 |  | 
| 1448 | 
             
                                    this.ws.onclose = (event) => {
         | 
| 1449 | 
            -
                                        console.log('WebSocket closed:', event.reason);
         | 
| 1450 | 
            -
             | 
| 1451 | 
             
                                        // Force state reset
         | 
| 1452 | 
             
                                        this.isGenerating = false;
         | 
| 1453 | 
             
                                        this.ws = null;
         | 
| @@ -1571,7 +1568,6 @@ | |
| 1571 |  | 
| 1572 | 
             
                            // Check if generation is complete
         | 
| 1573 | 
             
                            if (this.frameCount >= this.maxFrames) {
         | 
| 1574 | 
            -
                                console.log('Generation complete - stopping processes but keeping WebSocket open');
         | 
| 1575 | 
             
                                this.isGenerating = false;
         | 
| 1576 | 
             
                                this.stopFrameExtraction();
         | 
| 1577 | 
             
                                this.stopRecording();
         | 
|  | |
| 257 | 
             
                    }
         | 
| 258 |  | 
| 259 | 
             
                    .video-grid.text-mode .output-panel {
         | 
| 260 | 
            +
                        max-width: 65%;
         | 
| 261 | 
             
                        margin: 0 auto;
         | 
| 262 | 
             
                    }
         | 
| 263 |  | 
| 264 | 
            +
                    @media (max-width: 900px) {
         | 
| 265 | 
            +
                        .video-grid.text-mode .output-panel {
         | 
| 266 | 
            +
                            max-width: 100%;
         | 
| 267 | 
            +
                        }
         | 
| 268 | 
            +
                    }
         | 
| 269 | 
            +
             | 
| 270 | 
             
                    .panel {
         | 
| 271 | 
             
                        background: #fafaf9;
         | 
| 272 | 
             
                        border-radius: 8px;
         | 
|  | |
| 336 | 
             
                        display: flex;
         | 
| 337 | 
             
                        gap: 10px;
         | 
| 338 | 
             
                        margin-bottom: 20px;
         | 
| 339 | 
            +
                        max-width: 600px;
         | 
| 340 | 
            +
                        margin-left: auto;
         | 
| 341 | 
            +
                        margin-right: auto;
         | 
| 342 | 
             
                    }
         | 
| 343 |  | 
| 344 | 
             
                    .mode-btn {
         | 
|  | |
| 641 | 
             
                            flex-direction: column;
         | 
| 642 | 
             
                            gap: 15px;
         | 
| 643 | 
             
                        }
         | 
| 644 | 
            +
             | 
| 645 | 
            +
                        .mode-toggle {
         | 
| 646 | 
            +
                            max-width: 100%;
         | 
| 647 | 
            +
                        }
         | 
| 648 | 
             
                    }
         | 
| 649 | 
             
                </style>
         | 
| 650 | 
             
            </head>
         | 
|  | |
| 675 | 
             
                            </div>
         | 
| 676 | 
             
                        </header>
         | 
| 677 |  | 
| 678 | 
            +
                        <!-- Mode Selection -->
         | 
| 679 | 
            +
                        <div class="mode-toggle">
         | 
| 680 | 
            +
                            <button id="webcamModeBtn" class="mode-btn active">Webcam-to-Video</button>
         | 
| 681 | 
            +
                            <button id="videoModeBtn" class="mode-btn">Video-to-Video</button>
         | 
| 682 | 
            +
                            <button id="textModeBtn" class="mode-btn">Text-to-Video</button>
         | 
| 683 | 
            +
                        </div>
         | 
| 684 | 
            +
             | 
| 685 | 
             
                        <!-- Video Grid: Webcam Left, Output Right -->
         | 
| 686 | 
             
                        <div id="videoGrid" class="video-grid">
         | 
| 687 | 
             
                            <!-- Input Panel (Webcam or Video) -->
         | 
|  | |
| 744 |  | 
| 745 | 
             
                        <!-- Controls Panel -->
         | 
| 746 | 
             
                        <div class="panel">
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 747 | 
             
                            <!-- Prompt (Main Setting) -->
         | 
| 748 | 
             
                            <div class="form-group">
         | 
| 749 | 
             
                                <label>Prompt <span style="color: #15803d; font-size: 0.75rem;">(updates sent every 2 seconds)</span></label>
         | 
|  | |
| 934 | 
             
                        hasFalKey: false,
         | 
| 935 |  | 
| 936 | 
             
                        async init() {
         | 
|  | |
|  | |
| 937 | 
             
                            // Check if user has FAL key
         | 
| 938 | 
             
                            this.hasFalKey = limitModal.hasFalKey();
         | 
| 939 |  | 
| 940 | 
             
                            // Try to restore saved auth from localStorage
         | 
| 941 | 
             
                            const saved = localStorage.getItem(AUTH_STORAGE_KEY);
         | 
| 942 | 
             
                            if (saved) {
         | 
|  | |
| 943 | 
             
                                try {
         | 
| 944 | 
             
                                    const authState = JSON.parse(saved);
         | 
| 945 | 
             
                                    await this.validateAndSetAuth(authState.token);
         | 
|  | |
| 948 | 
             
                                    this.clearAuth();
         | 
| 949 | 
             
                                }
         | 
| 950 | 
             
                            } else {
         | 
|  | |
| 951 | 
             
                                this.showLoginStrip();
         | 
| 952 | 
             
                            }
         | 
| 953 | 
             
                        },
         | 
| 954 |  | 
| 955 | 
             
                        async validateAndSetAuth(token) {
         | 
|  | |
| 956 | 
             
                            try {
         | 
| 957 | 
             
                                const response = await fetch('/api/whoami', {
         | 
| 958 | 
             
                                    headers: { 'Authorization': `Bearer ${token}` }
         | 
|  | |
| 963 | 
             
                                }
         | 
| 964 |  | 
| 965 | 
             
                                const data = await response.json();
         | 
|  | |
| 966 | 
             
                                this.token = token;
         | 
| 967 | 
             
                                this.user = data;
         | 
| 968 | 
             
                                this.canStart = data.can_start;
         | 
|  | |
| 982 | 
             
                        },
         | 
| 983 |  | 
| 984 | 
             
                        loginWithOAuth() {
         | 
|  | |
|  | |
| 985 | 
             
                            window.location.href = '/api/auth/login';
         | 
| 986 | 
             
                        },
         | 
| 987 |  | 
|  | |
| 1300 | 
             
                                this.showInfo('Webcam started successfully');
         | 
| 1301 | 
             
                            } catch (error) {
         | 
| 1302 | 
             
                                this.showError(`Failed to access webcam: ${error.message}`);
         | 
|  | |
| 1303 | 
             
                                // Switch back to text mode if webcam fails
         | 
| 1304 | 
             
                                this.setMode('text');
         | 
| 1305 | 
             
                            }
         | 
|  | |
| 1363 | 
             
                                        this.showError('Failed to start session');
         | 
| 1364 | 
             
                                        return;
         | 
| 1365 | 
             
                                    }
         | 
|  | |
|  | |
| 1366 | 
             
                                }
         | 
| 1367 |  | 
| 1368 | 
             
                                const prompt = document.getElementById('prompt').value.trim();
         | 
|  | |
| 1392 |  | 
| 1393 | 
             
                                // Check if WebSocket is already open and ready
         | 
| 1394 | 
             
                                if (this.ws && this.ws.readyState === WebSocket.OPEN) {
         | 
|  | |
| 1395 | 
             
                                    this.showInfo('Starting new generation...');
         | 
| 1396 | 
             
                                    await this.sendInitialParams();
         | 
| 1397 |  | 
|  | |
| 1432 | 
             
                                                    this.disconnect();
         | 
| 1433 | 
             
                                                }
         | 
| 1434 | 
             
                                            } catch (e) {
         | 
| 1435 | 
            +
                                                console.error('WebSocket message parse error:', e);
         | 
| 1436 | 
             
                                            }
         | 
| 1437 | 
             
                                        } else if (event.data instanceof ArrayBuffer) {
         | 
| 1438 | 
             
                                            await this.displayFrame(event.data);
         | 
|  | |
| 1445 | 
             
                                    };
         | 
| 1446 |  | 
| 1447 | 
             
                                    this.ws.onclose = (event) => {
         | 
|  | |
|  | |
| 1448 | 
             
                                        // Force state reset
         | 
| 1449 | 
             
                                        this.isGenerating = false;
         | 
| 1450 | 
             
                                        this.ws = null;
         | 
|  | |
| 1568 |  | 
| 1569 | 
             
                            // Check if generation is complete
         | 
| 1570 | 
             
                            if (this.frameCount >= this.maxFrames) {
         | 
|  | |
| 1571 | 
             
                                this.isGenerating = false;
         | 
| 1572 | 
             
                                this.stopFrameExtraction();
         | 
| 1573 | 
             
                                this.stopRecording();
         | 
