pdf instructions update

This commit is contained in:
Manish Tanwar 2025-11-15 03:56:00 +05:30
parent b7bfd679dd
commit dc770d65d3
13 changed files with 40 additions and 3088 deletions

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ __pycache__/
# Development Claude Notes files
.claude/
overview.txt
*.pdf
# C extensions
*.so

View file

@ -1,434 +0,0 @@
# 503 Error Fix - Implementation Summary
**Date:** 2025-11-13
**Status:** ✅ **COMPLETED**
**Issue:** 503 UNAVAILABLE errors when processing long videos (chunk 2/2 failures)
---
## Problem Analysis
### **Root Cause:**
```
The application was overwhelming the Gemini API with:
1. ❌ Parallel requests (4 workers) exceeding free tier rate limit (5 RPM)
2. ❌ Insufficient delays between requests (2 seconds vs required 12 seconds)
3. ❌ Chunk duration (54 min) exceeding Google's limit for videos with audio (45 min)
4. ❌ Basic retry logic that didn't handle 503 errors
```
### **The 503 Error:**
```
Error: Failed to process chunk 2/2:
503 UNAVAILABLE: {'error': {'code': 503, 'message': 'The model is overloaded.
Please try again later.', 'status': 'UNAVAILABLE'}}
```
**Why it happened:**
- Free tier: 5 RPM = 1 request every 12 seconds
- Old behavior: 4 parallel workers × 2 second delay = 4 requests in 2 seconds ❌
- Result: API overloaded → 503 error
---
## Solution Implemented
### **1. Fixed Chunk Duration**
**Change:**
```python
# video_splitter.py line 26
DEFAULT_CHUNK_DURATION = 43 # Changed from 54 to 43 minutes
```
**Reason:**
- Google Gemini 2.5 Pro limits:
- With audio: **~45 minutes max**
- Without audio: **~60 minutes max**
- Old 54-minute chunks exceeded the 45-min audio limit
- New 43-minute chunks stay safely under the limit
---
### **2. Smart Rate Limiting**
**New Configuration:**
```python
# video_processor.py lines 54-58
MIN_REQUEST_INTERVAL_FREE = 12 # 12 seconds for free tier (5 RPM)
MIN_REQUEST_INTERVAL_PAID = 1 # 1 second for paid tier (60 RPM)
MAX_RETRY_ATTEMPTS = 5 # Up to 5 attempts (not infinite!)
RETRY_DELAYS = [5, 10, 20, 40, 60] # Exponential backoff
```
**How it works:**
```
Free Tier (5 RPM):
- Request 1 → Wait 12s → Request 2 → Wait 12s → Request 3
- Ensures: 60 seconds / 5 requests = 12 seconds between each
Paid Tier (60 RPM):
- Request 1 → Wait 1s → Request 2 → Wait 1s → Request 3
- Faster processing with higher limits
```
---
### **3. Intelligent Retry Logic**
**New Method:** `_make_api_request_with_retry()`
**Handles:**
- ✅ **503 UNAVAILABLE** (API overload) → Retry with exponential backoff
- ✅ **429 TOO_MANY_REQUESTS** (rate limit) → Retry with exponential backoff
- ✅ **500 INTERNAL_SERVER_ERROR** → Retry with exponential backoff
- ✅ **Network errors** (timeout, connection) → Retry with 5s delay
- ❌ **400 INVALID_ARGUMENT** → Fail immediately (not retryable)
**Retry Strategy:**
```
Attempt 1: Initial try
↓ (fails with 503)
Attempt 2: Wait 5 seconds → Retry
↓ (fails with 503)
Attempt 3: Wait 10 seconds → Retry
↓ (fails with 503)
Attempt 4: Wait 20 seconds → Retry
↓ (fails with 503)
Attempt 5: Wait 40 seconds → Final retry
↓ (if still fails)
STOP → Return error (NOT INFINITE!)
```
---
### **4. Reduced Parallel Workers**
**Change:**
```python
# video_processor.py line 48
DEFAULT_MAX_WORKERS = 2 # Reduced from 4 to 2
```
**Auto-Configuration:**
```python
if GEMINI_API_TIER == "free":
max_workers = 2 # Safe for 5 RPM
elif GEMINI_API_TIER == "paid":
max_workers = 4 # Can handle 60 RPM
```
**Impact:**
- Free tier: 2 workers × 12s delay = 1 request every 12s ✅ Safe
- Paid tier: 4 workers × 1s delay = Fast processing ✅ Safe
---
### **5. API Tier Detection**
**New Method:** `_detect_api_tier()`
**Configuration:**
```bash
# .env file
GEMINI_API_TIER=free # or "paid"
```
**Benefits:**
- Automatically adjusts rate limits based on your subscription
- Prevents overload on free tier
- Maximizes speed on paid tier
- Easy to switch without code changes
---
## Files Modified
### **Modified Files (3):**
| File | Lines Changed | Changes |
|------|---------------|---------|
| `backend/video_splitter.py` | Line 26 | Chunk duration: 54 → 43 minutes |
| `backend/video_processor.py` | +200 lines | Rate limiting, retry logic, API tier detection |
| `backend/.env` | +5 lines | Added GEMINI_API_TIER configuration |
| `backend/.env.example` | +23 lines | Documented new configuration options |
---
## Configuration
### **Environment Variables (.env):**
```bash
# REQUIRED: Your API key
GOOGLE_API_KEY=your_key_here
# IMPORTANT: Set your API tier
# This is KEY to preventing 503 errors!
GEMINI_API_TIER=free # or "paid"
# Optional: Override parallel workers
# (Auto-configured based on tier if not set)
# MAX_PARALLEL_CHUNKS=2
# Model configuration
VIDEO_PROCESSOR_MODEL=gemini-2.5-pro
VIDEO_SYNTHESIS_MODEL=gemini-2.5-pro
```
---
## How It Prevents 503 Errors
### **Before Fix:**
```
Long video (2 hours) → Split into 3 chunks (54 min each)
Process with 4 parallel workers:
Worker 1: Chunk 1 (t=0s) ✅ Success
Worker 2: Chunk 2 (t=0s) ❌ 503 UNAVAILABLE
Worker 3: Chunk 3 (t=0s) ❌ 503 UNAVAILABLE
Worker 4: (idle)
All 3 requests hit API simultaneously → Overload → 503
```
### **After Fix:**
```
Long video (2 hours) → Split into 3 chunks (43 min each)
Process with 2 parallel workers + rate limiting:
Worker 1: Chunk 1 (t=0s) → Wait 12s ✅ Success
Worker 2: Chunk 2 (t=12s) → Wait 12s ✅ Success
Worker 1: Chunk 3 (t=24s) → Wait 12s ✅ Success
Requests spaced 12 seconds apart → Within rate limit → No 503
```
---
## Testing Scenarios
### **Test Case 1: Short Video (<43 min)**
```
Input: 30-minute video
Expected: Process directly (no splitting)
Result: ✅ Works (1 API call)
```
### **Test Case 2: Long Video (2 hours)**
```
Input: 2-hour video
Expected: Split into ~3 chunks (43 min each)
Processing:
- Chunk 1: t=0s ✅
- Chunk 2: t=12s ✅ (no 503!)
- Chunk 3: t=24s ✅ (no 503!)
Result: ✅ All chunks succeed
```
### **Test Case 3: Very Long Video (5 hours)**
```
Input: 5-hour video
Expected: Split into ~7 chunks
Processing:
- Worker 1: Chunks 1,3,5,7 at t=0s, 24s, 48s, 72s
- Worker 2: Chunks 2,4,6 at t=12s, 36s, 60s
Result: ✅ All chunks succeed with proper spacing
```
### **Test Case 4: Batch Mode (3 videos × 90 min)**
```
Input: 3 videos, each 90 minutes
Expected: Each split into 3 chunks = 9 total chunks
Processing: Rate limited, 2 workers
Result: ✅ All 9 chunks process successfully
```
---
## Performance Comparison
### **Free Tier (5 RPM):**
| Scenario | Before | After |
|----------|--------|-------|
| 2-hour video | ❌ Fails (503) | ✅ Success (36s total) |
| 5-hour video | ❌ Fails (503) | ✅ Success (84s total) |
| Success rate | ~30-40% | **~98%+** |
### **Paid Tier (60 RPM):**
| Scenario | Before | After |
|----------|--------|-------|
| 2-hour video | ⚠️ Unreliable | ✅ Success (6s total) |
| 5-hour video | ⚠️ Unreliable | ✅ Success (14s total) |
| Success rate | ~70% | **~99%+** |
---
## Retry Examples
### **Scenario 1: Temporary 503 Error**
```
Attempt 1: 503 UNAVAILABLE
↓ Wait 5s
Attempt 2: ✅ SUCCESS
Result: Video processed successfully after 1 retry
```
### **Scenario 2: Persistent Overload**
```
Attempt 1: 503 UNAVAILABLE
↓ Wait 5s
Attempt 2: 503 UNAVAILABLE
↓ Wait 10s
Attempt 3: 503 UNAVAILABLE
↓ Wait 20s
Attempt 4: ✅ SUCCESS
Result: Video processed after 3 retries (35s delay)
```
### **Scenario 3: Complete Failure**
```
Attempt 1: 503 UNAVAILABLE
Attempt 2: 503 UNAVAILABLE (5s)
Attempt 3: 503 UNAVAILABLE (10s)
Attempt 4: 503 UNAVAILABLE (20s)
Attempt 5: 503 UNAVAILABLE (40s)
Result: ❌ FAIL with error report
User sees: "API temporarily overloaded. Please try again in a few minutes."
```
---
## Error Messages
### **Old Error (Before Fix):**
```
Error: Failed to process chunk 2/2: Error processing video:
503 UNAVAILABLE. {'error': {'code': 503, 'message': 'The model is overloaded.'}}
```
### **New Error (After Fix with Retry):**
```
[Video: example.mp4] Retryable error (attempt 1/5): 503 - The model is overloaded
[Video: example.mp4] Waiting 5s before retry...
[Video: example.mp4] Retry attempt 2/5
[Video: example.mp4] ✓ Request succeeded after 2 attempts
```
### **New Error (If All Retries Fail):**
```
❌ Gemini API is temporarily overloaded
💡 Suggested Fix:
The API is temporarily overloaded. The system will automatically retry.
If this persists:
1. Wait a few minutes and try again
2. Reduce parallel processing: set MAX_PARALLEL_CHUNKS=1 in .env
3. Set GEMINI_API_TIER=free in .env for conservative rate limiting
📋 Error ID: E7F8A1B2
```
---
## Troubleshooting
### **Still Getting 503 Errors?**
**Step 1: Verify configuration**
```bash
cd backend
cat .env | grep GEMINI_API_TIER
# Should show: GEMINI_API_TIER=free
```
**Step 2: Reduce parallel workers**
```bash
echo "MAX_PARALLEL_CHUNKS=1" >> .env
```
**Step 3: Check logs**
```bash
# Watch rate limiting in action
journalctl -u video-query -f | grep "Rate limiting"
# Should see: "Rate limiting: waiting 12.0s before next API call"
```
**Step 4: Verify chunk duration**
```bash
cd backend
python -c "from video_splitter import VideoSplitter; print(VideoSplitter.DEFAULT_CHUNK_DURATION)"
# Should show: 43
```
---
## Benefits Summary
✅ **No more 503 errors on long videos**
✅ **Automatic rate limiting based on API tier**
✅ **Intelligent retry with exponential backoff**
✅ **Chunk duration respects Google's 45-min limit**
✅ **Works reliably on free tier (5 RPM)**
✅ **Fast processing on paid tier (60 RPM)**
✅ **Clear error messages with suggested fixes**
✅ **User-friendly error IDs for support**
---
## Next Steps
1. **Test with a long video:**
```bash
cd backend
python run.py
# Upload a 2-hour video through the frontend
```
2. **Monitor the logs:**
```bash
# Watch rate limiting work
tail -f logs/video_query.log | grep "Rate limiting"
# Watch retry logic
tail -f logs/video_query.log | grep "Retry"
```
3. **If on paid tier:**
```bash
# Update .env to unlock faster processing
sed -i 's/GEMINI_API_TIER=free/GEMINI_API_TIER=paid/' backend/.env
# Restart
python backend/run.py
```
---
## Conclusion
The 503 errors were caused by:
1. Rate limit violations (too many parallel requests)
2. Inadequate delays between requests
3. Chunk durations exceeding API limits
All issues have been fixed with:
1. ✅ Smart rate limiting (12s for free, 1s for paid)
2. ✅ Reduced parallel workers (2 for free, 4 for paid)
3. ✅ Shorter chunks (43 min vs 54 min)
4. ✅ Intelligent retry logic (up to 5 attempts)
5. ✅ API tier auto-detection
**The application now handles long videos reliably on both free and paid tiers!**
---
**Ready to test? Start the application:**
```bash
cd backend
python run.py
```

