Update files

parent bc26540c
Pipeline #194 failed with stages
# Changelog # Changelog
All notable changes to Video AI Analysis Tool will be documented in this file. All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
...@@ -9,80 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -9,80 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Multi-process architecture with separate web, backend, and worker processes - Multi-process architecture with separate web, backend, and worker processes
- User registration system with email confirmation - Support for CUDA and ROCm backends for analysis and training
- Token-based usage system with configurable pricing - Configuration page to select GPU backends
- Payment processing integration (Stripe, PayPal, cryptocurrency) - Self-contained communication using sockets
- Modern SaaS-style landing page for non-authenticated users - GPLv3 license and copyright notices
- REST API for programmatic access - Build scripts for creating standalone executables
- Job queue management with real-time status updates - Startup script for launching all processes
- Admin panel for user and system management
- Email notification system for confirmations and receipts
- Support for both CUDA and ROCm GPU backends
- Comprehensive configuration system
- Professional documentation and licensing
### Changed ### Changed
- Refactored from monolithic Flask app to multi-process architecture - Refactored monolithic Flask app into distributed processes
- Improved security with proper authentication and session management - Updated communication protocol to use sockets instead of multiprocessing queues
- Enhanced user experience with modern web interface
- Better error handling and logging throughout the application ### Technical Details
- Backend process handles request routing based on configuration
### Technical Improvements - Worker processes register with backend and handle specific tasks
- Socket-based inter-process communication - Web interface communicates with backend via TCP sockets
- SQLite database for persistent configuration and user data - Results are passed via temporary JSON files
- Modular code structure with clear separation of concerns - Configuration stored in user config directory
- Type hints and comprehensive documentation \ No newline at end of file
- Automated build scripts for different GPU backends
## [0.1.0] - 2024-01-01
### Added
- Initial release with basic video/image analysis functionality
- Flask web interface for uploading and analyzing media
- Qwen2.5-VL model integration for AI-powered analysis
- Frame extraction from videos with configurable intervals
- Basic configuration system
- Simple build and deployment scripts
### Known Issues
- Monolithic architecture limiting scalability
- No user authentication or management
- Limited payment integration
- Basic documentation and testing
---
## Types of Changes
- `Added` for new features
- `Changed` for changes in existing functionality
- `Deprecated` for soon-to-be removed features
- `Removed` for now removed features
- `Fixed` for any bug fixes
- `Security` in case of vulnerabilities
## Version Numbering
This project uses [Semantic Versioning](https://semver.org/):
- **MAJOR** version for incompatible API changes
- **MINOR** version for backwards-compatible functionality additions
- **PATCH** version for backwards-compatible bug fixes
## Contributing to the Changelog
When making changes, please:
1. Update the "Unreleased" section above with your changes
2. Move items from "Unreleased" to a new version section when releasing
3. Follow the existing format and categorization
## Release Process
1. Update version numbers in relevant files
2. Move changes from "Unreleased" to the new version section
3. Create a git tag for the release
4. Update documentation if needed
5. Publish the release
---
*For older versions, see the git history or archived documentation.*
\ No newline at end of file
...@@ -2,204 +2,82 @@ ...@@ -2,204 +2,82 @@
## Overview ## Overview
Video AI Analysis Tool uses a multi-process architecture designed for scalability, reliability, and optimal GPU utilization. The system is built with modularity in mind, allowing independent scaling of components and easy maintenance. Video AI is a multi-process application designed for analyzing images and videos using AI models. The system supports both CUDA and ROCm backends for GPU acceleration.
## System Components ## Components
### 1. Web Interface Process (`vidai/web.py`) ### 1. Web Interface Process (`vidai/web.py`)
- Flask-based web server
**Purpose**: Handles user interactions and serves the web application. - Serves the user interface
- Sends requests to backend via TCP socket (port 5001)
**Responsibilities**: - Polls for results via temporary JSON files
- User authentication and session management
- File upload handling and validation
- Job submission to backend
- Result display and user interface
- API endpoint serving
**Technology**: Flask web framework with custom authentication decorators.
**Communication**: Connects to backend via TCP sockets on port 5001.
### 2. Backend Process (`vidai/backend.py`) ### 2. Backend Process (`vidai/backend.py`)
- Central message router
**Purpose**: Central routing and coordination hub. - Listens for web requests on port 5001
- Listens for worker connections on port 5002
**Responsibilities**: - Routes requests to appropriate workers based on configuration
- Request validation and preprocessing - Handles configuration updates
- Load balancing across worker processes
- Configuration management and updates
- Result aggregation and caching
- Health monitoring of worker processes
**Technology**: Custom socket server with message routing logic.
**Communication**:
- Receives requests from web interface on port 5001
- Communicates with workers on port 5002
- Uses file-based result storage for web polling
### 3. Worker Processes ### 3. Worker Processes
- **Analysis Workers** (`vidai/worker_analysis.py`): Handle image/video analysis
- **Training Workers** (`vidai/worker_training.py`): Handle model training
- Each worker type has CUDA and ROCm variants
- Workers register with backend and process assigned tasks
## Communication Protocol
### Message Format
```json
{
"msg_type": "message_type",
"msg_id": "unique_id",
"data": {
// message-specific data
}
}
```
### Message Types
- `analyze_request`: Web to backend for analysis jobs
- `train_request`: Web to backend for training jobs
- `config_update`: Web to backend for configuration changes
- `get_config`: Web to backend to retrieve current config
- `register`: Worker to backend registration
- `analyze_response`: Worker to backend with results
- `train_response`: Worker to backend with results
- `config_response`: Backend to web with config data
## Configuration
Configuration is stored in `~/.config/vidai/config.json`:
```json
{
"analysis_backend": "cuda",
"training_backend": "cuda"
}
```
## Process Management
Use the provided scripts:
- `start.sh [cuda|rocm]`: Launch all processes
- `build.sh [cuda|rocm]`: Build standalone executables
- `setup.sh [cuda|rocm]`: Set up virtual environment
#### Analysis Worker (`vidai/worker_analysis.py`) ## Data Flow
**Purpose**: Handles AI-powered media analysis jobs.
**Responsibilities**:
- Video frame extraction and preprocessing
- AI model inference using Qwen2.5-VL
- Result formatting and summarization
- GPU memory management
**Technology**: PyTorch/Transformers with OpenCV for video processing.
#### Training Worker (`vidai/worker_training.py`)
**Purpose**: Handles model training and fine-tuning jobs.
**Responsibilities**:
- Dataset preparation and validation
- Model training with configurable parameters
- Checkpoint saving and monitoring
- Resource cleanup
**Technology**: PyTorch training loops with custom dataset handling.
### 4. Shared Components
#### Communication Module (`vidai/comm.py`)
**Purpose**: Standardized inter-process communication.
**Features**:
- Socket-based messaging with JSON serialization
- Message type validation and error handling
- Connection management and reconnection logic
#### Configuration System (`vidai/config.py`)
**Purpose**: Centralized configuration management.
**Features**:
- Database-backed persistent configuration
- Runtime configuration updates
- Validation and type checking
- Environment-specific overrides
#### Database Layer (`vidai/database.py`)
**Purpose**: Data persistence and user management.
**Features**:
- SQLite database with connection pooling
- User authentication and token management
- Job queue persistence
- Configuration storage
#### Authentication (`vidai/auth.py`)
**Purpose**: User authentication and authorization.
**Features**: 1. User submits request via web interface
- Session-based authentication 2. Web sends message to backend
- API token generation and validation 3. Backend routes to appropriate worker based on config
- Role-based access control 4. Worker processes request and sends result back
- Password hashing and security 5. Backend forwards result to web via file
6. Web displays result to user
## Data Flow ## Security Considerations
### Analysis Job Flow - All processes run locally
- No external network exposure by default
1. **User Upload**: User uploads file via web interface - File-based result passing for simplicity
2. **Request Submission**: Web process sends analysis request to backend - Consider implementing authentication for production use
3. **Worker Assignment**: Backend routes request to appropriate analysis worker \ No newline at end of file
4. **Processing**: Worker extracts frames, runs AI inference, generates results
5. **Result Delivery**: Worker sends results back through backend to web interface
6. **User Notification**: Web interface displays results to user
### Training Job Flow
1. **Dataset Upload**: User provides training data and parameters
2. **Job Queuing**: Training request added to persistent queue
3. **Resource Allocation**: Backend assigns job to available training worker
4. **Model Training**: Worker executes training loop with monitoring
5. **Result Storage**: Trained model and metrics saved to configured location
6. **Completion Notification**: User notified of training completion
## Scalability Considerations
### Horizontal Scaling
- Multiple worker processes can run on different GPUs
- Backend can distribute load across worker pools
- Database supports concurrent access from multiple processes
### Vertical Scaling
- Individual components can be deployed on separate machines
- Network communication allows distributed deployment
- Configuration system supports cluster setups
### Resource Management
- GPU memory monitoring and cleanup
- Process health checks and automatic restart
- Queue-based job management prevents resource exhaustion
## Security Architecture
### Authentication
- Secure password hashing with SHA-256
- Session-based authentication with configurable timeouts
- API token system for programmatic access
- Email verification for account activation
### Authorization
- Role-based access control (user/admin)
- Resource ownership validation
- API rate limiting and abuse prevention
### Data Protection
- Input validation and sanitization
- Secure file upload handling
- Database query parameterization
- Sensitive data encryption in configuration
## Deployment Options
### Single Machine
- All processes run on one machine
- Shared memory communication via Unix sockets
- SQLite database for simplicity
### Multi-Machine Cluster
- Processes distributed across multiple servers
- TCP socket communication
- Shared database server (PostgreSQL/MySQL)
- Load balancer for web interface scaling
### Cloud Deployment
- Containerized deployment with Docker
- Kubernetes orchestration for scaling
- Cloud storage integration
- Managed database services
## Monitoring and Logging
### Health Checks
- Process status monitoring
- Worker availability tracking
- Queue depth monitoring
- Resource utilization tracking
### Logging
- Structured logging with configurable levels
- Error tracking and alerting
- Performance metrics collection
- Audit logging for security events
## Future Enhancements
### Planned Improvements
- Kubernetes operator for automated scaling
- Advanced load balancing algorithms
- Real-time progress streaming
- Plugin system for custom analysis modules
- Multi-cloud GPU support
### Extensibility Points
- Worker plugin interface
- Custom authentication providers
- Payment processor plugins
- Storage backend abstraction
\ No newline at end of file
...@@ -43,9 +43,19 @@ def set_training_backend(backend: str) -> None: ...@@ -43,9 +43,19 @@ def set_training_backend(backend: str) -> None:
set_config('training_backend', backend) set_config('training_backend', backend)
def get_runpod_enabled() -> bool:
"""Check if RunPod integration is enabled."""
return get_config('runpod_enabled', 'false').lower() == 'true'
def set_runpod_enabled(enabled: bool) -> None: def set_runpod_enabled(enabled: bool) -> None:
"""Enable or disable RunPod integration.""" """Enable or disable RunPod integration."""
set_config('runpod_enabled', enabled) set_config('runpod_enabled', 'true' if enabled else 'false')
def get_runpod_api_key() -> str:
"""Get RunPod API key."""
return get_config('runpod_api_key', '')
def set_runpod_api_key(api_key: str) -> None: def set_runpod_api_key(api_key: str) -> None:
...@@ -53,11 +63,21 @@ def set_runpod_api_key(api_key: str) -> None: ...@@ -53,11 +63,21 @@ def set_runpod_api_key(api_key: str) -> None:
set_config('runpod_api_key', api_key) set_config('runpod_api_key', api_key)
def get_runpod_template_id() -> str:
"""Get RunPod template ID."""
return get_config('runpod_template_id', 'vidai-analysis-latest')
def set_runpod_template_id(template_id: str) -> None: def set_runpod_template_id(template_id: str) -> None:
"""Set RunPod template ID.""" """Set RunPod template ID."""
set_config('runpod_template_id', template_id) set_config('runpod_template_id', template_id)
def get_runpod_gpu_type() -> str:
"""Get preferred RunPod GPU type."""
return get_config('runpod_gpu_type', 'NVIDIA RTX A4000')
def set_runpod_gpu_type(gpu_type: str) -> None: def set_runpod_gpu_type(gpu_type: str) -> None:
"""Set preferred RunPod GPU type.""" """Set preferred RunPod GPU type."""
set_config('runpod_gpu_type', gpu_type) set_config('runpod_gpu_type', gpu_type)
......
...@@ -16,360 +16,106 @@ ...@@ -16,360 +16,106 @@
""" """
Web interface process for Video AI. Web interface process for Video AI.
Serves the web UI with authentication, REST API, and queue management. Serves the web UI for analysis and training.
""" """
from flask import Flask, request, render_template_string, send_from_directory, make_response, jsonify, redirect, url_for from flask import Flask, request, render_template_string, send_from_directory
import os import os
import sys
import json import json
import uuid import uuid
import time import time
from typing import Optional
from .comm import SocketCommunicator, Message from .comm import SocketCommunicator, Message
from .config import ( from .config import get_all_settings
get_system_prompt_content, set_system_prompt_content, get_all_settings,
set_analysis_backend, set_training_backend, set_default_model, set_frame_interval,
get_comm_type, set_comm_type, set_max_concurrent_jobs
)
from .auth import login_user, logout_user, get_current_user, require_auth, require_admin, api_authenticate, register_user, confirm_email
from .email import send_registration_confirmation
from .database import get_default_user_tokens, update_user_tokens, get_user_tokens, get_token_price
from .payments import get_token_packages, calculate_price, process_payment, create_payment_intent, get_available_processors
from .database import (
create_user, get_all_users, update_user_role, delete_user,
create_api_token, get_user_api_tokens, revoke_api_token
)
from .queue import queue_manager
app = Flask(__name__) app = Flask(__name__)
app.secret_key = os.environ.get('VIDAI_SECRET_KEY', 'dev-secret-key-change-in-production')
os.makedirs('static', exist_ok=True) os.makedirs('static', exist_ok=True)
# Session cookie name
SESSION_COOKIE = 'vidai_session'
# Communicator to backend # Communicator to backend
from .config import get_backend_host, get_backend_web_port comm = SocketCommunicator(host='localhost', port=5001)
comm_type = get_comm_type()
if comm_type == 'unix': def send_to_backend(msg_type: str, data: dict) -> str:
comm = SocketCommunicator(socket_path='/tmp/vidai_web.sock', comm_type='unix') """Send message to backend and return message id."""
else: msg_id = str(uuid.uuid4())
comm = SocketCommunicator(host=get_backend_host(), port=get_backend_web_port(), comm_type='tcp') message = Message(msg_type, msg_id, data)
comm.connect() try:
comm.connect()
def get_session_id() -> Optional[str]: comm.send_message(message)
"""Get session ID from cookie.""" return msg_id
return request.cookies.get(SESSION_COOKIE) except Exception as e:
print(f"Failed to send message to backend: {e}")
def require_login(): return msg_id
"""Decorator to require login."""
def decorator(f): def get_result(msg_id: str) -> dict:
def wrapper(*args, **kwargs): """Poll for result from backend."""
session_id = get_session_id() result_file = f"/tmp/vidai_results/{msg_id}.json"
if not session_id or not get_current_user(session_id): for _ in range(100): # Poll for 10 seconds
return redirect(url_for('login')) if os.path.exists(result_file):
return f(*args, **kwargs) with open(result_file, 'r') as f:
wrapper.__name__ = f.__name__ data = json.load(f)
return wrapper os.unlink(result_file)
return decorator return data
time.sleep(0.1)
def require_admin_route(): return {'error': 'Timeout waiting for result'}
"""Decorator to require admin role."""
def decorator(f):
def wrapper(*args, **kwargs):
session_id = get_session_id()
if not require_admin(session_id):
return "Access denied", 403
return f(*args, **kwargs)
wrapper.__name__ = f.__name__
return wrapper
return decorator
def api_auth():
"""Decorator for API authentication."""
def decorator(f):
def wrapper(*args, **kwargs):
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if not token:
return jsonify({'error': 'No token provided'}), 401
user = api_authenticate(token)
if not user:
return jsonify({'error': 'Invalid token'}), 401
request.user = user
return f(*args, **kwargs)
wrapper.__name__ = f.__name__
return wrapper
return decorator
# Authentication Routes
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username and password:
session_id = login_user(username, password)
if session_id:
resp = make_response(redirect(url_for('dashboard')))
resp.set_cookie(SESSION_COOKIE, session_id, httponly=True, max_age=3600)
return resp
else:
error = "Invalid credentials"
else:
error = "Please provide username and password"
html = '''
<!DOCTYPE html>
<html>
<head>
<title>Login - Video AI</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
.login-form { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); width: 300px; }
h1 { text-align: center; color: #333; margin-bottom: 30px; }
form { display: flex; flex-direction: column; }
label { margin-bottom: 5px; font-weight: bold; }
input { padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; }
button { background: #007bff; color: white; padding: 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
button:hover { background: #0056b3; }
.error { color: red; margin-bottom: 15px; text-align: center; }
</style>
</head>
<body>
<div class="login-form">
<h1>Video AI Login</h1>
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
<form method="post">
<label>Username:</label>
<input type="text" name="username" required>
<label>Password:</label>
<input type="password" name="password" required>
<button type="submit">Login</button>
</form>
</div>
</body>
</html>
'''
return render_template_string(html, error=error)
@app.route('/register', methods=['GET', 'POST'])
def register():
error = None
success = None
@app.route('/', methods=['GET', 'POST'])
def index():
result = None
if request.method == 'POST': if request.method == 'POST':
username = request.form.get('username') model_path = request.form.get('model_path', 'Qwen/Qwen2.5-VL-7B-Instruct')
password = request.form.get('password') prompt = request.form.get('prompt', 'Describe this image.')
confirm_password = request.form.get('confirm_password') local_path = request.form.get('local_path')
email = request.form.get('email')
if local_path:
if not all([username, password, confirm_password, email]): data = {
error = "All fields are required" 'model_path': model_path,
elif password != confirm_password: 'prompt': prompt,
error = "Passwords do not match" 'local_path': local_path
elif len(password) < 8: }
error = "Password must be at least 8 characters long" msg_id = send_to_backend('analyze_request', data)
else: result_data = get_result(msg_id)
# Register user if 'data' in result_data:
success_reg, message = register_user(username, password, email) result = result_data['data'].get('result', 'Analysis completed')
if success_reg:
# Send confirmation email
if send_registration_confirmation(email, username, message):
success = "Registration successful! Please check your email to confirm your account."
else:
success = "Registration successful, but email confirmation could not be sent. Please contact support."
else: else:
error = message result = result_data.get('error', 'Error')
html = ''' html = '''
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Register - Video AI</title> <title>VideoModel AI</title>
<style> <style>
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; display: flex; justify-content: center; align-items: center; min-height: 100vh; } body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px; }
.register-form { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); width: 400px; } .container { max-width: 800px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { text-align: center; color: #333; margin-bottom: 30px; } h1 { color: #333; text-align: center; }
form { display: flex; flex-direction: column; } form { margin-bottom: 20px; }
label { margin-bottom: 5px; font-weight: bold; } label { display: block; margin-bottom: 5px; }
input { padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; } input[type="text"] { width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; }
button { background: #28a745; color: white; padding: 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } input[type="submit"] { background: #007bff; color: white; padding: 10px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #218838; } .result { background: #e9ecef; padding: 10px; border-radius: 4px; }
.error { color: red; margin-bottom: 15px; text-align: center; }
.success { color: green; margin-bottom: 15px; text-align: center; }
.login-link { text-align: center; margin-top: 20px; }
.login-link a { color: #007bff; text-decoration: none; }
</style> </style>
</head> </head>
<body> <body>
<div class="register-form"> <div class="container">
<h1>Create Account</h1> <h1>VideoModel AI Web Interface</h1>
{% if error %} <nav><a href="/">Analysis</a> | <a href="/train">Training</a> | <a href="/config">Configuration</a></nav>
<div class="error">{{ error }}</div> <h2>Analyze Image/Video</h2>
{% endif %}
{% if success %}
<div class="success">{{ success }}</div>
{% endif %}
<form method="post"> <form method="post">
<label>Username:</label> <label>Model Path: <input type="text" name="model_path" value="Qwen/Qwen2.5-VL-7B-Instruct"></label>
<input type="text" name="username" required> <label>Local Path: <input type="text" name="local_path" placeholder="Path to local file"></label>
<label>Prompt: <textarea name="prompt" rows="3">Describe this image.</textarea></label>
<label>Email:</label> <input type="submit" value="Analyze">
<input type="email" name="email" required>
<label>Password:</label>
<input type="password" name="password" required>
<label>Confirm Password:</label>
<input type="password" name="confirm_password" required>
<button type="submit">Register</button>
</form> </form>
<div class="login-link"> {% if result %}
<a href="/login">Already have an account? Login</a> <div class="result">
</div> <h3>Result:</h3>
</div> <p>{{ result }}</p>
</body>
</html>
'''
return render_template_string(html, error=error, success=success)
@app.route('/confirm/<token>')
def confirm_email_route(token):
if confirm_email(token):
message = "Email confirmed successfully! You can now login."
success = True
else:
message = "Invalid or expired confirmation link."
success = False
html = f'''
<!DOCTYPE html>
<html>
<head>
<title>Email Confirmation - Video AI</title>
<style>
body {{ font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; display: flex; justify-content: center; align-items: center; min-height: 100vh; }}
.confirm-box {{ background: white; padding: 40px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); width: 400px; text-align: center; }}
h1 {{ color: #333; margin-bottom: 20px; }}
.message {{ margin: 20px 0; padding: 15px; border-radius: 4px; }}
.success {{ background: #d4edda; color: #155724; }}
.error {{ background: #f8d7da; color: #721c24; }}
a {{ display: inline-block; margin-top: 20px; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }}
</style>
</head>
<body>
<div class="confirm-box">
<h1>Email Confirmation</h1>
<div class="message {'success' if success else 'error'}">
{message}
</div>
<a href="/login">Continue to Login</a>
</div>
</body>
</html>
'''
return html
@app.route('/logout')
def logout():
session_id = get_session_id()
if session_id:
logout_user(session_id)
resp = make_response(redirect(url_for('login')))
resp.delete_cookie(SESSION_COOKIE)
return resp
@app.route('/')
def index():
session_id = get_session_id()
user = get_current_user(session_id) if session_id else None
if user:
# Show dashboard for logged-in users
return dashboard()
else:
# Show landing page for non-logged-in users
return render_template_string(open('templates/landing.html').read())
@require_login()
def dashboard():
session_id = get_session_id()
user = get_current_user(session_id)
user_jobs = queue_manager.get_user_jobs(user['id'])
html = '''
<!DOCTYPE html>
<html>
<head>
<title>Dashboard - Video AI</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 1200px; margin: auto; }
.header { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; }
.nav { display: flex; gap: 20px; }
.nav a { text-decoration: none; color: #007bff; padding: 10px; border-radius: 4px; }
.nav a:hover { background: #f0f0f0; }
.content { display: grid; grid-template-columns: 1fr 300px; gap: 20px; }
.main { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
.sidebar { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1, h2 { color: #333; }
.job-item { border: 1px solid #ddd; padding: 10px; margin: 10px 0; border-radius: 4px; }
.status-queued { color: orange; }
.status-processing { color: blue; }
.status-completed { color: green; }
.status-failed { color: red; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Video AI Dashboard</h1>
<div class="nav">
<a href="/analyze">Analyze</a>
<a href="/train">Train</a>
<a href="/queue">Queue</a>
{% if user.role == 'admin' %}
<a href="/admin/users">Users</a>
<a href="/admin/config">Config</a>
{% endif %}
<a href="/api">API</a>
<a href="/logout">Logout</a>
</div>
</div>
<div class="content">
<div class="main">
<h2>Welcome, {{ user.username }}!</h2>
<p>Role: {{ user.role }}</p>
<h3>Recent Jobs</h3>
{% for job in user_jobs[:5] %}
<div class="job-item">
<strong>{{ job.request_type }}</strong> - <span class="status-{{ job.status }}">{{ job.status }}</span>
{% if job.status == 'queued' %}
<br>Position: {{ job.position }}, Est. time: {{ job.estimated_time }}s, Est. tokens: {{ job.estimated_tokens }}
{% endif %}
{% if job.used_tokens %}
<br>Used tokens: {{ job.used_tokens }}
{% endif %}
</div>
{% endfor %}
</div>
<div class="sidebar">
<h3>Quick Actions</h3>
<a href="/analyze" style="display: block; margin: 10px 0; padding: 10px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; text-align: center;">New Analysis</a>
<a href="/train" style="display: block; margin: 10px 0; padding: 10px; background: #28a745; color: white; text-decoration: none; border-radius: 4px; text-align: center;">New Training</a>
</div>
</div> </div>
{% endif %}
</div> </div>
</body> </body>
</html> </html>
''' '''
return render_template_string(html, user=user, user_jobs=user_jobs) return render_template_string(html, result=result)
# Application Routes # Application Routes
@app.route('/analyze', methods=['GET', 'POST']) @app.route('/analyze', methods=['GET', 'POST'])
...@@ -1618,6 +1364,280 @@ def train(): ...@@ -1618,6 +1364,280 @@ def train():
''' '''
return render_template_string(html, message=message) return render_template_string(html, message=message)
@app.route('/config', methods=['GET', 'POST'])
def config():
if request.method == 'POST':
data = {
'analysis_backend': request.form.get('analysis_backend', 'cuda'),
'training_backend': request.form.get('training_backend', 'cuda')
}
msg_id = send_to_backend('config_update', data)
result_data = get_result(msg_id)
status = result_data.get('data', {}).get('status', 'Error')
# Get current config
msg_id = send_to_backend('get_config', {})
config_data = get_result(msg_id)
current_config = config_data.get('data', {})
html = '''
<!DOCTYPE html>
<html>
<head>
<title>Configuration</title>
<style>
body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 800px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { color: #333; text-align: center; }
form { margin-bottom: 20px; }
label { display: block; margin-bottom: 5px; }
select { width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; }
input[type="submit"] { background: #007bff; color: white; padding: 10px; border: none; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<div class="container">
<h1>Configuration</h1>
<nav><a href="/">Analysis</a> | <a href="/train">Training</a> | <a href="/config">Configuration</a></nav>
<form method="post">
<label>Analysis Backend:
<select name="analysis_backend">
<option value="cuda" {% if current_config.analysis_backend == 'cuda' %}selected{% endif %}>CUDA</option>
<option value="rocm" {% if current_config.analysis_backend == 'rocm' %}selected{% endif %}>ROCm</option>
</select>
</label>
<label>Training Backend:
<select name="training_backend">
<option value="cuda" {% if current_config.training_backend == 'cuda' %}selected{% endif %}>CUDA</option>
<option value="rocm" {% if current_config.training_backend == 'rocm' %}selected{% endif %}>ROCm</option>
</select>
</label>
<input type="submit" value="Save Configuration">
</form>
</div>
</body>
</html>
'''
return render_template_string(html, current_config=current_config)
@app.route('/static/<path:filename>')
def serve_static(filename):
return send_from_directory('static', filename)
if __name__ == "__main__":
app.run(host='0.0.0.0', debug=True)
@app.route('/train', methods=['GET', 'POST'])
def train():
message = None
if request.method == 'POST':
data = {
'output_model': request.form.get('output_model', './VideoModel'),
'description': request.form.get('description', ''),
'train_dir': request.form.get('train_dir', '')
}
msg_id = send_to_backend('train_request', data)
result_data = get_result(msg_id)
if 'data' in result_data:
message = result_data['data'].get('message', 'Training completed')
else:
message = result_data.get('error', 'Error')
html = '''
<!DOCTYPE html>
<html>
<head>
<title>VideoModel AI - Training</title>
<style>
body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 800px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { color: #333; text-align: center; }
form { margin-bottom: 20px; }
label { display: block; margin-bottom: 5px; }
input[type="text"] { width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; }
input[type="submit"] { background: #007bff; color: white; padding: 10px; border: none; border-radius: 4px; cursor: pointer; }
.message { background: #e9ecef; padding: 10px; border-radius: 4px; }
</style>
</head>
<body>
<div class="container">
<h1>VideoModel AI Web Interface</h1>
<nav><a href="/">Analysis</a> | <a href="/train">Training</a> | <a href="/config">Configuration</a></nav>
<h2>Train Model</h2>
<form method="post">
<label>Output Model Path: <input type="text" name="output_model" value="./VideoModel"></label>
<label>Description: <textarea name="description"></textarea></label>
<label>Training Directory: <input type="text" name="train_dir"></label>
<input type="submit" value="Start Training">
</form>
{% if message %}
<div class="message">
<p>{{ message }}</p>
</div>
{% endif %}
</div>
</body>
</html>
'''
return render_template_string(html, message=message)
@app.route('/config', methods=['GET', 'POST'])
def config():
if request.method == 'POST':
# Update local config
from .config import set_analysis_backend, set_training_backend
set_analysis_backend(request.form.get('analysis_backend', 'cuda'))
set_training_backend(request.form.get('training_backend', 'cuda'))
# Send to backend for routing updates
data = {
'analysis_backend': request.form.get('analysis_backend', 'cuda'),
'training_backend': request.form.get('training_backend', 'cuda')
}
msg_id = send_to_backend('config_update', data)
result_data = get_result(msg_id)
status = result_data.get('data', {}).get('status', 'Error')
# Get current config
current_config = get_all_settings()
html = '''
<!DOCTYPE html>
<html>
<head>
<title>Configuration</title>
<style>
body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 800px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { color: #333; text-align: center; }
form { margin-bottom: 20px; }
label { display: block; margin-bottom: 5px; }
select, input[type="text"], input[type="number"] { width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; }
input[type="submit"] { background: #007bff; color: white; padding: 10px; border: none; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<div class="container">
<h1>Configuration</h1>
<nav><a href="/">Analysis</a> | <a href="/train">Training</a> | <a href="/config">Configuration</a></nav>
<form method="post">
<label>Analysis Backend:
<select name="analysis_backend">
<option value="cuda" ''' + ('selected' if current_config['analysis_backend'] == 'cuda' else '') + '''>CUDA</option>
<option value="rocm" ''' + ('selected' if current_config['analysis_backend'] == 'rocm' else '') + '''>ROCm</option>
</select>
</label>
<label>Training Backend:
<select name="training_backend">
<option value="cuda" ''' + ('selected' if current_config['training_backend'] == 'cuda' else '') + '''>CUDA</option>
<option value="rocm" ''' + ('selected' if current_config['training_backend'] == 'rocm' else '') + '''>ROCm</option>
</select>
</label>
<input type="submit" value="Save Configuration">
</form>
</div>
</body>
</html>
'''
return html
@app.route('/system', methods=['GET', 'POST'])
def system_page():
from .config import get_system_prompt_content, set_system_prompt_content
if request.method == 'POST':
system_prompt = request.form.get('system_prompt', '')
set_system_prompt_content('default', system_prompt)
current_prompt = get_system_prompt_content()
html = f'''
<!DOCTYPE html>
<html>
<head>
<title>System Prompt</title>
<style>
body {{ font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px; }}
.container {{ max-width: 800px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }}
h1 {{ color: #333; text-align: center; }}
textarea {{ width: 100%; height: 200px; }}
input[type="submit"] {{ background: #007bff; color: white; padding: 10px; border: none; border-radius: 4px; cursor: pointer; }}
input[type="submit"]:hover {{ background: #0056b3; }}
</style>
</head>
<body>
<div class="container">
<h1>Edit System Prompt</h1>
<form method="post">
<textarea name="system_prompt">{current_prompt}</textarea><br>
<input type="submit" value="Save">
</form>
<a href="/">Back to Analysis</a>
</div>
</body>
</html>
'''
return html
@app.route('/static/<path:filename>')
def serve_static(filename):
return send_from_directory('static', filename)
if __name__ == "__main__":
from .config import get_web_host, get_web_port
app.run(host=get_web_host(), port=get_web_port(), debug=True)
@app.route('/train', methods=['GET', 'POST'])
def train():
message = None
if request.method == 'POST':
data = {
'output_model': request.form.get('output_model', './VideoModel'),
'description': request.form.get('description', ''),
'train_dir': request.form.get('train_dir', '')
}
msg_id = send_to_backend('train_request', data)
result_data = get_result(msg_id)
if 'data' in result_data:
message = result_data['data'].get('message', 'Training completed')
else:
message = result_data.get('error', 'Error')
html = '''
<!DOCTYPE html>
<html>
<head>
<title>VideoModel AI - Training</title>
<style>
body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 800px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { color: #333; text-align: center; }
form { margin-bottom: 20px; }
label { display: block; margin-bottom: 5px; }
input[type="text"] { width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; }
input[type="submit"] { background: #007bff; color: white; padding: 10px; border: none; border-radius: 4px; cursor: pointer; }
.message { background: #e9ecef; padding: 10px; border-radius: 4px; }
</style>
</head>
<body>
<div class="container">
<h1>VideoModel AI Web Interface</h1>
<nav><a href="/">Analysis</a> | <a href="/train">Training</a> | <a href="/config">Configuration</a></nav>
<h2>Train Model</h2>
<form method="post">
<label>Output Model Path: <input type="text" name="output_model" value="./VideoModel"></label>
<label>Description: <textarea name="description"></textarea></label>
<label>Training Directory: <input type="text" name="train_dir"></label>
<input type="submit" value="Start Training">
</form>
{% if message %}
<div class="message">
<p>{{ message }}</p>
</div>
{% endif %}
</div>
</body>
</html>
'''
return render_template_string(html, message=message)
@app.route('/config', methods=['GET', 'POST']) @app.route('/config', methods=['GET', 'POST'])
def config(): def config():
if request.method == 'POST': if request.method == 'POST':
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment