Spaces:
Running
Running
| import streamlit as st | |
| import time | |
| import random | |
| import datetime | |
| from utils import ( | |
| analyze_sentiment, | |
| generate_ai_response, | |
| get_mock_users, | |
| get_mock_rooms, | |
| get_initial_messages | |
| ) | |
| # --- PAGE CONFIGURATION --- | |
| st.set_page_config( | |
| page_title="چت روم هوشمند", | |
| page_icon="🤖", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| # --- CSS STYLING (Ported from PHP/HTML) --- | |
| st.markdown(""" | |
| <style> | |
| /* Import Fonts */ | |
| @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100;300;400;500;700;900&display=swap'); | |
| @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css'); | |
| :root { | |
| --glass-bg: rgba(255, 255, 255, 0.05); | |
| --glass-border: rgba(255, 255, 255, 0.1); | |
| --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| --text-color: #ffffff; | |
| --bg-gradient: linear-gradient(-45deg, #0f0c29, #302b63, #24243e, #4a1c40); | |
| } | |
| /* Global resets */ | |
| .stApp { | |
| background: var(--bg-gradient); | |
| background-size: 400% 400%; | |
| animation: gradientBG 20s ease infinite; | |
| font-family: 'Vazirmatn', sans-serif !important; | |
| color: var(--text-color); | |
| direction: rtl; | |
| } | |
| @keyframes gradientBG { | |
| 0% { background-position: 0% 50%; } | |
| 50% { background-position: 100% 50%; } | |
| 100% { background-position: 0% 50%; } | |
| } | |
| /* Hide Streamlit Default Elements */ | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| header {visibility: hidden;} | |
| .stDeployButton {display: none;} | |
| /* Glassmorphism Containers */ | |
| .glass-panel { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 24px; | |
| box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3); | |
| } | |
| /* Login Page Specifics */ | |
| .login-container { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 80vh; | |
| } | |
| .login-box { | |
| max-width: 450px; | |
| width: 100%; | |
| padding: 40px; | |
| text-align: center; | |
| } | |
| /* Chat Layout */ | |
| .chat-layout { | |
| display: flex; | |
| gap: 20px; | |
| height: calc(100vh - 100px); | |
| padding: 20px; | |
| box-sizing: border-box; | |
| } | |
| .sidebar-area { | |
| width: 280px; | |
| display: flex; | |
| flex-direction: column; | |
| padding: 20px; | |
| } | |
| .main-chat-area { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| padding: 20px; | |
| overflow: hidden; | |
| } | |
| /* User List */ | |
| .user-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| padding: 10px; | |
| margin-bottom: 8px; | |
| background: rgba(255,255,255,0.02); | |
| border-radius: 10px; | |
| transition: background 0.2s; | |
| } | |
| .user-item:hover { | |
| background: rgba(255,255,255,0.08); | |
| } | |
| .user-avatar { | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 50%; | |
| background: var(--primary-gradient); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: bold; | |
| } | |
| .status-dot { | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| background: #555; | |
| } | |
| .status-online { | |
| background: #00d2d3; | |
| box-shadow: 0 0 8px #00d2d3; | |
| } | |
| /* Messages */ | |
| .messages-container { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 10px; | |
| margin-bottom: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 15px; | |
| scrollbar-width: thin; | |
| scrollbar-color: rgba(255,255,255,0.2) transparent; | |
| } | |
| .message { | |
| max-width: 70%; | |
| padding: 12px 16px; | |
| border-radius: 16px; | |
| position: relative; | |
| line-height: 1.5; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .message.own { | |
| align-self: flex-end; | |
| background: var(--primary-gradient); | |
| border-bottom-right-radius: 4px; | |
| } | |
| .message.other { | |
| align-self: flex-start; | |
| background: rgba(255, 255, 255, 0.1); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-bottom-left-radius: 4px; | |
| } | |
| .message-header { | |
| display: flex; | |
| justify-content: space-between; | |
| font-size: 0.75rem; | |
| opacity: 0.8; | |
| margin-bottom: 5px; | |
| } | |
| .message-content { | |
| white-space: pre-wrap; | |
| } | |
| /* Input Area */ | |
| .chat-input-container { | |
| display: flex; | |
| gap: 10px; | |
| align-items: center; | |
| } | |
| .emoji-bar { | |
| display: flex; | |
| gap: 8px; | |
| margin-top: 10px; | |
| } | |
| .emoji-btn { | |
| background: rgba(255,255,255,0.05); | |
| border: none; | |
| color: white; | |
| width: 35px; | |
| height: 35px; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| transition: transform 0.2s; | |
| font-size: 1.2rem; | |
| } | |
| .emoji-btn:hover { | |
| transform: scale(1.2); | |
| background: rgba(255,255,255,0.15); | |
| } | |
| /* Custom Streamlit Elements overrides */ | |
| div[data-testid="stTextInput"] > div > div > input { | |
| background: rgba(0,0,0,0.3); | |
| color: white; | |
| border: 1px solid var(--glass-border); | |
| border-radius: 12px; | |
| } | |
| div[data-testid="stSelectbox"] > div > div > select { | |
| background: rgba(0,0,0,0.3); | |
| color: white; | |
| border: 1px solid var(--glass-border); | |
| } | |
| .stButton > button { | |
| border-radius: 12px; | |
| font-weight: bold; | |
| transition: all 0.2s; | |
| } | |
| .primary-btn { | |
| background: var(--primary-gradient) !important; | |
| border: none !important; | |
| color: white !important; | |
| } | |
| .secondary-btn { | |
| background: rgba(255,255,255,0.1) !important; | |
| border: 1px solid rgba(255,255,255,0.2) !important; | |
| color: white !important; | |
| } | |
| /* Admin Actions */ | |
| .admin-actions { | |
| font-size: 0.7rem; | |
| margin-top: 5px; | |
| text-align: left; | |
| cursor: pointer; | |
| color: #ff6b6b; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # --- SESSION STATE INITIALIZATION --- | |
| if 'logged_in' not in st.session_state: | |
| st.session_state.logged_in = False | |
| if 'username' not in st.session_state: | |
| st.session_state.username = "" | |
| if 'role' not in st.session_state: | |
| st.session_state.role = "user" # or 'admin' | |
| if 'current_room' not in st.session_state: | |
| st.session_state.current_room = 1 | |
| if 'messages' not in st.session_state: | |
| st.session_state.messages = get_initial_messages() | |
| if 'users' not in st.session_state: | |
| st.session_state.users = get_mock_users() | |
| # --- HELPER FUNCTIONS --- | |
| def render_message(msg): | |
| is_own = msg['user_id'] == st.session_state.username # Simulating ID check | |
| msg_class = "own" if is_own else "other" | |
| sender_name = "شما" if is_own else msg['username'] | |
| # Admin delete button logic | |
| admin_html = "" | |
| if st.session_state.role == 'admin' and not is_own: | |
| admin_html = f""" | |
| <div class="admin-actions" onclick="deleteMessage({msg['id']})"> | |
| <i class="fas fa-trash"></i> حذف پیام | |
| </div> | |
| """ | |
| time_str = msg['time'] | |
| return f""" | |
| <div class="message {msg_class}"> | |
| <div class="message-header"> | |
| <span>{sender_name}</span> | |
| <span>{time_str}</span> | |
| </div> | |
| <div class="message-content">{msg['text']}</div> | |
| {admin_html} | |
| </div> | |
| """ | |
| def get_room_name(room_id): | |
| rooms = get_mock_rooms() | |
| for r in rooms: | |
| if r['id'] == room_id: | |
| return r['name'] | |
| return "اتاق ناشناس" | |
| # --- PAGES --- | |
| def login_page(): | |
| st.markdown(""" | |
| <div class="login-container"> | |
| <div class="glass-panel login-box"> | |
| <div style="font-size: 3rem; margin-bottom: 20px;">🤖</div> | |
| <h1 style="margin-bottom: 10px;">چت روم هوشمند</h1> | |
| <p style="opacity: 0.7; margin-bottom: 30px;">ورود به دنیای هوش مصنوعی</p> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="font-size: 0.8rem; color: rgba(255, 255, 255, 0.5); text-decoration: none; border: 1px solid rgba(255, 255, 255, 0.1); padding: 4px 12px; border-radius: 20px; display: inline-block; margin-bottom: 20px;"> | |
| Built with anycoder | |
| </a> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with st.container(): | |
| col1, col2, col3 = st.columns([1, 2, 1]) | |
| with col2: | |
| # Hidden Login Form logic | |
| with st.form("login_form", clear_on_submit=True): | |
| username_input = st.text_input("نام کاربری", placeholder="نام کاربری خود را وارد کنید") | |
| password_input = st.text_input("رمز عبور", type="password", placeholder="رمز عبور") | |
| submit_btn = st.form_submit_button("ورود به سیستم", use_container_width=True) | |
| # Simple auth simulation | |
| if submit_btn: | |
| if username_input and password_input: | |
| st.session_state.logged_in = True | |
| st.session_state.username = username_input | |
| # Simulate admin login if username is admin | |
| st.session_state.role = 'admin' if username_input.lower() == 'admin' else 'user' | |
| st.rerun() | |
| else: | |
| st.error("لطفاً نام کاربری و رمز عبور را وارد کنید.") | |
| st.markdown("<div style='text-align: center; margin-top: 20px; opacity: 0.5;'>حساب کاربری ندارید؟ <a href='#' style='color: #a29bfe;'>ثبت نام کنید</a></div>", unsafe_allow_html=True) | |
| def chat_page(): | |
| # Layout | |
| sidebar, main = st.columns([1, 3]) | |
| current_room_id = st.session_state.current_room | |
| room_name = get_room_name(current_room_id) | |
| # --- SIDEBAR --- | |
| with sidebar: | |
| st.markdown(f""" | |
| <div class="glass-panel sidebar-area"> | |
| <h3 style="margin-top:0;"><i class="fas fa-users"></i> کاربران آنلاین</h3> | |
| <div style="margin-bottom: 20px; font-size: 0.9rem; opacity: 0.8;">{room_name}</div> | |
| """, unsafe_allow_html=True) | |
| # Room Selector | |
| rooms = get_mock_rooms() | |
| room_options = {r['name']: r['id'] for r in rooms} | |
| selected_room_name = st.selectbox( | |
| "تغییر اتاق", | |
| options=list(room_options.keys()), | |
| index=list(room_options.values()).index(current_room_id), | |
| label_visibility="collapsed" | |
| ) | |
| if selected_room_name != room_name: | |
| st.session_state.current_room = room_options[selected_room_name] | |
| st.rerun() | |
| st.markdown("---") | |
| # User List | |
| users_html = "" | |
| for user in st.session_state.users: | |
| status_class = "status-online" if user['online'] else "" | |
| status_text = "آنلاین" if user['online'] else "آفلاین" | |
| users_html += f""" | |
| <div class="user-item"> | |
| <div class="user-avatar">{user['name'][0]}</div> | |
| <div style="flex:1"> | |
| <div>{user['name']}</div> | |
| <div style="font-size: 0.75rem; opacity: 0.6;"> | |
| <span class="status-dot {status_class}"></span> {status_text} | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| st.markdown(users_html, unsafe_allow_html=True) | |
| st.markdown("</div>", unsafe_allow_html=True) # Close sidebar | |
| # Logout | |
| if st.button("خروج از سیستم", use_container_width=True, key="logout_btn"): | |
| st.session_state.logged_in = False | |
| st.session_state.messages = [] # clear session | |
| st.rerun() | |
| # --- MAIN CHAT AREA --- | |
| with main: | |
| st.markdown(f""" | |
| <div class="glass-panel main-chat-area"> | |
| <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 15px;"> | |
| <div> | |
| <h2 style="margin:0;"><i class="fas fa-comments"></i> {room_name}</h2> | |
| <p style="margin:0; opacity:0.6; font-size:0.9rem;">گفتگوی آزاد و محترمانه</p> | |
| </div> | |
| <div> | |
| {f'<button class="secondary-btn" onclick="alert(\'Only Admin\')" style="padding: 5px 15px; font-size: 0.8rem;"><i class="fas fa-trash"></i> پاک کردن</button>' if st.session_state.role == 'admin' else ''} | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Messages Container | |
| with st.container(): | |
| msg_container = st.container() | |
| # Filter messages for current room | |
| room_messages = [m for m in st.session_state.messages if m['room_id'] == current_room_id] | |
| with msg_container: | |
| st.markdown('<div class="messages-container">', unsafe_allow_html=True) | |
| if not room_messages: | |
| st.markdown(""" | |
| <div style="text-align: center; opacity: 0.5; margin-top: 50px;"> | |
| <i class="fas fa-comments" style="font-size: 3rem;"></i> | |
| <p>هنوز هیچ پیامی نیست. اولین نفر باشید!</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| else: | |
| for msg in room_messages: | |
| st.markdown(render_message(msg), unsafe_allow_html=True) | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| # Input Area | |
| with st.form("chat_form", clear_on_submit=True): | |
| col_input, col_send = st.columns([5, 1]) | |
| with col_input: | |
| user_input = st.text_input("message_input", placeholder="پیام خود را بنویسید...", label_visibility="collapsed") | |
| with col_send: | |
| submit = st.form_submit_button("ارسال", use_container_width=True) | |
| # Emoji Bar (Custom HTML buttons) | |
| st.markdown(""" | |
| <div class="emoji-bar"> | |
| <button class="emoji-btn" onclick="setInput('😊')">😊</button> | |
| <button class="emoji-btn" onclick="setInput('😂')">😂</button> | |
| <button class="emoji-btn" onclick="setInput('❤️')">❤️</button> | |
| <button class="emoji-btn" onclick="setInput('👍')">👍</button> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # JS to handle emoji clicks (using streamlit's set_query_param or similar trick is hard, | |
| # so we stick to Python handling for simplicity here, or just visual buttons) | |
| # For a functional emoji click in Streamlit without components, we can use st.session_state | |
| emojis = ["😊", "😂", "❤️", "👍"] | |
| cols = st.columns(4) | |
| for i, emoji in enumerate(emojis): | |
| if cols[i].button(emoji, key=f"emoji_{i}"): | |
| st.session_state.temp_input = user_input + emoji | |
| st.rerun() | |
| if submit and user_input: | |
| # Add User Message | |
| new_msg = { | |
| "id": int(time.time() * 1000), | |
| "room_id": current_room_id, | |
| "user_id": st.session_state.username, | |
| "username": st.session_state.username, | |
| "text": user_input, | |
| "time": datetime.datetime.now().strftime("%H:%M"), | |
| "is_deleted": False | |
| } | |
| st.session_state.messages.append(new_msg) | |
| st.rerun() | |
| # Simulate AI/Bot Response | |
| time.sleep(0.5) # Simulate thinking | |
| response_text = generate_ai_response(user_input) | |
| bot_msg = { | |
| "id": int(time.time() * 1000) + 1, | |
| "room_id": current_room_id, | |
| "user_id": "ai_bot", | |
| "username": "دستیار هوشمند", | |
| "text": response_text, | |
| "time": datetime.datetime.now().strftime("%H:%M"), | |
| "is_deleted": False | |
| } | |
| st.session_state.messages.append(bot_msg) | |
| st.rerun() | |
| st.markdown("</div>", unsafe_allow_html=True) # Close main-chat-area | |
| # --- MAIN NAVIGATION LOGIC --- | |
| # Use temp_input for emoji appending | |
| if 'temp_input' not in st.session_state: | |
| st.session_state.temp_input = "" | |
| def main(): | |
| if not st.session_state.logged_in: | |
| login_page() | |
| else: | |
| chat_page() | |
| if __name__ == "__main__": | |
| main() |