View file

@ -1,349 +0,0 @@
# Batch Processing Improvements - Implementation Summary
**Date**: 2025-11-10
**Status**: ✅ All Phases Completed
## Overview
Implemented comprehensive improvements to batch video processing including model consistency fixes, specialized synthesis strategies, enhanced logging, and configurable options. All videos in a batch are now processed with the same prompt and synthesized intelligently based on content type.
---
## Changes Implemented
### ✅ Phase 1: Enhanced Logging
**File Modified**: `backend/video_processor.py`
**Changes**:
- Added structured logging with `[Stage 1]`, `[Stage 2]`, `[Traceability]`, and `[Metrics]` prefixes
- Implemented configurable debug-level logging for prompts and summaries
- Added performance metrics tracking (stage times, avg time per video, API call count)
- Added video-to-summary-to-result traceability logging
**New Log Output**:
```
Batch abc123: [Stage 1] Processing video 1/3: meeting1.mp4
Batch abc123: [Stage 1] Video 1 complete: 1,245 chars in 45.2s
Batch abc123: [Stage 2] Detected prompt type: meeting_summary
Batch abc123: [Stage 2] Synthesis complete: 3,456 chars in 15.3s
Batch abc123: [Traceability] Video-to-summary mapping:
Batch abc123: - Video 1: meeting1.mp4 → Summary 1
Batch abc123: [Metrics] Stage 1: 135.6s, Stage 2: 15.3s, Total: 150.9s
```
**Lines Modified**: 987-1055, 1123-1247
---
### ✅ Phase 2: Model Consistency Fix
**File Modified**: `backend/video_processor.py`
**Changes**:
- Changed synthesis model from `gemini-2.0-flash-exp` to `gemini-2.5-pro`
- Added model configuration constants at class level
- Made models configurable via environment variables
**Before**:
```python
# Individual processing
model="gemini-2.5-pro"
# Batch synthesis
model="gemini-2.0-flash-exp" # ❌ INCONSISTENT
```
**After**:
```python
# Both use same model
self.processing_model = "gemini-2.5-pro"
self.synthesis_model = "gemini-2.5-pro" # ✅ CONSISTENT
```
**Lines Modified**: 48-50, 82-88, 339, 553, 1252
---
### ✅ Phase 3: Specialized Synthesis Strategies
**File Modified**: `backend/video_processor.py`
**Changes**:
- Added `_detect_prompt_type()` method to classify prompts
- Added `_create_synthesis_prompt_meeting()` for meeting summaries
- Added `_create_synthesis_prompt_documentation()` for process docs
- Updated `_synthesize_final_result()` to route to specialized strategies
**Prompt Type Detection**:
```python
def _detect_prompt_type(self, prompt: str, summaries: List[str]) -> str:
"""
Detects: meeting_summary | documentation | documentation_with_charts | generic
"""
# Keywords: meeting, discussion, action item → meeting_summary
# Keywords: documentation, process, training → documentation
# Keywords: diagram, chart, mermaid → documentation_with_charts
```
**Meeting Synthesis Strategy**:
- Consolidates discussion points across all videos
- Creates master action items list (removes duplicates)
- Formats with clear sections: Overview, Discussion, Action Items, Outcomes
**Documentation Synthesis Strategy**:
- Combines steps into sequential guide
- Numbers steps continuously (Step 1, Step 2, ...)
- Includes Prerequisites, Tips, Troubleshooting sections
**Lines Added**: 1195-1441
---
### ✅ Phase 4: Configuration Options
**Files Modified**:
- `backend/video_processor.py`
- `backend/.env.example` (created)
**New Environment Variables**:
| Variable | Default | Description |
|----------|---------|-------------|
| `VIDEO_PROCESSOR_MODEL` | `gemini-2.5-pro` | Model for individual video processing |
| `VIDEO_SYNTHESIS_MODEL` | `gemini-2.5-pro` | Model for batch synthesis |
| `BATCH_PROCESSING_LOG_PROMPTS` | `false` | Enable prompt logging (debug) |
| `BATCH_PROCESSING_LOG_SUMMARIES` | `false` | Enable summary preview logging (debug) |
**Usage Example**:
```bash
# Enable detailed logging for debugging
export BATCH_PROCESSING_LOG_PROMPTS=true
export BATCH_PROCESSING_LOG_SUMMARIES=true
# Use different model for synthesis (optional)
export VIDEO_SYNTHESIS_MODEL=gemini-2.0-flash-exp
```
**Lines Modified**: 82-88, 1003-1004, 1016-1017, 1150-1151, 1170-1171, 1190-1192, 1204-1205, 1240-1242, 1272-1273
---
### ✅ Documentation Updates
**File Modified**: `CLAUDE.md`
**Sections Added/Updated**:
1. **Backend Setup**: Added .env example with all configuration options
2. **Production Deployment**: Updated environment configuration section
3. **Key Architecture Components**: Added comprehensive Batch Processing Architecture section
4. **Configuration Files**: Documented all environment variables
5. **Troubleshooting**: Added Batch Processing Issues section with debugging guide
**New Documentation Sections**:
- Batch Processing Architecture
- Batch Processing Flow (4-stage explanation)
- Logging Levels guide
- Troubleshooting: Inconsistent summaries
- Troubleshooting: Prompt visibility
- Troubleshooting: Video-to-result mapping
- Troubleshooting: Performance issues
---
## How to Use
### Normal Operation (Default)
```bash
# No changes needed - works out of the box
GOOGLE_API_KEY=your_key
```
### Enable Debugging
```bash
# In backend/.env
GOOGLE_API_KEY=your_key
BATCH_PROCESSING_LOG_PROMPTS=true
BATCH_PROCESSING_LOG_SUMMARIES=true
# Restart backend
sudo systemctl restart video-query
# View logs with filtering
journalctl -u video-query -f | grep "Batch"
```
### View Traceability (Always Enabled)
```bash
# See which video contributed to which part of result
journalctl -u video-query -f | grep "Traceability"
```
### View Performance Metrics (Always Enabled)
```bash
# See timing breakdown and API call counts
journalctl -u video-query -f | grep "Metrics"
```
---
## Verification
### Test Batch Processing
```bash
# Process multiple videos as batch
curl -X POST http://localhost:5010/api/process-batch \
-H "Content-Type: application/json" \
-d '{
"videos": [
{"file_path": "/tmp/video1.mp4", "filename": "meeting_part1.mp4", "order": 1},
{"file_path": "/tmp/video2.mp4", "filename": "meeting_part2.mp4", "order": 2}
],
"prompt": "Generate a detailed meeting summary with action items",
"batch_id": "test-batch-001"
}'
# Check logs for:
# 1. Prompt type detection: "Detected prompt type: meeting_summary"
# 2. Model consistency: "model: gemini-2.5-pro" for both stages
# 3. Traceability: Video-to-summary mapping
# 4. Performance: Stage 1/2 timing
```
### Expected Log Output
```
2025-11-10 10:30:00 - Batch test-batch-001: Processing 2 videos (meeting_part1.mp4, meeting_part2.mp4)
2025-11-10 10:30:00 - Batch test-batch-001: [Stage 1] Direct processing of 2 videos
2025-11-10 10:30:05 - Batch test-batch-001: [Stage 1] Processing video 1/2: meeting_part1.mp4
2025-11-10 10:30:50 - Batch test-batch-001: [Stage 1] Video 1 complete: 1,234 chars in 45.2s
2025-11-10 10:30:55 - Batch test-batch-001: [Stage 1] Processing video 2/2: meeting_part2.mp4
2025-11-10 10:31:40 - Batch test-batch-001: [Stage 1] Video 2 complete: 1,567 chars in 45.1s
2025-11-10 10:31:40 - Batch test-batch-001: [Stage 1] Complete - 2 summaries in 95.3s
2025-11-10 10:31:40 - Batch test-batch-001: [Traceability] Video-to-summary mapping:
2025-11-10 10:31:40 - Batch test-batch-001: - Video 1: meeting_part1.mp4 → Summary 1
2025-11-10 10:31:40 - Batch test-batch-001: - Video 2: meeting_part2.mp4 → Summary 2
2025-11-10 10:31:40 - Batch test-batch-001: [Stage 2] Synthesizing 2 summaries
2025-11-10 10:31:40 - Batch test-batch-001: [Stage 2] Combined summaries: 2 summaries, 2801 total chars
2025-11-10 10:31:40 - Batch test-batch-001: [Stage 2] Detected prompt type: meeting_summary
2025-11-10 10:31:40 - Batch test-batch-001: [Stage 2] Sending synthesis request to Gemini API (model: gemini-2.5-pro)
2025-11-10 10:31:55 - Batch test-batch-001: [Stage 2] Synthesis complete: 3,456 chars in 15.2s
2025-11-10 10:31:55 - Batch test-batch-001: [Metrics] Stage 1: 95.3s, Stage 2: 15.2s, Total: 110.5s
2025-11-10 10:31:55 - Batch test-batch-001: [Metrics] Avg time per video: 47.7s
```
---
## Benefits
### 1. Model Consistency ✅
- **Before**: Different models for processing vs synthesis
- **After**: Same model (gemini-2.5-pro) ensures consistent quality
- **Impact**: More predictable and reliable results
### 2. Specialized Synthesis ✅
- **Before**: Generic synthesis for all content types
- **After**: Tailored strategies for meetings, documentation, diagrams
- **Impact**: Better quality summaries that match user intent
### 3. Enhanced Visibility ✅
- **Before**: Limited logging, hard to debug issues
- **After**: Comprehensive logging with traceability and metrics
- **Impact**: Easy troubleshooting and performance optimization
### 4. Configurability ✅
- **Before**: Models and logging hardcoded
- **After**: Configurable via environment variables
- **Impact**: Flexible for different use cases and debugging
---
## Files Changed
| File | Lines Modified | Changes |
|------|---------------|---------|
| `backend/video_processor.py` | ~200 lines | Model config, logging, synthesis strategies |
| `backend/.env.example` | New file | Configuration documentation |
| `CLAUDE.md` | ~100 lines | Architecture docs, troubleshooting guide |
| `BATCH_PROCESSING_IMPROVEMENTS.md` | New file | This summary document |
---
## Rollback Instructions
If issues arise, rollback is simple:
### Option 1: Use Git
```bash
cd /path/to/video-query
git checkout HEAD~1 backend/video_processor.py
sudo systemctl restart video-query
```
### Option 2: Disable New Features
```bash
# In backend/.env
VIDEO_SYNTHESIS_MODEL=gemini-2.0-flash-exp # Revert to old model
BATCH_PROCESSING_LOG_PROMPTS=false
BATCH_PROCESSING_LOG_SUMMARIES=false
sudo systemctl restart video-query
```
---
## Next Steps
### Recommended Testing
1. **Test with meeting videos**: Verify meeting-specific synthesis
2. **Test with documentation videos**: Verify documentation synthesis
3. **Test with diagrams**: Verify diagram merging
4. **Load test**: Process batch with 5+ videos
5. **Performance test**: Compare stage 1 vs stage 2 times
### Future Enhancements (Optional)
1. Add structured JSON logging for log aggregation tools
2. Add Prometheus metrics for monitoring
3. Add batch processing status webhooks
4. Add configurable synthesis strategies per user/tenant
5. Add caching for similar prompts
---
## Support
### Enable Debug Logging
```bash
# In backend/.env
BATCH_PROCESSING_LOG_PROMPTS=true
BATCH_PROCESSING_LOG_SUMMARIES=true
# View filtered logs
journalctl -u video-query -f | grep -E "(Batch|Stage|Traceability|Metrics)"
```
### Common Issues
See `CLAUDE.md` → Troubleshooting → Batch Processing Issues
### Questions
Refer to updated documentation in `CLAUDE.md`:
- Batch Processing Architecture section
- Configuration Files section
- Troubleshooting section
---
## Implementation Summary
**Phase 1**: Enhanced Logging - COMPLETE
**Phase 2**: Model Consistency - COMPLETE
**Phase 3**: Specialized Synthesis - COMPLETE
**Phase 4**: Configuration Options - COMPLETE
**Documentation**: Updated CLAUDE.md - COMPLETE
**Total Implementation Time**: ~3 hours
**Testing Recommended**: 1-2 hours
**Production Risk**: Low (backward compatible, configurable)
---
**End of Implementation Summary**

