Spaces:
Sleeping
Sleeping
feat: migrate batch generator backend to SQL Server with auto-provisioned tables
Browse files- app.py +147 -50
- requirements.txt +1 -0
app.py
CHANGED
|
@@ -2622,14 +2622,56 @@ def api_content_planner_generate():
|
|
| 2622 |
return jsonify({'error': f"Lỗi lập kế hoạch nội dung: {_err_msg(str(e))}"}), 500
|
| 2623 |
|
| 2624 |
# ==============================================
|
| 2625 |
-
# TOOL: AI BATCH GENERATOR
|
| 2626 |
# ==============================================
|
| 2627 |
import uuid
|
| 2628 |
import threading
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2629 |
|
| 2630 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2631 |
|
| 2632 |
-
|
|
|
|
|
|
|
|
|
|
| 2633 |
# Model configuration
|
| 2634 |
if mode == 'video':
|
| 2635 |
model_version = "748dae3f714ccf25a72064119d854203f1917b2b8c56fa769f3e498c0d9c491f"
|
|
@@ -2641,10 +2683,31 @@ def run_batch_replicate(batch_id, prompts, mode, replicate_token):
|
|
| 2641 |
'Content-Type': 'application/json'
|
| 2642 |
}
|
| 2643 |
|
| 2644 |
-
|
| 2645 |
-
|
| 2646 |
-
|
| 2647 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2648 |
# Prepare payload
|
| 2649 |
payload = {
|
| 2650 |
"version": model_version,
|
|
@@ -2655,7 +2718,7 @@ def run_batch_replicate(batch_id, prompts, mode, replicate_token):
|
|
| 2655 |
payload["input"]["guidance_scale"] = 1.0
|
| 2656 |
|
| 2657 |
try:
|
| 2658 |
-
#
|
| 2659 |
req = urllib.request.Request(
|
| 2660 |
"https://api.replicate.com/v1/predictions",
|
| 2661 |
data=json.dumps(payload).encode('utf-8'),
|
|
@@ -2669,7 +2732,7 @@ def run_batch_replicate(batch_id, prompts, mode, replicate_token):
|
|
| 2669 |
if not prediction_id:
|
| 2670 |
raise Exception("Không nhận được prediction_id từ Replicate")
|
| 2671 |
|
| 2672 |
-
#
|
| 2673 |
poll_url = f"https://api.replicate.com/v1/predictions/{prediction_id}"
|
| 2674 |
success = False
|
| 2675 |
for _ in range(30): # max 60 seconds polling (30 * 2)
|
|
@@ -2682,33 +2745,60 @@ def run_batch_replicate(batch_id, prompts, mode, replicate_token):
|
|
| 2682 |
pred_status = status_data.get('status')
|
| 2683 |
if pred_status == 'succeeded':
|
| 2684 |
output = status_data.get('output')
|
|
|
|
| 2685 |
if isinstance(output, list) and len(output) > 0:
|
| 2686 |
-
|
| 2687 |
elif isinstance(output, str):
|
| 2688 |
-
|
| 2689 |
else:
|
| 2690 |
-
|
| 2691 |
|
| 2692 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2693 |
success = True
|
| 2694 |
break
|
| 2695 |
elif pred_status in ['failed', 'canceled']:
|
| 2696 |
-
|
| 2697 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2698 |
success = True
|
| 2699 |
break
|
| 2700 |
|
| 2701 |
if not success:
|
| 2702 |
-
|
| 2703 |
-
|
|
|
|
|
|
|
|
|
|
| 2704 |
|
| 2705 |
except Exception as e:
|
| 2706 |
-
|
| 2707 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2708 |
|
| 2709 |
# Mark batch job as completed
|
| 2710 |
-
|
| 2711 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2712 |
|
| 2713 |
|
| 2714 |
@app.route('/batch-generator')
|
|
@@ -2729,33 +2819,28 @@ def api_batch_generator_start():
|
|
| 2729 |
if not raw_prompts or not isinstance(raw_prompts, list):
|
| 2730 |
return jsonify({'error': 'Danh sách prompt không hợp lệ'}), 400
|
| 2731 |
|
| 2732 |
-
|
| 2733 |
-
prompts_list = []
|
| 2734 |
-
for p in raw_prompts:
|
| 2735 |
-
p_str = str(p).strip()
|
| 2736 |
-
if p_str:
|
| 2737 |
-
prompts_list.append({
|
| 2738 |
-
'prompt': p_str,
|
| 2739 |
-
'status': 'queued',
|
| 2740 |
-
'output_url': None,
|
| 2741 |
-
'error': None
|
| 2742 |
-
})
|
| 2743 |
-
|
| 2744 |
if not prompts_list:
|
| 2745 |
return jsonify({'error': 'Không tìm thấy prompt hợp lệ nào'}), 400
|
| 2746 |
|
| 2747 |
batch_id = str(uuid.uuid4())
|
| 2748 |
-
|
| 2749 |
-
|
| 2750 |
-
|
| 2751 |
-
|
| 2752 |
-
|
| 2753 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2754 |
|
| 2755 |
# Start processing thread
|
| 2756 |
thread = threading.Thread(
|
| 2757 |
-
target=
|
| 2758 |
-
args=(batch_id,
|
| 2759 |
)
|
| 2760 |
thread.daemon = True
|
| 2761 |
thread.start()
|
|
@@ -2769,15 +2854,27 @@ def api_batch_generator_start():
|
|
| 2769 |
|
| 2770 |
@app.route('/api/batch-generator/status/<batch_id>')
|
| 2771 |
def api_batch_generator_status(batch_id):
|
| 2772 |
-
|
| 2773 |
-
|
| 2774 |
-
|
| 2775 |
-
|
| 2776 |
-
|
| 2777 |
-
|
| 2778 |
-
|
| 2779 |
-
|
| 2780 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2781 |
|
| 2782 |
if __name__ == '__main__':
|
| 2783 |
app.run(debug=True, port=5000)
|
|
|
|
| 2622 |
return jsonify({'error': f"Lỗi lập kế hoạch nội dung: {_err_msg(str(e))}"}), 500
|
| 2623 |
|
| 2624 |
# ==============================================
|
| 2625 |
+
# TOOL: AI BATCH GENERATOR (SQL SERVER CONNECTED)
|
| 2626 |
# ==============================================
|
| 2627 |
import uuid
|
| 2628 |
import threading
|
| 2629 |
+
import pymssql
|
| 2630 |
+
|
| 2631 |
+
def get_db_connection():
|
| 2632 |
+
return pymssql.connect(
|
| 2633 |
+
server='db57971.public.databaseasp.net',
|
| 2634 |
+
user='db57971',
|
| 2635 |
+
password='Qn4@3F#p!9fD',
|
| 2636 |
+
database='db57971',
|
| 2637 |
+
charset='utf8'
|
| 2638 |
+
)
|
| 2639 |
|
| 2640 |
+
def init_db():
|
| 2641 |
+
try:
|
| 2642 |
+
conn = get_db_connection()
|
| 2643 |
+
cursor = conn.cursor()
|
| 2644 |
+
cursor.execute("""
|
| 2645 |
+
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='batch_jobs' AND xtype='U')
|
| 2646 |
+
CREATE TABLE batch_jobs (
|
| 2647 |
+
id VARCHAR(50) PRIMARY KEY,
|
| 2648 |
+
mode VARCHAR(10) NOT NULL,
|
| 2649 |
+
status VARCHAR(20) NOT NULL,
|
| 2650 |
+
created_at DATETIME DEFAULT GETDATE()
|
| 2651 |
+
)
|
| 2652 |
+
""")
|
| 2653 |
+
cursor.execute("""
|
| 2654 |
+
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='batch_prompts' AND xtype='U')
|
| 2655 |
+
CREATE TABLE batch_prompts (
|
| 2656 |
+
id INT IDENTITY(1,1) PRIMARY KEY,
|
| 2657 |
+
batch_id VARCHAR(50) FOREIGN KEY REFERENCES batch_jobs(id) ON DELETE CASCADE,
|
| 2658 |
+
prompt_text NVARCHAR(MAX) NOT NULL,
|
| 2659 |
+
status VARCHAR(20) NOT NULL,
|
| 2660 |
+
output_url VARCHAR(2083) NULL,
|
| 2661 |
+
error_message NVARCHAR(MAX) NULL,
|
| 2662 |
+
created_at DATETIME DEFAULT GETDATE()
|
| 2663 |
+
)
|
| 2664 |
+
""")
|
| 2665 |
+
conn.commit()
|
| 2666 |
+
conn.close()
|
| 2667 |
+
print("SQL Server initialized and tables verified successfully.")
|
| 2668 |
+
except Exception as e:
|
| 2669 |
+
print(f"Error initializing SQL Server database: {e}")
|
| 2670 |
|
| 2671 |
+
# Run database setup on startup
|
| 2672 |
+
init_db()
|
| 2673 |
+
|
| 2674 |
+
def run_batch_replicate_db(batch_id, mode, replicate_token):
|
| 2675 |
# Model configuration
|
| 2676 |
if mode == 'video':
|
| 2677 |
model_version = "748dae3f714ccf25a72064119d854203f1917b2b8c56fa769f3e498c0d9c491f"
|
|
|
|
| 2683 |
'Content-Type': 'application/json'
|
| 2684 |
}
|
| 2685 |
|
| 2686 |
+
try:
|
| 2687 |
+
# Fetch all prompts to process
|
| 2688 |
+
conn = get_db_connection()
|
| 2689 |
+
cursor = conn.cursor(as_dict=True)
|
| 2690 |
+
cursor.execute("SELECT id, prompt_text FROM batch_prompts WHERE batch_id = %s", (batch_id,))
|
| 2691 |
+
prompts = cursor.fetchall()
|
| 2692 |
+
conn.close()
|
| 2693 |
+
except Exception as e:
|
| 2694 |
+
print(f"Error fetching prompts from DB: {e}")
|
| 2695 |
+
return
|
| 2696 |
+
|
| 2697 |
+
for prompt_item in prompts:
|
| 2698 |
+
prompt_id = prompt_item['id']
|
| 2699 |
+
prompt_text = prompt_item['prompt_text']
|
| 2700 |
+
|
| 2701 |
+
# 1. Update status to 'processing'
|
| 2702 |
+
try:
|
| 2703 |
+
conn = get_db_connection()
|
| 2704 |
+
cursor = conn.cursor()
|
| 2705 |
+
cursor.execute("UPDATE batch_prompts SET status = 'processing' WHERE id = %d", (prompt_id,))
|
| 2706 |
+
conn.commit()
|
| 2707 |
+
conn.close()
|
| 2708 |
+
except Exception as e:
|
| 2709 |
+
print(f"Error updating status to processing: {e}")
|
| 2710 |
+
|
| 2711 |
# Prepare payload
|
| 2712 |
payload = {
|
| 2713 |
"version": model_version,
|
|
|
|
| 2718 |
payload["input"]["guidance_scale"] = 1.0
|
| 2719 |
|
| 2720 |
try:
|
| 2721 |
+
# Create prediction request
|
| 2722 |
req = urllib.request.Request(
|
| 2723 |
"https://api.replicate.com/v1/predictions",
|
| 2724 |
data=json.dumps(payload).encode('utf-8'),
|
|
|
|
| 2732 |
if not prediction_id:
|
| 2733 |
raise Exception("Không nhận được prediction_id từ Replicate")
|
| 2734 |
|
| 2735 |
+
# Polling loop
|
| 2736 |
poll_url = f"https://api.replicate.com/v1/predictions/{prediction_id}"
|
| 2737 |
success = False
|
| 2738 |
for _ in range(30): # max 60 seconds polling (30 * 2)
|
|
|
|
| 2745 |
pred_status = status_data.get('status')
|
| 2746 |
if pred_status == 'succeeded':
|
| 2747 |
output = status_data.get('output')
|
| 2748 |
+
output_url = None
|
| 2749 |
if isinstance(output, list) and len(output) > 0:
|
| 2750 |
+
output_url = output[0]
|
| 2751 |
elif isinstance(output, str):
|
| 2752 |
+
output_url = output
|
| 2753 |
else:
|
| 2754 |
+
output_url = str(output)
|
| 2755 |
|
| 2756 |
+
# Update status to 'done' in DB
|
| 2757 |
+
conn = get_db_connection()
|
| 2758 |
+
cursor = conn.cursor()
|
| 2759 |
+
cursor.execute("UPDATE batch_prompts SET status = 'done', output_url = %s WHERE id = %d", (output_url, prompt_id))
|
| 2760 |
+
conn.commit()
|
| 2761 |
+
conn.close()
|
| 2762 |
success = True
|
| 2763 |
break
|
| 2764 |
elif pred_status in ['failed', 'canceled']:
|
| 2765 |
+
error_msg = status_data.get('error') or "Đã bị hủy hoặc lỗi"
|
| 2766 |
+
# Update status to 'failed' in DB
|
| 2767 |
+
conn = get_db_connection()
|
| 2768 |
+
cursor = conn.cursor()
|
| 2769 |
+
cursor.execute("UPDATE batch_prompts SET status = 'failed', error_message = %s WHERE id = %d", (error_msg, prompt_id))
|
| 2770 |
+
conn.commit()
|
| 2771 |
+
conn.close()
|
| 2772 |
success = True
|
| 2773 |
break
|
| 2774 |
|
| 2775 |
if not success:
|
| 2776 |
+
conn = get_db_connection()
|
| 2777 |
+
cursor = conn.cursor()
|
| 2778 |
+
cursor.execute("UPDATE batch_prompts SET status = 'failed', error_message = 'Quá thời gian chờ phản hồi (Timeout)' WHERE id = %d", (prompt_id,))
|
| 2779 |
+
conn.commit()
|
| 2780 |
+
conn.close()
|
| 2781 |
|
| 2782 |
except Exception as e:
|
| 2783 |
+
# Update status to 'failed' in DB
|
| 2784 |
+
try:
|
| 2785 |
+
conn = get_db_connection()
|
| 2786 |
+
cursor = conn.cursor()
|
| 2787 |
+
cursor.execute("UPDATE batch_prompts SET status = 'failed', error_message = %s WHERE id = %d", (str(e), prompt_id))
|
| 2788 |
+
conn.commit()
|
| 2789 |
+
conn.close()
|
| 2790 |
+
except Exception as dbe:
|
| 2791 |
+
print(f"Error updating failure in DB: {dbe}")
|
| 2792 |
|
| 2793 |
# Mark batch job as completed
|
| 2794 |
+
try:
|
| 2795 |
+
conn = get_db_connection()
|
| 2796 |
+
cursor = conn.cursor()
|
| 2797 |
+
cursor.execute("UPDATE batch_jobs SET status = 'completed' WHERE id = %s", (batch_id,))
|
| 2798 |
+
conn.commit()
|
| 2799 |
+
conn.close()
|
| 2800 |
+
except Exception as e:
|
| 2801 |
+
print(f"Error updating batch job completion: {e}")
|
| 2802 |
|
| 2803 |
|
| 2804 |
@app.route('/batch-generator')
|
|
|
|
| 2819 |
if not raw_prompts or not isinstance(raw_prompts, list):
|
| 2820 |
return jsonify({'error': 'Danh sách prompt không hợp lệ'}), 400
|
| 2821 |
|
| 2822 |
+
prompts_list = [str(p).strip() for p in raw_prompts if str(p).strip()]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2823 |
if not prompts_list:
|
| 2824 |
return jsonify({'error': 'Không tìm thấy prompt hợp lệ nào'}), 400
|
| 2825 |
|
| 2826 |
batch_id = str(uuid.uuid4())
|
| 2827 |
+
|
| 2828 |
+
try:
|
| 2829 |
+
# Insert batch job and prompts in database
|
| 2830 |
+
conn = get_db_connection()
|
| 2831 |
+
cursor = conn.cursor()
|
| 2832 |
+
cursor.execute("INSERT INTO batch_jobs (id, mode, status) VALUES (%s, %s, %s)", (batch_id, mode, 'running'))
|
| 2833 |
+
for prompt_text in prompts_list:
|
| 2834 |
+
cursor.execute("INSERT INTO batch_prompts (batch_id, prompt_text, status) VALUES (%s, %s, %s)", (batch_id, prompt_text, 'queued'))
|
| 2835 |
+
conn.commit()
|
| 2836 |
+
conn.close()
|
| 2837 |
+
except Exception as e:
|
| 2838 |
+
return jsonify({'error': f'Lỗi lưu thông tin vào CSDL SQL Server: {e}'}), 500
|
| 2839 |
|
| 2840 |
# Start processing thread
|
| 2841 |
thread = threading.Thread(
|
| 2842 |
+
target=run_batch_replicate_db,
|
| 2843 |
+
args=(batch_id, mode, replicate_token)
|
| 2844 |
)
|
| 2845 |
thread.daemon = True
|
| 2846 |
thread.start()
|
|
|
|
| 2854 |
|
| 2855 |
@app.route('/api/batch-generator/status/<batch_id>')
|
| 2856 |
def api_batch_generator_status(batch_id):
|
| 2857 |
+
try:
|
| 2858 |
+
conn = get_db_connection()
|
| 2859 |
+
cursor = conn.cursor(as_dict=True)
|
| 2860 |
+
cursor.execute("SELECT status, mode FROM batch_jobs WHERE id = %s", (batch_id,))
|
| 2861 |
+
job = cursor.fetchone()
|
| 2862 |
+
if not job:
|
| 2863 |
+
conn.close()
|
| 2864 |
+
return jsonify({'error': 'Không tìm thấy mã Batch'}), 404
|
| 2865 |
+
|
| 2866 |
+
cursor.execute("SELECT prompt_text AS prompt, status, output_url, error_message AS error FROM batch_prompts WHERE batch_id = %s ORDER BY id ASC", (batch_id,))
|
| 2867 |
+
prompts = cursor.fetchall()
|
| 2868 |
+
conn.close()
|
| 2869 |
+
|
| 2870 |
+
return jsonify({
|
| 2871 |
+
'success': True,
|
| 2872 |
+
'status': job['status'],
|
| 2873 |
+
'mode': job['mode'],
|
| 2874 |
+
'prompts': prompts
|
| 2875 |
+
})
|
| 2876 |
+
except Exception as e:
|
| 2877 |
+
return jsonify({'error': f'Lỗi truy vấn CSDL SQL Server: {e}'}), 500
|
| 2878 |
|
| 2879 |
if __name__ == '__main__':
|
| 2880 |
app.run(debug=True, port=5000)
|
requirements.txt
CHANGED
|
@@ -14,3 +14,4 @@ vieneu
|
|
| 14 |
moviepy
|
| 15 |
replicate
|
| 16 |
gradio-client
|
|
|
|
|
|
| 14 |
moviepy
|
| 15 |
replicate
|
| 16 |
gradio-client
|
| 17 |
+
pymssql
|