FlameF0X commited on
Commit
60bbdd3
·
verified ·
1 Parent(s): 7decbfe

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +200 -0
app.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import time
3
+ from datetime import datetime
4
+ from collections import deque
5
+
6
+ # --- GLOBAL SERVER STATE (In-Memory Only) ---
7
+ # We use a simple dictionary to store the last 50 messages for active channels.
8
+ # When the Space restarts or sleeps, all data is wiped.
9
+ class ChatServer:
10
+ def __init__(self):
11
+ # channels = { "channel_name": deque([messages], maxlen=50) }
12
+ self.channels = {"Main": deque(maxlen=50)}
13
+ # active_users = { "username": {"last_seen": timestamp, "profile_pic": url} }
14
+ self.active_users = {}
15
+ # dms = { "user1-user2": deque(maxlen=50) }
16
+ self.dms = {}
17
+
18
+ def prune_users(self):
19
+ """Remove users who haven't polled in 30 seconds."""
20
+ now = time.time()
21
+ expired = [u for u, data in self.active_users.items() if now - data['last_seen'] > 30]
22
+ for u in expired:
23
+ del self.active_users[u]
24
+
25
+ def broadcast(self, channel, user, message, profile_pic):
26
+ if channel not in self.channels:
27
+ self.channels[channel] = deque(maxlen=50)
28
+
29
+ msg_data = {
30
+ "user": user,
31
+ "text": message,
32
+ "pic": profile_pic,
33
+ "time": datetime.now().strftime("%H:%M")
34
+ }
35
+ self.channels[channel].append(msg_data)
36
+
37
+ def send_dm(self, sender, recipient, message, profile_pic):
38
+ dm_key = "-".join(sorted([sender, recipient]))
39
+ if dm_key not in self.dms:
40
+ self.dms[dm_key] = deque(maxlen=50)
41
+
42
+ msg_data = {
43
+ "user": sender,
44
+ "text": message,
45
+ "pic": profile_pic,
46
+ "time": datetime.now().strftime("%H:%M")
47
+ }
48
+ self.dms[dm_key].append(msg_data)
49
+
50
+ server = ChatServer()
51
+
52
+ # --- UTILS ---
53
+
54
+ def get_messages_html(messages):
55
+ if not messages:
56
+ return "<div style='color: gray; text-align: center; margin-top: 20px;'>No messages yet. Start the conversation!</div>"
57
+
58
+ html = "<div style='display: flex; flex-direction: column; gap: 10px;'>"
59
+ for m in messages:
60
+ html += f"""
61
+ <div style="display: flex; align-items: flex-start; gap: 10px;">
62
+ <img src="{m['pic']}" style="width: 35px; height: 35px; border-radius: 50%; border: 1px solid #ddd;">
63
+ <div style="background: rgba(100, 100, 100, 0.1); padding: 8px 12px; border-radius: 12px; max-width: 80%;">
64
+ <div style="font-weight: bold; font-size: 0.8em; margin-bottom: 2px;">{m['user']} <span style="font-weight: normal; color: gray; margin-left: 5px;">{m['time']}</span></div>
65
+ <div style="font-size: 0.95em;">{m['text']}</div>
66
+ </div>
67
+ </div>
68
+ """
69
+ html += "</div>"
70
+ return html
71
+
72
+ def get_active_users_html(current_user):
73
+ server.prune_users()
74
+ html = "<div style='display: flex; flex-direction: column; gap: 5px;'>"
75
+ for user, data in server.active_users.items():
76
+ status = "🟢" if user != current_user else "👤 (You)"
77
+ html += f"""
78
+ <div style="display: flex; align-items: center; gap: 8px; font-size: 0.9em; padding: 4px;">
79
+ <img src="{data['pic']}" style="width: 20px; height: 20px; border-radius: 50%;">
80
+ <span>{user}</span>
81
+ <span style="font-size: 0.7em;">{status}</span>
82
+ </div>
83
+ """
84
+ html += "</div>"
85
+ return html
86
+
87
+ # --- GRADIO INTERFACE ---
88
+
89
+ with gr.Blocks(css="""
90
+ #chat-container { height: 500px; overflow-y: auto; border: 1px solid #ddd; padding: 15px; border-radius: 8px; background: var(--body-background-fill); }
91
+ .user-list { border-left: 1px solid #ddd; padding-left: 15px; }
92
+ #msg-input input { border-radius: 20px !important; }
93
+ """) as demo:
94
+
95
+ # HF OAuth session data
96
+ user_session = gr.State(None)
97
+ current_room = gr.State("Main")
98
+ dm_target = gr.State(None)
99
+
100
+ with gr.Row():
101
+ with gr.Column(scale=8):
102
+ gr.Markdown(f"# 🤗 HF Live Community Chat")
103
+ with gr.Column(scale=2):
104
+ login_btn = gr.LoginButton()
105
+
106
+ with gr.Row(visible=False) as chat_ui:
107
+ # Sidebar
108
+ with gr.Column(scale=2, variant="panel"):
109
+ gr.Markdown("### 🏠 Rooms")
110
+ room_list = gr.Radio(["Main"], value="Main", label="Join a Channel")
111
+ new_room_name = gr.Textbox(placeholder="Create room...", label=None, show_label=False)
112
+ create_room_btn = gr.Button("Create & Join", size="sm")
113
+
114
+ gr.Markdown("### ✉️ Direct Messages")
115
+ dm_user_input = gr.Textbox(placeholder="Username...", label="DM Someone")
116
+ start_dm_btn = gr.Button("Open DM", size="sm")
117
+
118
+ gr.Markdown("### 👥 Online Now")
119
+ active_users_box = gr.HTML()
120
+
121
+ # Main Chat Area
122
+ with gr.Column(scale=8):
123
+ room_title = gr.Markdown("## # Main")
124
+ chat_display = gr.HTML(elem_id="chat-container")
125
+
126
+ with gr.Row():
127
+ msg_input = gr.Textbox(
128
+ placeholder="Type a message...",
129
+ show_label=False,
130
+ scale=9,
131
+ elem_id="msg-input"
132
+ )
133
+ send_btn = gr.Button("Send", scale=1, variant="primary")
134
+
135
+ # --- LOGIC ---
136
+
137
+ def on_load(profile: gr.OAuthProfile | None):
138
+ if profile is None:
139
+ return gr.update(visible=False), None
140
+
141
+ username = profile.username
142
+ pic = profile.profile_image or "https://huggingface.co/front/assets/huggingface_logo-noborder.svg"
143
+
144
+ # Register user
145
+ server.active_users[username] = {"last_seen": time.time(), "pic": pic}
146
+ return gr.update(visible=True), {"name": username, "pic": pic}
147
+
148
+ demo.load(on_load, None, [chat_ui, user_session])
149
+
150
+ def refresh_chat(session, room, dm_user):
151
+ if not session: return "", ""
152
+
153
+ # Heartbeat
154
+ server.active_users[session['name']]['last_seen'] = time.time()
155
+
156
+ if dm_user:
157
+ dm_key = "-".join(sorted([session['name'], dm_user]))
158
+ msgs = list(server.dms.get(dm_key, []))
159
+ title = f"## 💬 DM with {dm_user}"
160
+ else:
161
+ msgs = list(server.channels.get(room, []))
162
+ title = f"## # {room}"
163
+
164
+ return get_messages_html(msgs), get_active_users_html(session['name']), title
165
+
166
+ timer = gr.Timer(1)
167
+ timer.tick(refresh_chat, [user_session, current_room, dm_target], [chat_display, active_users_box, room_title])
168
+
169
+ def send_msg(text, session, room, dm_user):
170
+ if not text or not session: return ""
171
+
172
+ if dm_user:
173
+ server.send_dm(session['name'], dm_user, text, session['pic'])
174
+ else:
175
+ server.broadcast(room, session['name'], text, session['pic'])
176
+ return ""
177
+
178
+ send_btn.click(send_msg, [msg_input, user_session, current_room, dm_target], [msg_input])
179
+ msg_input.submit(send_msg, [msg_input, user_session, current_room, dm_target], [msg_input])
180
+
181
+ def create_and_join(name, current_options):
182
+ if not name: return gr.update(), "Main"
183
+ clean_name = name.strip().replace(" ", "-")
184
+ new_options = list(set(current_options + [clean_name]))
185
+ return gr.update(choices=new_options, value=clean_name), clean_name, None
186
+
187
+ create_room_btn.click(create_and_join, [new_room_name, room_list], [room_list, current_room, dm_target])
188
+
189
+ def switch_room(name):
190
+ return name, None
191
+
192
+ room_list.change(switch_room, room_list, [current_room, dm_target])
193
+
194
+ def start_dm(target):
195
+ if not target: return "Main", None
196
+ return "Main", target
197
+
198
+ start_dm_btn.click(start_dm, dm_user_input, [current_room, dm_target])
199
+
200
+ demo.launch()