Implement multi-process architecture with CUDA/ROCm backend selection

- Refactor monolithic Flask app into separate processes:
  * Web interface process (vidai/web.py)
  * Backend routing process (vidai/backend.py)
  * Analysis worker processes (vidai/worker_analysis.py)
  * Training worker processes (vidai/worker_training.py)

- Add self-contained inter-process communication using TCP sockets
- Implement configuration system for backend selection (CUDA/ROCm)
- Add GPLv3 licensing and copyright notices
- Create comprehensive documentation and build scripts
- Update main launcher to manage all processes

This architecture provides better scalability, allows independent GPU backend selection, and maintains clean separation of concerns.
parent 56e233bb
......@@ -249,23 +249,37 @@ Examples:
print("Press Ctrl+C to stop")
# Start backend process
backend_cmd = get_python_command(module='vidai.backend')
backend_cmd = [sys.executable, '-m', 'vidai.backend']
backend_proc = subprocess.Popen(backend_cmd)
# Start analysis worker
analysis_cmd = [sys.executable, '-m', 'vidai.worker_analysis', args.analysis_backend]
analysis_proc = subprocess.Popen(analysis_cmd)
# Start training worker
training_cmd = [sys.executable, '-m', 'vidai.worker_training', args.training_backend]
training_proc = subprocess.Popen(training_cmd)
# Start web process
web_cmd = get_python_command(module='vidai.web')
web_cmd = [sys.executable, '-m', 'vidai.web']
web_proc = subprocess.Popen(web_cmd)
try:
# Wait for processes
backend_proc.wait()
analysis_proc.wait()
training_proc.wait()
web_proc.wait()
except KeyboardInterrupt:
print("Shutting down...")
backend_proc.terminate()
web_proc.terminate()
backend_proc.wait()
training_proc.terminate()
analysis_proc.terminate()
backend_proc.terminate()
web_proc.wait()
training_proc.wait()
analysis_proc.wait()
backend_proc.wait()
if __name__ == "__main__":
main()
\ No newline at end of file
......@@ -22,164 +22,103 @@ Manages request routing between web interface and worker processes.
import time
import threading
from .comm import SocketServer, Message
from .config import get_analysis_backend, get_training_backend, set_analysis_backend, set_training_backend, get_comm_type, get_use_runpod_pods, set_use_runpod_pods
from .compat import get_socket_path, get_default_comm_type
from .queue import queue_manager
from .runpod import runpod_manager, is_runpod_enabled, create_analysis_pod, create_training_pod, RunPodPod
from .config import get_analysis_backend, get_training_backend, set_analysis_backend, set_training_backend
worker_sockets = {} # type: dict
active_pods = {} # type: dict[str, RunPodPod]
pod_workers = {} # type: dict[str, str] # pod_id -> worker_type
def create_worker_pod(worker_type: str) -> Optional[RunPodPod]:
"""Create a new worker pod on RunPod."""
if not is_runpod_enabled():
return None
try:
if worker_type == 'analysis':
pod = create_analysis_pod()
elif worker_type == 'training':
pod = create_training_pod()
else:
return None
if pod and runpod_manager.wait_for_pod_ready(pod):
active_pods[pod.pod_id] = pod
pod_workers[pod.pod_id] = worker_type
print(f"Created and ready pod {pod.pod_id} for {worker_type}")
return pod
else:
print(f"Failed to create or start pod for {worker_type}")
except Exception as e:
print(f"Error creating pod: {e}")
return None
def get_available_pod(worker_type: str) -> Optional[RunPodPod]:
"""Get an available pod for the worker type, creating one if needed."""
# First, check for existing available pods
for pod_id, pod in active_pods.items():
if pod.worker_type == worker_type and pod.status == 'RUNNING' and pod_id not in worker_sockets:
return pod
# No available pod, create a new one
return create_worker_pod(worker_type)
def cleanup_idle_pods():
"""Clean up idle pods periodically."""
while True:
try:
runpod_manager.cleanup_idle_pods(max_age=1800) # 30 minutes
time.sleep(300) # Check every 5 minutes
except Exception as e:
print(f"Error cleaning up pods: {e}")
time.sleep(60)
def handle_web_message(message: Message) -> Message:
"""Handle messages from web interface."""
if message.msg_type == 'analyze_request':
# Check if we should use RunPod pods
if get_use_runpod_pods() and is_runpod_enabled():
pod = get_available_pod('analysis')
if pod:
# Send job to pod
return Message('ack', message.msg_id, {'status': 'pod_assigned', 'pod_id': pod.pod_id})
else:
return Message('error', message.msg_id, {'error': 'No pods available'})
backend = get_analysis_backend()
worker_key = f'analysis_{backend}'
if worker_key in worker_sockets:
# Forward to worker
worker_sockets[worker_key].sendall(
f'{{"msg_type": "{message.msg_type}", "msg_id": "{message.msg_id}", "data": {message.data}}}\n'.encode('utf-8')
)
return None # No immediate response
else:
# Use local workers or queue
return Message('ack', message.msg_id, {'status': 'queued'})
return Message('error', message.msg_id, {'error': f'Worker {worker_key} not available'})
elif message.msg_type == 'train_request':
if get_use_runpod_pods() and is_runpod_enabled():
pod = get_available_pod('training')
if pod:
return Message('ack', message.msg_id, {'status': 'pod_assigned', 'pod_id': pod.pod_id})
else:
return Message('error', message.msg_id, {'error': 'No pods available'})
backend = get_training_backend()
worker_key = f'training_{backend}'
if worker_key in worker_sockets:
worker_sockets[worker_key].sendall(
f'{{"msg_type": "{message.msg_type}", "msg_id": "{message.msg_id}", "data": {message.data}}}\n'.encode('utf-8')
)
return None
else:
return Message('ack', message.msg_id, {'status': 'queued'})
return Message('error', message.msg_id, {'error': f'Worker {worker_key} not available'})
elif message.msg_type == 'config_update':
data = message.data
if 'analysis_backend' in data:
set_analysis_backend(data['analysis_backend'])
if 'training_backend' in data:
set_training_backend(data['training_backend'])
if 'use_runpod_pods' in data:
set_use_runpod_pods(data['use_runpod_pods'])
return Message('config_response', message.msg_id, {'status': 'updated'})
elif message.msg_type == 'get_config':
return Message('config_response', message.msg_id, {
'analysis_backend': get_analysis_backend(),
'training_backend': get_training_backend(),
'use_runpod_pods': get_use_runpod_pods(),
'runpod_enabled': is_runpod_enabled()
'training_backend': get_training_backend()
})
return Message('error', message.msg_id, {'error': 'Unknown message type'})
# Worker handling is now done by the queue manager
def handle_worker_message(message: Message, client_sock) -> None:
"""Handle messages from workers."""
if message.msg_type == 'register':
worker_type = message.data.get('type')
if worker_type:
worker_sockets[worker_type] = client_sock
print(f"Worker {worker_type} registered")
elif message.msg_type in ['analyze_response', 'train_response']:
# Forward to web - but since web is connected via different server, need to store or something
# For simplicity, assume web polls for results, but since socket, perhaps have a pending responses dict
# This is getting complex. Perhaps use a shared dict or file for results.
# To keep simple, since web is Flask, it can have a global dict for results, but since separate process, hard.
# Perhaps the backend sends to web via its own connection, but web connects per request.
# For responses, backend can store in a file or database, and web reads from there.
# But to keep self-contained, use a simple JSON file for pending results.
# Web sends request with id, backend processes, stores result in file with id, web polls for result file.
# Yes, that's ad-hoc.
import os
result_dir = '/tmp/vidai_results'
os.makedirs(result_dir, exist_ok=True)
with open(os.path.join(result_dir, f"{message.msg_id}.json"), 'w') as f:
import json
json.dump({
'msg_type': message.msg_type,
'msg_id': message.msg_id,
'data': message.data
}, f)
def worker_message_handler(message: Message, client_sock) -> None:
"""Handler for worker messages."""
handle_worker_message(message, client_sock)
def backend_process() -> None:
"""Main backend process loop."""
print("Starting Video AI Backend...")
from .config import is_cluster_client, get_cluster_port
from .cluster_master import start_cluster_master
if is_cluster_client():
print("Running as cluster client - backend not needed")
return
# Start cluster master
cluster_thread = threading.Thread(target=start_cluster_master, args=(get_cluster_port(),), daemon=True)
cluster_thread.start()
# Start web server on port 5001
web_server = SocketServer(port=5001)
web_server.start(handle_web_message)
# Start pod cleanup thread if RunPod is enabled
if is_runpod_enabled():
cleanup_thread = threading.Thread(target=cleanup_idle_pods, daemon=True)
cleanup_thread.start()
print("RunPod pod cleanup thread started")
comm_type = get_comm_type()
print(f"Using {comm_type} sockets for communication")
if comm_type == 'unix':
# Start web server on Unix socket
socket_path = get_socket_path('web')
if socket_path:
web_server = SocketServer(socket_path=socket_path, comm_type='unix')
web_server.start(handle_web_message)
else:
# Fall back to TCP on Windows
web_server = SocketServer(host='localhost', port=5001, comm_type='tcp')
web_server.start(handle_web_message)
else:
# Start web server on TCP
from .config import get_backend_web_port
web_server = SocketServer(host='localhost', port=get_backend_web_port(), comm_type='tcp')
web_server.start(handle_web_message)
# Start worker server on port 5002
worker_server = SocketServer(port=5002)
worker_server.start(worker_message_handler)
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Backend shutting down...")
# Clean up all active pods
for pod_id in list(active_pods.keys()):
print(f"Terminating pod {pod_id}...")
runpod_manager.terminate_pod(pod_id)
web_server.stop()
worker_server.stop()
if __name__ == "__main__":
......
......@@ -117,1199 +117,6 @@ def index():
'''
return render_template_string(html, result=result)
# Application Routes
@app.route('/analyze', methods=['GET', 'POST'])
@require_login()
def analyze():
session_id = get_session_id()
user = get_current_user(session_id)
result = None
queue_id = None
if request.method == 'POST':
model_path = request.form.get('model_path', 'Qwen/Qwen2.5-VL-7B-Instruct')
prompt = request.form.get('prompt', 'Describe this image.')
uploaded_file = request.files.get('file')
local_path = request.form.get('local_path')
data = {
'model_path': model_path,
'prompt': prompt,
'user_id': user['id']
}
if uploaded_file and uploaded_file.filename:
# Save uploaded file temporarily
temp_path = f"/tmp/{uuid.uuid4()}_{uploaded_file.filename}"
uploaded_file.save(temp_path)
data['local_path'] = temp_path
elif local_path:
data['local_path'] = local_path
# Submit to queue
queue_id = queue_manager.submit_job(user['id'], 'analyze', data)
result = f"Analysis job submitted. Queue ID: {queue_id}"
html = '''
<!DOCTYPE html>
<html>
<head>
<title>Analyze - Video AI</title>
<style>
body { font-family: Arial, sans-serif; background: #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; }
.nav { margin-bottom: 20px; }
.nav a { text-decoration: none; color: #007bff; margin-right: 15px; }
form { margin-bottom: 20px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"], input[type="file"], textarea { width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px; }
button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #0056b3; }
.result { background: #e9ecef; padding: 15px; border-radius: 4px; margin-top: 20px; }
</style>
</head>
<body>
<div class="container">
<div class="nav">
<a href="/">Dashboard</a> |
<a href="/analyze">Analyze</a> |
<a href="/train">Train</a> |
<a href="/queue">Queue</a> |
<a href="/logout">Logout</a>
</div>
<h1>Analyze Image/Video</h1>
<form method="post" enctype="multipart/form-data">
<label>Model Path:</label>
<input type="text" name="model_path" value="Qwen/Qwen2.5-VL-7B-Instruct" required>
<label>Upload File:</label>
<input type="file" name="file" accept="image/*,video/*">
<label>Or Local Path:</label>
<input type="text" name="local_path" placeholder="Path to local file">
<label>Prompt:</label>
<textarea name="prompt" rows="3" required>Describe this image.</textarea>
<button type="submit">Submit Analysis</button>
</form>
{% if result %}
<div class="result">
<h3>Result:</h3>
<p>{{ result }}</p>
{% if queue_id %}
<p><a href="/queue/{{ queue_id }}">Check Status</a></p>
{% endif %}
</div>
{% endif %}
</div>
</body>
</html>
'''
return render_template_string(html, result=result, queue_id=queue_id)
@app.route('/queue')
@require_login()
def queue():
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>Queue - Video AI</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 1000px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { color: #333; }
.nav { margin-bottom: 20px; }
.nav a { text-decoration: none; color: #007bff; margin-right: 15px; }
.job-table { width: 100%; border-collapse: collapse; }
.job-table th, .job-table td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
.job-table th { background: #f8f9fa; font-weight: bold; }
.token-info { font-size: 0.9em; color: #666; }
.status-queued { color: orange; }
.status-processing { color: blue; }
.status-completed { color: green; }
.status-failed { color: red; }
</style>
</head>
<body>
<div class="container">
<div class="nav">
<a href="/">Dashboard</a> |
<a href="/analyze">Analyze</a> |
<a href="/train">Train</a> |
<a href="/queue">Queue</a> |
<a href="/logout">Logout</a>
</div>
<h1>Processing Queue</h1>
<table class="job-table">
<thead>
<tr>
<th>ID</th>
<th>Type</th>
<th>Status</th>
<th>Created</th>
<th>Tokens</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for job in user_jobs %}
<tr>
<td>{{ job.id }}</td>
<td>{{ job.request_type }}</td>
<td class="status-{{ job.status }}">{{ job.status }}</td>
<td>{{ job.created_at }}</td>
<td class="token-info">
Est: {{ job.estimated_tokens }}
{% if job.used_tokens %}
<br>Used: {{ job.used_tokens }}
{% endif %}
</td>
<td><a href="/queue/{{ job.id }}">View Details</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
'''
return render_template_string(html, user_jobs=user_jobs)
@app.route('/queue/<int:queue_id>')
@require_login()
def queue_detail(queue_id):
session_id = get_session_id()
user = get_current_user(session_id)
job = queue_manager.get_job_status(queue_id)
if not job or job['user_id'] != user['id']:
return "Job not found", 404
html = '''
<!DOCTYPE html>
<html>
<head>
<title>Job {{ job.id }} - Video AI</title>
<style>
body { font-family: Arial, sans-serif; background: #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; }
.nav { margin-bottom: 20px; }
.nav a { text-decoration: none; color: #007bff; margin-right: 15px; }
.job-detail { margin: 20px 0; }
.job-detail div { margin: 10px 0; }
.status-queued { color: orange; }
.status-processing { color: blue; }
.status-completed { color: green; }
.status-failed { color: red; }
.result { background: #f8f9fa; padding: 15px; border-radius: 4px; margin-top: 20px; }
</style>
</head>
<body>
<div class="container">
<div class="nav">
<a href="/">Dashboard</a> |
<a href="/queue">Queue</a> |
<a href="/logout">Logout</a>
</div>
<h1>Job Details - {{ job.id }}</h1>
<div class="job-detail">
<div><strong>Type:</strong> {{ job.request_type }}</div>
<div><strong>Status:</strong> <span class="status-{{ job.status }}">{{ job.status }}</span></div>
<div><strong>Created:</strong> {{ job.created_at }}</div>
{% if job.started_at %}
<div><strong>Started:</strong> {{ job.started_at }}</div>
{% endif %}
{% if job.completed_at %}
<div><strong>Completed:</strong> {{ job.completed_at }}</div>
{% endif %}
{% if job.status == 'queued' %}
<div><strong>Queue Position:</strong> {{ job.position }}</div>
<div><strong>Estimated Time:</strong> {{ job.estimated_time }} seconds</div>
<div><strong>Estimated Tokens:</strong> {{ job.estimated_tokens }}</div>
{% if job.used_tokens %}
<div><strong>Used Tokens:</strong> {{ job.used_tokens }}</div>
{% endif %}
{% endif %}
{% if job.error_message %}
<div><strong>Error:</strong> {{ job.error_message }}</div>
{% endif %}
</div>
{% if job.result %}
<div class="result">
<h3>Result:</h3>
<pre>{{ job.result }}</pre>
</div>
{% endif %}
</div>
</body>
</html>
'''
return render_template_string(html, job=job)
# Admin Routes
@app.route('/admin/users', methods=['GET', 'POST'])
@require_admin_route()
def admin_users():
if request.method == 'POST':
action = request.form.get('action')
if action == 'create':
username = request.form.get('username')
password = request.form.get('password')
email = request.form.get('email')
role = request.form.get('role', 'user')
if create_user(username, password, email, role):
return redirect(url_for('admin_users'))
elif action == 'update_role':
user_id = int(request.form.get('user_id'))
role = request.form.get('role')
update_user_role(user_id, role)
return redirect(url_for('admin_users'))
elif action == 'delete':
user_id = int(request.form.get('user_id'))
delete_user(user_id)
return redirect(url_for('admin_users'))
users = get_all_users()
html = '''
<!DOCTYPE html>
<html>
<head>
<title>User Management - Video AI</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 1000px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { color: #333; }
.nav { margin-bottom: 20px; }
.nav a { text-decoration: none; color: #007bff; margin-right: 15px; }
.user-table { width: 100%; border-collapse: collapse; }
.user-table th, .user-table td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
.user-table th { background: #f8f9fa; font-weight: bold; }
form { margin: 20px 0; padding: 20px; background: #f8f9fa; border-radius: 4px; }
input, select { padding: 8px; margin: 5px; border: 1px solid #ddd; border-radius: 4px; }
button { background: #007bff; color: white; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #0056b3; }
</style>
</head>
<body>
<div class="container">
<div class="nav">
<a href="/">Dashboard</a> |
<a href="/admin/users">Users</a> |
<a href="/admin/config">Config</a> |
<a href="/admin/cluster">Cluster</a> |
<a href="/logout">Logout</a>
</div>
<h1>User Management</h1>
<h2>Create User</h2>
<form method="post">
<input type="hidden" name="action" value="create">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<input type="email" name="email" placeholder="Email">
<select name="role">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<button type="submit">Create User</button>
</form>
<h2>Users</h2>
<table class="user-table">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
<th>Role</th>
<th>Active</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email or '-' }}</td>
<td>{{ user.role }}</td>
<td>{{ 'Yes' if user.active else 'No' }}</td>
<td>
<form method="post" style="display: inline;">
<input type="hidden" name="action" value="update_role">
<input type="hidden" name="user_id" value="{{ user.id }}">
<select name="role">
<option value="user" {% if user.role == 'user' %}selected{% endif %}>User</option>
<option value="admin" {% if user.role == 'admin' %}selected{% endif %}>Admin</option>
</select>
<button type="submit">Update</button>
</form>
{% if user.username != 'admin' %}
<form method="post" style="display: inline; margin-left: 10px;">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="user_id" value="{{ user.id }}">
<button type="submit" onclick="return confirm('Delete user?')">Delete</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
'''
return render_template_string(html, users=users)
@app.route('/admin/config', methods=['GET', 'POST'])
@require_admin_route()
def admin_config():
if request.method == 'POST':
# Backend configuration
set_analysis_backend(request.form.get('analysis_backend', 'cuda'))
set_training_backend(request.form.get('training_backend', 'cuda'))
set_comm_type(request.form.get('comm_type', 'unix'))
set_max_concurrent_jobs(int(request.form.get('max_concurrent_jobs', '1')))
set_default_model(request.form.get('default_model', 'Qwen/Qwen2.5-VL-7B-Instruct'))
set_frame_interval(int(request.form.get('frame_interval', '10')))
# RunPod configuration
from .config import set_runpod_enabled, set_runpod_api_key, set_runpod_template_id, set_runpod_gpu_type, set_use_runpod_pods
set_use_runpod_pods(request.form.get('use_runpod_pods') == 'true')
api_key = request.form.get('runpod_api_key')
if api_key:
set_runpod_api_key(api_key)
set_runpod_enabled(True)
template_id = request.form.get('runpod_template_id')
if template_id:
set_runpod_template_id(template_id)
gpu_type = request.form.get('runpod_gpu_type')
if gpu_type:
set_runpod_gpu_type(gpu_type)
# Registration and token configuration
from .config import set_allow_registration, set_default_user_tokens, set_token_price, set_base_url
set_allow_registration(request.form.get('allow_registration') == 'true')
set_default_user_tokens(int(request.form.get('default_user_tokens', '100')))
set_token_price(float(request.form.get('token_price_usd', '0.10')))
set_base_url(request.form.get('base_url', 'http://localhost:5000'))
# Email configuration
from .config import set_smtp_server, set_smtp_port, set_smtp_credentials, set_smtp_security
set_smtp_server(request.form.get('smtp_server', 'smtp.gmail.com'))
set_smtp_port(int(request.form.get('smtp_port', '587')))
smtp_username = request.form.get('smtp_username')
smtp_password = request.form.get('smtp_password')
if smtp_username and smtp_password:
set_smtp_credentials(smtp_username, smtp_password)
set_smtp_security(
request.form.get('smtp_use_tls') == 'true',
request.form.get('smtp_use_ssl') == 'true'
)
# Payment configuration
from .config import set_stripe_keys, set_paypal_credentials, set_crypto_addresses
stripe_pub = request.form.get('stripe_publishable_key')
stripe_sec = request.form.get('stripe_secret_key')
if stripe_pub and stripe_sec:
set_stripe_keys(stripe_pub, stripe_sec)
paypal_id = request.form.get('paypal_client_id')
paypal_secret = request.form.get('paypal_client_secret')
if paypal_id and paypal_secret:
set_paypal_credentials(paypal_id, paypal_secret)
btc_addr = request.form.get('btc_payment_address')
eth_addr = request.form.get('eth_payment_address')
if btc_addr or eth_addr:
set_crypto_addresses(btc_addr or '', eth_addr or '')
# Database configuration
from .config import set_db_type, set_db_sqlite_path, set_db_mysql_config
db_type = request.form.get('db_type', 'sqlite')
set_db_type(db_type)
if db_type == 'sqlite':
set_db_sqlite_path(request.form.get('db_sqlite_path', 'vidai.db'))
elif db_type == 'mysql':
set_db_mysql_config(
host=request.form.get('db_mysql_host', 'localhost'),
port=int(request.form.get('db_mysql_port', '3306')),
user=request.form.get('db_mysql_user', 'vidai'),
password=request.form.get('db_mysql_password', ''),
database=request.form.get('db_mysql_database', 'vidai'),
charset=request.form.get('db_mysql_charset', 'utf8mb4')
)
# Network configuration
from .config import set_web_host, set_web_port, set_backend_host, set_backend_web_port, set_backend_worker_port
set_web_host(request.form.get('web_host', '0.0.0.0'))
set_web_port(int(request.form.get('web_port', '5000')))
set_backend_host(request.form.get('backend_host', 'localhost'))
set_backend_web_port(int(request.form.get('backend_web_port', '5001')))
set_backend_worker_port(int(request.form.get('backend_worker_port', '5002')))
settings = get_all_settings()
html = '''
<!DOCTYPE html>
<html>
<head>
<title>Configuration - Video AI</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 1000px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { color: #333; }
.nav { margin-bottom: 20px; }
.nav a { text-decoration: none; color: #007bff; margin-right: 15px; }
.section { border: 1px solid #ddd; padding: 15px; margin: 20px 0; border-radius: 5px; }
.section h3 { margin-top: 0; color: #007bff; }
form { margin: 20px 0; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input, select { width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; }
button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #0056b3; }
.checkbox-label { display: inline-block; margin-right: 10px; }
.status { padding: 10px; border-radius: 4px; margin: 10px 0; }
.status.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.status.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
</style>
</head>
<body>
<div class="container">
<div class="nav">
<a href="/">Dashboard</a> |
<a href="/admin/users">Users</a> |
<a href="/admin/config">Config</a> |
<a href="/logout">Logout</a>
</div>
<h1>System Configuration</h1>
<div class="section">
<h3>Backend Configuration</h3>
<form method="post">
<label>Analysis Backend:</label>
<select name="analysis_backend">
<option value="cuda" {% if settings.analysis_backend == 'cuda' %}selected{% endif %}>CUDA</option>
<option value="rocm" {% if settings.analysis_backend == 'rocm' %}selected{% endif %}>ROCm</option>
</select>
<label>Training Backend:</label>
<select name="training_backend">
<option value="cuda" {% if settings.training_backend == 'cuda' %}selected{% endif %}>CUDA</option>
<option value="rocm" {% if settings.training_backend == 'rocm' %}selected{% endif %}>ROCm</option>
</select>
<label>Communication Type:</label>
<select name="comm_type">
<option value="unix" {% if settings.comm_type == 'unix' %}selected{% endif %}>Unix Socket</option>
<option value="tcp" {% if settings.comm_type == 'tcp' %}selected{% endif %}>TCP Socket</option>
</select>
<label>Max Concurrent Jobs:</label>
<input type="number" name="max_concurrent_jobs" value="{{ settings.max_concurrent_jobs }}" min="1" max="10">
<label>Default Model:</label>
<input type="text" name="default_model" value="{{ settings.default_model }}">
<label>Frame Interval:</label>
<input type="number" name="frame_interval" value="{{ settings.frame_interval }}" min="1">
<button type="submit">Save Backend Configuration</button>
</form>
</div>
<div class="section">
<h3>RunPod.io Integration</h3>
<p>Enable dynamic GPU pod creation on RunPod.io for on-demand processing.</p>
<div class="status {{ 'success' if settings.get('runpod_enabled', False) else 'error' }}">
RunPod Status: {{ 'Enabled' if settings.get('runpod_enabled', False) else 'Disabled' }}
</div>
<form method="post">
<label>
<input type="checkbox" name="use_runpod_pods" value="true" {% if settings.get('use_runpod_pods', False) %}checked{% endif %}>
<span class="checkbox-label">Use RunPod pods for analysis jobs</span>
</label>
<label>RunPod API Key:</label>
<input type="password" name="runpod_api_key" placeholder="Enter your RunPod API key">
<label>Template ID:</label>
<input type="text" name="runpod_template_id" value="{{ settings.get('runpod_template_id', 'vidai-analysis-latest') }}" placeholder="RunPod template ID">
<label>GPU Type:</label>
<select name="runpod_gpu_type">
<option value="NVIDIA RTX A4000" {% if settings.get('runpod_gpu_type', 'NVIDIA RTX A4000') == 'NVIDIA RTX A4000' %}selected{% endif %}>NVIDIA RTX A4000</option>
<option value="NVIDIA RTX A5000" {% if settings.get('runpod_gpu_type') == 'NVIDIA RTX A5000' %}selected{% endif %}>NVIDIA RTX A5000</option>
<option value="NVIDIA RTX 3090" {% if settings.get('runpod_gpu_type') == 'NVIDIA RTX 3090' %}selected{% endif %}>NVIDIA RTX 3090</option>
<option value="NVIDIA RTX 4090" {% if settings.get('runpod_gpu_type') == 'NVIDIA RTX 4090' %}selected{% endif %}>NVIDIA RTX 4090</option>
</select>
<button type="submit">Save RunPod Configuration</button>
</form>
</div>
<div class="section">
<h3>User Registration & Tokens</h3>
<form method="post">
<label>
<input type="checkbox" name="allow_registration" value="true" {% if settings.get('allow_registration', True) %}checked{% endif %}>
<span class="checkbox-label">Allow user registration</span>
</label>
<label>Default Tokens per User:</label>
<input type="number" name="default_user_tokens" value="{{ settings.get('default_user_tokens', 100) }}" min="0">
<label>Token Price (USD per token):</label>
<input type="number" name="token_price_usd" value="{{ settings.get('token_price_usd', 0.10) }}" step="0.01" min="0">
<label>Base URL:</label>
<input type="url" name="base_url" value="{{ settings.get('base_url', 'http://localhost:5000') }}" placeholder="http://your-domain.com">
<button type="submit">Save Registration Settings</button>
</form>
</div>
<div class="section">
<h3>Email Configuration</h3>
<form method="post">
<label>SMTP Server:</label>
<input type="text" name="smtp_server" value="{{ settings.get('smtp_server', 'smtp.gmail.com') }}">
<label>SMTP Port:</label>
<input type="number" name="smtp_port" value="{{ settings.get('smtp_port', 587) }}" min="1" max="65535">
<label>SMTP Username:</label>
<input type="text" name="smtp_username" value="{{ settings.get('smtp_username', '') }}">
<label>SMTP Password:</label>
<input type="password" name="smtp_password" placeholder="Enter SMTP password">
<label>
<input type="checkbox" name="smtp_use_tls" value="true" {% if settings.get('smtp_use_tls', True) %}checked{% endif %}>
<span class="checkbox-label">Use TLS</span>
</label>
<label>
<input type="checkbox" name="smtp_use_ssl" value="true" {% if settings.get('smtp_use_ssl', False) %}checked{% endif %}>
<span class="checkbox-label">Use SSL</span>
</label>
<button type="submit">Save Email Configuration</button>
</form>
</div>
<div class="section">
<h3>Payment Configuration</h3>
<form method="post">
<h4>Stripe (Credit Cards)</h4>
<label>Publishable Key:</label>
<input type="text" name="stripe_publishable_key" value="{{ settings.get('stripe_publishable_key', '') }}">
<label>Secret Key:</label>
<input type="password" name="stripe_secret_key" placeholder="Enter Stripe secret key">
<h4>PayPal</h4>
<label>Client ID:</label>
<input type="text" name="paypal_client_id" value="{{ settings.get('paypal_client_id', '') }}">
<label>Client Secret:</label>
<input type="password" name="paypal_client_secret" placeholder="Enter PayPal client secret">
<h4>Cryptocurrency</h4>
<label>Bitcoin Address:</label>
<input type="text" name="btc_payment_address" value="{{ settings.get('btc_payment_address', '') }}" placeholder="Enter BTC address">
<label>Ethereum Address:</label>
<input type="text" name="eth_payment_address" value="{{ settings.get('eth_payment_address', '') }}" placeholder="Enter ETH address">
<button type="submit">Save Payment Configuration</button>
</form>
</div>
<div class="section">
<h3>Database Configuration</h3>
<form method="post">
<label>Database Type:</label>
<select name="db_type">
<option value="sqlite" {% if settings.get('db_type', 'sqlite') == 'sqlite' %}selected{% endif %}>SQLite</option>
<option value="mysql" {% if settings.get('db_type', 'sqlite') == 'mysql' %}selected{% endif %}>MySQL</option>
</select>
<div id="sqlite_settings" style="{% if settings.get('db_type', 'sqlite') != 'sqlite' %}display: none;{% endif %}">
<label>SQLite Database Path:</label>
<input type="text" name="db_sqlite_path" value="{{ settings.get('db_sqlite_path', 'vidai.db') }}">
</div>
<div id="mysql_settings" style="{% if settings.get('db_type', 'sqlite') != 'mysql' %}display: none;{% endif %}">
<label>MySQL Host:</label>
<input type="text" name="db_mysql_host" value="{{ settings.get('db_mysql_host', 'localhost') }}">
<label>MySQL Port:</label>
<input type="number" name="db_mysql_port" value="{{ settings.get('db_mysql_port', 3306) }}" min="1" max="65535">
<label>MySQL Username:</label>
<input type="text" name="db_mysql_user" value="{{ settings.get('db_mysql_user', 'vidai') }}">
<label>MySQL Password:</label>
<input type="password" name="db_mysql_password" placeholder="Enter MySQL password">
<label>MySQL Database:</label>
<input type="text" name="db_mysql_database" value="{{ settings.get('db_mysql_database', 'vidai') }}">
<label>MySQL Charset:</label>
<input type="text" name="db_mysql_charset" value="{{ settings.get('db_mysql_charset', 'utf8mb4') }}">
</div>
<button type="submit">Save Database Configuration</button>
</form>
<script>
document.querySelector('select[name="db_type"]').addEventListener('change', function() {
const dbType = this.value;
document.getElementById('sqlite_settings').style.display = dbType === 'sqlite' ? 'block' : 'none';
document.getElementById('mysql_settings').style.display = dbType === 'mysql' ? 'block' : 'none';
});
</script>
</div>
<div class="section">
<h3>Network Configuration</h3>
<form method="post">
<label>Web Interface Host:</label>
<input type="text" name="web_host" value="{{ settings.get('web_host', '0.0.0.0') }}">
<label>Web Interface Port:</label>
<input type="number" name="web_port" value="{{ settings.get('web_port', 5000) }}" min="1" max="65535">
<label>Backend Host:</label>
<input type="text" name="backend_host" value="{{ settings.get('backend_host', 'localhost') }}">
<label>Backend Web Port:</label>
<input type="number" name="backend_web_port" value="{{ settings.get('backend_web_port', 5001) }}" min="1" max="65535">
<label>Backend Worker Port:</label>
<input type="number" name="backend_worker_port" value="{{ settings.get('backend_worker_port', 5002) }}" min="1" max="65535">
<button type="submit">Save Network Configuration</button>
</form>
</div>
</div>
</body>
</html>
'''
return render_template_string(html, settings=settings)
@app.route('/admin/cluster', methods=['GET', 'POST'])
@require_admin_route()
def admin_cluster():
from .cluster_master import cluster_master
if request.method == 'POST':
action = request.form.get('action')
if action == 'enable_process':
process_key = request.form.get('process_key')
cluster_master.enable_process(process_key)
elif action == 'disable_process':
process_key = request.form.get('process_key')
cluster_master.disable_process(process_key)
elif action == 'update_weight':
process_key = request.form.get('process_key')
weight = int(request.form.get('weight', 10))
cluster_master.update_process_weight(process_key, weight)
return redirect(url_for('admin_cluster'))
# Get cluster status
clients = cluster_master.clients
processes = cluster_master.processes
process_queue = dict(cluster_master.process_queue)
html = '''
<!DOCTYPE html>
<html>
<head>
<title>Cluster Management - Video AI</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 1200px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { color: #333; }
.nav { margin-bottom: 20px; }
.nav a { text-decoration: none; color: #007bff; margin-right: 15px; }
.section { margin: 30px 0; }
.client-card, .process-card { border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 4px; }
.client-card { background: #f8f9fa; }
.process-card { background: #fff; }
.status-active { color: green; }
.status-disabled { color: red; }
.status-queued { color: orange; }
.weight-input { width: 60px; padding: 5px; }
button { background: #007bff; color: white; padding: 5px 10px; border: none; border-radius: 4px; cursor: pointer; margin: 2px; }
button:hover { background: #0056b3; }
button.disable { background: #dc3545; }
button.disable:hover { background: #c82333; }
</style>
</head>
<body>
<div class="container">
<div class="nav">
<a href="/">Dashboard</a> |
<a href="/admin/users">Users</a> |
<a href="/admin/config">Config</a> |
<a href="/admin/cluster">Cluster</a> |
<a href="/logout">Logout</a>
</div>
<h1>Cluster Management</h1>
<div class="section">
<h2>Connected Clients ({{ clients|length }})</h2>
{% for client_id, client_info in clients.items() %}
<div class="client-card">
<h3>Client: {{ client_id[:8] }}...</h3>
<p>Type: {{ client_info.info.type }}</p>
<p>Connected: {{ client_info.connected_at | strftime('%Y-%m-%d %H:%M:%S') }}</p>
<p>Last Seen: {{ client_info.last_seen | strftime('%Y-%m-%d %H:%M:%S') }}</p>
</div>
{% endfor %}
{% if not clients %}
<p>No clients connected</p>
{% endif %}
</div>
<div class="section">
<h2>Available Processes ({{ processes|length }})</h2>
{% for proc_key, proc_info in processes.items() %}
<div class="process-card">
<h3>{{ proc_info.name }} <span class="status-{{ proc_info.status }}">{{ proc_info.status }}</span></h3>
<p>Client: {{ proc_info.client_id[:8] }}...</p>
<p>Model: {{ proc_info.model }}</p>
<form method="post" style="display: inline;">
<input type="hidden" name="action" value="update_weight">
<input type="hidden" name="process_key" value="{{ proc_key }}">
<label>Weight: <input type="number" name="weight" value="{{ proc_info.weight }}" min="1" max="100" class="weight-input"></label>
<button type="submit">Update</button>
</form>
{% if proc_info.status == 'active' %}
<form method="post" style="display: inline;">
<input type="hidden" name="action" value="disable_process">
<input type="hidden" name="process_key" value="{{ proc_key }}">
<button type="submit" class="disable">Disable</button>
</form>
{% else %}
<form method="post" style="display: inline;">
<input type="hidden" name="action" value="enable_process">
<input type="hidden" name="process_key" value="{{ proc_key }}">
<button type="submit">Enable</button>
</form>
{% endif %}
</div>
{% endfor %}
{% if not processes %}
<p>No processes registered</p>
{% endif %}
</div>
<div class="section">
<h2>Load Balancing Queues</h2>
{% for proc_type, queue in process_queue.items() %}
<h3>{{ proc_type.title() }} Processes</h3>
<ol>
{% for proc_key, weight in queue %}
<li>{{ proc_key }} (weight: {{ weight }})</li>
{% endfor %}
</ol>
{% endfor %}
{% if not process_queue %}
<p>No load balancing queues</p>
{% endif %}
</div>
</div>
</body>
</html>
'''
return render_template_string(html, clients=clients, processes=processes, process_queue=process_queue)
# API Routes
@app.route('/api/analyze', methods=['POST'])
@api_auth()
def api_analyze():
user = request.user
data = request.get_json()
job_data = {
'model_path': data.get('model_path', 'Qwen/Qwen2.5-VL-7B-Instruct'),
'prompt': data.get('prompt', 'Describe this image.'),
'local_path': data.get('file_path'),
'user_id': user['id']
}
queue_id = queue_manager.submit_job(user['id'], 'analyze', job_data)
return jsonify({'queue_id': queue_id, 'status': 'queued'})
@app.route('/api/train', methods=['POST'])
@api_auth()
def api_train():
user = request.user
data = request.get_json()
job_data = {
'output_model': data.get('output_model', './VideoModel'),
'description': data.get('description', ''),
'train_dir': data.get('train_dir'),
'user_id': user['id']
}
queue_id = queue_manager.submit_job(user['id'], 'train', job_data)
return jsonify({'queue_id': queue_id, 'status': 'queued'})
@app.route('/api/queue/<int:queue_id>', methods=['GET'])
@api_auth()
def api_queue_status(queue_id):
user = request.user
job = queue_manager.get_job_status(queue_id)
if not job or job['user_id'] != user['id']:
return jsonify({'error': 'Job not found'}), 404
return jsonify(job)
@app.route('/api/queue', methods=['GET'])
@api_auth()
def api_user_queue():
user = request.user
jobs = queue_manager.get_user_jobs(user['id'])
return jsonify(jobs)
@app.route('/api/tokens', methods=['GET', 'POST'])
@api_auth()
def api_tokens():
user = request.user
if request.method == 'POST':
token = create_api_token(user['id'])
return jsonify({'token': token})
tokens = get_user_api_tokens(user['id'])
return jsonify(tokens)
@app.route('/api')
@require_login()
def api_docs():
session_id = get_session_id()
user = get_current_user(session_id)
html = '''
<!DOCTYPE html>
<html>
<head>
<title>API Documentation - Video AI</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 1000px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1, h2 { color: #333; }
.nav { margin-bottom: 20px; }
.nav a { text-decoration: none; color: #007bff; margin-right: 15px; }
pre { background: #f8f9fa; padding: 15px; border-radius: 4px; overflow-x: auto; }
code { background: #f8f9fa; padding: 2px 4px; border-radius: 2px; }
.endpoint { margin: 20px 0; padding: 15px; border-left: 4px solid #007bff; background: #f8f9fa; }
</style>
</head>
<body>
<div class="container">
<div class="nav">
<a href="/">Dashboard</a> |
<a href="/api">API</a> |
<a href="/logout">Logout</a>
</div>
<h1>Video AI REST API</h1>
<h2>Authentication</h2>
<p>Use Bearer token authentication:</p>
<pre>Authorization: Bearer YOUR_API_TOKEN</pre>
<h2>Get API Token</h2>
<div class="endpoint">
<strong>POST /api/tokens</strong>
<p>Generate a new API token</p>
</div>
<h2>Analysis</h2>
<div class="endpoint">
<strong>POST /api/analyze</strong>
<pre>{
"model_path": "Qwen/Qwen2.5-VL-7B-Instruct",
"prompt": "Describe this image.",
"file_path": "/path/to/image.jpg"
}</pre>
</div>
<h2>Training</h2>
<div class="endpoint">
<strong>POST /api/train</strong>
<pre>{
"output_model": "./MyModel",
"description": "Custom training",
"train_dir": "/path/to/training/data"
}</pre>
</div>
<h2>Queue Status</h2>
<div class="endpoint">
<strong>GET /api/queue/<queue_id></strong>
<p>Get job status by ID</p>
</div>
<div class="endpoint">
<strong>GET /api/queue</strong>
<p>Get all user jobs</p>
</div>
</div>
</body>
</html>
'''
return render_template_string(html)
@app.route('/tokens', methods=['GET', 'POST'])
@require_login()
def tokens():
session_id = get_session_id()
user = get_current_user(session_id)
user_tokens = get_user_tokens(user['id'])
token_packages = get_token_packages()
processors = get_available_processors()
message = None
error = None
if request.method == 'POST':
action = request.form.get('action')
if action == 'purchase':
package_tokens = int(request.form.get('tokens', 0))
processor_name = request.form.get('processor', 'stripe')
if package_tokens > 0:
amount = calculate_price(package_tokens)
success, msg, transaction_id = process_payment(user['id'], processor_name, package_tokens, amount)
if success:
message = msg
user_tokens = get_user_tokens(user['id']) # Refresh token count
else:
error = msg
html = '''
<!DOCTYPE html>
<html>
<head>
<title>Token Management - Video AI</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 1000px; 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; }
.token-balance { background: #28a745; color: white; padding: 10px 20px; border-radius: 20px; font-weight: bold; }
.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); }
.packages { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 20px 0; }
.package { border: 2px solid #e0e0e0; padding: 20px; border-radius: 8px; text-align: center; transition: all 0.3s ease; }
.package:hover { border-color: #007bff; transform: translateY(-2px); }
.package.popular { border-color: #28a745; position: relative; }
.package.popular::before { content: 'Most Popular'; position: absolute; top: -10px; left: 50%; transform: translateX(-50%); background: #28a745; color: white; padding: 3px 10px; border-radius: 10px; font-size: 0.8rem; }
.package h3 { margin: 10px 0; }
.price { font-size: 1.5rem; font-weight: bold; color: #007bff; margin: 10px 0; }
.btn { display: inline-block; padding: 8px 16px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 5px; }
.btn:hover { background: #0056b3; }
.message { padding: 10px; border-radius: 4px; margin: 10px 0; }
.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Token Management</h1>
<div class="nav">
<a href="/">Dashboard</a> |
<a href="/analyze">Analyze</a> |
<a href="/logout">Logout</a>
</div>
</div>
<div class="token-balance">
Current Balance: ''' + str(user_tokens) + ''' tokens
</div>
{% if message %}
<div class="message success">''' + str(message) + '''</div>
{% endif %}
{% if error %}
<div class="message error">''' + str(error) + '''</div>
{% endif %}
<div class="content">
<div class="main">
<h2>Purchase Tokens</h2>
<div class="packages">
{% for package in token_packages %}
<div class="package {% if package.popular %}popular{% endif %}">
<h3>{% raw %}{{ package.tokens }}{% endraw %} Tokens</h3>
<div class="price">${% raw %}{{ package.price }}{% endraw %}</div>
<form method="post" style="display: inline;">
<input type="hidden" name="action" value="purchase">
<input type="hidden" name="tokens" value="{% raw %}{{ package.tokens }}{% endraw %}">
<select name="processor" style="margin: 5px 0;">
{% for name, proc in processors.items() %}
{% if proc.enabled %}
<option value="{% raw %}{{ name }}{% endraw %}">{% raw %}{{ proc.name }}{% endraw %}</option>
{% endif %}
{% endfor %}
</select><br>
<button type="submit" class="btn">Purchase</button>
</form>
</div>
{% endfor %}
</div>
<h3>Custom Amount</h3>
<form method="post">
<input type="hidden" name="action" value="purchase">
<input type="number" name="tokens" placeholder="Number of tokens" min="10" required>
<select name="processor">
{% for name, proc in processors.items() %}
{% if proc.enabled %}
<option value="{% raw %}{{ name }}{% endraw %}">{% raw %}{{ proc.name }}{% endraw %}</option>
{% endif %}
{% endfor %}
</select>
<button type="submit" class="btn">Purchase Custom Amount</button>
</form>
</div>
<div class="sidebar">
<h3>Token Usage</h3>
<p>Tokens are used for video analysis jobs:</p>
<ul>
<li>~10 tokens per minute of video</li>
<li>~50 tokens per image analysis</li>
<li>~200 tokens per training job</li>
</ul>
<h3>Payment Methods</h3>
<ul>
<li><strong>Credit Card:</strong> Instant processing via Stripe</li>
<li><strong>PayPal:</strong> Coming soon</li>
<li><strong>Crypto:</strong> BTC/ETH addresses provided</li>
</ul>
</div>
</div>
</div>
</body>
</html>
'''
return render_template_string(html, user=user, user_tokens=user_tokens, token_packages=token_packages, processors=processors, message=message, error=error)
@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, get_debug
app.run(host=get_web_host(), port=get_web_port(), debug=get_debug())
@app.route('/', methods=['GET', 'POST'])
def index():
result = None
if request.method == 'POST':
model_path = request.form.get('model_path', 'Qwen/Qwen2.5-VL-7B-Instruct')
prompt = request.form.get('prompt', 'Describe this image.')
uploaded_file = request.files.get('file')
local_path = request.form.get('local_path')
if uploaded_file and uploaded_file.filename:
# For simplicity, assume file is uploaded, but in real, need to handle
# For now, send path or something
data = {
'model_path': model_path,
'prompt': prompt,
'file_name': uploaded_file.filename,
# In real, upload file to temp and send path
}
msg_id = send_to_backend('analyze_request', data)
result_data = get_result(msg_id)
if 'data' in result_data:
result = result_data['data'].get('result', 'Analysis completed')
else:
result = result_data.get('error', 'Error')
elif local_path:
data = {
'model_path': model_path,
'prompt': prompt,
'local_path': local_path
}
msg_id = send_to_backend('analyze_request', data)
result_data = get_result(msg_id)
if 'data' in result_data:
result = result_data['data'].get('result', 'Analysis completed')
else:
result = result_data.get('error', 'Error')
html = '''
<!DOCTYPE html>
<html>
<head>
<title>VideoModel AI</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"], input[type="file"] { 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; }
.result { 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>Analyze Image/Video</h2>
<form method="post" enctype="multipart/form-data">
<label>Model Path: <input type="text" name="model_path" value="Qwen/Qwen2.5-VL-7B-Instruct"></label>
<p><a href="/system">Edit System Prompt</a></p>
<label>Upload File: <input type="file" name="file" accept="image/*,video/*"></label>
<label>Or Local Path: <input type="text" name="local_path"></label>
<label>Prompt: <textarea name="prompt" rows="3">Describe this image.</textarea></label>
<input type="submit" value="Analyze">
</form>
{% if result %}
<div class="result">
<h3>Result:</h3>
<p>{{ result }}</p>
</div>
{% endif %}
</div>
</body>
</html>
'''
return render_template_string(html, result=result)
@app.route('/train', methods=['GET', 'POST'])
def train():
message = None
......@@ -1424,327 +231,5 @@ def config():
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'])
def config():
if request.method == 'POST':
# Update local config
set_analysis_backend(request.form.get('analysis_backend', 'cuda'))
set_training_backend(request.form.get('training_backend', 'cuda'))
set_comm_type(request.form.get('comm_type', 'unix'))
set_default_model(request.form.get('default_model', 'Qwen/Qwen2.5-VL-7B-Instruct'))
set_frame_interval(int(request.form.get('frame_interval', 10)))
# 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>
<label>Communication Type:
<select name="comm_type">
<option value="unix" ''' + ('selected' if current_config['comm_type'] == 'unix' else '') + '''>Unix Socket</option>
<option value="tcp" ''' + ('selected' if current_config['comm_type'] == 'tcp' else '') + '''>TCP Socket</option>
</select>
</label>
<label>Default Model: <input type="text" name="default_model" value="''' + str(current_config['default_model']) + '''"></label>
<label>Frame Interval (seconds): <input type="number" name="frame_interval" value="''' + str(current_config['frame_interval']) + '''" min="1"></label>
<input type="submit" value="Save Configuration">
</form>
</div>
</body>
</html>
'''
return html
@app.route('/system', methods=['GET', 'POST'])
def system_page():
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__":
app.run(host='0.0.0.0', debug=True)
\ No newline at end of file
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