View file

@ -1,224 +0,0 @@
# Bug Fix: Batch Processing Error
**Date**: 2025-11-10
**Status**: ✅ Fixed
**Severity**: Critical (prevented batch processing from working)
---
## Error Description
**Error Message**:
```
This Final Unified Meeting Summary could not be generated.
Reason: The underlying analysis of all video segments failed, resulting in error messages instead of summaries.
Error details from all provided segments: [Error: not enough values to unpack (expected 5, got 4)]
```
**Root Cause**: Tuple unpacking mismatch in parallel processing code
---
## Technical Details
### Problem
In `video_processor.py`, the `_process_chunks_two_stage()` method calls `_process_single_chunk()` with only 4 parameters, but the function expects 5 parameters.
**Expected signature** (line 660):
```python
def _process_single_chunk(self, chunk_info: Tuple[int, str, str, int, str]):
chunk_index, chunk_path, chunk_prompt, total_chunks, user_email = chunk_info
# ^^^^^^^^^^^^^ MISSING!
```
**Incorrect call** (line 1155 - before fix):
```python
future = executor.submit(
self._process_single_chunk,
(i, chunk_path, summary_prompt, user_email) # Only 4 params!
)
```
### Additional Issue
The result handling was also incorrect. The function returns `(chunk_index, result_dict)`, but the code was treating `result_dict` as a string directly instead of extracting the `'content'` field.
**Incorrect handling** (line 1163 - before fix):
```python
chunk_idx, summary = future.result() # summary is a dict, not a string!
chunk_summaries.append((chunk_idx, summary))
```
---
## Fixes Applied
### Fix 1: Added missing `total_chunks` parameter
**File**: `backend/video_processor.py`
**Line**: 1155
**Before**:
```python
future = executor.submit(
self._process_single_chunk,
(i, chunk_path, summary_prompt, user_email)
)
```
**After**:
```python
future = executor.submit(
self._process_single_chunk,
(i, chunk_path, summary_prompt, len(chunk_paths), user_email)
)
```
### Fix 2: Extract content from result dict
**File**: `backend/video_processor.py`
**Lines**: 1163-1178
**Before**:
```python
chunk_idx, summary = future.result()
chunk_summaries.append((chunk_idx, summary))
```
**After**:
```python
chunk_idx, result = future.result()
# Extract content from result dict
if result.get('success'):
summary = result.get('content', '')
else:
summary = f"[Error: {result.get('message', 'Unknown error')}]"
chunk_summaries.append((chunk_idx, summary))
```
---
## Impact
### Before Fix
- ❌ Batch processing with chunking completely broken
- ❌ Error: "not enough values to unpack (expected 5, got 4)"
- ❌ Users could not process multiple long videos as batch
### After Fix
- ✅ Batch processing with chunking works correctly
- ✅ All 5 parameters passed correctly
- ✅ Result content extracted properly
- ✅ Users can process multiple long videos as batch
---
## Testing
### Verified Scenarios
1. **Batch with 2 short videos** (< 54 min each, no chunking):
- Uses direct processing path
- ✅ Not affected by this bug (different code path)
2. **Batch with 1 long video** (> 54 min, needs chunking):
- Uses chunking + parallel processing
- ✅ Fixed by this patch
3. **Batch with mixed videos** (some short, one long):
- Long video gets chunked, short ones don't
- ✅ Fixed by this patch
### Test Command
```bash
# Test batch processing with long video
curl -X POST http://localhost:5010/api/process-batch \
-H "Content-Type: application/json" \
-d '{
"videos": [
{"file_path": "/path/to/long_video1.mp4", "filename": "video1.mp4", "order": 1},
{"file_path": "/path/to/long_video2.mp4", "filename": "video2.mp4", "order": 2}
],
"prompt": "Generate a detailed meeting summary",
"batch_id": "test-batch"
}'
```
---
## Related Code
### Other Parallel Processing (Not Affected)
The `_process_chunks_parallel()` method (line 686-733) used for individual long videos was **NOT affected** because it was already correctly passing 5 parameters:
```python
# Line 706 - CORRECT (not modified)
chunk_infos.append((i, chunk_path, chunk_prompt, num_chunks, user_email))
```
---
## Files Modified
- `backend/video_processor.py` (2 sections fixed)
- Line 1155: Added missing `total_chunks` parameter
- Lines 1163-1178: Fixed result dict extraction
---
## Deployment
### Apply Fix
```bash
cd /path/to/video-query
# Pull latest changes (if in git)
git pull
# Or manually update video_processor.py with fixes
# Restart backend
sudo systemctl restart video-query
# Verify
journalctl -u video-query -f
```
### Verify Fix
```bash
# Check logs show proper processing
journalctl -u video-query -f | grep "Stage 1"
# Should see:
# Batch xxx: [Stage 1] Chunk 1/5 complete (1/5 total)
# NOT: "not enough values to unpack"
```
---
## Prevention
To prevent similar issues:
1. **Type Hints**: Function signatures already have type hints
2. **Testing**: Add unit tests for parallel processing
3. **Code Review**: Check tuple unpacking matches function signatures
---
## Related Issues
This bug was introduced during the enhancement work (see `BATCH_PROCESSING_IMPROVEMENTS.md`) when adding detailed logging to the `_process_chunks_two_stage()` method. The original code was refactored but the tuple unpacking wasn't updated consistently.
---
**Status**: ✅ Fixed and verified
**Testing**: Manual testing recommended for batch processing with long videos
**Risk**: Low - targeted fix with minimal changes

View file

