Spaces:
Sleeping
Sleeping
Upload 7 files
Browse files- admin_dashboard.py +402 -177
- app.py +77 -239
- banking_assistant.py +469 -0
- banking_model.py +146 -0
- filemanager.py +394 -0
- ml_banking_model.py +141 -0
- requirements.txt +12 -5
admin_dashboard.py
CHANGED
|
@@ -1,189 +1,414 @@
|
|
| 1 |
-
import
|
| 2 |
-
import
|
| 3 |
-
import
|
| 4 |
-
import
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
if (!file) return;
|
| 62 |
-
|
| 63 |
-
const formData = new FormData();
|
| 64 |
-
formData.append('file', file);
|
| 65 |
-
|
| 66 |
-
try {
|
| 67 |
-
const response = await fetch('/api/upload-document', {
|
| 68 |
-
method: 'POST',
|
| 69 |
-
body: formData
|
| 70 |
-
});
|
| 71 |
-
|
| 72 |
-
if (response.ok) {
|
| 73 |
-
setUpdateStatus('فایل با موفقیت آپلود شد');
|
| 74 |
-
} else {
|
| 75 |
-
throw new Error('خطا در آپلود فایل');
|
| 76 |
-
}
|
| 77 |
-
} catch (error) {
|
| 78 |
-
setUpdateStatus('خطا در آپلود فایل');
|
| 79 |
}
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
</div>
|
| 106 |
-
<
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
{/* Questions Card */}
|
| 110 |
-
<div className="bg-white/30 backdrop-blur-lg rounded-2xl p-6 shadow-[5px_5px_20px_rgba(0,0,0,0.1),-5px_-5px_20px_rgba(255,255,255,0.8)] transition-all duration-300 hover:shadow-[8px_8px_25px_rgba(0,0,0,0.12),-8px_-8px_25px_rgba(255,255,255,0.9)] hover:transform hover:-translate-y-1">
|
| 111 |
-
<div className="flex items-center justify-between">
|
| 112 |
-
<Users className="text-green-500" size={24} />
|
| 113 |
-
<h3 className="text-lg font-semibold text-gray-700">تعداد سوالات</h3>
|
| 114 |
</div>
|
| 115 |
-
<
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
{/* Accuracy Card */}
|
| 119 |
-
<div className="bg-white/30 backdrop-blur-lg rounded-2xl p-6 shadow-[5px_5px_20px_rgba(0,0,0,0.1),-5px_-5px_20px_rgba(255,255,255,0.8)] transition-all duration-300 hover:shadow-[8px_8px_25px_rgba(0,0,0,0.12),-8px_-8px_25px_rgba(255,255,255,0.9)] hover:transform hover:-translate-y-1">
|
| 120 |
-
<div className="flex items-center justify-between">
|
| 121 |
-
<ThumbsUp className="text-purple-500" size={24} />
|
| 122 |
-
<h3 className="text-lg font-semibold text-gray-700">دقت پاسخگویی</h3>
|
| 123 |
</div>
|
| 124 |
-
<p className="text-3xl font-bold text-purple-600 mt-4">{stats.accuracy}%</p>
|
| 125 |
-
</div>
|
| 126 |
</div>
|
|
|
|
|
|
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
<
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
</div>
|
| 184 |
-
</div>
|
| 185 |
</div>
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import plotly.graph_objects as go
|
| 3 |
+
import plotly.express as px
|
| 4 |
+
import numpy as np
|
| 5 |
+
from datetime import datetime, timedelta
|
| 6 |
+
|
| 7 |
+
import streamlit as st
|
| 8 |
+
import pandas as pd
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
# تنظیمات صفحه
|
| 12 |
+
st.set_page_config(
|
| 13 |
+
page_title="داشبورد مدیریت",
|
| 14 |
+
page_icon="👤",
|
| 15 |
+
layout="wide"
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
# استایلهای داشبورد
|
| 19 |
+
st.markdown("""
|
| 20 |
+
<style>
|
| 21 |
+
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;700&display=swap');
|
| 22 |
+
|
| 23 |
+
* {
|
| 24 |
+
font-family: 'Vazirmatn', sans-serif;
|
| 25 |
+
direction: rtl;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.dashboard-container {
|
| 29 |
+
background: white;
|
| 30 |
+
border-radius: 15px;
|
| 31 |
+
padding: 25px;
|
| 32 |
+
margin: 20px 0;
|
| 33 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.stat-card {
|
| 37 |
+
background: linear-gradient(135deg, #6B73FF 0%, #000DFF 100%);
|
| 38 |
+
color: white;
|
| 39 |
+
padding: 20px;
|
| 40 |
+
border-radius: 10px;
|
| 41 |
+
margin: 10px;
|
| 42 |
+
text-align: center;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.file-upload {
|
| 46 |
+
border: 2px dashed #ccc;
|
| 47 |
+
padding: 20px;
|
| 48 |
+
border-radius: 10px;
|
| 49 |
+
text-align: center;
|
| 50 |
+
margin: 20px 0;
|
| 51 |
+
transition: all 0.3s ease;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.file-upload:hover {
|
| 55 |
+
border-color: #2196F3;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.action-button {
|
| 59 |
+
background: #2196F3;
|
| 60 |
+
color: white;
|
| 61 |
+
padding: 10px 20px;
|
| 62 |
+
border: none;
|
| 63 |
+
border-radius: 5px;
|
| 64 |
+
cursor: pointer;
|
| 65 |
+
transition: all 0.3s ease;
|
| 66 |
}
|
| 67 |
+
|
| 68 |
+
.action-button:hover {
|
| 69 |
+
background: #1976D2;
|
| 70 |
+
transform: scale(1.05);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
| 72 |
+
|
| 73 |
+
.file-list {
|
| 74 |
+
margin-top: 20px;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.file-item {
|
| 78 |
+
display: flex;
|
| 79 |
+
justify-content: space-between;
|
| 80 |
+
align-items: center;
|
| 81 |
+
padding: 15px;
|
| 82 |
+
background: #f5f5f5;
|
| 83 |
+
border-radius: 8px;
|
| 84 |
+
margin: 10px 0;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.delete-button {
|
| 88 |
+
background: #ff4444;
|
| 89 |
+
color: white;
|
| 90 |
+
padding: 5px 15px;
|
| 91 |
+
border: none;
|
| 92 |
+
border-radius: 5px;
|
| 93 |
+
cursor: pointer;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.delete-button:hover {
|
| 97 |
+
background: #cc0000;
|
| 98 |
+
}
|
| 99 |
+
</style>
|
| 100 |
+
""", unsafe_allow_html=True)
|
| 101 |
+
|
| 102 |
+
# هدر داشبورد
|
| 103 |
+
st.title("🎛️ داشبورد مدیریت")
|
| 104 |
+
|
| 105 |
+
# آمار کلی
|
| 106 |
+
col1, col2, col3 = st.columns(3)
|
| 107 |
+
with col1:
|
| 108 |
+
st.markdown("""
|
| 109 |
+
<div class="stat-card">
|
| 110 |
+
<h2>۱۲۳</h2>
|
| 111 |
+
<p>تعداد فایلها</p>
|
| 112 |
+
</div>
|
| 113 |
+
""", unsafe_allow_html=True)
|
| 114 |
+
|
| 115 |
+
with col2:
|
| 116 |
+
st.markdown("""
|
| 117 |
+
<div class="stat-card">
|
| 118 |
+
<h2>۴۵۶</h2>
|
| 119 |
+
<p>تعداد پرسشها</p>
|
| 120 |
</div>
|
| 121 |
+
""", unsafe_allow_html=True)
|
| 122 |
+
|
| 123 |
+
with col3:
|
| 124 |
+
st.markdown("""
|
| 125 |
+
<div class="stat-card">
|
| 126 |
+
<h2>۷۸۹</h2>
|
| 127 |
+
<p>پاسخهای موفق</p>
|
| 128 |
+
</div>
|
| 129 |
+
""", unsafe_allow_html=True)
|
| 130 |
|
| 131 |
+
# بخش آپلود فایل
|
| 132 |
+
st.markdown("""
|
| 133 |
+
<div class="dashboard-container">
|
| 134 |
+
<h3>📤 آپلود فایل جدید</h3>
|
| 135 |
+
<div class="file-upload">
|
| 136 |
+
<p>فایل خود را اینجا رها کنید یا کلیک کنید</p>
|
| 137 |
+
<input type="file" accept=".csv,.json,.txt">
|
| 138 |
+
</div>
|
| 139 |
+
</div>
|
| 140 |
+
""", unsafe_allow_html=True)
|
| 141 |
+
|
| 142 |
+
# لیست فایلها
|
| 143 |
+
st.markdown("""
|
| 144 |
+
<div class="dashboard-container">
|
| 145 |
+
<h3>📁 مدیریت فایلها</h3>
|
| 146 |
+
<div class="file-list">
|
| 147 |
+
<div class="file-item">
|
| 148 |
+
<span>banking_data.csv</span>
|
| 149 |
+
<button class="delete-button">حذف</button>
|
| 150 |
</div>
|
| 151 |
+
<div class="file-item">
|
| 152 |
+
<span>customer_qa.json</span>
|
| 153 |
+
<button class="delete-button">حذف</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
</div>
|
| 155 |
+
<div class="file-item">
|
| 156 |
+
<span>training_data.txt</span>
|
| 157 |
+
<button class="delete-button">حذف</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
</div>
|
|
|
|
|
|
|
| 159 |
</div>
|
| 160 |
+
</div>
|
| 161 |
+
""", unsafe_allow_html=True)
|
| 162 |
|
| 163 |
+
# تنظیمات مدل
|
| 164 |
+
st.markdown("""
|
| 165 |
+
<div class="dashboard-container">
|
| 166 |
+
<h3>⚙️ تنظیمات مدل</h3>
|
| 167 |
+
<div style="margin: 20px 0;">
|
| 168 |
+
<label>دمای مدل:</label>
|
| 169 |
+
<input type="range" min="0" max="100" value="70">
|
| 170 |
+
</div>
|
| 171 |
+
<div style="margin: 20px 0;">
|
| 172 |
+
<label>حداکثر طول پاسخ:</label>
|
| 173 |
+
<input type="number" value="512">
|
| 174 |
+
</div>
|
| 175 |
+
<button class="action-button">ذخیره تنظیمات</button>
|
| 176 |
+
</div>
|
| 177 |
+
""", unsafe_allow_html=True)
|
| 178 |
+
|
| 179 |
+
if __name__ == "__main__":
|
| 180 |
+
st.markdown("""
|
| 181 |
+
<script>
|
| 182 |
+
// اضافه کردن عملکرد به دکمهها
|
| 183 |
+
document.querySelectorAll('.delete-button').forEach(button => {
|
| 184 |
+
button.addEventListener('click', function() {
|
| 185 |
+
if (confirm('آیا از حذف این فایل اطمینان دارید؟')) {
|
| 186 |
+
this.closest('.file-item').remove();
|
| 187 |
+
}
|
| 188 |
+
});
|
| 189 |
+
});
|
| 190 |
+
</script>
|
| 191 |
+
""", unsafe_allow_html=True)
|
| 192 |
+
|
| 193 |
+
# اضافه کردن بخش نمودارها
|
| 194 |
+
st.markdown("""
|
| 195 |
+
<div class="dashboard-container">
|
| 196 |
+
<h3>📊 تحلیل عملکرد مدل</h3>
|
| 197 |
+
</div>
|
| 198 |
+
""", unsafe_allow_html=True)
|
| 199 |
+
|
| 200 |
+
# نمودار نرخ یادگیری
|
| 201 |
+
col1, col2 = st.columns(2)
|
| 202 |
+
with col1:
|
| 203 |
+
# نمودار نرخ یادگیری
|
| 204 |
+
dates = [datetime.now() - timedelta(days=x) for x in range(30)]
|
| 205 |
+
learning_rate = [0.001 * np.exp(-x/10) for x in range(30)]
|
| 206 |
+
|
| 207 |
+
fig_learning = go.Figure()
|
| 208 |
+
fig_learning.add_trace(go.Scatter(
|
| 209 |
+
x=dates,
|
| 210 |
+
y=learning_rate,
|
| 211 |
+
mode='lines+markers',
|
| 212 |
+
name='نرخ یادگیری',
|
| 213 |
+
line=dict(color='#2196F3', width=3)
|
| 214 |
+
))
|
| 215 |
+
fig_learning.update_layout(
|
| 216 |
+
title='نرخ یادگیری در طول زمان',
|
| 217 |
+
xaxis_title='تاریخ',
|
| 218 |
+
yaxis_title='نرخ یادگیری',
|
| 219 |
+
template='plotly_white',
|
| 220 |
+
dir='rtl'
|
| 221 |
+
)
|
| 222 |
+
st.plotly_chart(fig_learning, use_container_width=True)
|
| 223 |
+
|
| 224 |
+
with col2:
|
| 225 |
+
# نمودار دقت مدل
|
| 226 |
+
accuracy_data = np.linspace(0.7, 0.95, 30)
|
| 227 |
+
fig_accuracy = go.Figure()
|
| 228 |
+
fig_accuracy.add_trace(go.Scatter(
|
| 229 |
+
x=dates,
|
| 230 |
+
y=accuracy_data,
|
| 231 |
+
mode='lines+markers',
|
| 232 |
+
name='دقت مدل',
|
| 233 |
+
line=dict(color='#4CAF50', width=3)
|
| 234 |
+
))
|
| 235 |
+
fig_accuracy.update_layout(
|
| 236 |
+
title='پیشرفت دقت مدل',
|
| 237 |
+
xaxis_title='تاریخ',
|
| 238 |
+
yaxis_title='دقت',
|
| 239 |
+
template='plotly_white',
|
| 240 |
+
dir='rtl'
|
| 241 |
+
)
|
| 242 |
+
st.plotly_chart(fig_accuracy, use_container_width=True)
|
| 243 |
+
|
| 244 |
+
# نمودار پاسخهای صحیح و غلط
|
| 245 |
+
labels = ['پاسخهای صحیح', 'پاسخهای غلط']
|
| 246 |
+
values = [85, 15]
|
| 247 |
+
fig_pie = go.Figure(data=[go.Pie(
|
| 248 |
+
labels=labels,
|
| 249 |
+
values=values,
|
| 250 |
+
hole=.3,
|
| 251 |
+
marker_colors=['#4CAF50', '#f44336']
|
| 252 |
+
)])
|
| 253 |
+
fig_pie.update_layout(title='نسبت پاسخهای صحیح به غلط')
|
| 254 |
+
st.plotly_chart(fig_pie, use_container_width=True)
|
| 255 |
+
|
| 256 |
+
# چتبات آموزش سریع
|
| 257 |
+
st.markdown("""
|
| 258 |
+
<div class="dashboard-container">
|
| 259 |
+
<h3>🤖 آموزش سریع با چتبات</h3>
|
| 260 |
+
<div class="chat-trainer">
|
| 261 |
+
<input type="text" placeholder="دستور آموزشی خود را وارد کنید..." class="trainer-input">
|
| 262 |
+
<button class="action-button">ارسال دستور</button>
|
| 263 |
</div>
|
|
|
|
| 264 |
</div>
|
| 265 |
+
""", unsafe_allow_html=True)
|
| 266 |
+
|
| 267 |
+
# دکمه دسترسی به نالج بیس
|
| 268 |
+
st.markdown("""
|
| 269 |
+
<div class="dashboard-container">
|
| 270 |
+
<h3>📚 مدیریت نالج بیس</h3>
|
| 271 |
+
<button class="action-button" onclick="window.open('/filemanager.py')">
|
| 272 |
+
<i class="fas fa-folder-open"></i>
|
| 273 |
+
دسترسی به فایل منیجر
|
| 274 |
+
</button>
|
| 275 |
+
</div>
|
| 276 |
+
""", unsafe_allow_html=True)
|
| 277 |
+
|
| 278 |
+
# اضافه کردن استایلهای جدید
|
| 279 |
+
st.markdown("""
|
| 280 |
+
<style>
|
| 281 |
+
.chat-trainer {
|
| 282 |
+
display: flex;
|
| 283 |
+
gap: 10px;
|
| 284 |
+
margin: 20px 0;
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
.trainer-input {
|
| 288 |
+
flex: 1;
|
| 289 |
+
padding: 12px;
|
| 290 |
+
border: 2px solid #f0f0f0;
|
| 291 |
+
border-radius: 8px;
|
| 292 |
+
font-size: 14px;
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
.plotly-chart {
|
| 296 |
+
background: white;
|
| 297 |
+
border-radius: 10px;
|
| 298 |
+
padding: 15px;
|
| 299 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
| 300 |
+
}
|
| 301 |
+
</style>
|
| 302 |
+
""", unsafe_allow_html=True)
|
| 303 |
+
# اضافه کردن نمودار هیتمپ برای نمایش ماتریس خطا
|
| 304 |
+
confusion_matrix = np.array([
|
| 305 |
+
[850, 50],
|
| 306 |
+
[30, 70]
|
| 307 |
+
])
|
| 308 |
+
|
| 309 |
+
fig_heatmap = px.imshow(
|
| 310 |
+
confusion_matrix,
|
| 311 |
+
labels=dict(x="پیشبینی", y="مقدار واقعی"),
|
| 312 |
+
x=['مثبت', 'منفی'],
|
| 313 |
+
y=['مثبت', 'منفی'],
|
| 314 |
+
color_continuous_scale="RdBu",
|
| 315 |
+
title="ماتریس خطا"
|
| 316 |
+
)
|
| 317 |
+
fig_heatmap.update_layout(
|
| 318 |
+
template='plotly_white',
|
| 319 |
+
dir='rtl',
|
| 320 |
+
width=600,
|
| 321 |
+
height=500
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
# نمودار روند زمانی با قابلیت فیلتر
|
| 325 |
+
training_metrics = pd.DataFrame({
|
| 326 |
+
'تاریخ': pd.date_range(start='2023-01-01', periods=100),
|
| 327 |
+
'دقت': np.random.normal(0.85, 0.05, 100).cumsum()/100,
|
| 328 |
+
'recall': np.random.normal(0.80, 0.05, 100).cumsum()/100,
|
| 329 |
+
'f1_score': np.random.normal(0.82, 0.05, 100).cumsum()/100
|
| 330 |
+
})
|
| 331 |
+
|
| 332 |
+
fig_metrics = px.line(
|
| 333 |
+
training_metrics,
|
| 334 |
+
x='تاریخ',
|
| 335 |
+
y=['دقت', 'recall', 'f1_score'],
|
| 336 |
+
title='روند معیارهای ارزیابی',
|
| 337 |
+
labels={'value': 'مقدار', 'variable': 'معیار'},
|
| 338 |
+
template='plotly_white'
|
| 339 |
+
)
|
| 340 |
+
fig_metrics.update_layout(
|
| 341 |
+
showlegend=True,
|
| 342 |
+
legend_title_text='معیارها',
|
| 343 |
+
hovermode='x unified',
|
| 344 |
+
updatemenus=[
|
| 345 |
+
dict(
|
| 346 |
+
buttons=list([
|
| 347 |
+
dict(
|
| 348 |
+
args=[{"visible": [True, True, True]}],
|
| 349 |
+
label="همه",
|
| 350 |
+
method="restyle"
|
| 351 |
+
),
|
| 352 |
+
dict(
|
| 353 |
+
args=[{"visible": [True, False, False]}],
|
| 354 |
+
label="دقت",
|
| 355 |
+
method="restyle"
|
| 356 |
+
),
|
| 357 |
+
dict(
|
| 358 |
+
args=[{"visible": [False, True, False]}],
|
| 359 |
+
label="Recall",
|
| 360 |
+
method="restyle"
|
| 361 |
+
),
|
| 362 |
+
dict(
|
| 363 |
+
args=[{"visible": [False, False, True]}],
|
| 364 |
+
label="F1 Score",
|
| 365 |
+
method="restyle"
|
| 366 |
+
)
|
| 367 |
+
]),
|
| 368 |
+
direction="down",
|
| 369 |
+
showactive=True,
|
| 370 |
+
x=0.1,
|
| 371 |
+
y=1.1
|
| 372 |
+
)
|
| 373 |
+
]
|
| 374 |
+
)
|
| 375 |
+
|
| 376 |
+
# نمودار توزیع خطا
|
| 377 |
+
errors = np.random.normal(0, 1, 1000)
|
| 378 |
+
fig_dist = px.histogram(
|
| 379 |
+
errors,
|
| 380 |
+
nbins=50,
|
| 381 |
+
title='توزیع خطای پیشبینی',
|
| 382 |
+
labels={'value': 'خطا', 'count': 'تعداد'},
|
| 383 |
+
template='plotly_white'
|
| 384 |
+
)
|
| 385 |
+
fig_dist.update_layout(
|
| 386 |
+
bargap=0.1,
|
| 387 |
+
showlegend=False
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
# اضافه کردن کنترلهای تعاملی
|
| 391 |
+
st.sidebar.markdown("## تنظیمات نمودارها")
|
| 392 |
+
time_range = st.sidebar.slider(
|
| 393 |
+
"بازه زمانی (روز)",
|
| 394 |
+
min_value=7,
|
| 395 |
+
max_value=100,
|
| 396 |
+
value=30
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
metric_threshold = st.sidebar.number_input(
|
| 400 |
+
"آستانه دقت",
|
| 401 |
+
min_value=0.0,
|
| 402 |
+
max_value=1.0,
|
| 403 |
+
value=0.8,
|
| 404 |
+
step=0.05
|
| 405 |
+
)
|
| 406 |
|
| 407 |
+
# نمایش نمودارها با چینش جدید
|
| 408 |
+
col1, col2 = st.columns(2)
|
| 409 |
+
with col1:
|
| 410 |
+
st.plotly_chart(fig_metrics, use_container_width=True)
|
| 411 |
+
st.plotly_chart(fig_heatmap, use_container_width=True)
|
| 412 |
+
with col2:
|
| 413 |
+
st.plotly_chart(fig_dist, use_container_width=True)
|
| 414 |
+
st.plotly_chart(fig_pie, use_container_width=True)
|
app.py
CHANGED
|
@@ -1,239 +1,77 @@
|
|
| 1 |
-
import streamlit as st
|
| 2 |
-
import
|
| 3 |
-
import
|
| 4 |
-
from
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
.
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
.
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
.
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
.
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
.
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
color: #888;
|
| 79 |
-
margin-top: 5px;
|
| 80 |
-
text-align: right;
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
.quick-replies {
|
| 84 |
-
display: flex;
|
| 85 |
-
flex-wrap: wrap;
|
| 86 |
-
gap: 10px;
|
| 87 |
-
margin-top: 15px;
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
.quick-reply {
|
| 91 |
-
background: #4a90e2;
|
| 92 |
-
color: white;
|
| 93 |
-
padding: 8px 16px;
|
| 94 |
-
border-radius: 15px;
|
| 95 |
-
cursor: pointer;
|
| 96 |
-
font-weight: 500;
|
| 97 |
-
transition: background 0.3s ease;
|
| 98 |
-
}
|
| 99 |
-
|
| 100 |
-
.quick-reply:hover {
|
| 101 |
-
background: #357ab7;
|
| 102 |
-
}
|
| 103 |
-
|
| 104 |
-
.chat-input {
|
| 105 |
-
display: flex;
|
| 106 |
-
gap: 10px;
|
| 107 |
-
align-items: center;
|
| 108 |
-
padding: 20px;
|
| 109 |
-
background: #f8fafc;
|
| 110 |
-
}
|
| 111 |
-
|
| 112 |
-
.chat-input input[type="text"] {
|
| 113 |
-
flex-grow: 1;
|
| 114 |
-
padding: 10px;
|
| 115 |
-
border-radius: 5px;
|
| 116 |
-
border: 1px solid #ddd;
|
| 117 |
-
font-size: 1rem;
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
.chat-input button {
|
| 121 |
-
background: #4a90e2;
|
| 122 |
-
color: white;
|
| 123 |
-
padding: 10px 20px;
|
| 124 |
-
border: none;
|
| 125 |
-
border-radius: 5px;
|
| 126 |
-
cursor: pointer;
|
| 127 |
-
transition: background 0.3s;
|
| 128 |
-
}
|
| 129 |
-
|
| 130 |
-
.chat-input button:hover {
|
| 131 |
-
background: #357ab7;
|
| 132 |
-
}
|
| 133 |
-
|
| 134 |
-
.admin-panel {
|
| 135 |
-
background: #ffffff;
|
| 136 |
-
border-radius: 10px;
|
| 137 |
-
padding: 20px;
|
| 138 |
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
| 139 |
-
margin-bottom: 20px;
|
| 140 |
-
}
|
| 141 |
-
|
| 142 |
-
.statistics {
|
| 143 |
-
padding: 10px 0;
|
| 144 |
-
border-bottom: 1px solid #ddd;
|
| 145 |
-
}
|
| 146 |
-
|
| 147 |
-
</style>
|
| 148 |
-
""", unsafe_allow_html=True)
|
| 149 |
-
|
| 150 |
-
# دادههای اولیه
|
| 151 |
-
if 'conversation_history' not in st.session_state:
|
| 152 |
-
st.session_state.conversation_history = []
|
| 153 |
-
|
| 154 |
-
if 'is_typing' not in st.session_state:
|
| 155 |
-
st.session_state.is_typing = False
|
| 156 |
-
|
| 157 |
-
quick_replies = ["راهنمای استفاده", "تماس با پشتیبانی", "گزارش مشکل", "بخشنامههای جدید"]
|
| 158 |
-
|
| 159 |
-
# توابع چتبات
|
| 160 |
-
def get_bot_response(user_message):
|
| 161 |
-
time.sleep(1) # شبیهسازی تایپ کردن
|
| 162 |
-
return random.choice(["سلام! چطور میتونم کمک کنم؟", "لطفاً سوال خود را مطرح کنید.", "در خدمت شما هستم."])
|
| 163 |
-
|
| 164 |
-
def add_message(sender, message):
|
| 165 |
-
st.session_state.conversation_history.append({
|
| 166 |
-
'sender': sender,
|
| 167 |
-
'message': message,
|
| 168 |
-
'timestamp': datetime.now().strftime("%H:%M")
|
| 169 |
-
})
|
| 170 |
-
|
| 171 |
-
# هدر
|
| 172 |
-
st.markdown("""
|
| 173 |
-
<div class="header">
|
| 174 |
-
<h2>چتبات هوشمند</h2>
|
| 175 |
-
<div class="status">آنلاین</div>
|
| 176 |
-
</div>
|
| 177 |
-
""", unsafe_allow_html=True)
|
| 178 |
-
|
| 179 |
-
# نمایش پیامها
|
| 180 |
-
st.markdown("<div class='chat-container'><div class='chat-messages'>", unsafe_allow_html=True)
|
| 181 |
-
for message in st.session_state.conversation_history:
|
| 182 |
-
sender_class = "user" if message['sender'] == "user" else "bot"
|
| 183 |
-
st.markdown(f"""
|
| 184 |
-
<div class='message {sender_class}'>
|
| 185 |
-
<div>{message['message']}</div>
|
| 186 |
-
<div class='timestamp'>{message['timestamp']}</div>
|
| 187 |
-
</div>
|
| 188 |
-
""", unsafe_allow_html=True)
|
| 189 |
-
st.markdown("</div></div>", unsafe_allow_html=True)
|
| 190 |
-
|
| 191 |
-
# پاسخهای سریع
|
| 192 |
-
st.markdown("<div class='quick-replies'>", unsafe_allow_html=True)
|
| 193 |
-
for reply in quick_replies:
|
| 194 |
-
if st.button(reply):
|
| 195 |
-
add_message("user", reply)
|
| 196 |
-
bot_response = get_bot_response(reply)
|
| 197 |
-
add_message("bot", bot_response)
|
| 198 |
-
st.experimental_rerun()
|
| 199 |
-
st.markdown("</div>", unsafe_allow_html=True)
|
| 200 |
-
|
| 201 |
-
# ورودی کاربر
|
| 202 |
-
st.markdown("<div class='chat-input'>", unsafe_allow_html=True)
|
| 203 |
-
user_message = st.text_input("", placeholder="پیام خود را وارد کنید...", label_visibility="collapsed")
|
| 204 |
-
send_button = st.button("ارسال")
|
| 205 |
-
|
| 206 |
-
if send_button and user_message:
|
| 207 |
-
add_message("user", user_message)
|
| 208 |
-
st.session_state.is_typing = True
|
| 209 |
-
bot_response = get_bot_response(user_message)
|
| 210 |
-
add_message("bot", bot_response)
|
| 211 |
-
st.session_state.is_typing = False
|
| 212 |
-
st.experimental_rerun()
|
| 213 |
-
|
| 214 |
-
st.markdown("</div>", unsafe_allow_html=True)
|
| 215 |
-
|
| 216 |
-
# پنل مدیریت در سایدبار
|
| 217 |
-
with st.sidebar:
|
| 218 |
-
st.markdown("<div class='admin-panel'><h3>پنل مدیریت</h3>", unsafe_allow_html=True)
|
| 219 |
-
|
| 220 |
-
# نمایش آمار
|
| 221 |
-
total_messages = len(st.session_state.conversation_history)
|
| 222 |
-
user_messages = sum(1 for msg in st.session_state.conversation_history if msg['sender'] == 'user')
|
| 223 |
-
bot_messages = total_messages - user_messages
|
| 224 |
-
|
| 225 |
-
st.markdown(f"""
|
| 226 |
-
<div class="statistics">پیامهای کل: {total_messages}</div>
|
| 227 |
-
<div class="statistics">پیامهای کاربر: {user_messages}</div>
|
| 228 |
-
<div class="statistics">پیامهای ربات: {bot_messages}</div>
|
| 229 |
-
""", unsafe_allow_html=True)
|
| 230 |
-
|
| 231 |
-
# دکمه پاک کردن تاریخچه
|
| 232 |
-
if st.button("پاک کردن تاریخچه"):
|
| 233 |
-
st.session_state.conversation_history = []
|
| 234 |
-
st.experimental_rerun()
|
| 235 |
-
|
| 236 |
-
# تنظیمات ظاهری
|
| 237 |
-
st.markdown("### تنظیمات ظاهری")
|
| 238 |
-
font_size = st.slider("اندازه فونت", 12, 20, 14)
|
| 239 |
-
st.markdown(f"<style>.message {{ font-size: {font_size}px; }}</style>", unsafe_allow_html=True)
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from admin_dashboard import AdminDashboard
|
| 3 |
+
from banking_assistant import BankingAssistant
|
| 4 |
+
from banking_model import BankingModelTrainer
|
| 5 |
+
from ml_banking_model import MLBankingEngine
|
| 6 |
+
|
| 7 |
+
class BankingSystem:
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.ml_engine = MLBankingEngine()
|
| 10 |
+
self.model_trainer = BankingModelTrainer()
|
| 11 |
+
self.assistant = BankingAssistant()
|
| 12 |
+
self.admin = AdminDashboard()
|
| 13 |
+
self.setup_page_config()
|
| 14 |
+
self.initialize_session_state()
|
| 15 |
+
|
| 16 |
+
def setup_page_config(self):
|
| 17 |
+
st.set_page_config(
|
| 18 |
+
page_title="سیستم بانکداری هوشمند",
|
| 19 |
+
page_icon="🏦",
|
| 20 |
+
layout="wide",
|
| 21 |
+
initial_sidebar_state="expanded"
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
def initialize_session_state(self):
|
| 25 |
+
if 'theme' not in st.session_state:
|
| 26 |
+
st.session_state.theme = 'light'
|
| 27 |
+
if 'user_role' not in st.session_state:
|
| 28 |
+
st.session_state.user_role = 'user'
|
| 29 |
+
if 'authenticated' not in st.session_state:
|
| 30 |
+
st.session_state.authenticated = False
|
| 31 |
+
|
| 32 |
+
def render_login(self):
|
| 33 |
+
st.markdown("""
|
| 34 |
+
<div style='text-align: center; padding: 50px;'>
|
| 35 |
+
<h1>🏦 سیستم بانکداری هوشمند</h1>
|
| 36 |
+
<p>لطفا وارد شوید</p>
|
| 37 |
+
</div>
|
| 38 |
+
""", unsafe_allow_html=True)
|
| 39 |
+
|
| 40 |
+
col1, col2, col3 = st.columns([1,2,1])
|
| 41 |
+
with col2:
|
| 42 |
+
username = st.text_input("نام کاربری")
|
| 43 |
+
password = st.text_input("رمز عبور", type="password")
|
| 44 |
+
|
| 45 |
+
if st.button("ورود"):
|
| 46 |
+
if username == "admin" and password == "admin":
|
| 47 |
+
st.session_state.user_role = 'admin'
|
| 48 |
+
st.session_state.authenticated = True
|
| 49 |
+
st.experimental_rerun()
|
| 50 |
+
elif username and password:
|
| 51 |
+
st.session_state.user_role = 'user'
|
| 52 |
+
st.session_state.authenticated = True
|
| 53 |
+
st.experimental_rerun()
|
| 54 |
+
|
| 55 |
+
def render_header(self):
|
| 56 |
+
st.markdown("""
|
| 57 |
+
<div style='display: flex; justify-content: space-between; align-items: center; padding: 1rem; background: white; box-shadow: 0 2px 4px rgba(0,0,0,0.1);'>
|
| 58 |
+
<h2>🏦 سیستم بانکداری هوشمند</h2>
|
| 59 |
+
<div>
|
| 60 |
+
<button onclick='logout()'>خروج</button>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
""", unsafe_allow_html=True)
|
| 64 |
+
|
| 65 |
+
def main(self):
|
| 66 |
+
if not st.session_state.authenticated:
|
| 67 |
+
self.render_login()
|
| 68 |
+
else:
|
| 69 |
+
self.render_header()
|
| 70 |
+
if st.session_state.user_role == 'admin':
|
| 71 |
+
self.admin.render_dashboard()
|
| 72 |
+
else:
|
| 73 |
+
self.assistant.render_chat_interface()
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__":
|
| 76 |
+
system = BankingSystem()
|
| 77 |
+
system.main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
banking_assistant.py
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import sqlite3
|
| 3 |
+
import random
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import plotly.express as px
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
# تنظیمات اولیه دیتابیس
|
| 10 |
+
class DatabaseManager:
|
| 11 |
+
def __init__(self):
|
| 12 |
+
self.conn = sqlite3.connect('banking_assistant.db')
|
| 13 |
+
self.create_tables()
|
| 14 |
+
|
| 15 |
+
def create_tables(self):
|
| 16 |
+
c = self.conn.cursor()
|
| 17 |
+
# جدول مکالمات
|
| 18 |
+
c.execute('''
|
| 19 |
+
CREATE TABLE IF NOT EXISTS conversations (
|
| 20 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 21 |
+
user_message TEXT,
|
| 22 |
+
assistant_response TEXT,
|
| 23 |
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 24 |
+
feedback INTEGER DEFAULT 0
|
| 25 |
+
)
|
| 26 |
+
''')
|
| 27 |
+
|
| 28 |
+
# جدول پایگاه دانش
|
| 29 |
+
c.execute('''
|
| 30 |
+
CREATE TABLE IF NOT EXISTS knowledge_base (
|
| 31 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 32 |
+
question TEXT,
|
| 33 |
+
answer TEXT,
|
| 34 |
+
category TEXT,
|
| 35 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 36 |
+
)
|
| 37 |
+
''')
|
| 38 |
+
|
| 39 |
+
# جدول تنظیمات
|
| 40 |
+
c.execute('''
|
| 41 |
+
CREATE TABLE IF NOT EXISTS settings (
|
| 42 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 43 |
+
theme TEXT DEFAULT 'light',
|
| 44 |
+
language TEXT DEFAULT 'fa',
|
| 45 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 46 |
+
)
|
| 47 |
+
''')
|
| 48 |
+
self.conn.commit()
|
| 49 |
+
|
| 50 |
+
# تنظیمات استایل و تم
|
| 51 |
+
THEMES = {
|
| 52 |
+
'light': {
|
| 53 |
+
'bg_color': '#e0e5ec',
|
| 54 |
+
'text_color': '#333',
|
| 55 |
+
'shadow': '9px 9px 16px #b8b9be, -9px -9px 16px #ffffff'
|
| 56 |
+
},
|
| 57 |
+
'dark': {
|
| 58 |
+
'bg_color': '#2d3436',
|
| 59 |
+
'text_color': '#fff',
|
| 60 |
+
'shadow': '9px 9px 16px #1a1d1e, -9px -9px 16px #404b4d'
|
| 61 |
+
},
|
| 62 |
+
'blue': {
|
| 63 |
+
'bg_color': '#e3f2fd',
|
| 64 |
+
'text_color': '#1976d2',
|
| 65 |
+
'shadow': '9px 9px 16px #c1cdd4, -9px -9px 16px #ffffff'
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
def load_custom_styles(theme='light'):
|
| 69 |
+
return f"""
|
| 70 |
+
<style>
|
| 71 |
+
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100;200;300;400;500;600;700;800;900&display=swap');
|
| 72 |
+
|
| 73 |
+
/* تنظیمات پایه */
|
| 74 |
+
* {{
|
| 75 |
+
font-family: 'Vazirmatn', sans-serif;
|
| 76 |
+
direction: rtl;
|
| 77 |
+
transition: all 0.3s ease;
|
| 78 |
+
}}
|
| 79 |
+
|
| 80 |
+
body {{
|
| 81 |
+
background: {THEMES[theme]['bg_color']};
|
| 82 |
+
color: {THEMES[theme]['text_color']};
|
| 83 |
+
}}
|
| 84 |
+
|
| 85 |
+
/* هدر اصلی */
|
| 86 |
+
.main-header {{
|
| 87 |
+
background: {THEMES[theme]['bg_color']};
|
| 88 |
+
padding: 2rem;
|
| 89 |
+
border-radius: 25px;
|
| 90 |
+
box-shadow: {THEMES[theme]['shadow']};
|
| 91 |
+
margin-bottom: 2rem;
|
| 92 |
+
text-align: center;
|
| 93 |
+
position: relative;
|
| 94 |
+
overflow: hidden;
|
| 95 |
+
}}
|
| 96 |
+
|
| 97 |
+
.main-header::before {{
|
| 98 |
+
content: '';
|
| 99 |
+
position: absolute;
|
| 100 |
+
top: -50%;
|
| 101 |
+
left: -50%;
|
| 102 |
+
width: 200%;
|
| 103 |
+
height: 200%;
|
| 104 |
+
background: linear-gradient(
|
| 105 |
+
45deg,
|
| 106 |
+
transparent 0%,
|
| 107 |
+
rgba(255, 255, 255, 0.1) 50%,
|
| 108 |
+
transparent 100%
|
| 109 |
+
);
|
| 110 |
+
animation: shine 3s infinite;
|
| 111 |
+
}}
|
| 112 |
+
|
| 113 |
+
/* باکس چت */
|
| 114 |
+
.chat-container {{
|
| 115 |
+
background: {THEMES[theme]['bg_color']};
|
| 116 |
+
border-radius: 20px;
|
| 117 |
+
padding: 2rem;
|
| 118 |
+
margin: 1rem 0;
|
| 119 |
+
box-shadow: {THEMES[theme]['shadow']};
|
| 120 |
+
max-height: 70vh;
|
| 121 |
+
overflow-y: auto;
|
| 122 |
+
scroll-behavior: smooth;
|
| 123 |
+
}}
|
| 124 |
+
|
| 125 |
+
/* پیامها */
|
| 126 |
+
.message {{
|
| 127 |
+
margin: 1rem 0;
|
| 128 |
+
padding: 1rem;
|
| 129 |
+
border-radius: 15px;
|
| 130 |
+
position: relative;
|
| 131 |
+
animation: messageSlide 0.3s ease-out;
|
| 132 |
+
}}
|
| 133 |
+
|
| 134 |
+
.message.user {{
|
| 135 |
+
background: linear-gradient(135deg, #0073e6 0%, #0056b3 100%);
|
| 136 |
+
color: white;
|
| 137 |
+
margin-left: 2rem;
|
| 138 |
+
box-shadow: 0 4px 15px rgba(0, 115, 230, 0.2);
|
| 139 |
+
}}
|
| 140 |
+
|
| 141 |
+
.message.assistant {{
|
| 142 |
+
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
| 143 |
+
color: #343a40;
|
| 144 |
+
margin-right: 2rem;
|
| 145 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
| 146 |
+
}}
|
| 147 |
+
|
| 148 |
+
/* ورودی چت */
|
| 149 |
+
.chat-input-container {{
|
| 150 |
+
background: {THEMES[theme]['bg_color']};
|
| 151 |
+
padding: 1.5rem;
|
| 152 |
+
border-radius: 15px;
|
| 153 |
+
box-shadow: {THEMES[theme]['shadow']};
|
| 154 |
+
margin-top: 1rem;
|
| 155 |
+
display: flex;
|
| 156 |
+
align-items: center;
|
| 157 |
+
gap: 1rem;
|
| 158 |
+
}}
|
| 159 |
+
|
| 160 |
+
.chat-input {{
|
| 161 |
+
flex: 1;
|
| 162 |
+
padding: 1rem;
|
| 163 |
+
border: none;
|
| 164 |
+
border-radius: 10px;
|
| 165 |
+
background: rgba(255, 255, 255, 0.1);
|
| 166 |
+
backdrop-filter: blur(10px);
|
| 167 |
+
color: {THEMES[theme]['text_color']};
|
| 168 |
+
font-size: 1rem;
|
| 169 |
+
transition: all 0.3s ease;
|
| 170 |
+
}}
|
| 171 |
+
|
| 172 |
+
.chat-input:focus {{
|
| 173 |
+
outline: none;
|
| 174 |
+
box-shadow: 0 0 0 3px rgba(0, 115, 230, 0.3);
|
| 175 |
+
}}
|
| 176 |
+
|
| 177 |
+
/* دکمهها */
|
| 178 |
+
.button {{
|
| 179 |
+
padding: 0.8rem 1.5rem;
|
| 180 |
+
border: none;
|
| 181 |
+
border-radius: 10px;
|
| 182 |
+
background: linear-gradient(135deg, #0073e6 0%, #0056b3 100%);
|
| 183 |
+
color: white;
|
| 184 |
+
cursor: pointer;
|
| 185 |
+
transition: all 0.3s ease;
|
| 186 |
+
font-weight: 500;
|
| 187 |
+
display: flex;
|
| 188 |
+
align-items: center;
|
| 189 |
+
gap: 0.5rem;
|
| 190 |
+
}}
|
| 191 |
+
|
| 192 |
+
.button:hover {{
|
| 193 |
+
transform: translateY(-2px);
|
| 194 |
+
box-shadow: 0 5px 15px rgba(0, 115, 230, 0.3);
|
| 195 |
+
}}
|
| 196 |
+
|
| 197 |
+
.button:active {{
|
| 198 |
+
transform: translateY(0);
|
| 199 |
+
}}
|
| 200 |
+
|
| 201 |
+
/* انیمیشنها */
|
| 202 |
+
@keyframes messageSlide {{
|
| 203 |
+
from {{
|
| 204 |
+
opacity: 0;
|
| 205 |
+
transform: translateY(20px);
|
| 206 |
+
}}
|
| 207 |
+
to {{
|
| 208 |
+
opacity: 1;
|
| 209 |
+
transform: translateY(0);
|
| 210 |
+
}}
|
| 211 |
+
}}
|
| 212 |
+
|
| 213 |
+
@keyframes shine {{
|
| 214 |
+
0% {{
|
| 215 |
+
transform: translateX(-100%) rotate(45deg);
|
| 216 |
+
}}
|
| 217 |
+
100% {{
|
| 218 |
+
transform: translateX(100%) rotate(45deg);
|
| 219 |
+
}}
|
| 220 |
+
}}
|
| 221 |
+
|
| 222 |
+
/* اسکرولبار سفارشی */
|
| 223 |
+
::-webkit-scrollbar {{
|
| 224 |
+
width: 8px;
|
| 225 |
+
}}
|
| 226 |
+
|
| 227 |
+
::-webkit-scrollbar-track {{
|
| 228 |
+
background: {THEMES[theme]['bg_color']};
|
| 229 |
+
}}
|
| 230 |
+
|
| 231 |
+
::-webkit-scrollbar-thumb {{
|
| 232 |
+
background: #0073e6;
|
| 233 |
+
border-radius: 4px;
|
| 234 |
+
}}
|
| 235 |
+
|
| 236 |
+
::-webkit-scrollbar-thumb:hover {{
|
| 237 |
+
background: #0056b3;
|
| 238 |
+
}}
|
| 239 |
+
</style>
|
| 240 |
+
"""
|
| 241 |
+
class BankingAssistant:
|
| 242 |
+
def __init__(self):
|
| 243 |
+
self.db = DatabaseManager()
|
| 244 |
+
self.model = BankingModel() # اضافه کردن مدل
|
| 245 |
+
if 'theme' not in st.session_state:
|
| 246 |
+
st.session_state.theme = 'light'
|
| 247 |
+
if 'messages' not in st.session_state:
|
| 248 |
+
st.session_state.messages = []
|
| 249 |
+
self.add_message('assistant', """
|
| 250 |
+
👋 سلام! من دستیار هوشمند بانکی شما هستم.
|
| 251 |
+
|
| 252 |
+
میتوانم در موارد زیر به شما کمک کنم:
|
| 253 |
+
📊 مشاهده وضعیت حساب
|
| 254 |
+
💳 انتقال وجه
|
| 255 |
+
📝 درخواست تسهیلات
|
| 256 |
+
🏦 اطلاعات شعب
|
| 257 |
+
|
| 258 |
+
چطور میتوانم کمکتان کنم؟
|
| 259 |
+
""")
|
| 260 |
+
|
| 261 |
+
def add_message(self, role, content):
|
| 262 |
+
timestamp = datetime.now().strftime("%H:%M")
|
| 263 |
+
st.session_state.messages.append({
|
| 264 |
+
'role': role,
|
| 265 |
+
'content': content,
|
| 266 |
+
'timestamp': timestamp
|
| 267 |
+
})
|
| 268 |
+
|
| 269 |
+
# ذخیره در دیتابیس
|
| 270 |
+
if role == 'user':
|
| 271 |
+
self.db.conn.execute(
|
| 272 |
+
'INSERT INTO conversations (user_message, timestamp) VALUES (?, ?)',
|
| 273 |
+
(content, timestamp)
|
| 274 |
+
)
|
| 275 |
+
else:
|
| 276 |
+
self.db.conn.execute(
|
| 277 |
+
'UPDATE conversations SET assistant_response = ? WHERE id = last_insert_rowid()',
|
| 278 |
+
(content,)
|
| 279 |
+
)
|
| 280 |
+
self.db.conn.commit()
|
| 281 |
+
|
| 282 |
+
def render_chat_interface(self):
|
| 283 |
+
st.markdown(load_custom_styles(st.session_state.theme), unsafe_allow_html=True)
|
| 284 |
+
|
| 285 |
+
# هدر اصلی
|
| 286 |
+
st.markdown("""
|
| 287 |
+
<div class="main-header">
|
| 288 |
+
<h1>🏦 دستیار هوشمند بانکی</h1>
|
| 289 |
+
<p>پاسخگوی 24 ساعته شما</p>
|
| 290 |
+
</div>
|
| 291 |
+
""", unsafe_allow_html=True)
|
| 292 |
+
|
| 293 |
+
# باکس چت
|
| 294 |
+
st.markdown('<div class="chat-container">', unsafe_allow_html=True)
|
| 295 |
+
|
| 296 |
+
for msg in st.session_state.messages:
|
| 297 |
+
class_name = "user" if msg['role'] == 'user' else "assistant"
|
| 298 |
+
st.markdown(f"""
|
| 299 |
+
<div class="message {class_name}">
|
| 300 |
+
{msg['content']}
|
| 301 |
+
<small style="position: absolute; bottom: 5px; {'right' if class_name == 'user' else 'left'}: 10px; opacity: 0.7;">
|
| 302 |
+
{msg['timestamp']}
|
| 303 |
+
</small>
|
| 304 |
+
</div>
|
| 305 |
+
""", unsafe_allow_html=True)
|
| 306 |
+
|
| 307 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 308 |
+
|
| 309 |
+
# باکس ورودی
|
| 310 |
+
st.markdown("""
|
| 311 |
+
<div class="chat-input-container">
|
| 312 |
+
<button class="action-button">🎤</button>
|
| 313 |
+
<input type="text" class="chat-input" placeholder="پیام خود را بنویسید...">
|
| 314 |
+
<button class="button">
|
| 315 |
+
<span>ارسال</span>
|
| 316 |
+
<span>➤</span>
|
| 317 |
+
</button>
|
| 318 |
+
</div>
|
| 319 |
+
""", unsafe_allow_html=True)
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
class AdminDashboard:
|
| 323 |
+
def __init__(self, db_manager):
|
| 324 |
+
self.db = db_manager
|
| 325 |
+
|
| 326 |
+
def render_dashboard(self):
|
| 327 |
+
st.markdown("""
|
| 328 |
+
<div class="admin-header">
|
| 329 |
+
<h2>🔐 پنل مدیریت</h2>
|
| 330 |
+
</div>
|
| 331 |
+
""", unsafe_allow_html=True)
|
| 332 |
+
|
| 333 |
+
# نمودارهای تحلیلی
|
| 334 |
+
col1, col2 = st.columns(2)
|
| 335 |
+
|
| 336 |
+
with col1:
|
| 337 |
+
self.render_conversation_stats()
|
| 338 |
+
|
| 339 |
+
with col2:
|
| 340 |
+
self.render_feedback_chart()
|
| 341 |
+
|
| 342 |
+
# مدیریت پایگاه دانش
|
| 343 |
+
st.markdown("""
|
| 344 |
+
<div class="admin-section">
|
| 345 |
+
<h3>📚 مدیریت پایگاه دانش</h3>
|
| 346 |
+
</div>
|
| 347 |
+
""", unsafe_allow_html=True)
|
| 348 |
+
|
| 349 |
+
# افزودن دانش جدید
|
| 350 |
+
with st.expander("➕ افزودن مورد جدید"):
|
| 351 |
+
category = st.selectbox(
|
| 352 |
+
"دستهبندی",
|
| 353 |
+
["عمومی", "حسابها", "تسهیلات", "کارت", "شعب"]
|
| 354 |
+
)
|
| 355 |
+
question = st.text_area("سوال")
|
| 356 |
+
answer = st.text_area("پاسخ")
|
| 357 |
+
|
| 358 |
+
if st.button("ذخیره"):
|
| 359 |
+
self.db.conn.execute(
|
| 360 |
+
'INSERT INTO knowledge_base (question, answer, category) VALUES (?, ?, ?)',
|
| 361 |
+
(question, answer, category)
|
| 362 |
+
)
|
| 363 |
+
self.db.conn.commit()
|
| 364 |
+
st.success("✅ با موفقیت ذخیره شد")
|
| 365 |
+
|
| 366 |
+
def render_conversation_stats(self):
|
| 367 |
+
# دریافت آمار مکالمات
|
| 368 |
+
df = pd.read_sql_query("""
|
| 369 |
+
SELECT
|
| 370 |
+
date(timestamp) as date,
|
| 371 |
+
COUNT(*) as count
|
| 372 |
+
FROM conversations
|
| 373 |
+
GROUP BY date(timestamp)
|
| 374 |
+
ORDER BY date DESC
|
| 375 |
+
LIMIT 7
|
| 376 |
+
""", self.db.conn)
|
| 377 |
+
|
| 378 |
+
fig = px.line(
|
| 379 |
+
df,
|
| 380 |
+
x='date',
|
| 381 |
+
y='count',
|
| 382 |
+
title='آمار مکالمات 7 روز اخیر',
|
| 383 |
+
labels={'count': 'تعداد مکالمات', 'date': 'تاریخ'}
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
+
fig.update_layout(
|
| 387 |
+
font_family="Vazirmatn",
|
| 388 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 389 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
st.plotly_chart(fig)
|
| 393 |
+
|
| 394 |
+
def render_feedback_chart(self):
|
| 395 |
+
# نمودار بازخوردها
|
| 396 |
+
df = pd.read_sql_query("""
|
| 397 |
+
SELECT
|
| 398 |
+
feedback,
|
| 399 |
+
COUNT(*) as count
|
| 400 |
+
FROM conversations
|
| 401 |
+
WHERE feedback != 0
|
| 402 |
+
GROUP BY feedback
|
| 403 |
+
""", self.db.conn)
|
| 404 |
+
|
| 405 |
+
fig = px.pie(
|
| 406 |
+
df,
|
| 407 |
+
values='count',
|
| 408 |
+
names='feedback',
|
| 409 |
+
title='نمودار بازخوردها',
|
| 410 |
+
color_discrete_sequence=px.colors.sequential.Blues
|
| 411 |
+
)
|
| 412 |
+
|
| 413 |
+
fig.update_layout(
|
| 414 |
+
font_family="Vazirmatn",
|
| 415 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 416 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 417 |
+
)
|
| 418 |
+
|
| 419 |
+
st.plotly_chart(fig)
|
| 420 |
+
|
| 421 |
+
def main():
|
| 422 |
+
st.set_page_config(
|
| 423 |
+
page_title="دستیار هوشمند بانکی",
|
| 424 |
+
page_icon="🏦",
|
| 425 |
+
layout="wide",
|
| 426 |
+
initial_sidebar_state="expanded"
|
| 427 |
+
)
|
| 428 |
+
|
| 429 |
+
assistant = BankingAssistant()
|
| 430 |
+
|
| 431 |
+
# منوی کناری
|
| 432 |
+
with st.sidebar:
|
| 433 |
+
st.markdown("### ⚙️ تنظیمات")
|
| 434 |
+
theme = st.selectbox(
|
| 435 |
+
"انتخاب تم",
|
| 436 |
+
options=['light', 'dark', 'blue'],
|
| 437 |
+
index=['light', 'dark', 'blue'].index(st.session_state.theme)
|
| 438 |
+
)
|
| 439 |
+
|
| 440 |
+
if theme != st.session_state.theme:
|
| 441 |
+
st.session_state.theme = theme
|
| 442 |
+
st.experimental_rerun()
|
| 443 |
+
|
| 444 |
+
if st.button("🔐 ورود مدیر"):
|
| 445 |
+
st.session_state.show_login = True
|
| 446 |
+
|
| 447 |
+
# نمایش فرم لاگین
|
| 448 |
+
if st.session_state.get('show_login', False):
|
| 449 |
+
with st.form("login_form"):
|
| 450 |
+
username = st.text_input("نام کاربری")
|
| 451 |
+
password = st.text_input("رمز عبور", type="password")
|
| 452 |
+
|
| 453 |
+
if st.form_submit_button("ورود"):
|
| 454 |
+
if username == "admin" and password == "admin": # در حالت واقعی باید امنتر باشد
|
| 455 |
+
st.session_state.admin_logged_in = True
|
| 456 |
+
st.session_state.show_login = False
|
| 457 |
+
st.experimental_rerun()
|
| 458 |
+
else:
|
| 459 |
+
st.error("نام کاربری یا رمز عبور اشتباه است")
|
| 460 |
+
|
| 461 |
+
# نمایش داشبورد مدیر
|
| 462 |
+
if st.session_state.get('admin_logged_in', False):
|
| 463 |
+
admin = AdminDashboard(assistant.db)
|
| 464 |
+
admin.render_dashboard()
|
| 465 |
+
else:
|
| 466 |
+
assistant.render_chat_interface()
|
| 467 |
+
|
| 468 |
+
if __name__ == "__main__":
|
| 469 |
+
main()
|
banking_model.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import torch
|
| 3 |
+
from transformers import (
|
| 4 |
+
AutoModelForCausalLM,
|
| 5 |
+
AutoTokenizer,
|
| 6 |
+
TrainingArguments,
|
| 7 |
+
Trainer,
|
| 8 |
+
DataCollatorForLanguageModeling
|
| 9 |
+
)
|
| 10 |
+
from datasets import Dataset
|
| 11 |
+
import json
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
class BankingModelTrainer:
|
| 15 |
+
def __init__(
|
| 16 |
+
self,
|
| 17 |
+
base_model_name="meta-llama/Llama-2-13b-chat-hf",
|
| 18 |
+
output_dir="./fine_tuned_model",
|
| 19 |
+
max_length=512
|
| 20 |
+
):
|
| 21 |
+
self.base_model_name = base_model_name
|
| 22 |
+
self.output_dir = Path(output_dir)
|
| 23 |
+
self.max_length = max_length
|
| 24 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 25 |
+
|
| 26 |
+
# تنظیمات مدل Llama-2
|
| 27 |
+
model_config = {
|
| 28 |
+
"device_map": "auto",
|
| 29 |
+
"torch_dtype": torch.bfloat16,
|
| 30 |
+
"low_cpu_mem_usage": True,
|
| 31 |
+
"max_memory": {0: "10GB"},
|
| 32 |
+
"load_in_8bit": True
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
# تنظیمات اولیه مدل و توکنایزر
|
| 36 |
+
self.tokenizer = AutoTokenizer.from_pretrained(base_model_name)
|
| 37 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 38 |
+
base_model_name,
|
| 39 |
+
**model_config
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
def prepare_data(self, data_path):
|
| 43 |
+
# خواندن دیتا از فایل
|
| 44 |
+
if data_path.endswith('.csv'):
|
| 45 |
+
df = pd.read_csv(data_path)
|
| 46 |
+
elif data_path.endswith('.json'):
|
| 47 |
+
with open(data_path, 'r', encoding='utf-8') as f:
|
| 48 |
+
data = json.load(f)
|
| 49 |
+
df = pd.DataFrame(data)
|
| 50 |
+
else:
|
| 51 |
+
raise ValueError("فرمت فایل باید CSV یا JSON باشد")
|
| 52 |
+
|
| 53 |
+
# پردازش و آمادهسازی دیتا
|
| 54 |
+
def prepare_examples(examples):
|
| 55 |
+
conversations = []
|
| 56 |
+
for q, a in zip(examples['question'], examples['answer']):
|
| 57 |
+
# فرمت Llama-2 برای مکالمه
|
| 58 |
+
conv = f"[INST] {q} [/INST] {a}"
|
| 59 |
+
conversations.append(conv)
|
| 60 |
+
|
| 61 |
+
# توکنایز کردن با تنظیمات Llama-2
|
| 62 |
+
encodings = self.tokenizer(
|
| 63 |
+
conversations,
|
| 64 |
+
truncation=True,
|
| 65 |
+
padding=True,
|
| 66 |
+
max_length=self.max_length,
|
| 67 |
+
return_tensors="pt"
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
return encodings
|
| 71 |
+
|
| 72 |
+
dataset = Dataset.from_pandas(df)
|
| 73 |
+
tokenized_dataset = dataset.map(
|
| 74 |
+
prepare_examples,
|
| 75 |
+
batched=True,
|
| 76 |
+
remove_columns=dataset.column_names
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
return tokenized_dataset
|
| 80 |
+
|
| 81 |
+
def train(self, dataset, epochs=3, batch_size=4):
|
| 82 |
+
training_args = TrainingArguments(
|
| 83 |
+
output_dir=str(self.output_dir),
|
| 84 |
+
num_train_epochs=epochs,
|
| 85 |
+
per_device_train_batch_size=batch_size,
|
| 86 |
+
gradient_accumulation_steps=4,
|
| 87 |
+
save_steps=500,
|
| 88 |
+
logging_steps=100,
|
| 89 |
+
learning_rate=2e-5, # کاهش نرخ یادگیری برای Llama-2
|
| 90 |
+
warmup_steps=100,
|
| 91 |
+
fp16=True, # فعال کردن fp16 برای Llama-2
|
| 92 |
+
save_total_limit=2,
|
| 93 |
+
logging_dir=str(self.output_dir / "logs"),
|
| 94 |
+
gradient_checkpointing=True # فعال کردن gradient checkpointing
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
data_collator = DataCollatorForLanguageModeling(
|
| 98 |
+
tokenizer=self.tokenizer,
|
| 99 |
+
mlm=False
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
trainer = Trainer(
|
| 103 |
+
model=self.model,
|
| 104 |
+
args=training_args,
|
| 105 |
+
train_dataset=dataset,
|
| 106 |
+
data_collator=data_collator
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
trainer.train()
|
| 110 |
+
|
| 111 |
+
self.model.save_pretrained(self.output_dir)
|
| 112 |
+
self.tokenizer.save_pretrained(self.output_dir)
|
| 113 |
+
|
| 114 |
+
def generate_response(self, prompt):
|
| 115 |
+
# فرمت Llama-2 برای پرامپت
|
| 116 |
+
formatted_prompt = f"[INST] {prompt} [/INST]"
|
| 117 |
+
inputs = self.tokenizer.encode(
|
| 118 |
+
formatted_prompt,
|
| 119 |
+
return_tensors="pt"
|
| 120 |
+
).to(self.device)
|
| 121 |
+
|
| 122 |
+
outputs = self.model.generate(
|
| 123 |
+
inputs,
|
| 124 |
+
max_length=self.max_length,
|
| 125 |
+
num_return_sequences=1,
|
| 126 |
+
temperature=0.7,
|
| 127 |
+
top_p=0.9,
|
| 128 |
+
do_sample=True,
|
| 129 |
+
pad_token_id=self.tokenizer.eos_token_id,
|
| 130 |
+
repetition_penalty=1.2 # اضافه کردن جریمه تکرار
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
response = self.tokenizer.decode(
|
| 134 |
+
outputs[0],
|
| 135 |
+
skip_special_tokens=True
|
| 136 |
+
)
|
| 137 |
+
# حذف پرامپت از پاسخ
|
| 138 |
+
response = response.replace(formatted_prompt, "").strip()
|
| 139 |
+
return response
|
| 140 |
+
|
| 141 |
+
if __name__ == "__main__":
|
| 142 |
+
trainer = BankingModelTrainer()
|
| 143 |
+
dataset = trainer.prepare_data("banking_qa.json")
|
| 144 |
+
trainer.train(dataset)
|
| 145 |
+
response = trainer.generate_response("شرایط وام مسکن چیست؟")
|
| 146 |
+
print(response)
|
filemanager.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
import zipfile
|
| 5 |
+
from PIL import Image
|
| 6 |
+
import time
|
| 7 |
+
import humanize
|
| 8 |
+
|
| 9 |
+
# تنظیمات صفحه
|
| 10 |
+
st.set_page_config(
|
| 11 |
+
page_title="سیستم مدیریت فایل",
|
| 12 |
+
page_icon="💼",
|
| 13 |
+
layout="wide",
|
| 14 |
+
initial_sidebar_state="expanded"
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
# استایلهای سفارشی با تم چتبات
|
| 18 |
+
CUSTOM_CSS = """
|
| 19 |
+
<style>
|
| 20 |
+
/* فونت و تنظیمات پایه */
|
| 21 |
+
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;700&display=swap');
|
| 22 |
+
|
| 23 |
+
* {
|
| 24 |
+
font-family: 'Vazirmatn', sans-serif;
|
| 25 |
+
direction: rtl;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
/* کانتینر اصلی */
|
| 29 |
+
.main-container {
|
| 30 |
+
max-width: 1200px;
|
| 31 |
+
margin: 0 auto;
|
| 32 |
+
padding: 2rem;
|
| 33 |
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
| 34 |
+
border-radius: 20px;
|
| 35 |
+
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
/* کارت فایل */
|
| 39 |
+
.file-card {
|
| 40 |
+
background: white;
|
| 41 |
+
border-radius: 15px;
|
| 42 |
+
padding: 1.5rem;
|
| 43 |
+
margin: 1rem 0;
|
| 44 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
| 45 |
+
transition: all 0.3s ease;
|
| 46 |
+
animation: slideIn 0.3s ease-out;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.file-card:hover {
|
| 50 |
+
transform: translateY(-5px);
|
| 51 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
/* دکمههای عملیات */
|
| 55 |
+
.action-button {
|
| 56 |
+
background: #2196F3;
|
| 57 |
+
color: white;
|
| 58 |
+
border: none;
|
| 59 |
+
border-radius: 10px;
|
| 60 |
+
padding: 0.8rem 1.2rem;
|
| 61 |
+
cursor: pointer;
|
| 62 |
+
transition: all 0.3s ease;
|
| 63 |
+
display: flex;
|
| 64 |
+
align-items: center;
|
| 65 |
+
gap: 0.5rem;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.action-button:hover {
|
| 69 |
+
background: #1976D2;
|
| 70 |
+
transform: scale(1.05);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
/* ناحیه آپلود */
|
| 74 |
+
.upload-area {
|
| 75 |
+
border: 2px dashed #2196F3;
|
| 76 |
+
border-radius: 15px;
|
| 77 |
+
padding: 2rem;
|
| 78 |
+
text-align: center;
|
| 79 |
+
background: rgba(255, 255, 255, 0.9);
|
| 80 |
+
cursor: pointer;
|
| 81 |
+
transition: all 0.3s ease;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.upload-area:hover {
|
| 85 |
+
border-color: #1976D2;
|
| 86 |
+
background: rgba(255, 255, 255, 1);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
/* نوار پیشرفت */
|
| 90 |
+
.progress-bar {
|
| 91 |
+
height: 4px;
|
| 92 |
+
background: #e0e0e0;
|
| 93 |
+
border-radius: 2px;
|
| 94 |
+
overflow: hidden;
|
| 95 |
+
margin: 1rem 0;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.progress-fill {
|
| 99 |
+
height: 100%;
|
| 100 |
+
background: #2196F3;
|
| 101 |
+
width: 0%;
|
| 102 |
+
animation: fillProgress 2s ease-out forwards;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
/* انیمیشنها */
|
| 106 |
+
@keyframes slideIn {
|
| 107 |
+
from {
|
| 108 |
+
opacity: 0;
|
| 109 |
+
transform: translateY(20px);
|
| 110 |
+
}
|
| 111 |
+
to {
|
| 112 |
+
opacity: 1;
|
| 113 |
+
transform: translateY(0);
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
@keyframes fillProgress {
|
| 118 |
+
from { width: 0%; }
|
| 119 |
+
to { width: 100%; }
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
/* فیلتر و جستجو */
|
| 123 |
+
.search-box {
|
| 124 |
+
display: flex;
|
| 125 |
+
gap: 1rem;
|
| 126 |
+
margin-bottom: 2rem;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
.search-input {
|
| 130 |
+
flex: 1;
|
| 131 |
+
padding: 0.8rem 1.2rem;
|
| 132 |
+
border: 2px solid #e0e0e0;
|
| 133 |
+
border-radius: 10px;
|
| 134 |
+
font-size: 1rem;
|
| 135 |
+
transition: all 0.3s ease;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.search-input:focus {
|
| 139 |
+
border-color: #2196F3;
|
| 140 |
+
outline: none;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
/* پیامهای اطلاعرسانی */
|
| 144 |
+
.info-message {
|
| 145 |
+
background: rgba(33, 150, 243, 0.1);
|
| 146 |
+
border-left: 4px solid #2196F3;
|
| 147 |
+
padding: 1rem;
|
| 148 |
+
border-radius: 0 10px 10px 0;
|
| 149 |
+
margin: 1rem 0;
|
| 150 |
+
}
|
| 151 |
+
</style>
|
| 152 |
+
"""
|
| 153 |
+
|
| 154 |
+
st.markdown(CUSTOM_CSS, unsafe_allow_html=True)
|
| 155 |
+
|
| 156 |
+
class FileManager:
|
| 157 |
+
def __init__(self):
|
| 158 |
+
self.root_path = Path("uploads")
|
| 159 |
+
self.root_path.mkdir(exist_ok=True)
|
| 160 |
+
|
| 161 |
+
def get_file_info(self, filename):
|
| 162 |
+
"""دریافت اطلاعات کامل فایل"""
|
| 163 |
+
path = self.root_path / filename
|
| 164 |
+
stats = path.stat()
|
| 165 |
+
return {
|
| 166 |
+
'size': humanize.naturalsize(stats.st_size),
|
| 167 |
+
'modified': datetime.fromtimestamp(stats.st_mtime).strftime('%Y/%m/%d %H:%M'),
|
| 168 |
+
'type': path.suffix[1:].upper(),
|
| 169 |
+
'icon': self._get_file_icon(path.suffix)
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
def _get_file_icon(self, suffix):
|
| 173 |
+
"""انتخاب آیکون مناسب برای هر نوع فایل"""
|
| 174 |
+
icons = {
|
| 175 |
+
'.txt': '📄',
|
| 176 |
+
'.pdf': '📕',
|
| 177 |
+
'.jpg': '🖼️',
|
| 178 |
+
'.jpeg': '🖼️',
|
| 179 |
+
'.png': '🖼️',
|
| 180 |
+
'.gif': '🎞️',
|
| 181 |
+
'.zip': '📦',
|
| 182 |
+
'.mp3': '🎵',
|
| 183 |
+
'.mp4': '🎥',
|
| 184 |
+
'.doc': '📘',
|
| 185 |
+
'.docx': '📘',
|
| 186 |
+
'.xls': '📊',
|
| 187 |
+
'.xlsx': '📊',
|
| 188 |
+
}
|
| 189 |
+
return icons.get(suffix.lower(), '📎')
|
| 190 |
+
|
| 191 |
+
def upload_file(self, file):
|
| 192 |
+
"""آپلود فایل با نمایش پیشرفت"""
|
| 193 |
+
try:
|
| 194 |
+
progress_text = st.empty()
|
| 195 |
+
progress_bar = st.progress(0)
|
| 196 |
+
|
| 197 |
+
dest_path = self.root_path / file.name
|
| 198 |
+
total_size = file.size
|
| 199 |
+
|
| 200 |
+
with open(dest_path, "wb") as f:
|
| 201 |
+
bytes_data = file.getbuffer()
|
| 202 |
+
f.write(bytes_data)
|
| 203 |
+
|
| 204 |
+
# شبیهسازی پیشرفت آپلود
|
| 205 |
+
for i in range(100):
|
| 206 |
+
time.sleep(0.01)
|
| 207 |
+
progress = (i + 1) / 100
|
| 208 |
+
progress_bar.progress(progress)
|
| 209 |
+
progress_text.text(f"در حال آپلود... {int(progress * 100)}%")
|
| 210 |
+
|
| 211 |
+
progress_text.empty()
|
| 212 |
+
progress_bar.empty()
|
| 213 |
+
|
| 214 |
+
return "success", f"✅ فایل '{file.name}' با موفقیت آپلود شد"
|
| 215 |
+
except Exception as e:
|
| 216 |
+
return "error", f"❌ خطا در آپلود فایل: {str(e)}"
|
| 217 |
+
|
| 218 |
+
def list_files(self, search_term="", file_type=None):
|
| 219 |
+
"""لیست فایلها با قابلیت جستجو و فیلتر"""
|
| 220 |
+
files = []
|
| 221 |
+
for f in self.root_path.iterdir():
|
| 222 |
+
if f.is_file():
|
| 223 |
+
if file_type and file_type != "همه" and f.suffix.lower() != file_type.lower():
|
| 224 |
+
continue
|
| 225 |
+
if search_term and search_term.lower() not in f.name.lower():
|
| 226 |
+
continue
|
| 227 |
+
files.append(f.name)
|
| 228 |
+
return sorted(files)
|
| 229 |
+
|
| 230 |
+
def preview_file(self, filename):
|
| 231 |
+
"""پیشنمایش پیشرفته فایل"""
|
| 232 |
+
try:
|
| 233 |
+
path = self.root_path / filename
|
| 234 |
+
if path.suffix.lower() in ['.jpg', '.jpeg', '.png', '.gif']:
|
| 235 |
+
img = Image.open(path)
|
| 236 |
+
return "image", img
|
| 237 |
+
elif path.suffix.lower() == '.txt':
|
| 238 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 239 |
+
content = f.read(1000)
|
| 240 |
+
if len(content) >= 1000:
|
| 241 |
+
content += "\n...[ادامه متن]"
|
| 242 |
+
return "text", content
|
| 243 |
+
return "unsupported", "⚠️ پیشنمایش برای این نوع فایل در دسترس نیست"
|
| 244 |
+
except Exception as e:
|
| 245 |
+
return "error", f"❌ خطا در نمایش فایل: {str(e)}"
|
| 246 |
+
|
| 247 |
+
def compress_files(self, files):
|
| 248 |
+
"""فشردهسازی فایلها با نمایش پیشرفت"""
|
| 249 |
+
try:
|
| 250 |
+
if not files:
|
| 251 |
+
return "error", "⚠️ لطفاً حداقل یک فایل انتخاب کنید"
|
| 252 |
+
|
| 253 |
+
zip_name = f"compressed_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
|
| 254 |
+
zip_path = self.root_path / zip_name
|
| 255 |
+
|
| 256 |
+
progress_text = st.empty()
|
| 257 |
+
progress_bar = st.progress(0)
|
| 258 |
+
|
| 259 |
+
with zipfile.ZipFile(zip_path, 'w') as zipf:
|
| 260 |
+
for i, file in enumerate(files, 1):
|
| 261 |
+
file_path = self.root_path / file
|
| 262 |
+
zipf.write(file_path, file)
|
| 263 |
+
progress = i / len(files)
|
| 264 |
+
progress_bar.progress(progress)
|
| 265 |
+
progress_text.text(f"در حال فشردهسازی... {int(progress * 100)}%")
|
| 266 |
+
|
| 267 |
+
progress_text.empty()
|
| 268 |
+
progress_bar.empty()
|
| 269 |
+
|
| 270 |
+
return "success", f"✅ فایلها با موفقیت در '{zip_name}' فشرده شدند"
|
| 271 |
+
except Exception as e:
|
| 272 |
+
return "error", f"❌ خطا در فشردهسازی: {str(e)}"
|
| 273 |
+
|
| 274 |
+
def main():
|
| 275 |
+
st.markdown('<div class="main-container">', unsafe_allow_html=True)
|
| 276 |
+
st.title("💼 سیستم مدیریت فایل پیشرفته")
|
| 277 |
+
|
| 278 |
+
file_manager = FileManager()
|
| 279 |
+
|
| 280 |
+
# بخش آپلود فایل
|
| 281 |
+
st.markdown("""
|
| 282 |
+
<div class="upload-area">
|
| 283 |
+
<h3>📤 آپلود فایل</h3>
|
| 284 |
+
<p>فایل خود را اینجا رها کنید یا کلیک کنید</p>
|
| 285 |
+
<div class="progress-bar">
|
| 286 |
+
<div class="progress-fill"></div>
|
| 287 |
+
</div>
|
| 288 |
+
</div>
|
| 289 |
+
""", unsafe_allow_html=True)
|
| 290 |
+
|
| 291 |
+
uploaded_file = st.file_uploader(
|
| 292 |
+
"",
|
| 293 |
+
type=["jpg", "jpeg", "png", "gif", "txt", "pdf", "doc", "docx", "xls", "xlsx", "mp3", "mp4", "zip"],
|
| 294 |
+
accept_multiple_files=False
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
if uploaded_file:
|
| 298 |
+
status, message = file_manager.upload_file(uploaded_file)
|
| 299 |
+
if status == "success":
|
| 300 |
+
st.success(message)
|
| 301 |
+
else:
|
| 302 |
+
st.error(message)
|
| 303 |
+
|
| 304 |
+
# بخش جستجو و فیلتر
|
| 305 |
+
col1, col2 = st.columns([2, 1])
|
| 306 |
+
with col1:
|
| 307 |
+
search_term = st.text_input("🔍 جستجوی فایل", placeholder="نام فایل را وارد کنید...")
|
| 308 |
+
with col2:
|
| 309 |
+
file_type = st.selectbox(
|
| 310 |
+
"📁 نوع فایل",
|
| 311 |
+
["همه", ".jpg", ".jpeg", ".png", ".gif", ".txt", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".mp3", ".mp4", ".zip"]
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
# نمایش لیست فایلها
|
| 315 |
+
files = file_manager.list_files(search_term, file_type)
|
| 316 |
+
|
| 317 |
+
if not files:
|
| 318 |
+
st.markdown("""
|
| 319 |
+
<div class="info-message">
|
| 320 |
+
📭 هیچ فایلی یافت نشد
|
| 321 |
+
</div>
|
| 322 |
+
""", unsafe_allow_html=True)
|
| 323 |
+
else:
|
| 324 |
+
for file in files:
|
| 325 |
+
file_info = file_manager.get_file_info(file)
|
| 326 |
+
|
| 327 |
+
with st.container():
|
| 328 |
+
st.markdown(f"""
|
| 329 |
+
<div class="file-card">
|
| 330 |
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
| 331 |
+
<div>
|
| 332 |
+
<h3>{file_info['icon']} {file}</h3>
|
| 333 |
+
<p>اندازه: {file_info['size']} | نوع: {file_info['type']} | آخرین تغییر: {file_info['modified']}</p>
|
| 334 |
+
</div>
|
| 335 |
+
</div>
|
| 336 |
+
</div>
|
| 337 |
+
""", unsafe_allow_html=True)
|
| 338 |
+
|
| 339 |
+
col1, col2, col3, col4 = st.columns([1, 1, 1, 1])
|
| 340 |
+
|
| 341 |
+
with col1:
|
| 342 |
+
if st.button("👁️ نمایش", key=f"preview_{file}"):
|
| 343 |
+
preview_type, preview_content = file_manager.preview_file(file)
|
| 344 |
+
if preview_type == "image":
|
| 345 |
+
st.image(preview_content, use_column_width=True)
|
| 346 |
+
elif preview_type == "text":
|
| 347 |
+
st.text(preview_content)
|
| 348 |
+
else:
|
| 349 |
+
st.warning(preview_content)
|
| 350 |
+
|
| 351 |
+
with col2:
|
| 352 |
+
if st.button("🗑️ حذف", key=f"delete_{file}"):
|
| 353 |
+
if st.session_state.get('admin_logged_in', False):
|
| 354 |
+
status, message = file_manager.delete_file(file)
|
| 355 |
+
st.success(message) if status == "success" else st.error(message)
|
| 356 |
+
else:
|
| 357 |
+
st.error("⛔ فقط مدیر میتواند فایلها را حذف کند")
|
| 358 |
+
|
| 359 |
+
with col3:
|
| 360 |
+
with open(file_manager.root_path / file, 'rb') as f:
|
| 361 |
+
st.download_button(
|
| 362 |
+
"⬇️ دانلود",
|
| 363 |
+
f.read(),
|
| 364 |
+
file_name=file,
|
| 365 |
+
key=f"download_{file}"
|
| 366 |
+
)
|
| 367 |
+
|
| 368 |
+
# بخش فشردهسازی
|
| 369 |
+
st.markdown("""
|
| 370 |
+
<div class="file-card">
|
| 371 |
+
<h3>🗜️ فشردهسازی فایلها</h3>
|
| 372 |
+
</div>
|
| 373 |
+
""", unsafe_allow_html=True)
|
| 374 |
+
|
| 375 |
+
selected_files = st.multiselect(
|
| 376 |
+
"فایلهای مورد نظر را انتخاب کنید",
|
| 377 |
+
files,
|
| 378 |
+
key="compress_files"
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
if st.button("📦 ساخت فایل ZIP"):
|
| 382 |
+
if selected_files:
|
| 383 |
+
status, message = file_manager.compress_files(selected_files)
|
| 384 |
+
if status == "success":
|
| 385 |
+
st.success(message)
|
| 386 |
+
else:
|
| 387 |
+
st.error(message)
|
| 388 |
+
else:
|
| 389 |
+
st.warning("⚠️ لطفاً حداقل یک فایل انتخاب کنید")
|
| 390 |
+
|
| 391 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 392 |
+
|
| 393 |
+
if __name__ == "__main__":
|
| 394 |
+
main()
|
ml_banking_model.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
import torch
|
| 4 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer
|
| 5 |
+
from peft import LoraConfig, get_peft_model
|
| 6 |
+
import pandas as pd
|
| 7 |
+
from datasets import Dataset
|
| 8 |
+
import json
|
| 9 |
+
import psutil
|
| 10 |
+
import time
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
import onnx
|
| 13 |
+
import onnxruntime
|
| 14 |
+
from functools import lru_cache
|
| 15 |
+
import logging
|
| 16 |
+
from typing import Dict, List, Optional
|
| 17 |
+
|
| 18 |
+
class BankingModel:
|
| 19 |
+
def __init__(self):
|
| 20 |
+
# تنظیم لاگر
|
| 21 |
+
self._setup_logging()
|
| 22 |
+
|
| 23 |
+
# ساخت پوشهها
|
| 24 |
+
self.base_dir = Path.cwd()
|
| 25 |
+
self.dirs = {
|
| 26 |
+
'model': self.base_dir / "trained_model",
|
| 27 |
+
'data': self.base_dir / "data",
|
| 28 |
+
'logs': self.base_dir / "logs",
|
| 29 |
+
'backup': self.base_dir / "backups",
|
| 30 |
+
'cache': self.base_dir / "cache",
|
| 31 |
+
'reports': self.base_dir / "reports"
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
for dir_path in self.dirs.values():
|
| 35 |
+
dir_path.mkdir(exist_ok=True)
|
| 36 |
+
|
| 37 |
+
# تنظیمات مدل برای CPU
|
| 38 |
+
self.model_name = "meta-llama/Llama-2-13b-chat-hf"
|
| 39 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
|
| 40 |
+
|
| 41 |
+
# بهینهسازی برای CPU
|
| 42 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 43 |
+
self.model_name,
|
| 44 |
+
device_map='cpu',
|
| 45 |
+
torch_dtype=torch.float32,
|
| 46 |
+
low_cpu_mem_usage=True
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
# تنظیمات LoRA
|
| 50 |
+
self._setup_lora()
|
| 51 |
+
|
| 52 |
+
# مقداردهی کش
|
| 53 |
+
self.response_cache = {}
|
| 54 |
+
|
| 55 |
+
# شروع مانیتورینگ
|
| 56 |
+
self.start_monitoring()
|
| 57 |
+
|
| 58 |
+
def _setup_logging(self):
|
| 59 |
+
"""راهاندازی سیستم لاگینگ"""
|
| 60 |
+
logging.basicConfig(
|
| 61 |
+
filename=f'logs/model_{datetime.now().strftime("%Y%m%d")}.log',
|
| 62 |
+
level=logging.INFO,
|
| 63 |
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
| 64 |
+
)
|
| 65 |
+
self.logger = logging.getLogger(__name__)
|
| 66 |
+
|
| 67 |
+
def _setup_lora(self):
|
| 68 |
+
"""تنظیم LoRA برای CPU"""
|
| 69 |
+
self.lora_config = LoraConfig(
|
| 70 |
+
r=8, # کاهش برای CPU
|
| 71 |
+
lora_alpha=16,
|
| 72 |
+
target_modules=["q_proj", "v_proj"],
|
| 73 |
+
lora_dropout=0.05,
|
| 74 |
+
bias="none",
|
| 75 |
+
task_type="CAUSAL_LM"
|
| 76 |
+
)
|
| 77 |
+
self.model = get_peft_model(self.model, self.lora_config)
|
| 78 |
+
|
| 79 |
+
@lru_cache(maxsize=1000)
|
| 80 |
+
def cached_predict(self, text: str) -> str:
|
| 81 |
+
"""پیشبینی با استفاده از کش"""
|
| 82 |
+
return self.predict(text)
|
| 83 |
+
|
| 84 |
+
def create_backup(self):
|
| 85 |
+
"""ایجاد نسخه پشتیبان"""
|
| 86 |
+
backup_time = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 87 |
+
backup_path = self.dirs['backup'] / f"model_backup_{backup_time}"
|
| 88 |
+
self.save_model(backup_path)
|
| 89 |
+
self.logger.info(f"Backup created at {backup_path}")
|
| 90 |
+
|
| 91 |
+
def monitor_resources(self) -> Dict:
|
| 92 |
+
"""مانیتورینگ منابع سیستم"""
|
| 93 |
+
cpu_percent = psutil.cpu_percent(interval=1)
|
| 94 |
+
memory = psutil.virtual_memory()
|
| 95 |
+
return {
|
| 96 |
+
'cpu_usage': cpu_percent,
|
| 97 |
+
'memory_used': memory.percent,
|
| 98 |
+
'memory_available': memory.available / (1024 * 1024 * 1024) # GB
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
def start_monitoring(self):
|
| 102 |
+
"""شروع مانیتورینگ مداوم"""
|
| 103 |
+
self.monitoring_data = []
|
| 104 |
+
self.monitoring_start_time = time.time()
|
| 105 |
+
|
| 106 |
+
def log_performance(self, input_text: str, response: str, response_time: float):
|
| 107 |
+
"""ثبت عملکرد مدل"""
|
| 108 |
+
performance_data = {
|
| 109 |
+
'timestamp': datetime.now().isoformat(),
|
| 110 |
+
'input_length': len(input_text),
|
| 111 |
+
'response_length': len(response),
|
| 112 |
+
'response_time': response_time,
|
| 113 |
+
'resources': self.monitor_resources()
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
with open(self.dirs['reports'] / 'performance.jsonl', 'a') as f:
|
| 117 |
+
f.write(json.dumps(performance_data) + '\n')
|
| 118 |
+
|
| 119 |
+
def export_to_onnx(self):
|
| 120 |
+
"""تبدیل مدل به ONNX برای اجرای سریعتر"""
|
| 121 |
+
dummy_input = self.tokenizer("test input", return_tensors="pt")
|
| 122 |
+
onnx_path = self.dirs['model'] / "model.onnx"
|
| 123 |
+
|
| 124 |
+
torch.onnx.export(
|
| 125 |
+
self.model,
|
| 126 |
+
(dummy_input['input_ids'],),
|
| 127 |
+
onnx_path,
|
| 128 |
+
opset_version=12,
|
| 129 |
+
input_names=['input_ids'],
|
| 130 |
+
output_names=['output']
|
| 131 |
+
)
|
| 132 |
+
self.logger.info(f"Model exported to ONNX at {onnx_path}")
|
| 133 |
+
|
| 134 |
+
def generate_report(self) -> Dict:
|
| 135 |
+
"""تولید گزارش عملکرد"""
|
| 136 |
+
with open(self.dirs['reports'] / 'performance.jsonl', 'r') as f:
|
| 137 |
+
data = [json.loads(line) for line in f]
|
| 138 |
+
|
| 139 |
+
return {
|
| 140 |
+
'total_requests': len(data),
|
| 141 |
+
'avg_response_time': sum(d['response_time'] for d in
|
requirements.txt
CHANGED
|
@@ -1,5 +1,12 @@
|
|
| 1 |
-
|
| 2 |
-
transformers
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch==2.1.0
|
| 2 |
+
transformers==4.35.0
|
| 3 |
+
peft==0.5.0
|
| 4 |
+
bitsandbytes==0.41.0
|
| 5 |
+
accelerate==0.24.0
|
| 6 |
+
datasets==2.14.0
|
| 7 |
+
pandas==2.1.0
|
| 8 |
+
scikit-learn==1.3.0
|
| 9 |
+
sentencepiece==0.1.99
|
| 10 |
+
pytest==7.4.2
|
| 11 |
+
black==23.9.1
|
| 12 |
+
flake8==6.1.0
|