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: ...@@ -249,23 +249,37 @@ Examples:
print("Press Ctrl+C to stop") print("Press Ctrl+C to stop")
# Start backend process # Start backend process
backend_cmd = get_python_command(module='vidai.backend') backend_cmd = [sys.executable, '-m', 'vidai.backend']
backend_proc = subprocess.Popen(backend_cmd) 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 # Start web process
web_cmd = get_python_command(module='vidai.web') web_cmd = [sys.executable, '-m', 'vidai.web']
web_proc = subprocess.Popen(web_cmd) web_proc = subprocess.Popen(web_cmd)
try: try:
# Wait for processes # Wait for processes
backend_proc.wait() backend_proc.wait()
analysis_proc.wait()
training_proc.wait()
web_proc.wait() web_proc.wait()
except KeyboardInterrupt: except KeyboardInterrupt:
print("Shutting down...") print("Shutting down...")
backend_proc.terminate()
web_proc.terminate() web_proc.terminate()
backend_proc.wait() training_proc.terminate()
analysis_proc.terminate()
backend_proc.terminate()
web_proc.wait() web_proc.wait()
training_proc.wait()
analysis_proc.wait()
backend_proc.wait()
if __name__ == "__main__": if __name__ == "__main__":
main() main()
\ No newline at end of file
...@@ -22,164 +22,103 @@ Manages request routing between web interface and worker processes. ...@@ -22,164 +22,103 @@ Manages request routing between web interface and worker processes.
import time import time
import threading import threading
from .comm import SocketServer, Message 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 .config import get_analysis_backend, get_training_backend, set_analysis_backend, set_training_backend
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
worker_sockets = {} # type: dict 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: def handle_web_message(message: Message) -> Message:
"""Handle messages from web interface.""" """Handle messages from web interface."""
if message.msg_type == 'analyze_request': if message.msg_type == 'analyze_request':
# Check if we should use RunPod pods backend = get_analysis_backend()
if get_use_runpod_pods() and is_runpod_enabled(): worker_key = f'analysis_{backend}'
pod = get_available_pod('analysis') if worker_key in worker_sockets:
if pod: # Forward to worker
# Send job to pod worker_sockets[worker_key].sendall(
return Message('ack', message.msg_id, {'status': 'pod_assigned', 'pod_id': pod.pod_id}) f'{{"msg_type": "{message.msg_type}", "msg_id": "{message.msg_id}", "data": {message.data}}}\n'.encode('utf-8')
else: )
return Message('error', message.msg_id, {'error': 'No pods available'}) return None # No immediate response
else: else:
# Use local workers or queue return Message('error', message.msg_id, {'error': f'Worker {worker_key} not available'})
return Message('ack', message.msg_id, {'status': 'queued'})
elif message.msg_type == 'train_request': elif message.msg_type == 'train_request':
if get_use_runpod_pods() and is_runpod_enabled(): backend = get_training_backend()
pod = get_available_pod('training') worker_key = f'training_{backend}'
if pod: if worker_key in worker_sockets:
return Message('ack', message.msg_id, {'status': 'pod_assigned', 'pod_id': pod.pod_id}) worker_sockets[worker_key].sendall(
else: f'{{"msg_type": "{message.msg_type}", "msg_id": "{message.msg_id}", "data": {message.data}}}\n'.encode('utf-8')
return Message('error', message.msg_id, {'error': 'No pods available'}) )
return None
else: 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': elif message.msg_type == 'config_update':
data = message.data data = message.data
if 'analysis_backend' in data: if 'analysis_backend' in data:
set_analysis_backend(data['analysis_backend']) set_analysis_backend(data['analysis_backend'])
if 'training_backend' in data: if 'training_backend' in data:
set_training_backend(data['training_backend']) 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'}) return Message('config_response', message.msg_id, {'status': 'updated'})
elif message.msg_type == 'get_config': elif message.msg_type == 'get_config':
return Message('config_response', message.msg_id, { return Message('config_response', message.msg_id, {
'analysis_backend': get_analysis_backend(), 'analysis_backend': get_analysis_backend(),
'training_backend': get_training_backend(), 'training_backend': get_training_backend()
'use_runpod_pods': get_use_runpod_pods(),
'runpod_enabled': is_runpod_enabled()
}) })
return Message('error', message.msg_id, {'error': 'Unknown message type'}) 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: def backend_process() -> None:
"""Main backend process loop.""" """Main backend process loop."""
print("Starting Video AI Backend...") print("Starting Video AI Backend...")
from .config import is_cluster_client, get_cluster_port # Start web server on port 5001
from .cluster_master import start_cluster_master web_server = SocketServer(port=5001)
web_server.start(handle_web_message)
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 pod cleanup thread if RunPod is enabled # Start worker server on port 5002
if is_runpod_enabled(): worker_server = SocketServer(port=5002)
cleanup_thread = threading.Thread(target=cleanup_idle_pods, daemon=True) worker_server.start(worker_message_handler)
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)
try: try:
while True: while True:
time.sleep(1) time.sleep(1)
except KeyboardInterrupt: except KeyboardInterrupt:
print("Backend shutting down...") 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() web_server.stop()
worker_server.stop()
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -117,1199 +117,6 @@ def index(): ...@@ -117,1199 +117,6 @@ def index():
''' '''
return render_template_string(html, result=result) 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']) @app.route('/train', methods=['GET', 'POST'])
def train(): def train():
message = None message = None
...@@ -1424,327 +231,5 @@ def config(): ...@@ -1424,327 +231,5 @@ def config():
def serve_static(filename): def serve_static(filename):
return send_from_directory('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__": if __name__ == "__main__":
app.run(host='0.0.0.0', debug=True) 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