@ -1,170 +0,0 @@
# CORS Fix Summary
## Issue
Frontend running on `http://localhost:3000` was blocked by CORS policy when trying to access backend API at `https://brandtechsandbox.oliver.solutions/video_query_back/api/init-upload`
### Error Message
```
Access to XMLHttpRequest at 'https://brandtechsandbox.oliver.solutions/video_query_back/api/init-upload'
from origin 'http://localhost:3000' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
```
---
## Root Cause
The OPTIONS preflight handler in `app.py` (lines 1124-1132) was only returning `https://ai-sandbox.oliver.solutions` as the allowed origin, not `http://localhost:3000`.
---
## Solution Implemented
### File: `backend/app.py`
#### Changed (lines 1123-1143):
```python
# Handle CORS preflight requests for all API routes
@app.route('/api/<path:path>', methods=['OPTIONS'])
def handle_options(path):
# Get the origin from the request
origin = request.headers.get('Origin')
allowed_origins = ['https://ai-sandbox.oliver.solutions', 'http://localhost:3000']
response = jsonify({})
# Allow the origin if it's in our allowed list
if origin in allowed_origins:
response.headers.add('Access-Control-Allow-Origin', origin)
else:
# Default to production origin
response.headers.add('Access-Control-Allow-Origin', 'https://ai-sandbox.oliver.solutions')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Requested-With')
response.headers.add('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
response.headers.add('Access-Control-Max-Age', '86400') # 24 hours
response.headers.add('Access-Control-Allow-Credentials', 'true')
return response
```
---
## What Changed
### Before:
- Hardcoded origin: `'https://ai-sandbox.oliver.solutions'`
- Did not check request origin
- Always returned same origin regardless of where request came from
### After:
- Dynamic origin checking
- List of allowed origins: `['https://ai-sandbox.oliver.solutions', 'http://localhost:3000']`
- Returns the origin that made the request if it's in the allowed list
- Falls back to production origin if request origin is not allowed
---
## Existing CORS Configuration (Already Correct)
The main CORS configuration on line 41-46 was already correct:
```python
CORS(app, resources={r"/api/*": {
"origins": ["https://ai-sandbox.oliver.solutions", "http://localhost:3000"],
"supports_credentials": True,
"methods": ["GET", "POST", "OPTIONS"],
"allow_headers": ["Content-Type", "X-Requested-With", "Authorization"]
}}, expose_headers=["Content-Disposition", "Authorization"])
```
---
## Testing
### Before Fix:
```
❌ localhost:3000 → backend API: CORS error
✅ production → backend API: Works
```
### After Fix:
```
✅ localhost:3000 → backend API: Should work
✅ production → backend API: Still works
```
---
## How to Test
1. **Start the backend** (if not already running):
```bash
cd backend
python3 run.py
# or
hypercorn run:app
```
2. **Start the frontend** on localhost:3000:
```bash
cd frontend
npm start
```
3. **Test the upload**:
- Open browser to `http://localhost:3000`
- Try uploading a video
- Check browser console for CORS errors
- Should see successful API calls
4. **Check Network Tab**:
- Open browser DevTools → Network tab
- Look for `init-upload` request
- Check Response Headers for:
- `Access-Control-Allow-Origin: http://localhost:3000`
- `Access-Control-Allow-Credentials: true`
---
## Additional Notes
### Why This Fix is Safe:
1. **Localhost is development only** - Won't be accessible in production
2. **Credentials still required** - Auth is still enforced
3. **Limited to /api/* routes** - Doesn't affect other routes
4. **Production origin still allowed** - No impact on deployed version
### If You Need to Add More Origins:
Update the `allowed_origins` list in `app.py` line 1128:
```python
allowed_origins = [
'https://ai-sandbox.oliver.solutions',
'http://localhost:3000',
'http://localhost:3001', # Add more as needed
'https://another-domain.com'
]
```
---
## Files Modified
1. ✅ `backend/app.py` - Updated OPTIONS handler (lines 1123-1143)
---
## Dependencies
- ✅ `flask-cors==5.0.1` - Already in requirements.txt
- ✅ No new dependencies needed
---
## Status
✅ **FIXED and READY FOR TESTING**
The CORS error should now be resolved. Try uploading a video from `http://localhost:3000` and verify it works.
---
Generated: 2025-10-16

View file

@ -1,396 +0,0 @@
# Cross-Platform Support & Error Reporting - Implementation Summary
**Date:** 2025-11-13
**Status:** ✅ **COMPLETED**
---
## Overview
Successfully implemented cross-platform support and comprehensive error reporting for the Video Query application. The system now works seamlessly on:
- ✅ Linux (Ubuntu, Debian, CentOS, RHEL)
- ✅ macOS (Intel and Apple Silicon M1/M2/M3)
- ✅ Windows WSL
---
## What Was Implemented
### 1. **New Files Created** (2 files)
#### `backend/system_utils.py` (620 lines)
**Purpose:** Cross-platform system utility path detection
**Features:**
- ✅ Automatic OS detection (Linux, macOS, Windows)
- ✅ Intelligent executable search across multiple locations
- ✅ macOS Apple Silicon support (`/opt/homebrew/bin/`)
- ✅ macOS Intel support (`/usr/local/bin/`)
- ✅ Linux standard paths (`/usr/bin/`, `/usr/local/bin/`, `/snap/bin/`)
- ✅ PATH environment variable fallback
- ✅ LRU caching for performance
- ✅ Executable verification (runs `-version` test)
- ✅ Detailed error messages with installation instructions
**Key Functions:**
```python
system_utils.find_ffprobe() # Find ffprobe executable
system_utils.find_ffmpeg() # Find ffmpeg executable
system_utils.find_wkhtmltopdf() # Find wkhtmltopdf executable
system_utils.get_system_info() # Get system information
```
#### `backend/error_reporter.py` (450 lines)
**Purpose:** Comprehensive error reporting and tracking
**Features:**
- ✅ Auto-categorization of errors (System, API, Video, Network, Upload, User, Unknown)
- ✅ Unique error IDs for tracking
- ✅ User-friendly error messages
- ✅ Technical debug information with stack traces
- ✅ Suggested fixes for common errors
- ✅ Context capture (file paths, operations, request data)
- ✅ System information gathering
- ✅ Recent errors storage (last 100)
- ✅ Error export to JSON
**Key Features:**
```python
ErrorReporter.capture_error() # Capture and report errors
error_report.format_user_message() # User-friendly format
error_report.format_technical() # Technical debug format
error_report.to_json() # Export to JSON
```
**Error Categories:**
1. **SYSTEM_ERROR** - Missing dependencies, file not found, permissions
2. **API_ERROR** - Gemini API issues (503, 429, 500)
3. **VIDEO_ERROR** - Corrupted files, encoding issues
4. **NETWORK_ERROR** - Connection timeouts, DNS issues
5. **UPLOAD_ERROR** - File upload failures
6. **USER_ERROR** - Invalid input or configuration
7. **UNKNOWN_ERROR** - Unexpected errors
---
### 2. **Modified Files** (4 files)
#### `backend/video_splitter.py`
**Changes:**
- ✅ Added imports: `system_utils`, `error_reporter`
- ✅ Line 51: Replaced hardcoded `/usr/bin/ffprobe` with `system_utils.find_ffprobe()`
- ✅ Lines 72-94: Enhanced error reporting in `get_video_duration()`
- ✅ Lines 265-292: Enhanced error reporting in `split_video()`
**Impact:**
- Now works on macOS (Intel and Apple Silicon)
- Better error messages when ffprobe is missing
- Detailed error context for debugging
#### `backend/video_processor.py`
**Changes:**
- ✅ Added imports: `system_utils`, `error_reporter`
- ✅ Line 206: Updated ffprobe subprocess call to use `system_utils.find_ffprobe()`
- ✅ Lines 401-416: Enhanced error reporting in `process_video()`
- ✅ Lines 822-838: Enhanced error reporting in `process_long_video()`
**Impact:**
- Cross-platform video validation
- Detailed error reports with unique IDs
- Suggested fixes returned to frontend
#### `backend/chunked_upload.py`
**Changes:**
- ✅ Added imports: `system_utils`, `error_reporter`
- ✅ Line 180: Updated ffprobe call for upload validation
- ✅ Lines 216-231: Enhanced error reporting for upload failures
**Impact:**
- Upload validation works on all platforms
- Better error tracking for failed uploads
#### `backend/app.py`
**Changes:**
- ✅ Added imports: `system_utils`, `error_reporter`
- ✅ Lines 1064-1077: Replaced hardcoded wkhtmltopdf path with `system_utils.find_wkhtmltopdf()`
- ✅ Lines 255-271: Enhanced error reporting in `/api/process`
- ✅ Lines 371-387: Enhanced error reporting in `/api/process-batch`
- ✅ Lines 1251-1267: Enhanced error reporting in `/api/generate-pdf`
**Impact:**
- PDF generation works on macOS
- All API endpoints return structured error information
- Error IDs included in responses for support
---
### 3. **Test Script Created**
#### `backend/test_system_setup.py`
**Purpose:** Verify system setup before running the application
**Features:**
- ✅ Tests system information detection
- ✅ Tests executable path detection (ffprobe, ffmpeg, wkhtmltopdf)
- ✅ Tests error reporting functionality
- ✅ Provides installation instructions if dependencies are missing
**Usage:**
```bash
cd backend
python test_system_setup.py
```
**Test Results on Current System (WSL Ubuntu):**
```
✅ ffprobe: Found at /usr/bin/ffprobe
✅ ffmpeg: Found at /usr/bin/ffmpeg
⚠️ wkhtmltopdf: Found but verification failed (known quirk, still works)
✅ Error reporting: All categories working correctly
```
---
## Platform-Specific Paths
### ffprobe/ffmpeg Locations:
| Platform | Paths Searched (in order) |
|----------|---------------------------|
| **Linux** | `/usr/bin/`, `/usr/local/bin/`, `/snap/bin/`, PATH |
| **macOS (Apple Silicon)** | `/opt/homebrew/bin/`, `/usr/local/bin/`, `/usr/bin/`, PATH |
| **macOS (Intel)** | `/usr/local/bin/`, `/opt/homebrew/bin/`, `/usr/bin/`, PATH |
| **Windows WSL** | `/usr/bin/`, `/usr/local/bin/`, PATH |
### wkhtmltopdf Locations:
| Platform | Paths Searched (in order) |
|----------|---------------------------|
| **Linux** | `/usr/bin/`, `/usr/local/bin/`, `/snap/bin/`, PATH |
| **macOS** | `/opt/homebrew/bin/`, `/usr/local/bin/`, `/usr/bin/`, PATH |
| **Windows WSL** | `/usr/bin/`, `/usr/local/bin/`, PATH |
---
## Error Reporting Examples
### Example 1: Missing Dependency
```json
{
"success": false,
"message": "❌ System dependency missing: FFmpeg/FFprobe is not installed\n\n💡 Suggested Fix:\nInstall FFmpeg:\n Ubuntu/Debian: sudo apt-get install ffmpeg\n macOS: brew install ffmpeg\n\n📋 Error ID: A3B5C7D9",
"error_id": "A3B5C7D9",
"error_category": "system"
}
```
### Example 2: API Overload (503)
```json
{
"success": false,
"message": "❌ Gemini API is temporarily overloaded\n\n💡 Suggested Fix:\nThe API is temporarily overloaded. The system will automatically retry.\nIf this persists:\n 1. Wait a few minutes\n 2. Set MAX_PARALLEL_CHUNKS=1 in .env\n 3. Set GEMINI_API_TIER=free in .env\n\n📋 Error ID: E7F8A1B2",
"error_id": "E7F8A1B2",
"error_category": "api"
}
```
### Example 3: Corrupted Video
```json
{
"success": false,
"message": "❌ Video file is incomplete or corrupted (missing header)\n\n💡 Suggested Fix:\n1. Try re-uploading the file\n2. Re-encode: ffmpeg -i input.mp4 -c copy output.mp4\n3. Ensure upload completed fully\n\n📋 Error ID: C4D5E6F7",
"error_id": "C4D5E6F7",
"error_category": "video"
}
```
---
## Installation Instructions by Platform
### macOS (Homebrew)
```bash
# Install Homebrew if not already installed
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Install dependencies
brew install ffmpeg wkhtmltopdf
# Test the setup
cd backend
python test_system_setup.py
```
### Ubuntu/Debian
```bash
# Update package list
sudo apt-get update
# Install dependencies
sudo apt-get install ffmpeg wkhtmltopdf
# Test the setup
cd backend
python test_system_setup.py
```
### CentOS/RHEL
```bash
# Enable EPEL repository
sudo yum install epel-release
# Install dependencies
sudo yum install ffmpeg wkhtmltopdf
# Test the setup
cd backend
python test_system_setup.py
```
---
## Usage Examples
### Check System Setup
```bash
cd backend
python test_system_setup.py
```
### Manual Testing in Python
```python
# Test system utilities
from system_utils import system_utils
print(system_utils.get_system_info())
print(f"ffprobe: {system_utils.find_ffprobe()}")
print(f"ffmpeg: {system_utils.find_ffmpeg()}")
print(f"wkhtmltopdf: {system_utils.find_wkhtmltopdf()}")
# Test error reporting
from error_reporter import ErrorReporter, ErrorCategory
try:
raise Exception("503 UNAVAILABLE: Model overloaded")
except Exception as e:
report = ErrorReporter.capture_error(e)
print(report.format_user_message())
```
---
## Benefits
### Before Implementation:
```
❌ Hardcoded paths: /usr/bin/ffprobe (fails on macOS)
❌ Generic errors: "Error processing video: [exception]"
❌ No error context or tracking
❌ Users must dig through logs to debug
❌ No suggested fixes
```
### After Implementation:
```
✅ Auto-detects executables on any platform
✅ Works on Linux, macOS (Intel & ARM), Windows WSL
✅ Clear error messages with unique IDs
✅ Auto-categorization of error types
✅ Suggested fixes for common issues
✅ Full error context for debugging
✅ Error tracking and export
✅ Installation instructions when dependencies missing
```
---
## Performance Impact
- **Negligible overhead:** Path detection uses LRU caching (cached after first lookup)
- **No impact on video processing:** Paths resolved once at startup
- **Error reporting:** Adds ~1-2ms per error (only on failures)
---
## Testing Checklist
- [x] Test on current system (WSL Ubuntu) ✅
- [x] Verify ffprobe detection ✅
- [x] Verify ffmpeg detection ✅
- [x] Verify wkhtmltopdf detection ✅
- [x] Test error categorization ✅
- [x] Test error message formatting ✅
- [x] Test suggested fix generation ✅
- [ ] Test on macOS (Intel) - *Not available*
- [ ] Test on macOS (Apple Silicon) - *Not available*
- [x] Verify no regressions in existing functionality ✅
---
## Known Issues
1. **wkhtmltopdf verification:** Sometimes fails version check even when working
- **Impact:** Minor - executable still works for PDF generation
- **Workaround:** None needed, functionality is not affected
---
## Next Steps
The cross-platform support is now complete. You can:
1. **Start the application:**
```bash
cd backend
python run.py
```
2. **Test on macOS** (when available):
- Clone the repo on a Mac
- Install dependencies: `brew install ffmpeg wkhtmltopdf`
- Run test: `python backend/test_system_setup.py`
- Start app: `python backend/run.py`
3. **Monitor error reports:**
- All errors now have unique IDs
- Users can reference error IDs when reporting issues
- Detailed logs available for debugging
---
## Files Modified/Created Summary
### New Files (2):
1. ✅ `backend/system_utils.py` (620 lines)
2. ✅ `backend/error_reporter.py` (450 lines)
3. ✅ `backend/test_system_setup.py` (180 lines) - Test script
### Modified Files (4):
1. ✅ `backend/video_splitter.py` (+30 lines)
2. ✅ `backend/video_processor.py` (+40 lines)
3. ✅ `backend/chunked_upload.py` (+20 lines)
4. ✅ `backend/app.py` (+50 lines)
**Total lines added:** ~1,400 lines
**Total files changed:** 7 files
---
## Conclusion
✅ **Implementation Complete**
The application now has:
- Full cross-platform support (Linux, macOS, Windows WSL)
- Comprehensive error reporting with unique IDs
- Auto-detection of system dependencies
- User-friendly error messages with suggested fixes
- Detailed technical logging for debugging
- Test script to verify setup
The application is ready to run on any supported platform without code changes!
---
**Questions or Issues?**
Run `python backend/test_system_setup.py` to diagnose any setup problems.

View file

@ -1,340 +0,0 @@
# MSAL & CORS Configuration Fix - Domain Alignment
## Issue Identified
The frontend and backend were configured for **different domains**, which would cause both CORS errors and MSAL authentication failures in production.
### Configuration Mismatch Before Fix:
**Frontend** (`frontend/public/config.js`):
- Domain: `https://brandtechsandbox.oliver.solutions`
- MSAL Redirect URI: `https://brandtechsandbox.oliver.solutions/video-query/`
- API Endpoints: `https://brandtechsandbox.oliver.solutions/video_query_back/*`
**Backend** (`backend/app.py` & `backend/chunked_upload.py`):
- CORS Allowed Origins: `https://ai-sandbox.oliver.solutions`
- OPTIONS Handler Default: `https://ai-sandbox.oliver.solutions`
---
## Errors That Would Have Occurred:
### 1. CORS Preflight Failure ❌
```
Access to XMLHttpRequest at 'https://brandtechsandbox.oliver.solutions/video_query_back/api/init-upload'
from origin 'https://brandtechsandbox.oliver.solutions' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
The 'Access-Control-Allow-Origin' header has a value 'https://ai-sandbox.oliver.solutions'
that is not equal to the supplied origin.
```
### 2. API Call Failures ❌
All API calls from frontend would fail because:
- Frontend sends from: `brandtechsandbox.oliver.solutions`
- Backend only allows: `ai-sandbox.oliver.solutions`
- Result: **403 Forbidden** or **CORS errors**
### 3. MSAL Authentication Would Work BUT... ⚠️
MSAL would technically work IF Azure AD B2C has `https://brandtechsandbox.oliver.solutions/video-query/` registered as a redirect URI.
**However**, you MUST verify in Azure AD B2C that this exact redirect URI is registered:
- Azure Portal → Azure AD B2C → App registrations
- Client ID: `9079054c-9620-4757-a256-23413042f1ef`
- Authentication → Redirect URIs → Must include: `https://brandtechsandbox.oliver.solutions/video-query/`
---
## Solution Applied
Updated backend CORS configuration to match the frontend domain: `brandtechsandbox.oliver.solutions`
### Files Modified:
#### 1. `backend/app.py` (Line 42)
**Before:**
```python
CORS(app, resources={r"/api/*": {
"origins": ["https://ai-sandbox.oliver.solutions", "http://localhost:3000"],
...
}})
```
**After:**
```python
CORS(app, resources={r"/api/*": {
"origins": ["https://brandtechsandbox.oliver.solutions", "http://localhost:3000"],
...
}})
```
#### 2. `backend/app.py` (Line 1128)
**Before:**
```python
allowed_origins = ['https://ai-sandbox.oliver.solutions', 'http://localhost:3000']
...
response.headers.add('Access-Control-Allow-Origin', 'https://ai-sandbox.oliver.solutions')
```
**After:**
```python
allowed_origins = ['https://brandtechsandbox.oliver.solutions', 'http://localhost:3000']
...
response.headers.add('Access-Control-Allow-Origin', 'https://brandtechsandbox.oliver.solutions')
```
#### 3. `backend/chunked_upload.py` (Line 18)
**Before:**
```python
allowed_origins = ['https://ai-sandbox.oliver.solutions', 'http://localhost:3000']
...
response.headers.add('Access-Control-Allow-Origin', 'https://ai-sandbox.oliver.solutions')
```
**After:**
```python
allowed_origins = ['https://brandtechsandbox.oliver.solutions', 'http://localhost:3000']
...
response.headers.add('Access-Control-Allow-Origin', 'https://brandtechsandbox.oliver.solutions')
```
---
## Current Configuration (After Fix):
### Frontend (`frontend/public/config.js`):
```javascript
{
"basePath": "/video-query",
"domain": "https://brandtechsandbox.oliver.solutions",
"msal": {
"clientId": "9079054c-9620-4757-a256-23413042f1ef",
"authority": "https://login.microsoftonline.com/e519c2e6-bc6d-4fdf-8d9c-923c2f002385",
"redirectUri": "https://brandtechsandbox.oliver.solutions/video-query/",
"postLogoutRedirectUri": "https://brandtechsandbox.oliver.solutions/video-query/",
"tenantId": "e519c2e6-bc6d-4fdf-8d9c-923c2f002385"
},
"api": {
"videoProcessingEndpoint": "https://brandtechsandbox.oliver.solutions/video_query_back/api/process",
"chunkedUploadEndpoint": "https://brandtechsandbox.oliver.solutions/video_query_back"
}
}
```
### Backend CORS (All Files):
```python
allowed_origins = ['https://brandtechsandbox.oliver.solutions', 'http://localhost:3000']
```
✅ **All domains now match!**
---
## MSAL Implementation Analysis
### ✅ MSAL Code Implementation is CORRECT
The MSAL implementation in the frontend is properly structured:
#### 1. **Dynamic Configuration Loading** (`frontend/src/auth/authConfig.js`)
- Uses `configLoader` to load runtime configuration
- Supports both production and local development configs
- Proxy-based config access for backward compatibility
#### 2. **Proper MSAL Initialization** (`frontend/src/auth/AuthProvider.js`)
- Creates `PublicClientApplication` with runtime config
- Properly initializes MSAL with `await instance.initialize()`
- Handles redirect responses at startup
- Sets active account from redirect response
- Implements event callbacks for auth events
#### 3. **Redirect Flow Handling**
```javascript
// Handle any initial redirect response at startup
const response = await instance.handleRedirectPromise();
if (response && response.account) {
instance.setActiveAccount(response.account);
}
```
#### 4. **Event-Driven Authentication**
```javascript
instance.addEventCallback((event) => {
if (event.eventType === EventType.LOGIN_SUCCESS) {
instance.setActiveAccount(event.payload.account);
if (event.interactionType === "redirect") {
window.location.reload();
}
}
});
```
### ✅ MSAL Will Work in Production IF:
**Critical Requirement:** The redirect URI **MUST** be registered in Azure AD B2C
**Verification Steps:**
1. Go to: Azure Portal → Azure AD B2C → App registrations
2. Find app: Client ID `9079054c-9620-4757-a256-23413042f1ef`
3. Navigate to: Authentication → Platform configurations → Single-page application
4. Verify redirect URI exists: `https://brandtechsandbox.oliver.solutions/video-query/`
5. Verify post-logout redirect URI exists: `https://brandtechsandbox.oliver.solutions/video-query/`
**If NOT registered:**
- Click "Add a platform" → "Single-page application"
- Add redirect URI: `https://brandtechsandbox.oliver.solutions/video-query/`
- Add post-logout redirect URI: `https://brandtechsandbox.oliver.solutions/video-query/`
- Click "Configure"
---
## Production Deployment Checklist
### ✅ Already Complete:
- [x] Frontend config.js uses `brandtechsandbox.oliver.solutions`
- [x] Backend CORS allows `brandtechsandbox.oliver.solutions`
- [x] All CORS handlers use matching domain
- [x] MSAL implementation code is correct
- [x] Redirect flow properly handled
- [x] Config loading works dynamically
### ⚠️ Requires Manual Verification:
- [ ] **Azure AD B2C redirect URI** is registered for `brandtechsandbox.oliver.solutions/video-query/`
- [ ] Apache/Nginx configuration routes `/video_query_back` to backend
- [ ] SSL certificates valid for `brandtechsandbox.oliver.solutions`
- [ ] DNS points to correct server
### 🔧 Deployment Steps:
1. **Verify Azure AD B2C Configuration**
```
1. Login to Azure Portal
2. Go to Azure AD B2C
3. Check app registration (Client ID: 9079054c-9620-4757-a256-23413042f1ef)
4. Verify redirect URI: https://brandtechsandbox.oliver.solutions/video-query/
```
2. **Deploy Frontend**
```bash
cd frontend
npm run build
sudo cp -r build/* /var/www/html/video-query/
```
3. **Deploy Backend**
```bash
cd backend
sudo systemctl restart video-query
# OR if using development server:
# python3 run.py
```
4. **Verify Web Server Configuration** (Apache example)
```apache
<VirtualHost *:443>
ServerName brandtechsandbox.oliver.solutions
# Frontend
Alias /video-query /var/www/html/video-query
# Backend proxy
ProxyPass /video_query_back http://localhost:5010
ProxyPassReverse /video_query_back http://localhost:5010
SSLEngine on
SSLCertificateFile /path/to/cert.crt
SSLCertificateKeyFile /path/to/key.key
</VirtualHost>
```
---
## Testing After Deployment
### Test 1: CORS Verification
```bash
curl -I -X OPTIONS \
-H "Origin: https://brandtechsandbox.oliver.solutions" \
-H "Access-Control-Request-Method: POST" \
https://brandtechsandbox.oliver.solutions/video_query_back/api/init-upload
```
**Expected Response Headers:**
```
Access-Control-Allow-Origin: https://brandtechsandbox.oliver.solutions
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,POST,OPTIONS
```
### Test 2: MSAL Authentication
1. Open: `https://brandtechsandbox.oliver.solutions/video-query/`
2. Click "Sign In"
3. Should redirect to Microsoft login
4. After login, should redirect back to: `https://brandtechsandbox.oliver.solutions/video-query/`
5. Should show user name in header
### Test 3: API Calls
1. Upload a video
2. Check browser console (F12) → Network tab
3. Verify API calls to `/video_query_back/api/*` succeed
4. Check response headers include: `Access-Control-Allow-Origin: https://brandtechsandbox.oliver.solutions`
---
## Troubleshooting
### Issue 1: MSAL Redirect Error
**Error:** `AADSTS50011: The redirect URI does not match...`
**Solution:**
- Check Azure AD B2C app registration
- Ensure redirect URI **exactly** matches: `https://brandtechsandbox.oliver.solutions/video-query/`
- Note the trailing slash `/` is required!
### Issue 2: CORS Still Failing
**Error:** `CORS policy: No 'Access-Control-Allow-Origin' header...`
**Solution:**
1. Check backend logs: `journalctl -u video-query -f`
2. Verify backend restarted after changes
3. Check Apache/Nginx proxy configuration
4. Verify SSL is working (CORS stricter with HTTPS)
### Issue 3: Infinite Redirect Loop
**Symptom:** Page keeps redirecting to Microsoft login
**Solution:**
1. Check browser console for errors
2. Verify `sessionStorage` is enabled in browser
3. Clear browser cache and cookies
4. Check if pop-up blocker is interfering
---
## Summary
### ✅ **Configuration Now Aligned:**
- Frontend: `brandtechsandbox.oliver.solutions`
- Backend CORS: `brandtechsandbox.oliver.solutions`
- Domains match perfectly ✓
### ✅ **MSAL Implementation:**
- Code is correct and production-ready ✓
- Handles redirects properly ✓
- Event callbacks configured ✓
### ⚠️ **Action Required:**
- **Verify Azure AD B2C redirect URI registration**
- Test authentication flow after deployment
- Monitor logs for any issues
### 📝 **Files Modified:**
1. `backend/app.py` - Updated CORS origins (2 locations)
2. `backend/chunked_upload.py` - Updated CORS handler (1 location)
### 🚀 **Ready for Production:**
Once Azure AD B2C redirect URI is verified, the application is ready for deployment to `https://brandtechsandbox.oliver.solutions/video-query/`
---
**Date:** 2025-10-22
**Status:** ✅ READY FOR DEPLOYMENT (after Azure AD B2C verification)

View file

@ -1,327 +0,0 @@
# PDF Generation Fix Summary
**Date:** 2025-11-13
**Issue:** PDF generation button not working
**Status:** ✅ **FIXED**
---
## Problem Identified
The PDF generation feature was failing due to **wkhtmltopdf verification issue** in the `system_utils.py` module.
### **Root Cause:**
The `verify_executable()` method in `system_utils.py` was using `-version` flag (single dash) to test executables, but **wkhtmltopdf requires `--version` flag (double dash)**.
**Error flow:**
```
1. User clicks "Download PDF" button
2. Backend tries to find wkhtmltopdf via system_utils.find_wkhtmltopdf()
3. Verification runs: wkhtmltopdf -version
4. wkhtmltopdf returns error: "Unknown switch -v"
5. Verification fails → FileNotFoundError raised
6. PDF generation fails
```
---
## Solution Implemented
### **Fix 1: Enhanced Executable Verification**
**File:** `backend/system_utils.py`
**Lines:** 261-319
**Changes:**
```python
# OLD (Failed):
result = subprocess.run([path, '-version'], ...)
if result.returncode == 0:
return True
else:
return False
# NEW (Works):
version_flags = ['--version', '-version', '-V'] # Try multiple flags
for flag in version_flags:
result = subprocess.run([path, flag], ...)
if result.returncode == 0 or result.stdout or result.stderr:
return True # Success!
# Fallback: If file exists and is executable, assume it works
return os.path.exists(path) and os.access(path, os.X_OK)
```
**Benefits:**
- ✅ Tries multiple version flags (`--version`, `-version`, `-V`)
- ✅ Accepts any output (stdout or stderr) as verification
- ✅ Falls back to simple existence check if no flags work
- ✅ Works with all executables (ffmpeg, ffprobe, wkhtmltopdf)
---
## Verification Tests
### **Test 1: wkhtmltopdf Detection**
```bash
$ python -c "from system_utils import system_utils; print(system_utils.find_wkhtmltopdf())"
# Output: /usr/bin/wkhtmltopdf
# ✅ SUCCESS
```
### **Test 2: App Imports**
```bash
$ source venv/bin/activate
$ python -c "from app import app; print('✓ App loaded')"
# Output: ✓ App loaded
# ✅ SUCCESS
```
### **Test 3: wkhtmltopdf Version**
```bash
$ /usr/bin/wkhtmltopdf --version
# Output: wkhtmltopdf 0.12.6
# ✅ Installed and working
```
---
## Frontend Verification
**File:** `frontend/src/components/ResultDisplay.js`
**PDF Button (Lines 481-491):**
```javascript
<button
className="btn btn-danger btn-sm"
onClick={downloadPdf}
disabled={isPdfLoading}
>
{isPdfLoading ? 'Generating...' : 'Download PDF'}
</button>
```
**Status:** ✅ **No changes needed** - Frontend code is correct
**PDF Generation Flow:**
1. User clicks "Download PDF"
2. Frontend converts Mermaid diagrams to PNG images
3. Sends HTML + PNG images to `/api/generate-pdf`
4. Backend uses wkhtmltopdf to generate PDF
5. PDF returned as base64 → Downloaded to user
---
## Files Modified
### **Modified (1 file):**
- ✅ `backend/system_utils.py` - Enhanced `verify_executable()` method (58 lines changed)
### **No changes needed:**
- ✅ `backend/app.py` - Already uses `system_utils.find_wkhtmltopdf()`
- ✅ `frontend/src/components/ResultDisplay.js` - Frontend code is correct
- ✅ `backend/.env` - Configuration is correct
---
## How PDF Generation Works Now
### **Complete Flow:**
```
1. USER ACTION:
User clicks "Download PDF" button
2. FRONTEND (ResultDisplay.js):
- Waits for Mermaid diagrams to fully render
- Converts all SVG diagrams to PNG images (base64)
- Collects HTML content + PNG images
3. API REQUEST:
POST /api/generate-pdf
Body: {
html: "<div>...</div>",
diagramPngs: {"mermaid-1": "data:image/png;base64,..."},
videoFileName: "example.mp4"
}
4. BACKEND (app.py):
- Finds wkhtmltopdf using system_utils ✅ NOW WORKS
- Embeds PNG images into HTML
- Runs wkhtmltopdf to generate PDF
- Returns PDF as base64
5. FRONTEND:
- Decodes base64 PDF
- Creates download link
- Triggers browser download
6. RESULT:
User gets PDF file downloaded ✅
```
---
## Testing the Fix
### **Test PDF Generation:**
1. **Start the application:**
```bash
cd backend
source venv/bin/activate
python run.py
```
2. **Process a video:**
- Upload a video through the frontend
- Wait for processing to complete
3. **Generate PDF:**
- Click "Download PDF" button
- Wait for PDF generation
- PDF should download automatically
**Expected Result:**
- ✅ No errors in browser console
- ✅ No errors in backend logs
- ✅ PDF downloads successfully
- ✅ Mermaid diagrams appear as images in PDF
---
## Common Issues & Solutions
### **Issue 1: "wkhtmltopdf not found"**
**Symptoms:**
- Error in logs: "wkhtmltopdf not found"
- PDF button fails silently
**Solution:**
```bash
# Ubuntu/Debian
sudo apt-get install wkhtmltopdf
# After install
cd backend
source venv/bin/activate
python run.py # Restart
```
### **Issue 2: PDF generation times out**
**Symptoms:**
- Button shows "Generating..." forever
- Network error in console
**Cause:** Large HTML content with many diagrams
**Solution:**
```bash
# Check backend logs
tail -f logs/video_query.log
# If you see timeouts, increase timeout in run.py
# Edit line 36-37:
config.read_timeout = 7200 # 2 hours
config.write_timeout = 7200 # 2 hours
```
### **Issue 3: Diagrams missing in PDF**
**Symptoms:**
- PDF generates but diagrams are blank/missing
**Cause:** Mermaid SVG → PNG conversion failed
**Solution:**
- Check browser console for errors
- Ensure mermaid diagrams render correctly on screen first
- Try clicking PDF button again after diagrams fully render
---
## Technical Details
### **wkhtmltopdf Version Compatibility**
**Installed Version:** 0.12.6
**Compatible with:**
- ✅ Ubuntu 20.04+
- ✅ Ubuntu 22.04+
- ✅ WSL (Windows Subsystem for Linux)
- ✅ macOS (via Homebrew)
**Requirements:**
- ✅ libcairo2 (for rendering)
- ✅ Python cairosvg module (for SVG processing)
- ✅ Proper executable permissions
---
## Verification Checklist
Before using PDF generation, verify:
- [ ] wkhtmltopdf installed: `which wkhtmltopdf`
- [ ] wkhtmltopdf works: `/usr/bin/wkhtmltopdf --version`
- [ ] system_utils can find it: `python -c "from system_utils import system_utils; print(system_utils.find_wkhtmltopdf())"`
- [ ] Backend starts: `source venv/bin/activate && python run.py`
- [ ] No import errors in logs
- [ ] Frontend can connect to backend
- [ ] Test PDF generation with a simple result
---
## Performance Notes
### **PDF Generation Time:**
| Content Type | Expected Time |
|--------------|---------------|
| Text only | 1-3 seconds |
| Text + 1-2 diagrams | 3-5 seconds |
| Text + 5+ diagrams | 5-10 seconds |
| Large document (10+ pages) | 10-20 seconds |
**Note:** First diagram conversion takes longest due to browser canvas setup.
---
## Conclusion
✅ **PDF generation is now working!**
**What was fixed:**
1. Enhanced executable verification to handle different version flags
2. Added fallback verification for executables without version flags
3. Improved error handling in system_utils
**What still works:**
- ✅ Cross-platform support (Linux, macOS, WSL)
- ✅ Mermaid diagram rendering
- ✅ SVG to PNG conversion
- ✅ HTML to PDF generation
- ✅ Automatic file naming based on video filename
**Ready to use!** 🎉
---
**To test:**
```bash
cd backend
source venv/bin/activate
python run.py
# Then open frontend and try PDF generation
```

View file

@ -1,25 +0,0 @@
from google import genai
import os
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
print(f"API Key set: {bool(os.getenv('GOOGLE_API_KEY'))}")
try:
# Initialize client with API key
api_key = os.getenv("GOOGLE_API_KEY")
client = genai.Client(api_key=api_key)
# Test connection
response = client.models.generate_content(
model='gemini-1.5-pro',
contents='Test the API connection'
)
print("API connection test successful!")
print(f"Response: {response.text}")
except Exception as e:
print(f"API error: {e}")

View file

@ -1,177 +0,0 @@
#!/usr/bin/env python
"""
Test script to verify cross-platform system utilities and error reporting.
This script:
1. Tests system utility detection (ffprobe, ffmpeg, wkhtmltopdf)
2. Tests error reporting functionality
3. Verifies all dependencies are properly installed
Run this script before starting the application to ensure everything is set up correctly.
"""
import sys
import os
# Add backend directory to path
sys.path.insert(0, os.path.dirname(__file__))
from system_utils import system_utils
from error_reporter import ErrorReporter, ErrorCategory
import platform
def print_header(text):
"""Print a formatted header."""
print("\n" + "="*80)
print(f" {text}")
print("="*80)
def print_section(text):
"""Print a formatted section."""
print(f"\n--- {text} ---")
def test_system_info():
"""Test system information gathering."""
print_header("SYSTEM INFORMATION")
info = system_utils.get_system_info()
print(f"\nPlatform: {info['platform_name']}")
print(f"Platform Type: {info['platform']}")
print(f"Machine: {info['platform_machine']}")
print(f"OS Version: {info['platform_version']}")
print(f"Python Version: {info['python_version']}")
print(f"Python Implementation: {info['python_implementation']}")
def test_executables():
"""Test executable detection."""
print_header("EXECUTABLE DETECTION")
executables = [
('ffprobe', system_utils.find_ffprobe),
('ffmpeg', system_utils.find_ffmpeg),
('wkhtmltopdf', system_utils.find_wkhtmltopdf)
]
results = []
all_found = True
for name, finder in executables:
print_section(f"Testing {name}")
try:
path = finder()
verified = system_utils.verify_executable(path, name)
status = "✓ FOUND" if verified else "⚠ FOUND (not verified)"
print(f" Status: {status}")
print(f" Path: {path}")
results.append((name, True, path))
except FileNotFoundError as e:
print(f" Status: ✗ NOT FOUND")
print(f" Error: {str(e)[:200]}")
results.append((name, False, None))
all_found = False
except Exception as e:
print(f" Status: ✗ ERROR")
print(f" Error: {str(e)[:200]}")
results.append((name, False, None))
all_found = False
return all_found, results
def test_error_reporting():
"""Test error reporting functionality."""
print_header("ERROR REPORTING TESTS")
test_cases = [
("System Error", FileNotFoundError("ffprobe not found")),
("API Error", Exception("503 UNAVAILABLE: Model overloaded")),
("Video Error", Exception("moov atom not found")),
("Network Error", ConnectionError("Connection timeout")),
]
print("\nTesting error categorization and reporting...")
for description, exception in test_cases:
print_section(description)
try:
raise exception
except Exception as e:
report = ErrorReporter.capture_error(
e,
context={'test': description}
)
print(f" Error ID: {report.error_id}")
print(f" Category: {report.category.value}")
print(f" Message: {report.message[:100]}")
if report.suggested_fix:
print(f" Fix: {report.suggested_fix[:100]}...")
def print_summary(all_found, results):
"""Print summary of test results."""
print_header("SUMMARY")
print("\nExecutable Status:")
for name, found, path in results:
status = "" if found else ""
print(f" {status} {name}: {'Found' if found else 'NOT FOUND'}")
print("\n" + "="*80)
if all_found:
print("✓ ALL DEPENDENCIES FOUND - System is ready!")
print("="*80)
return 0
else:
print("✗ SOME DEPENDENCIES MISSING - Please install them before running the app")
print("="*80)
print("\nInstallation instructions:")
system = platform.system().lower()
if 'darwin' in system:
print("\n macOS (Homebrew):")
print(" brew install ffmpeg wkhtmltopdf")
elif 'linux' in system:
print("\n Ubuntu/Debian:")
print(" sudo apt-get update")
print(" sudo apt-get install ffmpeg wkhtmltopdf")
print("\n CentOS/RHEL:")
print(" sudo yum install ffmpeg wkhtmltopdf")
else:
print("\n Windows:")
print(" Download ffmpeg from: https://ffmpeg.org/download.html")
print(" Download wkhtmltopdf from: https://wkhtmltopdf.org/downloads.html")
print("\n" + "="*80)
return 1
def main():
"""Main test function."""
print("\n" + "="*80)
print(" VIDEO QUERY APPLICATION - SYSTEM SETUP TEST")
print("="*80)
# Test system info
test_system_info()
# Test executables
all_found, results = test_executables()
# Test error reporting
test_error_reporting()
# Print summary
exit_code = print_summary(all_found, results)
return exit_code
if __name__ == "__main__":
try:
exit_code = main()
sys.exit(exit_code)
except KeyboardInterrupt:
print("\n\nTest interrupted by user.")
sys.exit(1)
except Exception as e:
print(f"\n\nFATAL ERROR: {str(e)}")
import traceback
traceback.print_exc()
sys.exit(1)

View file

@ -1,137 +0,0 @@
import unittest
from unittest.mock import patch, MagicMock
import json
import os
import tempfile
import datetime
from video_processor import VideoProcessor
class TestWebhookIntegration(unittest.TestCase):
"""Test cases for webhook integration in VideoProcessor."""
def setUp(self):
"""Set up test environment."""
# Create a VideoProcessor instance with a mock API key
self.video_processor = VideoProcessor(api_key="test_api_key")
# Create a temporary file to simulate a video
self.temp_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
self.temp_file.close()
# Write some dummy data to the file
with open(self.temp_file.name, 'wb') as f:
f.write(b'test video content')
def tearDown(self):
"""Clean up after tests."""
# Remove the temporary file
if os.path.exists(self.temp_file.name):
os.unlink(self.temp_file.name)
@patch('video_processor.genai')
@patch('video_processor.requests.post')
def test_webhook_called_on_successful_processing(self, mock_post, mock_genai):
"""Test that the webhook is called when video processing is successful."""
# Mock the genai API responses
mock_file = MagicMock()
mock_file.uri = "test_uri"
mock_file.name = "test_name"
mock_file.state.name = "ACTIVE"
mock_genai.upload_file.return_value = mock_file
# Mock the generate_content response
mock_response = MagicMock()
mock_part = MagicMock()
mock_part.text = "Test response content"
mock_response.parts = [mock_part]
mock_genai.GenerativeModel.return_value.generate_content.return_value = mock_response
# Set up the mock for the requests.post call
mock_post.return_value.status_code = 200
# Test data
test_prompt = "Test prompt for video processing"
test_email = "test.user@example.com"
# Call the process_video method
result = self.video_processor.process_video(
self.temp_file.name,
test_prompt,
test_email
)
# Verify the result is successful
self.assertTrue(result["success"])
self.assertEqual(result["content"], "Test response content")
# Verify webhook was called with correct data
mock_post.assert_called_once()
# Get the arguments the mock was called with
call_args = mock_post.call_args
# Verify URL
self.assertEqual(call_args[0][0], "https://hook.us1.make.celonis.com/8ri1h8b2he4wudp2jku69mgcxumzxf3v")
# Verify headers
self.assertEqual(call_args[1]["headers"], {"Content-Type": "application/json"})
# Verify timeout
self.assertEqual(call_args[1]["timeout"], 10)
# Parse and verify the payload
payload = json.loads(call_args[1]["data"])
self.assertEqual(payload["tool"], "VIDEOQUERY")
self.assertEqual(payload["user"], test_email)
self.assertEqual(payload["model"], "GEMINI")
self.assertEqual(payload["prompt"], test_prompt)
# Verify date format (should be ISO format)
try:
datetime.datetime.fromisoformat(payload["date"])
date_valid = True
except ValueError:
date_valid = False
self.assertTrue(date_valid, "Date should be in ISO format")
@patch('video_processor.genai')
@patch('video_processor.requests.post')
def test_webhook_error_does_not_affect_processing(self, mock_post, mock_genai):
"""Test that errors in the webhook don't affect the main processing flow."""
# Mock the genai API responses
mock_file = MagicMock()
mock_file.uri = "test_uri"
mock_file.name = "test_name"
mock_file.state.name = "ACTIVE"
mock_genai.upload_file.return_value = mock_file
# Mock the generate_content response
mock_response = MagicMock()
mock_part = MagicMock()
mock_part.text = "Test response content"
mock_response.parts = [mock_part]
mock_genai.GenerativeModel.return_value.generate_content.return_value = mock_response
# Set up the mock for the requests.post call to raise an exception
mock_post.side_effect = Exception("Webhook connection error")
# Test data
test_prompt = "Test prompt for video processing"
test_email = "test.user@example.com"
# Call the process_video method
result = self.video_processor.process_video(
self.temp_file.name,
test_prompt,
test_email
)
# Verify the result is still successful despite webhook error
self.assertTrue(result["success"])
self.assertEqual(result["content"], "Test response content")
# Verify webhook was called
mock_post.assert_called_once()
if __name__ == '__main__':
unittest.main()

View file

@ -1,46 +0,0 @@
"""
Manual test script for the webhook integration.
This script simulates a webhook call without processing a video,
allowing us to verify the webhook is working correctly.
"""
import logging
import sys
from video_processor import VideoProcessor
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger('webhook_test')
def test_webhook_manually():
"""Test the webhook call manually"""
# Create a VideoProcessor instance
try:
processor = VideoProcessor()
logger.info("VideoProcessor initialized")
# Test user email
test_email = "test.user@example.com"
# Test prompt
test_prompt = "Test prompt for webhook verification"
# Call the webhook method directly
logger.info(f"Sending test webhook call for user {test_email}")
processor.send_usage_webhook(test_email, test_prompt)
logger.info("Webhook test completed")
except Exception as e:
logger.error(f"Error in webhook test: {str(e)}")
import traceback
logger.error(traceback.format_exc())
if __name__ == "__main__":
test_webhook_manually()

View file

@ -585,7 +585,8 @@ class VideoProcessor:
def combine_chunk_responses(self, responses: List[str], prompt: str,
num_chunks: int) -> str:
"""
Intelligently combine responses from multiple video chunks.
Combine responses from multiple video chunks using simple concatenation.
For single-video chunks split due to duration.
Args:
responses: List of response texts from each chunk
@ -595,204 +596,18 @@ class VideoProcessor:
Returns:
Combined response text
"""
logger.info(f"Combining {len(responses)} chunk responses")
logger.info(f"Combining {len(responses)} chunk responses using simple concatenation")
# Detect the prompt type to determine combination strategy
prompt_lower = prompt.lower()
is_meeting_summary = "meeting" in prompt_lower or "summary" in prompt_lower
is_documentation = "documentation" in prompt_lower or "process" in prompt_lower
is_with_charts = "mermaid" in prompt_lower or "diagram" in prompt_lower or "chart" in prompt_lower
if is_with_charts:
return self._combine_with_charts(responses, num_chunks)
elif is_meeting_summary:
return self._combine_meeting_summary(responses, num_chunks)
elif is_documentation:
return self._combine_documentation(responses, num_chunks)
else:
return self._combine_generic(responses, num_chunks)
def _combine_generic(self, responses: List[str], num_chunks: int) -> str:
"""Generic combination: simple sequential joining with section headers."""
logger.info("Using generic combination strategy")
combined = []
combined.append(f"# Complete Video Analysis\n")
combined.append(f"*This video was processed in {num_chunks} parts due to its length.*\n")
combined.append(f"*This video was processed in {num_chunks} segments.*\n\n")
for i, response in enumerate(responses, 1):
combined.append(f"\n## Part {i} of {num_chunks}\n")
combined.append(f"## Segment {i} of {num_chunks}\n\n")
combined.append(response.strip())
combined.append(f"\n\n")
return "\n".join(combined)
def _combine_meeting_summary(self, responses: List[str], num_chunks: int) -> str:
"""Combination strategy optimized for meeting summaries."""
logger.info("Using meeting summary combination strategy")
# First, try to synthesize the segments into a unified summary
try:
logger.info("Attempting to synthesize segments into unified meeting summary")
synthesized = self._synthesize_meeting_segments(responses, num_chunks)
if synthesized:
return synthesized
else:
logger.warning("Synthesis failed, falling back to segment concatenation")
except Exception as e:
logger.warning(f"Error during synthesis: {e}, falling back to segment concatenation")
# Fallback: simple concatenation with formatting
combined = []
combined.append(f"# Complete Meeting Recording Summary\n")
combined.append(f"*This recording was analyzed in {num_chunks} segments.*\n")
combined.append(f"\n---\n")
# Combine all discussion points with clear time markers
for i, response in enumerate(responses, 1):
time_range = self._format_time_range(i, num_chunks)
combined.append(f"\n## Segment {i}: {time_range}\n")
combined.append(response.strip())
combined.append(f"\n---\n")
# Add consolidated note
combined.append(f"\n### Notes")
combined.append(f"- Review all segments above for discussion points and action items")
combined.append(f"- Total recording duration: ~{num_chunks * 50} minutes")
combined.append(f"- Recording was split into {num_chunks} segments for analysis")
return "\n".join(combined)
def _synthesize_meeting_segments(self, responses: List[str], num_chunks: int) -> Optional[str]:
"""
Use AI to synthesize multiple segment summaries into one unified meeting summary.
Args:
responses: List of segment summaries
num_chunks: Number of segments
Returns:
Unified meeting summary or None if synthesis fails
"""
try:
# Prepare the segments for synthesis
segments_text = ""
for i, response in enumerate(responses, 1):
time_range = self._format_time_range(i, num_chunks)
segments_text += f"\n\n### Segment {i} ({time_range}):\n{response.strip()}\n"
# Create synthesis prompt
synthesis_prompt = f"""You are analyzing a meeting recording that was split into {num_chunks} segments due to its length. Below are the summaries from each segment. Your task is to create ONE unified, comprehensive meeting summary that integrates all the information.
SEGMENT SUMMARIES:
{segments_text}
Please provide a SINGLE, UNIFIED meeting summary that:
1. Combines all discussion points into one cohesive narrative (not separated by segments)
2. Consolidates all action items into one master list (removing duplicates if any)
3. Identifies main themes and outcomes across the entire meeting
4. Maintains chronological flow where relevant
5. Uses clear sections: Meeting Summary, Discussion Points, Action Items (with owners)
Format the output as a professional meeting summary document. Do not reference the segments in your output - write as if this was analyzed as one continuous meeting."""
logger.info("Sending synthesis request to Gemini")
# Use the new retry logic with rate limiting
synthesis_response = self._make_api_request_with_retry(
model=self.synthesis_model,
contents=[{"text": synthesis_prompt}],
context="[Meeting Synthesis]"
)
if synthesis_response.parts:
synthesized_content = ""
for part in synthesis_response.parts:
if hasattr(part, 'text'):
synthesized_content += part.text
if synthesized_content:
logger.info("Successfully synthesized unified meeting summary")
# Add header noting this was synthesized
final_output = "# Meeting Summary\n\n"
final_output += f"*Synthesized from {num_chunks}-segment analysis*\n\n"
final_output += "---\n\n"
final_output += synthesized_content
return final_output
logger.warning("No content in synthesis response")
return None
except Exception as e:
logger.error(f"Error during synthesis: {str(e)}")
return None
def _combine_documentation(self, responses: List[str], num_chunks: int) -> str:
"""Combination strategy optimized for process documentation."""
logger.info("Using documentation combination strategy")
combined = []
combined.append(f"# Complete Process Documentation\n")
combined.append(f"*This process was documented from a {num_chunks}-part video recording.*\n")
combined.append(f"\n## Overview\n")
combined.append(f"This documentation covers the complete process shown in the video. "
f"The content has been organized sequentially across all segments.\n")
for i, response in enumerate(responses, 1):
combined.append(f"\n## Section {i}: {self._format_time_range(i, num_chunks)}\n")
combined.append(response.strip())
combined.append(f"\n\n---\n*End of documentation*")
return "\n".join(combined)
def _combine_with_charts(self, responses: List[str], num_chunks: int) -> str:
"""Combination strategy for documentation with Mermaid diagrams."""
logger.info("Using documentation with charts combination strategy")
combined = []
combined.append(f"# Complete Process Documentation with Workflow Diagrams\n")
combined.append(f"*This analysis spans {num_chunks} video segments.*\n")
# First, add all text content
combined.append(f"\n## Overview and Detailed Steps\n")
for i, response in enumerate(responses, 1):
combined.append(f"\n### Part {i}: {self._format_time_range(i, num_chunks)}\n")
# Separate mermaid diagrams from text content
parts = response.split("```mermaid")
text_part = parts[0].strip()
combined.append(text_part)
# Add mermaid diagrams in a dedicated section
if len(parts) > 1:
for j, diagram_part in enumerate(parts[1:], 1):
if "```" in diagram_part:
diagram_code = diagram_part.split("```")[0]
combined.append(f"\n**Workflow Diagram {i}.{j}:**\n")
combined.append(f"```mermaid{diagram_code}```\n")
# Add any remaining text after the diagram
remaining_text = "```".join(diagram_part.split("```")[1:]).strip()
if remaining_text:
combined.append(remaining_text)
combined.append(f"\n\n---\n*Complete documentation generated from {num_chunks}-part video analysis*")
return "\n".join(combined)
def _format_time_range(self, part_num: int, total_parts: int,
chunk_duration: int = 50) -> str:
"""Format time range for a video part."""
start_min = (part_num - 1) * chunk_duration
end_min = part_num * chunk_duration if part_num < total_parts else "End"
if isinstance(end_min, int):
return f"{start_min}-{end_min} minutes"
else:
return f"{start_min}+ minutes"
return "".join(combined)
def _process_single_chunk(self, chunk_info: Tuple[int, str, str, int, str]) -> Tuple[int, Dict[str, Any]]:
"""
@ -839,7 +654,9 @@ Format the output as a professional meeting summary document. Do not reference t
# Prepare chunk information for parallel processing
chunk_infos = []
for i, chunk_path in enumerate(chunk_paths):
chunk_prompt = self._create_chunk_prompt(prompt, i + 1, num_chunks)
# Extract video name from chunk path (remove _chunk_XX suffix)
video_name = os.path.basename(chunk_path).rsplit('_chunk_', 1)[0] if '_chunk_' in chunk_path else os.path.basename(chunk_path)
chunk_prompt = self._create_chunk_prompt(prompt, i + 1, num_chunks, video_name)
chunk_infos.append((i, chunk_path, chunk_prompt, num_chunks, user_email))
# Process chunks in parallel
@ -926,8 +743,11 @@ Format the output as a professional meeting summary document. Do not reference t
for i, chunk_path in enumerate(chunk_paths, 1):
logger.info(f"[Sequential] Processing chunk {i}/{len(chunk_paths)}: {chunk_path}")
# Extract video name from chunk path (remove _chunk_XX suffix)
video_name = os.path.basename(chunk_path).rsplit('_chunk_', 1)[0] if '_chunk_' in chunk_path else os.path.basename(chunk_path)
# Modify prompt to indicate this is part of a multi-part video
chunk_prompt = self._create_chunk_prompt(prompt, i, len(chunk_paths))
chunk_prompt = self._create_chunk_prompt(prompt, i, len(chunk_paths), video_name)
# Process this chunk
chunk_result = self.process_video(chunk_path, chunk_prompt, user_email)
@ -993,50 +813,24 @@ Format the output as a professional meeting summary document. Do not reference t
self.video_splitter.cleanup_chunks(chunk_paths)
def _create_chunk_prompt(self, original_prompt: str, chunk_num: int,
total_chunks: int) -> str:
total_chunks: int, video_name: str = "") -> str:
"""
Create a prompt for a video chunk that provides context about its position.
Create a prompt for a video chunk that provides minimal context about its position.
Args:
original_prompt: The original user prompt
chunk_num: Current chunk number (1-indexed)
total_chunks: Total number of chunks
video_name: Name of the video file (for context)
Returns:
Modified prompt for the chunk
Prompt with minimal system context, keeping user's prompt as primary instruction
"""
# For meeting summaries, modify the prompt to focus on just summarizing what's in this segment
prompt_lower = original_prompt.lower()
is_meeting = "meeting" in prompt_lower
context = f"""This is segment {chunk_num} of {total_chunks} from video "{video_name}".
Your response will be combined with responses from other segments to create the final result.
if is_meeting:
# For meetings, ask for a partial summary of this segment only
if chunk_num == 1:
context = f"[SEGMENT {chunk_num} of {total_chunks} - First 50 minutes] "
context += "Provide a summary of the discussion points and any action items covered in THIS segment only. "
context += "Do not try to provide a complete meeting summary - just summarize what happens in this part. "
elif chunk_num == total_chunks:
context = f"[SEGMENT {chunk_num} of {total_chunks} - Final segment] "
context += "Provide a summary of the discussion points and any action items covered in THIS final segment only. "
context += "This continues from previous segments, but only summarize what happens in this part. "
else:
context = f"[SEGMENT {chunk_num} of {total_chunks} - Middle segment] "
context += "Provide a summary of the discussion points and any action items covered in THIS segment only. "
context += "This is a middle portion of a longer recording - only summarize what happens in this part. "
return context + original_prompt
else:
# For other types, use the original approach
context = f"[PART {chunk_num} of {total_chunks}] "
if chunk_num == 1:
context += "This is the first segment of a longer video. "
elif chunk_num == total_chunks:
context += "This is the final segment continuing from previous parts. "
else:
context += "This is a middle segment continuing from previous parts. "
return context + original_prompt
{original_prompt}"""
return context
def process_video_auto(self, video_path: str, prompt: str,
user_email: str = "anonymous") -> Dict[str, Any]:
@ -1411,50 +1205,15 @@ Format the output as a professional meeting summary document. Do not reference t
Original user request:
{original_prompt}
Your task: Provide a CONCISE SUMMARY of this segment that captures:
1. Key information relevant to the user's request
2. Important details, facts, or insights
3. Any diagrams, charts, or structured data (preserve Mermaid syntax if applicable)
4. Chronological context if relevant
Keep the summary focused and information-dense. This summary will be combined with {total_chunks - 1} other summaries to create a final unified response.
Do NOT mention "this is segment X" or "this chunk contains". Just provide the factual content.
Provide a concise summary of this segment. Your summary will be combined with other summaries to create the final result.
"""
return summary_prompt
def _detect_prompt_type(self, prompt: str, summaries: List[str]) -> str:
"""
Detect the type of prompt to apply specialized synthesis strategy.
Args:
prompt: Original user prompt
summaries: List of summaries (to check content)
Returns:
Prompt type: "meeting_summary", "documentation", "documentation_with_charts", or "generic"
"""
prompt_lower = prompt.lower()
# Check for meeting-related keywords
if any(keyword in prompt_lower for keyword in ["meeting", "discussion", "action item", "agenda"]):
return "meeting_summary"
# Check for documentation keywords
if any(keyword in prompt_lower for keyword in ["documentation", "process", "training", "knowledge base", "step by step"]):
# Check if it also includes charts/diagrams
if any(keyword in prompt_lower for keyword in ["diagram", "chart", "mermaid", "workflow"]):
return "documentation_with_charts"
return "documentation"
# Default to generic
return "generic"
def _synthesize_final_result(self, summaries: List[str], chunk_metadata: List[Dict],
original_prompt: str, user_email: str) -> str:
"""
Synthesize all chunk summaries into single cohesive result using Gemini.
Uses prompt type detection to apply specialized synthesis strategies.
Uses a universal template that makes the user's prompt the primary instruction.
"""
# Extract video names for context
video_names = list(set(m['video_name'] for m in chunk_metadata))
@ -1470,30 +1229,22 @@ Do NOT mention "this is segment X" or "this chunk contains". Just provide the fa
logger.info(f"[Stage 2] Combined summaries: {len(summaries)} summaries, {total_summary_chars} total chars")
# Detect prompt type for specialized synthesis
prompt_type = self._detect_prompt_type(original_prompt, summaries)
logger.info(f"[Stage 2] Detected prompt type: {prompt_type}")
# Check for Mermaid diagrams
has_diagrams = any('```mermaid' in s for s in summaries)
# Create synthesis prompt based on type
if prompt_type == "meeting_summary":
synthesis_prompt = self._create_synthesis_prompt_meeting(
summaries_text, original_prompt, num_videos, video_names
)
elif prompt_type == "documentation":
synthesis_prompt = self._create_synthesis_prompt_documentation(
summaries_text, original_prompt, num_videos, video_names
)
elif has_diagrams:
synthesis_prompt = self._create_synthesis_prompt_with_diagrams(
summaries_text, original_prompt, num_videos, video_names
)
# Create universal synthesis prompt
if num_videos > 1:
video_context = f"{num_videos} videos: {', '.join(video_names)}"
else:
synthesis_prompt = self._create_synthesis_prompt_generic(
summaries_text, original_prompt, num_videos, video_names
)
video_context = f"video: {video_names[0]}"
synthesis_prompt = f"""You are creating a final unified response by combining multiple segment summaries from {video_context}.
Here are the segment summaries:
{summaries_text}
Original user request:
{original_prompt}
Your task: Create ONE cohesive response that fulfills the user's request. Integrate information from all summaries naturally, without mentioning segments or chunks.
"""
# Log synthesis prompt if configured
if self.log_prompts:
@ -1534,181 +1285,6 @@ Do NOT mention "this is segment X" or "this chunk contains". Just provide the fa
logger.error(f"[Stage 2] Synthesis failed: {str(e)}, using fallback")
return self._fallback_concatenation(summaries, chunk_metadata)
def _create_synthesis_prompt_generic(self, summaries_text: str, original_prompt: str,
num_videos: int, video_names: List[str]) -> str:
"""
Generic synthesis prompt for all content types.
"""
if num_videos > 1:
video_context = f"{num_videos} videos: {', '.join(video_names)}"
else:
video_context = f"one video: {video_names[0]}"
prompt = f"""You are creating a FINAL UNIFIED RESPONSE by synthesizing multiple segment summaries.
Context:
- Source: {video_context}
- The video(s) were split into segments for processing
- Below are summaries from each segment
Original user request:
"{original_prompt}"
Segment summaries:
{summaries_text}
Your task: Create ONE cohesive, unified response that:
1. FULFILLS the original user request completely
2. INTEGRATES information from all segments naturally
3. DOES NOT mention segments, chunks, or parts
4. MAINTAINS any requested format (lists, tables, structure)
5. CONSOLIDATES duplicate information
6. PRESERVES chronological flow if relevant
7. APPEARS as if analyzing the complete video in one pass
Quality requirements:
- No phrases like "In segment 1", "The first part", "Chunk 2 discusses"
- Natural transitions between topics
- Unified narrative or structure
- Professional, coherent final product
Begin your unified response:
"""
return prompt
def _create_synthesis_prompt_meeting(self, summaries_text: str, original_prompt: str,
num_videos: int, video_names: List[str]) -> str:
"""
Specialized synthesis prompt for meeting summaries.
"""
if num_videos > 1:
video_context = f"{num_videos} videos: {', '.join(video_names)}"
else:
video_context = f"one video: {video_names[0]}"
prompt = f"""You are creating a FINAL UNIFIED MEETING SUMMARY by synthesizing multiple segment summaries.
Context:
- Source: {video_context}
- The video(s) were split into segments for processing
- Below are summaries from each segment
Original user request:
"{original_prompt}"
Segment summaries:
{summaries_text}
Your task: Create ONE cohesive meeting summary that:
1. MEETING OVERVIEW: Provide a high-level summary of the meeting
2. DISCUSSION POINTS: Consolidate all discussion topics into logical sections
- Group related discussions together
- Maintain chronological flow where relevant
- Capture key decisions made
3. ACTION ITEMS: Create a MASTER LIST of all action items
- Format: "Action item - Owner (if mentioned) - Due date (if mentioned)"
- Consolidate duplicates
- Remove redundant items
4. KEY OUTCOMES: Summarize main conclusions and next steps
Quality requirements:
- Professional meeting summary format
- No phrases like "In segment 1", "The first part", "Chunk 2 discusses"
- Natural transitions between topics
- One unified document that reads as if from single analysis
- Clear, actionable items with owners where possible
Begin your unified meeting summary:
"""
return prompt
def _create_synthesis_prompt_documentation(self, summaries_text: str, original_prompt: str,
num_videos: int, video_names: List[str]) -> str:
"""
Specialized synthesis prompt for process documentation.
"""
if num_videos > 1:
video_context = f"{num_videos} videos: {', '.join(video_names)}"
else:
video_context = f"one video: {video_names[0]}"
prompt = f"""You are creating FINAL UNIFIED PROCESS DOCUMENTATION by synthesizing multiple segment summaries.
Context:
- Source: {video_context}
- The video(s) were split into segments for processing
- Below are summaries from each segment
Original user request:
"{original_prompt}"
Segment summaries:
{summaries_text}
Your task: Create ONE comprehensive process documentation that:
1. OVERVIEW: Provide a high-level description of the process
2. PREREQUISITES: List any requirements or setup needed (if mentioned)
3. STEP-BY-STEP INSTRUCTIONS: Combine all steps into one sequential guide
- Number steps sequentially (Step 1, Step 2, etc.)
- Include sub-steps where appropriate
- Be clear and detailed for someone new to the process
4. TIPS & BEST PRACTICES: Consolidate helpful tips
5. TROUBLESHOOTING: Include common issues and solutions (if mentioned)
Quality requirements:
- Clear, sequential flow from start to finish
- No phrases like "In segment 1", "The first part", "Chunk 2 shows"
- Professional documentation format
- Easy to follow for training or reference
- One unified guide that reads naturally
Begin your unified process documentation:
"""
return prompt
def _create_synthesis_prompt_with_diagrams(self, summaries_text: str, original_prompt: str,
num_videos: int, video_names: List[str]) -> str:
"""
Synthesis prompt specifically for content with Mermaid diagrams.
"""
prompt = f"""You are creating a FINAL UNIFIED RESPONSE by synthesizing multiple segment summaries that contain Mermaid diagrams.
Original user request:
"{original_prompt}"
Segment summaries (containing diagrams):
{summaries_text}
Your task: Create ONE unified response with a SINGLE MERGED DIAGRAM.
Requirements:
1. MERGE all Mermaid diagrams into ONE comprehensive diagram
2. Combine nodes, relationships, and flows intelligently
3. Remove duplicate nodes/edges
4. Maintain logical structure and connections
5. Synthesize accompanying text naturally
6. DO NOT mention segments or parts
Diagram merging strategy:
- If multiple flowcharts: combine into single flowchart with logical flow
- If multiple architecture diagrams: create unified architecture
- If sequential diagrams: show complete sequence
- Use clear labels and grouping where appropriate
Output format:
```mermaid
[Your merged diagram here]
```
[Unified explanatory text here]
Begin your unified response with merged diagram:
"""
return prompt
def _fallback_concatenation(self, summaries: List[str], chunk_metadata: List[Dict]) -> str:
"""
Fallback method when AI synthesis fails.