thomas-yanxin commited on
Commit
9597bad
·
verified ·
1 Parent(s): 0a97ef1

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +437 -0
app.py ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import time
4
+
5
+ import gradio as gr
6
+ import modelscope_studio.components.antd as antd
7
+ import modelscope_studio.components.antdx as antdx
8
+ import modelscope_studio.components.base as ms
9
+ import modelscope_studio.components.pro as pro
10
+ from mem0 import Memory
11
+ from modelscope_studio.components.pro.chatbot import (ChatbotBotConfig,
12
+ ChatbotPromptsConfig,
13
+ ChatbotUserConfig,
14
+ ChatbotWelcomeConfig)
15
+ from openai import OpenAI
16
+
17
+ config = {
18
+ "vector_store": {
19
+ "provider": "faiss",
20
+ "config": {
21
+ "collection_name": "test",
22
+ "path": "./faiss_memories",
23
+ "distance_strategy": "euclidean"
24
+ }
25
+ }
26
+ }
27
+
28
+ m = Memory.from_config(config)
29
+
30
+ gw_api_key = os.getenv("GW_API_KEY")
31
+
32
+ client = OpenAI(
33
+ base_url='https://api.geniuworks.com/v2',
34
+ api_key=gw_api_key,
35
+ )
36
+
37
+ model = "xinyuan-32b-v0609"
38
+ # model = "gpt-4.1-2025-04-14"
39
+
40
+ # 用户管理相关函数
41
+ USERS_FILE = "users.txt"
42
+
43
+ def load_users():
44
+ """加载已注册用户列表"""
45
+ if not os.path.exists(USERS_FILE):
46
+ return set()
47
+ with open(USERS_FILE, 'r', encoding='utf-8') as f:
48
+ return set(line.strip() for line in f if line.strip())
49
+
50
+ def save_user(username):
51
+ """保存新用户到文件"""
52
+ with open(USERS_FILE, 'a', encoding='utf-8') as f:
53
+ f.write(username + '\n')
54
+
55
+ def is_valid_username(username):
56
+ """验证用户名是否有效(仅英文字母和数字)"""
57
+ if not username:
58
+ return False
59
+ return bool(re.match(r'^[a-zA-Z][a-zA-Z0-9_]*$', username)) and len(username) >= 3
60
+
61
+ def login_user(username):
62
+ """用户登录验证"""
63
+ if not is_valid_username(username):
64
+ return False, "用户名无效!用户名必须以英文字母开头,只能包含英文字母、数字和下划线,且长度至少3位。"
65
+
66
+ users = load_users()
67
+ if username in users:
68
+ return True, f"欢迎回来,{username}!"
69
+ else:
70
+ return False, f"用户 {username} 未注册,请先注册。"
71
+
72
+ def register_user(username):
73
+ """用户注册"""
74
+ if not is_valid_username(username):
75
+ return False, "用户名无效!用户名必须以英文字母开头,只能包含英文字母、数字和下划线,且长度至少3位。"
76
+
77
+ users = load_users()
78
+ if username in users:
79
+ return False, f"用户名 {username} 已存在,请直接登录。"
80
+
81
+ save_user(username)
82
+ return True, f"注册成功!欢迎,{username}!"
83
+
84
+ def handle_auth(username, is_register):
85
+ """处理认证逻辑"""
86
+ if is_register:
87
+ success, message = register_user(username)
88
+ else:
89
+ success, message = login_user(username)
90
+
91
+ if success:
92
+ return (
93
+ gr.update(visible=False), # 隐藏登录界面
94
+ gr.update(visible=True), # 显示聊天界面
95
+ gr.update(message=message, type="success", visible=True), # 显示成功消息
96
+ username
97
+ )
98
+ else:
99
+ return (
100
+ gr.update(visible=True), # 保持登录界面可见
101
+ gr.update(visible=False), # 隐藏聊天界面
102
+ gr.update(message=message, type="error", visible=True), # 显示错误消息
103
+ ""
104
+ )
105
+
106
+ def prompt_select(e: gr.EventData):
107
+ return gr.update(value=e._data["payload"][0]["value"]["description"])
108
+
109
+
110
+ def clear():
111
+ return gr.update(value=None)
112
+
113
+
114
+ def retry(chatbot_value, e: gr.EventData, username=None):
115
+ index = e._data["payload"][0]["index"]
116
+ chatbot_value = chatbot_value[:index]
117
+
118
+ yield gr.update(loading=True), gr.update(value=chatbot_value), gr.update(
119
+ disabled=True)
120
+ for chunk in submit(None, chatbot_value, username):
121
+ yield chunk
122
+
123
+
124
+ def cancel(chatbot_value):
125
+ chatbot_value[-1]["loading"] = False
126
+ chatbot_value[-1]["status"] = "done"
127
+ chatbot_value[-1]["footer"] = "Chat completion paused"
128
+ return gr.update(value=chatbot_value), gr.update(loading=False), gr.update(
129
+ disabled=False)
130
+
131
+
132
+ def format_history(sender_value, history, username=None):
133
+ messages = []
134
+
135
+ # 添加系统提示,包含用户名信息
136
+ if username:
137
+ system_prompt = f"""You are Xinyuan, a large language model trained by Cylingo Group. You are a helpful assistant. 目前和你聊天的用户是{username}."""
138
+ messages.append({"role": "system", "content": system_prompt})
139
+
140
+ related_memories = m.search(query=sender_value, user_id=username)
141
+ print(related_memories)
142
+
143
+ related_memories_content = ""
144
+
145
+ # {'results': [{'id': '8de25384-f210-4442-a04f-cd6c7796a5b7', 'memory': 'Loves sci-fi movies', 'hash': '1110b1af77367917ea2022355a16f187', 'metadata': None, 'score': 0.1812809524839618, 'created_at': '2025-08-05T23:54:13.694114-07:00', 'updated_at': None, 'user_id': 'alice'}, {'id': 'a4aa36b6-0595-492c-b6b1-5013511820d1', 'memory': 'Not a big fan of thriller movies', 'hash': '028dfab4483f28980e292f62578d3293', 'metadata': None, 'score': 0.17128575336629281, 'created_at': '2025-08-05T23:54:13.691791-07:00', 'updated_at': None, 'user_id': 'alice'}, {'id': 'a736ea22-3042-4275-ab9b-596324348119', 'memory': 'Planning to watch a movie tonight', 'hash': 'bf55418607cfdca4afa311b5fd8496bd', 'metadata': None, 'score': 0.1213398963070364, 'created_at': '2025-08-05T23:54:13.687585-07:00', 'updated_at': None, 'user_id': 'alice'}]}
146
+ # 将related_memories按照score排序
147
+ if related_memories and 'results' in related_memories:
148
+ related_memories_list = sorted(related_memories['results'], key=lambda x: x['score'], reverse=True)
149
+
150
+ for id, item in enumerate(related_memories_list):
151
+ # 将score添加到memory中
152
+ related_memories_content += f"相关记忆{id}:\n内容:{item['memory']}\n相关度:{item['score']}\n\n"
153
+ if related_memories_content:
154
+ system_prompt += f"\n相关记忆:\n{related_memories_content}"
155
+ messages.insert(0, {"role": "system", "content": system_prompt})
156
+
157
+ for item in history:
158
+ if item["role"] == "user":
159
+ messages.append({"role": "user", "content": item["content"]})
160
+ elif item["role"] == "assistant":
161
+ # ignore thought message
162
+ messages.append({
163
+ "role": "assistant",
164
+ "content": item["content"][-1]["content"]
165
+ })
166
+
167
+
168
+ print(related_memories)
169
+ print(messages)
170
+ return messages
171
+
172
+
173
+ def submit(sender_value, chatbot_value, username=None):
174
+ if sender_value is not None:
175
+ chatbot_value.append({
176
+ "role": "user",
177
+ "content": sender_value,
178
+ })
179
+ history_messages = format_history(sender_value, chatbot_value, username)
180
+ chatbot_value.append({
181
+ "role": "assistant",
182
+ "content": [],
183
+ "loading": True,
184
+ "status": "pending"
185
+ })
186
+ yield {
187
+ sender: gr.update(value=None, loading=True),
188
+ clear_btn: gr.update(disabled=True),
189
+ chatbot: gr.update(value=chatbot_value)
190
+ }
191
+
192
+ try:
193
+ response = client.chat.completions.create(model=model,
194
+ messages=history_messages,
195
+ stream=True,
196
+ max_tokens=32768,
197
+ temperature=0.6,
198
+ top_p=0.95,
199
+ )
200
+
201
+ thought_done = False
202
+ start_time = time.time()
203
+ message_content = chatbot_value[-1]["content"]
204
+ # thought content
205
+ message_content.append({
206
+ "copyable": False,
207
+ "editable": False,
208
+ "type": "tool",
209
+ "content": "",
210
+ "options": {
211
+ "title": "Thinking..."
212
+ }
213
+ })
214
+ # content
215
+ message_content.append({
216
+ "type": "text",
217
+ "content": "",
218
+ })
219
+
220
+ # 收集完整的助手响应内容用于保存到内存
221
+ full_assistant_content = ""
222
+
223
+ for chunk in response:
224
+ try:
225
+ reasoning_content = chunk.choices[0].delta.reasoning_content
226
+ except:
227
+ reasoning_content = ""
228
+ try:
229
+ content = chunk.choices[0].delta.content
230
+ except:
231
+ content = ""
232
+ chatbot_value[-1]["loading"] = False
233
+ message_content[-2]["content"] += reasoning_content or ""
234
+ message_content[-1]["content"] += content or ""
235
+
236
+ # 收集助手的实际回复内容(不包括思考过程)
237
+ if content:
238
+ full_assistant_content += content
239
+
240
+ if content and not thought_done:
241
+ thought_done = True
242
+ thought_cost_time = "{:.2f}".format(time.time() - start_time)
243
+ message_content[-2]["options"][
244
+ "title"] = f"End of Thought ({thought_cost_time}s)"
245
+ message_content[-2]["options"]["status"] = "done"
246
+
247
+ yield {chatbot: gr.update(value=chatbot_value)}
248
+
249
+ # 在流式响应完成后保存到内存
250
+ if username and sender_value and full_assistant_content:
251
+ memory_messages = [
252
+ {'role': 'user', 'content': sender_value},
253
+ {'role': 'assistant', 'content': full_assistant_content}
254
+ ]
255
+ m.add(memory_messages, user_id=username)
256
+
257
+ chatbot_value[-1]["footer"] = "{:.2f}".format(time.time() -
258
+ start_time) + 's'
259
+ chatbot_value[-1]["status"] = "done"
260
+ yield {
261
+ clear_btn: gr.update(disabled=False),
262
+ sender: gr.update(loading=False),
263
+ chatbot: gr.update(value=chatbot_value),
264
+ }
265
+ except Exception as e:
266
+ chatbot_value[-1]["loading"] = False
267
+ chatbot_value[-1]["status"] = "done"
268
+ chatbot_value[-1]["content"] = "Failed to respond, please try again."
269
+ yield {
270
+ clear_btn: gr.update(disabled=False),
271
+ sender: gr.update(loading=False),
272
+ chatbot: gr.update(value=chatbot_value),
273
+ }
274
+ raise e
275
+
276
+
277
+ with gr.Blocks() as demo, ms.Application(), antdx.XProvider():
278
+ # 状态变量
279
+ current_user = gr.State("")
280
+
281
+ # 登录界面
282
+ with antd.Flex(vertical=True, gap="large", elem_id="login_container") as login_container:
283
+ with antd.Card(title="欢迎使用 Xinyuan 聊天助手"):
284
+ with antd.Flex(vertical=True, gap="middle"):
285
+ antd.Typography.Title("用户登录/注册", level=3)
286
+ antd.Typography.Text("请输入您的英文用户名(3位以上,仅支持英文字母、数字和下划线)")
287
+
288
+ username_input = antd.Input(
289
+ placeholder="请输入用户名(如:john_doe)",
290
+ size="large"
291
+ )
292
+
293
+ with antd.Flex(gap="small"):
294
+ login_btn = antd.Button("登录", type="primary", size="large")
295
+ register_btn = antd.Button("注册", size="large")
296
+
297
+ auth_message = antd.Alert(
298
+ message="请输入用户名",
299
+ type="info",
300
+ visible=False
301
+ )
302
+
303
+ # 聊天界面
304
+ with antd.Flex(vertical=True, gap="middle", visible=False) as chat_container:
305
+ # 用户信息栏
306
+ with antd.Flex(justify="space-between", align="center"):
307
+ user_info = gr.Markdown("")
308
+ logout_btn = antd.Button("退出登录", size="small")
309
+
310
+ chatbot = pro.Chatbot(
311
+ height=1000,
312
+ welcome_config=ChatbotWelcomeConfig(
313
+ variant="borderless",
314
+ icon="./xinyuan.png",
315
+ title=f"Hello, I'm Xinyuan👋",
316
+ description="You can input text to get started.",
317
+ prompts=ChatbotPromptsConfig(
318
+ title="How can I help you today?",
319
+ styles={
320
+ "list": {
321
+ "width": '100%',
322
+ },
323
+ "item": {
324
+ "flex": 1,
325
+ },
326
+ },
327
+ items=[{
328
+ "label":
329
+ "💝 心理学与实际应用",
330
+ "children": [{
331
+ "description":
332
+ "课题分离是什么意思?"
333
+ }, {
334
+ "description":
335
+ "回避型依恋和焦虑型依恋有什么区别?还有其他依恋类型吗?"
336
+ }, {
337
+ "description":
338
+ "为什么我背单词的时候总是只记得开头和结尾,中间全忘了?"
339
+ }]
340
+ }, {
341
+ "label":
342
+ "👪 儿童教育与发展",
343
+ "children": [{
344
+ "description":
345
+ "什么是正念养育?"
346
+ }, {
347
+ "description":
348
+ "2岁孩子分离焦虑严重,送托育中心天天哭闹怎么办?"
349
+ }, {
350
+ "description":
351
+ "4岁娃说话不清还爱打人,是心理问题还是欠管教?"
352
+ }]
353
+ }])),
354
+ user_config=ChatbotUserConfig(
355
+ avatar="https://api.dicebear.com/7.x/miniavs/svg?seed=3",
356
+ variant="shadow"),
357
+ bot_config=ChatbotBotConfig(
358
+ header='Xinyuan',
359
+ avatar="./xinyuan.png",
360
+ actions=["copy", "retry"],
361
+ variant="shadow"),
362
+ )
363
+
364
+ with antdx.Sender() as sender:
365
+ with ms.Slot("prefix"):
366
+ with antd.Button(value=None, color="default",
367
+ variant="text") as clear_btn:
368
+ with ms.Slot("icon"):
369
+ antd.Icon("ClearOutlined")
370
+
371
+ # 事件绑定
372
+ def handle_login(username):
373
+ return handle_auth(username, False)
374
+
375
+ def handle_register(username):
376
+ return handle_auth(username, True)
377
+
378
+ def handle_logout():
379
+ return (
380
+ gr.update(visible=True), # 显示登录界面
381
+ gr.update(visible=False), # 隐藏聊天界面
382
+ gr.update(message="已退出登录", type="info", visible=True),
383
+ gr.update(value=""), # 清空用户名输入
384
+ "", # 清空用户信息显示
385
+ "" # 清空当前用户状态
386
+ )
387
+
388
+ def update_user_info(username):
389
+ if username:
390
+ return f"**当前用户: {username}**"
391
+ return ""
392
+
393
+ # 登录按钮事件
394
+ login_btn.click(
395
+ fn=handle_login,
396
+ inputs=[username_input],
397
+ outputs=[login_container, chat_container, auth_message, current_user]
398
+ ).then(
399
+ fn=update_user_info,
400
+ inputs=[current_user],
401
+ outputs=[user_info]
402
+ )
403
+
404
+ # 注册按钮事件
405
+ register_btn.click(
406
+ fn=handle_register,
407
+ inputs=[username_input],
408
+ outputs=[login_container, chat_container, auth_message, current_user]
409
+ ).then(
410
+ fn=update_user_info,
411
+ inputs=[current_user],
412
+ outputs=[user_info]
413
+ )
414
+
415
+ # 退出登录按钮事件
416
+ logout_btn.click(
417
+ fn=handle_logout,
418
+ outputs=[login_container, chat_container, auth_message, username_input, user_info, current_user]
419
+ )
420
+
421
+ # 聊天功能事件绑定
422
+ clear_btn.click(fn=clear, outputs=[chatbot])
423
+ submit_event = sender.submit(fn=submit,
424
+ inputs=[sender, chatbot, current_user],
425
+ outputs=[sender, chatbot, clear_btn])
426
+ sender.cancel(fn=cancel,
427
+ inputs=[chatbot],
428
+ outputs=[chatbot, sender, clear_btn],
429
+ cancels=[submit_event],
430
+ queue=False)
431
+ chatbot.retry(fn=retry,
432
+ inputs=[chatbot, current_user],
433
+ outputs=[sender, chatbot, clear_btn])
434
+ chatbot.welcome_prompt_select(fn=prompt_select, outputs=[sender])
435
+
436
+ if __name__ == "__main__":
437
+ demo.queue().launch()