Feedbacks / app.py
EngReem85's picture
Update app.py
2100251 verified
Raw
History Blame Contribute Delete
30.7 kB
import gradio as gr
import sqlite3
import hashlib
import html
import re
import os
import json
import logging
from datetime import datetime, timedelta
from huggingface_hub import HfApi, hf_hub_download
import shutil
# ==================================================
# الإعدادات الأساسية
# ==================================================
DB_NAME = "testimonials.db"
REPO_ID = os.getenv("SPACE_ID", "EngReem85/Feedbacks")
TOKEN = os.getenv("HF_TOKEN")
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ==================================================
# CSS - تصميم دارك (بدون تحكم في القياسات)
# ==================================================
CUSTOM_CSS = """
/* CSS للتصميم الدارك */
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;600;700;800&display=swap');
* {
font-family: 'Tajawal', sans-serif !important;
}
.gradio-container {
background: #0f0f1a !important;
min-height: 100vh;
}
/* تأثيرات الحركة */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes shimmer {
0% { transform: translateX(-100%) rotate(45deg); }
100% { transform: translateX(100%) rotate(45deg); }
}
/* تحسين البطاقات في الوضع الدارك */
.testimonial-card {
animation: slideIn 0.5s ease-out;
transition: all 0.3s ease;
background: #1a1a2e !important;
border: 1px solid #2d2d44 !important;
}
.testimonial-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(99, 102, 241, 0.2) !important;
border-color: #6366f1 !important;
}
/* أزرار متألقة */
.glow-button {
background: linear-gradient(135deg, #667eea, #764ba2) !important;
color: white !important;
border: none !important;
padding: 12px 32px !important;
border-radius: 50px !important;
font-weight: 700 !important;
transition: all 0.3s ease !important;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;
}
.glow-button:hover {
transform: scale(1.05) !important;
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.6) !important;
}
/* تحسين حقول الإدخال في الوضع الدارك */
.gr-textbox input, .gr-textbox textarea {
border-radius: 12px !important;
border: 2px solid #2d2d44 !important;
transition: all 0.3s ease !important;
font-size: 1rem !important;
background: #1a1a2e !important;
color: #e2e8f0 !important;
}
.gr-textbox input:focus, .gr-textbox textarea:focus {
border-color: #6366f1 !important;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2) !important;
}
.gr-textbox input::placeholder, .gr-textbox textarea::placeholder {
color: #64748b !important;
}
/* شريط التمرير */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #1a1a2e;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #4f46e5;
}
/* تحسين العناوين في الوضع الدارك */
h1, h2, h3, h4, h5, h6, label {
color: #e2e8f0 !important;
}
/* تحسين الـ Dropdown في الوضع الدارك */
select, .gr-dropdown {
background: #1a1a2e !important;
color: #e2e8f0 !important;
border-color: #2d2d44 !important;
}
/* تحسين الـ Slider */
input[type="range"] {
accent-color: #6366f1;
}
/* تحسين النصوص */
p, span, div {
color: #cbd5e1;
}
/* تحسين الأزرار الثانوية */
.gr-button-secondary {
background: #2d2d44 !important;
color: #e2e8f0 !important;
border: 1px solid #3d3d5c !important;
}
.gr-button-secondary:hover {
background: #3d3d5c !important;
border-color: #6366f1 !important;
}
/* تحسين الإحصائيات */
.stat-card {
background: #1a1a2e !important;
border: 1px solid #2d2d44 !important;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-2px);
border-color: #6366f1 !important;
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.1) !important;
}
"""
# ==================================================
# إدارة قاعدة البيانات
# ==================================================
def download_db_from_hub():
"""تحميل قاعدة البيانات من Hugging Face Hub"""
try:
if not TOKEN:
logger.warning("HF_TOKEN غير موجود، سيتم إنشاء قاعدة بيانات جديدة")
return False
local_path = hf_hub_download(
repo_id=REPO_ID,
filename=DB_NAME,
token=TOKEN,
local_dir="./"
)
if local_path != DB_NAME:
shutil.copy2(local_path, DB_NAME)
logger.info("تم تحميل قاعدة البيانات من Hub")
return True
except Exception as e:
logger.warning(f"لم يتم العثور على قاعدة بيانات: {e}")
return False
def upload_db_to_hub():
"""رفع قاعدة البيانات إلى Hugging Face Hub"""
try:
if not TOKEN or not os.path.exists(DB_NAME):
return False
api = HfApi()
api.upload_file(
path_or_fileobj=DB_NAME,
path_in_repo=DB_NAME,
repo_id=REPO_ID,
token=TOKEN,
repo_type="space"
)
logger.info("تم رفع قاعدة البيانات إلى Hub")
return True
except Exception as e:
logger.error(f"فشل رفع قاعدة البيانات: {e}")
return False
# ==================================================
# قاعدة البيانات
# ==================================================
def get_connection():
return sqlite3.connect(DB_NAME, check_same_thread=False)
def init_db():
conn = get_connection()
c = conn.cursor()
c.execute("""
CREATE TABLE IF NOT EXISTS testimonials (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
role TEXT,
content TEXT NOT NULL,
rating INTEGER DEFAULT 5,
timestamp TEXT NOT NULL,
is_highlighted INTEGER DEFAULT 0,
ip_hash TEXT
)
""")
# الفهارس
c.execute("CREATE INDEX IF NOT EXISTS idx_timestamp ON testimonials(timestamp)")
c.execute("CREATE INDEX IF NOT EXISTS idx_ip_hash ON testimonials(ip_hash)")
c.execute("CREATE INDEX IF NOT EXISTS idx_highlighted ON testimonials(is_highlighted)")
c.execute("CREATE INDEX IF NOT EXISTS idx_rating ON testimonials(rating)")
conn.commit()
conn.close()
logger.info("تم تهيئة قاعدة البيانات")
# ==================================================
# الأدوات المساعدة
# ==================================================
def clean_text(text):
if not text:
return ""
text = text.strip()
text = re.sub(r"\s+", " ", text)
return text
def contains_bad_words(text):
bad_words = ["يلعن", "تبن", "سافل", "كلب", "خنزير", "عاهرة", "قحبة"]
pattern = r'\b(' + '|'.join(bad_words) + r')\b'
return bool(re.search(pattern, text.lower()))
def escape_safe(text):
return html.escape(text) if text else ""
def get_ip_hash(request: gr.Request):
try:
if request and hasattr(request, 'client') and request.client:
return hashlib.sha256(request.client.host.encode()).hexdigest()[:16]
except:
pass
return "unknown"
def format_time(timestamp):
try:
dt = datetime.fromisoformat(timestamp)
diff = datetime.now() - dt
if diff.days > 7:
return dt.strftime("%Y/%m/%d")
elif diff.days > 0:
return f"منذ {diff.days} يوم"
elif diff.seconds >= 3600:
return f"منذ {diff.seconds // 3600} ساعة"
elif diff.seconds >= 60:
return f"منذ {diff.seconds // 60} دقيقة"
return "الآن"
except:
return "غير معروف"
# ==================================================
# الإحصائيات
# ==================================================
def get_stats():
"""جلب الإحصائيات"""
conn = get_connection()
c = conn.cursor()
try:
c.execute("SELECT COUNT(*) FROM testimonials")
total = c.fetchone()[0]
c.execute("SELECT AVG(rating) FROM testimonials")
avg = c.fetchone()[0]
avg_rating = round(avg, 1) if avg else 0
except Exception as e:
logger.error(f"خطأ في الإحصائيات: {e}")
return {"total": 0, "avg_rating": 0}
finally:
conn.close()
return {
"total": total,
"avg_rating": avg_rating
}
def render_stats(stats):
"""عرض الإحصائيات"""
return f"""
<div style="
display:grid;
grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));
gap:1rem;
margin-bottom:1.5rem;
">
<div class="stat-card" style="
background:#1a1a2e;
padding:1.5rem;
border-radius:16px;
text-align:center;
border:1px solid #2d2d44;
transition: all 0.3s ease;
">
<div style="font-size:3rem;font-weight:700;color:#818cf8;">{stats['total']}</div>
<div style="color:#94a3b8;font-size:0.95rem;">📝 عدد الآراء</div>
</div>
<div class="stat-card" style="
background:#1a1a2e;
padding:1.5rem;
border-radius:16px;
text-align:center;
border:1px solid #2d2d44;
transition: all 0.3s ease;
">
<div style="font-size:3rem;font-weight:700;color:#fbbf24;">{stats['avg_rating']}</div>
<div style="color:#94a3b8;font-size:0.95rem;">⭐ متوسط التقييم</div>
</div>
</div>
"""
# ==================================================
# إضافة رأي
# ==================================================
def add_testimonial(name, role, content, rating, request: gr.Request):
content = clean_text(content)
if not content:
return """
<div style="
background: #450a0a;
color: #fca5a5;
padding: 12px 20px;
border-radius: 12px;
animation: slideDown 0.3s ease-out;
margin-bottom: 1rem;
border-right: 4px solid #ef4444;
">
❌ الرجاء كتابة رأيك
</div>
""", "", "", update_display(), "**0** / 800 حرف"
if len(content) > 800:
return """
<div style="
background: #450a0a;
color: #fca5a5;
padding: 12px 20px;
border-radius: 12px;
animation: slideDown 0.3s ease-out;
margin-bottom: 1rem;
border-right: 4px solid #ef4444;
">
❌ الحد الأقصى 800 حرف
</div>
""", "", "", update_display(), f"**{len(content)}** / 800 حرف"
if contains_bad_words(content):
return """
<div style="
background: #450a0a;
color: #fca5a5;
padding: 12px 20px;
border-radius: 12px;
animation: slideDown 0.3s ease-out;
margin-bottom: 1rem;
border-right: 4px solid #ef4444;
">
❌ يوجد كلمات غير مناسبة
</div>
""", "", "", update_display(), f"**{len(content)}** / 800 حرف"
ip_hash = get_ip_hash(request)
conn = get_connection()
c = conn.cursor()
# مكافحة البريد المزعج
one_hour_ago = (datetime.now() - timedelta(hours=1)).isoformat()
c.execute("SELECT COUNT(*) FROM testimonials WHERE ip_hash = ? AND timestamp >= ?",
(ip_hash, one_hour_ago))
if c.fetchone()[0] >= 3:
conn.close()
return """
<div style="
background: #450a0a;
color: #fca5a5;
padding: 12px 20px;
border-radius: 12px;
animation: slideDown 0.3s ease-out;
margin-bottom: 1rem;
border-right: 4px solid #ef4444;
">
❌ وصلت الحد المسموح (3 آراء في الساعة)
</div>
""", "", "", update_display(), "**0** / 800 حرف"
name = clean_text(name) or "مجهول"
role = clean_text(role) or ""
c.execute("""
INSERT INTO testimonials (name, role, content, rating, timestamp, is_highlighted, ip_hash)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (name, role, content, int(rating), datetime.now().isoformat(), 0, ip_hash))
conn.commit()
conn.close()
upload_db_to_hub()
return """
<div style="
background: #064e3b;
color: #6ee7b7;
padding: 12px 20px;
border-radius: 12px;
animation: slideDown 0.3s ease-out;
margin-bottom: 1rem;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.2);
border-right: 4px solid #10b981;
">
✅ تم نشر رأيك بنجاح! 🎉
</div>
""", "", "", update_display(), "**0** / 800 حرف"
# ==================================================
# جلب الآراء
# ==================================================
def get_testimonials(page=1, per_page=5):
conn = get_connection()
c = conn.cursor()
query = "SELECT id, name, role, content, rating, timestamp, is_highlighted FROM testimonials"
query += " ORDER BY timestamp DESC"
offset = (page - 1) * per_page
query += " LIMIT ? OFFSET ?"
c.execute(query, (per_page, offset))
rows = c.fetchall()
# العدد الإجمالي
c.execute("SELECT COUNT(*) FROM testimonials")
total = c.fetchone()[0]
conn.close()
return rows, total, (total + per_page - 1) // per_page if total > 0 else 1
# ==================================================
# عرض البطاقات
# ==================================================
def render_stars(rating):
"""عرض التقييم بشكل جميل"""
full_stars = '⭐' * int(rating)
empty_stars = '☆' * (5 - int(rating))
return f'<span style="font-size:1.1rem;color:#fbbf24;">{full_stars}{empty_stars}</span>'
def render_card(row):
"""عرض بطاقة الرأي"""
(id, name, role, content, rating, timestamp, is_highlighted) = row
highlight_style = ""
highlight_badge = ""
if is_highlighted:
highlight_style = "border-right:4px solid #fbbf24; background:linear-gradient(135deg, #1a1a2e, #2d1f0a);"
highlight_badge = """
<span style="
background: #fbbf24;
color: #1a1a2e;
padding: 2px 10px;
border-radius: 999px;
font-size: 0.7rem;
font-weight: 600;
">
⭐ مميز
</span>
"""
role_html = f'<span style="background:#312e81;color:#818cf8;padding:0.2rem 0.6rem;border-radius:999px;font-size:0.75rem;">{escape_safe(role)}</span>' if role else ""
return f"""
<div class="testimonial-card" id="card-{id}" style="
background:#1a1a2e;
border-radius:20px;
padding:1.2rem 1.5rem;
margin-bottom:1rem;
border:1px solid #2d2d44;
{highlight_style}
animation: slideIn 0.5s ease-out;
transition: all 0.3s ease;
">
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:0.5rem;">
<div style="display:flex;align-items:center;gap:0.8rem;">
<div style="
width:42px;
height:42px;
border-radius:50%;
background:linear-gradient(135deg, #667eea, #764ba2);
display:flex;
align-items:center;
justify-content:center;
color:white;
font-weight:700;
font-size:1.2rem;
flex-shrink:0;
">
{escape_safe(name[0].upper())}
</div>
<div>
<div style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;">
<span style="font-weight:700;color:#e2e8f0;font-size:1.05rem;">{escape_safe(name)}</span>
{role_html}
{highlight_badge}
</div>
<div style="display:flex;align-items:center;gap:0.5rem;margin-top:0.2rem;">
<span style="color:#64748b;font-size:0.75rem;">{format_time(timestamp)}</span>
</div>
</div>
</div>
</div>
<div style="
margin-top:0.8rem;
color:#cbd5e1;
line-height:1.8;
font-size:0.98rem;
padding:0.5rem 0;
">
{escape_safe(content)}
</div>
<div style="
margin-top:0.8rem;
padding-top:0.8rem;
border-top:1px solid #2d2d44;
display:flex;
justify-content:flex-end;
align-items:center;
flex-wrap:wrap;
gap:0.5rem;
">
{render_stars(rating)}
</div>
</div>
"""
# ==================================================
# تحديث العرض
# ==================================================
def update_display(page=1):
rows, total, total_pages = get_testimonials(page)
if not rows:
return """
<div style="
background:#1a1a2e;
border-radius:16px;
padding:3rem 2rem;
text-align:center;
border:2px dashed #2d2d44;
color:#64748b;
">
<div style="font-size:3rem;margin-bottom:1rem;">✨</div>
<div style="font-size:1.2rem;font-weight:600;color:#94a3b8;">لا توجد آراء حالياً</div>
<div style="font-size:0.9rem;margin-top:0.5rem;color:#64748b;">كن أول من يشارك رأيه!</div>
</div>
"""
html = ""
for row in rows:
html += render_card(row)
# أزرار التنقل
if total_pages > 1:
html += f"""
<div style="
display:flex;
justify-content:center;
align-items:center;
gap:0.8rem;
margin-top:1.2rem;
padding:0.5rem;
">
"""
if page > 1:
html += f'''
<button onclick="changePage({page-1})" style="
padding:0.5rem 1.5rem;
background:linear-gradient(135deg, #667eea, #764ba2);
color:white;
border:none;
border-radius:50px;
cursor:pointer;
font-size:0.9rem;
font-weight:600;
transition:all 0.3s ease;
box-shadow:0 4px 15px rgba(102,126,234,0.3);
">
⬅ السابق
</button>
'''
html += f'''
<span style="
padding:0.4rem 1.2rem;
background:#2d2d44;
border-radius:50px;
color:#94a3b8;
font-weight:600;
font-size:0.9rem;
">
{page} / {total_pages}
</span>
'''
if page < total_pages:
html += f'''
<button onclick="changePage({page+1})" style="
padding:0.5rem 1.5rem;
background:linear-gradient(135deg, #667eea, #764ba2);
color:white;
border:none;
border-radius:50px;
cursor:pointer;
font-size:0.9rem;
font-weight:600;
transition:all 0.3s ease;
box-shadow:0 4px 15px rgba(102,126,234,0.3);
">
التالي ➡
</button>
'''
html += "</div>"
return html
# ==================================================
# تصدير البيانات
# ==================================================
def export_data():
conn = get_connection()
c = conn.cursor()
c.execute("SELECT name, role, content, rating, timestamp FROM testimonials ORDER BY timestamp DESC")
rows = c.fetchall()
conn.close()
if not rows:
return "لا توجد بيانات للتصدير"
data = []
for row in rows:
data.append({
"الاسم": row[0],
"الدور": row[1],
"الرأي": row[2],
"التقييم": row[3],
"التاريخ": row[4]
})
return json.dumps(data, ensure_ascii=False, indent=2)
# ==================================================
# لوحة تحكم المشرف
# ==================================================
def admin_panel():
with gr.Blocks() as admin:
gr.Markdown("### 🔐 لوحة تحكم المشرف")
with gr.Row():
admin_key = gr.Textbox(label="مفتاح المشرف", type="password", scale=1)
action = gr.Radio(choices=[("حذف", "delete"), ("تمييز", "highlight")], label="الإجراء", scale=1)
testimonial_id = gr.Number(label="رقم الرأي", precision=0, minimum=1, scale=1)
execute_btn = gr.Button("تنفيذ", variant="primary")
result = gr.Markdown()
def admin_action(key, action, id):
expected = os.getenv("ADMIN_KEY", "admin123")
if key != expected:
return "❌ مفتاح المشرف غير صحيح"
conn = get_connection()
c = conn.cursor()
if action == "delete":
c.execute("DELETE FROM testimonials WHERE id = ?", (int(id),))
result_text = "✅ تم حذف الرأي"
else:
c.execute("""
UPDATE testimonials
SET is_highlighted = CASE WHEN is_highlighted = 1 THEN 0 ELSE 1 END
WHERE id = ?
""", (int(id),))
result_text = "✅ تم تحديث التمييز"
conn.commit()
conn.close()
upload_db_to_hub()
return result_text + " (تم التحديث)"
execute_btn.click(
fn=admin_action,
inputs=[admin_key, action, testimonial_id],
outputs=result
)
return admin
# ==================================================
# الواجهة الرئيسية
# ==================================================
def create_interface():
with gr.Blocks() as demo:
# ===== الهيدر =====
gr.HTML("""
<div style="
text-align:center;
padding: 2.5rem 2rem;
background: linear-gradient(135deg, #1a1a2e 0%, #2d1f4a 50%, #1a1a2e 100%);
border-radius: 24px;
margin-bottom: 2rem;
position: relative;
overflow: hidden;
border: 1px solid #2d2d44;
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
">
<!-- خلفية متحركة -->
<div style="
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle at 30% 40%, rgba(99,102,241,0.1) 0%, transparent 70%);
animation: shimmer 8s infinite;
"></div>
<div style="position:relative;z-index:1;">
<h1 style="
font-size:3rem;
font-weight:800;
margin:0;
background: linear-gradient(135deg, #818cf8, #a78bfa, #c084fc);
-webkit-background-clip:text;
-webkit-text-fill-color:transparent;
text-shadow: none;
">
من منظورهم
</h1>
<p style="
color:#94a3b8;
margin:0.5rem 0 0 0;
font-size:1.1rem;
font-weight:300;
">
.شارك رأيك بكل شفافية، فكل كلمة تُحدث فرقًا
</p>
</div>
</div>
""")
# ===== الإحصائيات =====
stats_display = gr.HTML()
# ===== المحتوى الرئيسي =====
with gr.Row(equal_height=False):
# ===== العمود الأيسر: إضافة رأي =====
with gr.Column(scale=1, min_width=300):
name_input = gr.Textbox(
label="الاسم",
placeholder="اختياري"
)
role_input = gr.Textbox(
label="الدور",
placeholder="مثال: مهندس برمجيات"
)
rating_input = gr.Slider(
minimum=1,
maximum=5,
value=5,
step=1,
label="⭐ التقييم"
)
content_input = gr.Textbox(
label="الرأي",
placeholder="اكتب رأيك هنا...",
lines=5,
max_lines=8
)
# مؤشر عدد الأحرف
char_count = gr.Markdown("**0** / 800 حرف")
def update_char_count(content):
return f"**{len(content)}** / 800 حرف"
content_input.change(
fn=update_char_count,
inputs=content_input,
outputs=char_count
)
with gr.Row():
submit_btn = gr.Button(
"📢 نشر الرأي",
variant="primary",
size="lg",
elem_classes="glow-button",
scale=2
)
clear_btn = gr.Button("🗑️ مسح", variant="secondary", size="lg", scale=1)
status_output = gr.Markdown()
# ===== العمود الأيمن: عرض الآراء =====
with gr.Column(scale=2, min_width=400):
gr.Markdown("### 💬 آراء المستخدمين")
with gr.Row():
refresh_btn = gr.Button("🔄 تحديث", variant="secondary", size="sm")
testimonials_display = gr.HTML()
# ===== أدوات إضافية =====
with gr.Row():
with gr.Column(scale=1):
with gr.Accordion("⚙️ أدوات إضافية", open=False):
with gr.Row():
export_btn = gr.Button("📥 تصدير البيانات (JSON)", size="sm")
export_output = gr.Textbox(label="", lines=3, visible=False)
admin_btn = gr.Button("🔐 لوحة المشرف", size="sm", variant="stop")
admin_interface = admin_panel()
admin_interface.visible = False
def toggle_admin():
return gr.update(visible=not admin_interface.visible)
admin_btn.click(
fn=toggle_admin,
outputs=admin_interface
)
# ===== الأحداث =====
def update_stats():
return render_stats(get_stats())
def full_update():
stats_html = render_stats(get_stats())
display = update_display()
return stats_html, display
# زر النشر
submit_btn.click(
fn=add_testimonial,
inputs=[name_input, role_input, content_input, rating_input],
outputs=[status_output, name_input, role_input, testimonials_display, char_count]
).then(
fn=update_stats,
outputs=[stats_display]
)
# زر التحديث
refresh_btn.click(
fn=full_update,
outputs=[stats_display, testimonials_display]
)
# زر التصدير
def show_export():
data = export_data()
return gr.update(visible=True, value=data)
export_btn.click(
fn=show_export,
outputs=export_output
)
# زر المسح
clear_btn.click(
fn=lambda: ("", "", "", "", "**0** / 800 حرف"),
outputs=[name_input, role_input, content_input, status_output, char_count]
)
# تحميل أولي
demo.load(
fn=full_update,
outputs=[stats_display, testimonials_display]
)
return demo
# ==================================================
# التشغيل
# ==================================================
if __name__ == "__main__":
logger.info("جاري تحميل قاعدة البيانات...")
if not download_db_from_hub():
logger.info("إنشاء قاعدة بيانات جديدة...")
init_db()
upload_db_to_hub()
else:
init_db()
demo = create_interface()
demo.launch(
server_name="0.0.0.0",
server_port=7860,
theme=gr.themes.Soft(
primary_hue="indigo",
secondary_hue="purple",
neutral_hue="slate",
),
css=CUSTOM_CSS
)