Spaces:
Running
Running
| import gradio as gr | |
| import random | |
| import time | |
| from datetime import datetime | |
| from typing import List, Dict, Tuple | |
| # Данные друзей и чатов | |
| friends_data = { | |
| "AlexGamer": {"status": "online", "avatar": "🎮", "last_seen": "Сейчас"}, | |
| "ProPlayer": {"status": "online", "avatar": "🏆", "last_seen": "Сейчас"}, | |
| "BuilderMaster": {"status": "away", "avatar": "🏗️", "last_seen": "5 мин назад"}, | |
| "SpeedRunner": {"status": "offline", "avatar": "⚡", "last_seen": "1 час назад"}, | |
| "CreativeMind": {"status": "online", "avatar": "🎨", "last_seen": "Сейчас"}, | |
| "TeamLeader": {"status": "online", "avatar": "👑", "last_seen": "Сейчас"}, | |
| "NinjaWarrior": {"status": "offline", "avatar": "🥷", "last_seen": "2 часа назад"}, | |
| "MysteryPlayer": {"status": "away", "avatar": "🎭", "last_seen": "15 мин назад"} | |
| } | |
| # История чатов | |
| chat_history = { | |
| "AlexGamer": [ | |
| {"role": "user", "content": "Привет! Как игра?", "time": "14:30"}, | |
| {"role": "friend", "content": "Привет! Отлично, новый рекорд поставил!", "time": "14:31"}, | |
| {"role": "user", "content": "Круто! В какую играл?", "time": "14:32"}, | |
| {"role": "friend", "content": "Tower of Hell, дошел до 15 уровня", "time": "14:33"} | |
| ], | |
| "ProPlayer": [ | |
| {"role": "friend", "content": "Готов к турниру сегодня?", "time": "13:45"}, | |
| {"role": "user", "content": "Да, жду не дождусь!", "time": "13:50"}, | |
| {"role": "friend", "content": "Встречаемся в 7 по серверу", "time": "13:52"} | |
| ], | |
| "BuilderMaster": [ | |
| {"role": "friend", "content": "Посмотри мой новый мир!", "time": "12:00"}, | |
| {"role": "user", "content": "Вау, это потрясающе!", "time": "12:15"} | |
| ] | |
| } | |
| def get_status_color(status: str) -> str: | |
| colors = { | |
| "online": "#00ff00", | |
| "away": "#ffaa00", | |
| "offline": "#666666" | |
| } | |
| return colors.get(status, "#666666") | |
| def format_chat_history(history: List[Dict]) -> List[Tuple[str, str]]: | |
| formatted = [] | |
| for msg in history: | |
| if msg["role"] == "user": | |
| formatted.append((msg["content"], None)) | |
| else: | |
| formatted.append((None, msg["content"])) | |
| return formatted | |
| def send_message(message: str, current_friend: str, history: List[Tuple[str, str]]) -> Tuple[List[Tuple[str, str]], str]: | |
| if not message.strip(): | |
| return history, "" | |
| current_time = datetime.now().strftime("%H:%M") | |
| # Добавляем сообщение в историю | |
| if current_friend not in chat_history: | |
| chat_history[current_friend] = [] | |
| chat_history[current_friend].append({ | |
| "role": "user", | |
| "content": message, | |
| "time": current_time | |
| }) | |
| # Форматируем для отображения | |
| new_history = format_chat_history(chat_history[current_friend]) | |
| # Симулируем ответ друга | |
| time.sleep(1) | |
| responses = [ | |
| "Отлично! 👍", | |
| "Понял, согласен!", | |
| "Круто, давай так!", | |
| "😄 Отличная идея!", | |
| "Обязательно попробую!", | |
| "Спасибо за информацию!", | |
| "Погнали! 🚀", | |
| "Это просто эпик!" | |
| ] | |
| bot_response = random.choice(responses) | |
| chat_history[current_friend].append({ | |
| "role": "friend", | |
| "content": bot_response, | |
| "time": current_time | |
| }) | |
| final_history = format_chat_history(chat_history[current_friend]) | |
| return final_history, "" | |
| def select_friend(friend_name: str) -> Tuple[List[Tuple[str, str]], str]: | |
| if friend_name in chat_history: | |
| return format_chat_history(chat_history[friend_name]), f"Чат с {friend_name}" | |
| return [], f"Начните чат с {friend_name}" | |
| def get_friends_list() -> List[Dict]: | |
| friends_list = [] | |
| for name, info in friends_data.items(): | |
| status_color = get_status_color(info["status"]) | |
| friends_list.append({ | |
| "name": name, | |
| "status": info["status"], | |
| "avatar": info["avatar"], | |
| "last_seen": info["last_seen"], | |
| "status_color": status_color | |
| }) | |
| return friends_list | |
| def create_friends_html() -> str: | |
| friends_html = "" | |
| for name, info in friends_data.items(): | |
| status_color = get_status_color(info["status"]) | |
| status_icon = "🟢" if info["status"] == "online" else "🟡" if info["status"] == "away" else "🔴" | |
| friends_html += f""" | |
| <div class="friend-item" onclick="selectFriend('{name}')" style="cursor: pointer; padding: 12px; margin: 5px 0; border-radius: 10px; background: rgba(255, 255, 255, 0.05); transition: all 0.3s ease;"> | |
| <div style="display: flex; align-items: center; gap: 10px;"> | |
| <span style="font-size: 24px;">{info['avatar']}</span> | |
| <div style="flex: 1;"> | |
| <div style="display: flex; align-items: center; gap: 5px;"> | |
| <span style="color: white; font-weight: bold;">{name}</span> | |
| <span style="color: {status_color}; font-size: 12px;">{status_icon}</span> | |
| </div> | |
| <div style="color: #888; font-size: 12px;">{info['last_seen']}</div> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| return friends_html | |
| # CSS для стилизации в стиле Roblox Aero | |
| custom_css = """ | |
| /* Основные стили в стиле Roblox Aero */ | |
| .gradio-container { | |
| background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%); | |
| color: #ffffff; | |
| font-family: 'Kalam', cursive; | |
| min-height: 100vh; | |
| } | |
| /* Glass эффект для компонентов */ | |
| .glass-effect { | |
| background: rgba(20, 20, 20, 0.7) !important; | |
| backdrop-filter: blur(10px); | |
| -webkit-backdrop-filter: blur(10px); | |
| border: 1px solid rgba(255, 255, 255, 0.1) !important; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1); | |
| } | |
| /* Стили для чата */ | |
| .message-user { | |
| background: linear-gradient(135deg, #ffd700, #ffed4e) !important; | |
| color: #000000 !important; | |
| border-radius: 18px 18px 4px 18px !important; | |
| margin-left: auto !important; | |
| font-weight: bold; | |
| } | |
| .message-friend { | |
| background: rgba(255, 255, 255, 0.1) !important; | |
| color: #ffffff !important; | |
| border-radius: 18px 18px 18px 4px !important; | |
| border: 1px solid rgba(255, 215, 0, 0.3); | |
| } | |
| /* Стили для кнопок */ | |
| .btn-primary { | |
| background: linear-gradient(135deg, #ffd700, #ffed4e) !important; | |
| color: #000000 !important; | |
| border: none !important; | |
| border-radius: 20px !important; | |
| font-weight: bold !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 6px 30px rgba(255, 215, 0, 0.5) !important; | |
| } | |
| /* Стили для друзей */ | |
| .friend-item:hover { | |
| background: rgba(255, 215, 0, 0.1) !important; | |
| border: 1px solid rgba(255, 215, 0, 0.3) !important; | |
| transform: translateX(5px); | |
| } | |
| /* Заголовки */ | |
| .chat-header { | |
| background: rgba(20, 20, 20, 0.9) !important; | |
| border-bottom: 2px solid #ffd700 !important; | |
| color: #ffd700 !important; | |
| font-weight: bold !important; | |
| } | |
| /* Анимации */ | |
| @keyframes glow { | |
| from { filter: drop-shadow(0 0 20px rgba(255, 215, 0, 0.5)); } | |
| to { filter: drop-shadow(0 0 30px rgba(255, 215, 0, 0.8)); } | |
| } | |
| .glow-text { | |
| animation: glow 2s ease-in-out infinite alternate; | |
| } | |
| /* Поле ввода */ | |
| .input-message { | |
| background: rgba(255, 255, 255, 0.1) !important; | |
| border: 1px solid rgba(255, 215, 0, 0.3) !important; | |
| border-radius: 20px !important; | |
| color: #ffffff !important; | |
| } | |
| .input-message::placeholder { | |
| color: rgba(255, 255, 255, 0.5) !important; | |
| } | |
| /* Табы */ | |
| .tab-nav { | |
| background: rgba(20, 20, 20, 0.8) !important; | |
| border-bottom: 1px solid rgba(255, 215, 0, 0.3) !important; | |
| } | |
| .tab-nav button { | |
| color: #ffffff !important; | |
| border: none !important; | |
| background: transparent !important; | |
| } | |
| .tab-nav button.selected { | |
| color: #ffd700 !important; | |
| border-bottom: 2px solid #ffd700 !important; | |
| } | |
| """ | |
| # JavaScript для интерактивности | |
| custom_js = """ | |
| function selectFriend(friendName) { | |
| // Находим и кликаем на соответствующего друга в интерфейсе | |
| const friendButtons = document.querySelectorAll('[data-testid="radio"]'); | |
| friendButtons.forEach(btn => { | |
| if(btn.textContent.includes(friendName)) { | |
| btn.click(); | |
| } | |
| }); | |
| } | |
| // Добавляем анимацию для новых сообщений | |
| const observer = new MutationObserver((mutations) => { | |
| mutations.forEach((mutation) => { | |
| if (mutation.type === 'childList') { | |
| const newMessages = mutation.addedNodes; | |
| newMessages.forEach(node => { | |
| if(node.classList && node.classList.contains('message-user')) { | |
| node.style.animation = 'slideInRight 0.3s ease'; | |
| } else if(node.classList && node.classList.contains('message-friend')) { | |
| node.style.animation = 'slideInLeft 0.3s ease'; | |
| } | |
| }); | |
| } | |
| }); | |
| }); | |
| // Наблюдаем за изменениями в чате | |
| const chatContainer = document.querySelector('[data-testid="chatbot"]'); | |
| if(chatContainer) { | |
| observer.observe(chatContainer, { childList: true, subtree: true }); | |
| } | |
| // Добавляем CSS анимации | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| @keyframes slideInRight { | |
| from { transform: translateX(100%); opacity: 0; } | |
| to { transform: translateX(0); opacity: 1; } | |
| } | |
| @keyframes slideInLeft { | |
| from { transform: translateX(-100%); opacity: 0; } | |
| to { transform: translateX(0); opacity: 1; } | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| """ | |
| with gr.Blocks() as demo: | |
| gr.HTML(""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Caveat:wght@400;700&display=swap'); | |
| </style> | |
| """) | |
| gr.HTML(""" | |
| <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, rgba(20,20,20,0.9), rgba(10,10,10,0.9)); | |
| border-bottom: 2px solid #ffd700; margin-bottom: 20px; border-radius: 15px;"> | |
| <h1 style="color: #ffd700; font-family: 'Caveat', cursive; font-size: 48px; margin: 0; text-shadow: 0 0 20px rgba(255, 215, 0, 0.5);"> | |
| 💬 Чат с друзьями | |
| </h1> | |
| <p style="color: #b0b0b0; font-family: 'Kalam', cursive; margin: 10px 0 0 0;"> | |
| Общайся с друзьями в реальном времени | |
| </p> | |
| <p style="margin-top: 10px;"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" | |
| style="color: #ffd700; text-decoration: none; font-size: 14px;"> | |
| Built with anycoder | |
| </a> | |
| </p> | |
| </div> | |
| """) | |
| with gr.Row(equal_height=True): | |
| # Список друзей | |
| with gr.Column(scale=1): | |
| gr.HTML('<h3 style="color: #ffd700; margin-bottom: 15px;">👥 Друзья</h3>') | |
| with gr.Group(elem_classes=["glass-effect"]): | |
| friends_html = gr.HTML(create_friends_html()) | |
| # Статистика | |
| gr.HTML(f""" | |
| <div style="margin-top: 20px; padding: 15px; background: rgba(255,215,0,0.1); border-radius: 10px;"> | |
| <div style="color: #ffd700; font-weight: bold; margin-bottom: 10px;">📊 Статистика</div> | |
| <div style="color: #ffffff; font-size: 14px;"> | |
| <div>🟢 Онлайн: {sum(1 for f in friends_data.values() if f['status'] == 'online')}</div> | |
| <div>🟡 Отошли: {sum(1 for f in friends_data.values() if f['status'] == 'away')}</div> | |
| <div>🔴 Офлайн: {sum(1 for f in friends_data.values() if f['status'] == 'offline')}</div> | |
| <div>📝 Всего друзей: {len(friends_data)}</div> | |
| </div> | |
| </div> | |
| """) | |
| # Основная область чата | |
| with gr.Column(scale=2): | |
| with gr.Row(): | |
| # Выбор друга | |
| friend_choice = gr.Radio( | |
| choices=list(friends_data.keys()), | |
| value="AlexGamer", | |
| label="Выберите друга для чата", | |
| elem_classes=["glass-effect"], | |
| interactive=True | |
| ) | |
| # История чата | |
| chatbot = gr.Chatbot( | |
| value=format_chat_history(chat_history.get("AlexGamer", [])), | |
| height=400, | |
| show_copy_button=True, | |
| bubble_full_width=False, | |
| elem_classes=["glass-effect"], | |
| avatar_images=(None, None) | |
| ) | |
| # Поле ввода сообщения | |
| with gr.Row(): | |
| msg_input = gr.Textbox( | |
| placeholder="Введите сообщение...", | |
| label="Сообщение", | |
| elem_classes=["input-message"], | |
| scale=4, | |
| container=False | |
| ) | |
| send_btn = gr.Button( | |
| "➤", | |
| elem_classes=["btn-primary"], | |
| scale=1, | |
| size="lg" | |
| ) | |
| # Кнопки действий | |
| with gr.Row(): | |
| emoji_btn = gr.Button("😊 Эмодзи", elem_classes=["btn-primary"], scale=1) | |
| voice_btn = gr.Button("🎤 Голос", elem_classes=["btn-primary"], scale=1) | |
| photo_btn = gr.Button("📷 Фото", elem_classes=["btn-primary"], scale=1) | |
| clear_btn = gr.Button("🗑️ Очистить", elem_classes=["btn-primary"], scale=1) | |
| # Обработчики событий | |
| def handle_friend_change(friend_name): | |
| return format_chat_history(chat_history.get(friend_name, [])) | |
| friend_choice.change( | |
| handle_friend_change, | |
| inputs=[friend_choice], | |
| outputs=[chatbot] | |
| ) | |
| def handle_send(message, friend, history): | |
| if message.strip(): | |
| return send_message(message, friend, history) | |
| return history, "" | |
| send_btn.click( | |
| handle_send, | |
| inputs=[msg_input, friend_choice, chatbot], | |
| outputs=[chatbot, msg_input] | |
| ) | |
| msg_input.submit( | |
| handle_send, | |
| inputs=[msg_input, friend_choice, chatbot], | |
| outputs=[chatbot, msg_input] | |
| ) | |
| # Обработка кнопок эмодзи | |
| def add_emoji(emoji_type): | |
| emojis = { | |
| "😊": "😊 😂 🤣 😍 😎 🤔 🤗 😴 🥳 🎉", | |
| "🎮": "🎮 🎯 🎪 🎨 🎭 🎪 🎯 🎲", | |
| "❤️": "❤️ 💛 💚 💙 💜 🖤 💔" | |
| } | |
| return emojis.get(emoji_type, "😊") | |
| emoji_btn.click( | |
| lambda: gr.Info("Выберите эмодзи: 😊 😂 🤣 😍 😎 🤔 🤗 😴 🥳 🎉"), | |
| inputs=[], | |
| outputs=[] | |
| ) | |
| voice_btn.click( | |
| lambda: gr.Info("🎤 Голосовые сообщения в разработке"), | |
| inputs=[], | |
| outputs=[] | |
| ) | |
| photo_btn.click( | |
| lambda: gr.Info("📷 Загрузка изображений в разработке"), | |
| inputs=[], | |
| outputs=[] | |
| ) | |
| clear_btn.click( | |
| lambda: ([], ""), | |
| outputs=[chatbot, msg_input] | |
| ) | |
| demo.launch( | |
| theme=gr.themes.Soft( | |
| primary_hue="yellow", | |
| secondary_hue="orange", | |
| neutral_hue="slate", | |
| font=gr.themes.GoogleFont("Kalam"), | |
| text_size="lg", | |
| spacing_size="lg", | |
| radius_size="md" | |
| ).set( | |
| body_background_fill="*primary_950", | |
| block_background_fill="*neutral_950", | |
| block_border_width="1px", | |
| block_border_color="*neutral_800", | |
| block_radius="15px", | |
| button_primary_background_fill="linear-gradient(135deg, *primary_600, *primary_400)", | |
| button_primary_background_fill_hover="linear-gradient(135deg, *primary_500, *primary_300)", | |
| button_primary_text_color="*neutral_950", | |
| chatbot_background_fill="*neutral_950", | |
| chatbot_message_user_background_fill="linear-gradient(135deg, *primary_600, *primary_400)", | |
| chatbot_message_user_text_color="*neutral_950", | |
| chatbot_message_assistant_background_fill="*neutral_800", | |
| chatbot_message_assistant_text_color="*neutral_100" | |
| ), | |
| css=custom_css, | |
| js=custom_js, | |
| footer_links=[ | |
| {"label": "Built with anycoder", "url": "https://huggingface.co/spaces/akhaliq/anycoder"} | |
| ] | |
| ) |