thomas-yanxin commited on
Commit
f106aa6
·
verified ·
1 Parent(s): c23cf05

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +552 -369
app.py CHANGED
@@ -1,6 +1,10 @@
 
1
  import os
2
  import re
3
  import time
 
 
 
4
 
5
  import gradio as gr
6
  import modelscope_studio.components.antd as antd
@@ -14,424 +18,603 @@ from modelscope_studio.components.pro.chatbot import (ChatbotBotConfig,
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()
 
1
+ import logging
2
  import os
3
  import re
4
  import time
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional, Tuple
8
 
9
  import gradio as gr
10
  import modelscope_studio.components.antd as antd
 
18
  ChatbotWelcomeConfig)
19
  from openai import OpenAI
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ # 配置日志
23
+ logging.basicConfig(
24
+ level=logging.INFO,
25
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
26
+ handlers=[
27
+ logging.FileHandler('xinyuan_chat.log'),
28
+ logging.StreamHandler()
29
+ ]
30
  )
31
+ logger = logging.getLogger(__name__)
32
+
33
+ @dataclass
34
+ class AppConfig:
35
+ """应用配置类"""
36
+ users_file: str = "users.txt"
37
+ memory_path: str = "./faiss_memories"
38
+ model_name: str = "xinyuan-32b-v0609"
39
+ max_tokens: int = 32768
40
+ temperature: float = 0.6
41
+ top_p: float = 0.95
42
+ chatbot_height: int = 1000
43
+ min_username_length: int = 3
44
+ max_memory_results: int = 5
45
+
46
+ class MemoryManager:
47
+ """记忆管理器"""
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
+ def __init__(self, config_path: str):
50
+ self.config = {
51
+ "vector_store": {
52
+ "provider": "faiss",
53
+ "config": {
54
+ "collection_name": "xinyuan_memories",
55
+ "path": config_path,
56
+ "distance_strategy": "euclidean"
57
+ }
58
+ }
59
+ }
60
+ try:
61
+ self.memory = Memory.from_config(self.config)
62
+ logger.info(f"Memory manager initialized with path: {config_path}")
63
+ except Exception as e:
64
+ logger.error(f"Failed to initialize memory manager: {e}")
65
+ raise
66
 
67
+ def search_memories(self, query: str, user_id: str, limit: int = 5) -> List[Dict[str, Any]]:
68
+ """搜索相关记忆"""
69
+ try:
70
+ if not query or not user_id:
71
+ return []
72
+
73
+ results = self.memory.search(query=query, user_id=user_id, limit=limit)
74
+ if results and 'results' in results:
75
+ return sorted(results['results'], key=lambda x: x.get('score', 0), reverse=True)
76
+ return []
77
+ except Exception as e:
78
+ logger.error(f"Error searching memories for user {user_id}: {e}")
79
+ return []
80
 
81
+ def add_memory(self, messages: List[Dict[str, str]], user_id: str) -> bool:
82
+ """添加记忆"""
83
+ try:
84
+ if not messages or not user_id:
85
+ return False
86
+
87
+ self.memory.add(messages, user_id=user_id)
88
+ logger.info(f"Memory added for user {user_id}")
89
+ return True
90
+ except Exception as e:
91
+ logger.error(f"Error adding memory for user {user_id}: {e}")
92
+ return False
93
+
94
+ class UserManager:
95
+ """用户管理器"""
96
 
97
+ def __init__(self, users_file: str, min_username_length: int = 3):
98
+ self.users_file = Path(users_file)
99
+ self.min_username_length = min_username_length
100
+ self._ensure_users_file_exists()
101
+
102
+ def _ensure_users_file_exists(self):
103
+ """确保用户文件存在"""
104
+ if not self.users_file.exists():
105
+ self.users_file.touch()
106
+ logger.info(f"Created users file: {self.users_file}")
107
+
108
+ def load_users(self) -> set:
109
+ """加载已注册用户列表"""
110
+ try:
111
+ with open(self.users_file, 'r', encoding='utf-8') as f:
112
+ users = {line.strip() for line in f if line.strip()}
113
+ logger.debug(f"Loaded {len(users)} users")
114
+ return users
115
+ except Exception as e:
116
+ logger.error(f"Error loading users: {e}")
117
+ return set()
118
+
119
+ def save_user(self, username: str) -> bool:
120
+ """保存新用户到文件"""
121
+ try:
122
+ with open(self.users_file, 'a', encoding='utf-8') as f:
123
+ f.write(f"{username}\n")
124
+ logger.info(f"User {username} saved to file")
125
+ return True
126
+ except Exception as e:
127
+ logger.error(f"Error saving user {username}: {e}")
128
+ return False
129
+
130
+ def is_valid_username(self, username: str) -> bool:
131
+ """验证用户名是否有效"""
132
+ if not username or not isinstance(username, str):
133
+ return False
134
+
135
+ # 检查长度
136
+ if len(username) < self.min_username_length:
137
+ return False
138
+
139
+ # 检查格式:以字母开头,只包含字母、数字和下划线
140
+ return bool(re.match(r'^[a-zA-Z][a-zA-Z0-9_]*$', username))
141
+
142
+ def login_user(self, username: str) -> Tuple[bool, str]:
143
+ """用户登录验证"""
144
+ if not self.is_valid_username(username):
145
+ return False, "用户名无效!用户名必须以英文字母开头,只能包含英文字母、数字和下划线,且长度至少3位。"
146
+
147
+ users = self.load_users()
148
+ if username in users:
149
+ logger.info(f"User {username} logged in successfully")
150
+ return True, f"欢迎回来,{username}!"
151
+ else:
152
+ logger.warning(f"Login attempt for unregistered user: {username}")
153
+ return False, f"用户 {username} 未注册,请先注册。"
154
+
155
+ def register_user(self, username: str) -> Tuple[bool, str]:
156
+ """用户注册"""
157
+ if not self.is_valid_username(username):
158
+ return False, "用户名无效!用户名必须以英文字母开头,只能包含英文字母、数字和下划线,且长度至少3位。"
159
+
160
+ users = self.load_users()
161
+ if username in users:
162
+ logger.warning(f"Registration attempt for existing user: {username}")
163
+ return False, f"用户名 {username} 已存在,请直接登录。"
164
+
165
+ if self.save_user(username):
166
+ logger.info(f"User {username} registered successfully")
167
+ return True, f"注册成功!欢迎,{username}!"
168
+ else:
169
+ return False, "注册失败,请稍后重试。"
170
+
171
+ class ChatManager:
172
+ """聊天管理器"""
173
+
174
+ def __init__(self, config: AppConfig, memory_manager: MemoryManager):
175
+ self.config = config
176
+ self.memory_manager = memory_manager
177
+ self.client = self._initialize_openai_client()
178
+
179
+ def _initialize_openai_client(self) -> OpenAI:
180
+ """初始化OpenAI客户端"""
181
+ try:
182
+ # 可以根据需要配置API密钥和基础URL
183
+ gw_api_key = os.getenv("GW_API_KEY")
184
+ client = OpenAI(
185
+ base_url='https://api.geniuworks.com/v2',
186
+ api_key=gw_api_key,
187
+ )
188
+ logger.info("OpenAI client initialized successfully")
189
+ return client
190
+ except Exception as e:
191
+ logger.error(f"Failed to initialize OpenAI client: {e}")
192
+ raise
193
+
194
+ def format_history(self, sender_value: str, history: List[Dict], username: Optional[str] = None) -> List[Dict[str, str]]:
195
+ """格式化聊天历史"""
196
+ messages = []
197
+
198
+ # 添加系统提示
199
+ if username:
200
+ system_prompt = f"""You are Xinyuan, a large language model trained by Cylingo Group. You are a helpful assistant. 目前和你聊天的用户是{username}."""
201
+
202
+ # 搜索相关记忆
203
+ if sender_value:
204
+ related_memories = self.memory_manager.search_memories(
205
+ query=sender_value,
206
+ user_id=username,
207
+ limit=self.config.max_memory_results
208
+ )
209
+
210
+ if related_memories:
211
+ memory_content = "\n相关记忆:\n"
212
+ for idx, memory in enumerate(related_memories):
213
+ memory_content += f"记忆{idx + 1}:{memory.get('memory', '')} (相关度: {memory.get('score', 0):.3f})\n"
214
+ system_prompt += memory_content
215
+
216
+ messages.append({"role": "system", "content": system_prompt})
217
+
218
+ # 添加历史对话
219
+ for item in history:
220
+ if item.get("role") == "user":
221
+ messages.append({"role": "user", "content": item.get("content", "")})
222
+ elif item.get("role") == "assistant" and item.get("content"):
223
+ # 提取助手回复的文本内容
224
+ content_list = item.get("content", [])
225
+ if content_list and len(content_list) > 1:
226
+ assistant_content = content_list[-1].get("content", "")
227
+ if assistant_content:
228
+ messages.append({"role": "assistant", "content": assistant_content})
229
+
230
+ return messages
231
+
232
+ def create_chat_completion(self, messages: List[Dict[str, str]]) -> Any:
233
+ """创建聊天完成请求"""
234
+ try:
235
+ return self.client.chat.completions.create(
236
+ model=self.config.model_name,
237
+ messages=messages,
238
+ stream=True,
239
+ max_tokens=self.config.max_tokens,
240
+ temperature=self.config.temperature,
241
+ top_p=self.config.top_p,
242
+ )
243
+ except Exception as e:
244
+ logger.error(f"Error creating chat completion: {e}")
245
+ raise
246
+
247
+ # 全局配置和管理器实例
248
+ config = AppConfig()
249
+ memory_manager = MemoryManager(config.memory_path)
250
+ user_manager = UserManager(config.users_file, config.min_username_length)
251
+ chat_manager = ChatManager(config, memory_manager)
252
+
253
+ # Gradio界面函数
254
+ def handle_auth(username: str, is_register: bool) -> Tuple:
255
+ """处理认证逻辑"""
256
+ try:
257
+ if is_register:
258
+ success, message = user_manager.register_user(username)
259
+ else:
260
+ success, message = user_manager.login_user(username)
261
+
262
+ if success:
263
+ return (
264
+ gr.update(visible=False), # 隐藏登录界面
265
+ gr.update(visible=True), # 显示聊天界面
266
+ gr.update(message=message, type="success", visible=True),
267
+ username
268
+ )
269
+ else:
270
+ return (
271
+ gr.update(visible=True), # 保持登录界面可见
272
+ gr.update(visible=False), # 隐藏聊天界面
273
+ gr.update(message=message, type="error", visible=True),
274
+ ""
275
+ )
276
+ except Exception as e:
277
+ logger.error(f"Error in handle_auth: {e}")
278
  return (
279
+ gr.update(visible=True),
280
+ gr.update(visible=False),
281
+ gr.update(message="系统错误,请稍后重试。", type="error", visible=True),
282
  ""
283
  )
284
 
285
+ def prompt_select(e: gr.EventData) -> gr.update:
286
+ """处理提示选择"""
287
+ try:
288
+ return gr.update(value=e._data["payload"][0]["value"]["description"])
289
+ except Exception as e:
290
+ logger.error(f"Error in prompt_select: {e}")
291
+ return gr.update(value="")
292
 
293
+ def clear() -> gr.update:
294
+ """清空聊天记录"""
295
  return gr.update(value=None)
296
 
297
+ def retry(chatbot_value: List, e: gr.EventData, username: Optional[str] = None):
298
+ """重试功能"""
299
+ try:
300
+ index = e._data["payload"][0]["index"]
301
+ chatbot_value = chatbot_value[:index]
302
+
303
+ yield gr.update(value=None, loading=True), gr.update(value=chatbot_value), gr.update(disabled=True)
304
+
305
+ for chunk in submit(None, chatbot_value, username):
306
+ yield chunk
307
+ except Exception as e:
308
+ logger.error(f"Error in retry: {e}")
309
+ yield gr.update(value=None, loading=False), gr.update(value=chatbot_value), gr.update(disabled=False)
310
 
311
+ def cancel(chatbot_value: List) -> Tuple:
312
+ """取消当前对话"""
313
+ try:
314
+ if chatbot_value:
315
+ chatbot_value[-1]["loading"] = False
316
+ chatbot_value[-1]["status"] = "done"
317
+ chatbot_value[-1]["footer"] = "Chat completion paused"
318
+
319
+ return (
320
+ gr.update(value=chatbot_value),
321
+ gr.update(loading=False),
322
+ gr.update(disabled=False)
323
+ )
324
+ except Exception as e:
325
+ logger.error(f"Error in cancel: {e}")
326
+ return (
327
+ gr.update(value=chatbot_value),
328
+ gr.update(loading=False),
329
+ gr.update(disabled=False)
330
+ )
 
 
 
 
 
 
 
 
 
 
331
 
332
+ def submit(sender_value: Optional[str], chatbot_value: List, username: Optional[str] = None):
333
+ """提交聊天消息"""
334
+ start_time = time.time()
 
 
 
 
 
 
 
 
335
 
336
+ try:
337
+ # 添加用户消息
338
+ if sender_value is not None:
339
+ chatbot_value.append({
340
+ "role": "user",
341
+ "content": sender_value,
 
 
342
  })
 
343
 
344
+ # 格式化历史消息
345
+ history_messages = chat_manager.format_history(sender_value, chatbot_value, username)
346
+
347
+ # 添加助手消息占位符
 
 
 
348
  chatbot_value.append({
349
+ "role": "assistant",
350
+ "content": [],
351
+ "loading": True,
352
+ "status": "pending"
353
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
 
355
+ # 更新UI状态
356
+ yield (
357
+ gr.update(value=None, loading=True), # sender
358
+ gr.update(value=chatbot_value), # chatbot
359
+ gr.update(disabled=True) # clear_btn
360
+ )
361
+
362
+ # 创建聊天完成请求
363
+ response = chat_manager.create_chat_completion(history_messages)
364
+
365
+ # 处理流式响应
366
  thought_done = False
 
367
  message_content = chatbot_value[-1]["content"]
368
+
369
+ # 初始化消息内容结构
370
  message_content.append({
371
  "copyable": False,
372
  "editable": False,
373
  "type": "tool",
374
  "content": "",
375
+ "options": {"title": "Thinking..."}
 
 
376
  })
 
377
  message_content.append({
378
  "type": "text",
379
  "content": "",
380
  })
381
 
 
382
  full_assistant_content = ""
383
 
384
+ # 处理流式响应
385
  for chunk in response:
386
  try:
387
+ reasoning_content = getattr(chunk.choices[0].delta, 'reasoning_content', None) or ""
388
+ content = getattr(chunk.choices[0].delta, 'content', None) or ""
389
+
390
+ chatbot_value[-1]["loading"] = False
391
+ message_content[-2]["content"] += reasoning_content
392
+ message_content[-1]["content"] += content
393
+
394
+ if content:
395
+ full_assistant_content += content
396
+
397
+ if content and not thought_done:
398
+ thought_done = True
399
+ thought_cost_time = f"{time.time() - start_time:.2f}"
400
+ message_content[-2]["options"]["title"] = f"End of Thought ({thought_cost_time}s)"
401
+ message_content[-2]["options"]["status"] = "done"
402
+
403
+ yield (
404
+ gr.update(), # sender
405
+ gr.update(value=chatbot_value), # chatbot
406
+ gr.update() # clear_btn
407
+ )
408
+
409
+ except Exception as chunk_error:
410
+ logger.error(f"Error processing chunk: {chunk_error}")
411
+ continue
412
+
413
+ # 保存到记忆
414
  if username and sender_value and full_assistant_content:
415
  memory_messages = [
416
  {'role': 'user', 'content': sender_value},
417
  {'role': 'assistant', 'content': full_assistant_content}
418
  ]
419
+ memory_manager.add_memory(memory_messages, username)
420
+
421
+ # 完成响应
422
+ total_time = f"{time.time() - start_time:.2f}s"
423
+ chatbot_value[-1]["footer"] = total_time
424
  chatbot_value[-1]["status"] = "done"
425
+
426
+ yield (
427
+ gr.update(loading=False), # sender
428
+ gr.update(value=chatbot_value), # chatbot
429
+ gr.update(disabled=False) # clear_btn
430
+ )
431
+
432
  except Exception as e:
433
+ logger.error(f"Error in submit: {e}")
434
+
435
+ # 错误处理
436
+ if chatbot_value:
437
+ chatbot_value[-1]["loading"] = False
438
+ chatbot_value[-1]["status"] = "done"
439
+ chatbot_value[-1]["content"] = "抱歉,处理您的请求时出现错误,请稍后重试。"
440
+
441
+ yield (
442
+ gr.update(loading=False), # sender
443
+ gr.update(value=chatbot_value), # chatbot
444
+ gr.update(disabled=False) # clear_btn
445
+ )
446
 
447
+ # 创建Gradio界面
448
+ def create_interface():
449
+ """创建Gradio界面"""
450
+ with gr.Blocks(title="Xinyuan 聊天助手") as demo, ms.Application(), antdx.XProvider():
451
+ # 状态变量
452
+ current_user = gr.State("")
453
+
454
+ # 登录界面
455
+ with antd.Flex(vertical=True, gap="large", elem_id="login_container") as login_container:
456
+ with antd.Card(title="欢迎使用 Xinyuan 聊天助手"):
457
+ with antd.Flex(vertical=True, gap="middle"):
458
+ antd.Typography.Title("用户登录/注册", level=3)
459
+ antd.Typography.Text("请输入您的英文用户名(3位以上,仅支持英文字母、数字和下划线)")
460
+
461
+ username_input = antd.Input(
462
+ placeholder="请输入用户名(如:john_doe)",
463
+ size="large"
464
+ )
465
+
466
+ with antd.Flex(gap="small"):
467
+ login_btn = antd.Button("登录", type="primary", size="large")
468
+ register_btn = antd.Button("注册", size="large")
469
+
470
+ auth_message = antd.Alert(
471
+ message="请输入用户名",
472
+ type="info",
473
+ visible=False
474
+ )
475
+
476
+ # 聊天界面
477
+ with antd.Flex(vertical=True, gap="middle", visible=False) as chat_container:
478
+ # 用户信息栏
479
+ with antd.Flex(justify="space-between", align="center"):
480
+ user_info = gr.Markdown("")
481
+ logout_btn = antd.Button("退出登录", size="small")
482
+
483
+ # 聊天机器人组件
484
+ chatbot = pro.Chatbot(
485
+ height=config.chatbot_height,
486
+ welcome_config=ChatbotWelcomeConfig(
487
+ variant="borderless",
488
+ icon="./xinyuan.png",
489
+ title="Hello, I'm Xinyuan👋",
490
+ description="You can input text to get started.",
491
+ prompts=ChatbotPromptsConfig(
492
+ title="How can I help you today?",
493
+ styles={
494
+ "list": {"width": '100%'},
495
+ "item": {"flex": 1},
496
  },
497
+ items=[
498
+ {
499
+ "label": "💝 心理学与实际应用",
500
+ "children": [
501
+ {"description": "课题分离是什么意思?"},
502
+ {"description": "回避型依恋和焦虑型依恋有什么区别?还有其他依恋类型吗?"},
503
+ {"description": "为什么我背单词的时候总是只记得开头和结尾,中间全忘了?"}
504
+ ]
505
+ },
506
+ {
507
+ "label": "👪 儿童教育与发展",
508
+ "children": [
509
+ {"description": "什么是正念养育?"},
510
+ {"description": "2岁孩子分离焦虑严重,送托育中心天天哭闹怎么办?"},
511
+ {"description": "4岁娃说话不清还爱打人,是心理问题还是欠管教?"}
512
+ ]
513
+ }
514
+ ]
515
+ )
516
+ ),
517
+ user_config=ChatbotUserConfig(
518
+ avatar="https://api.dicebear.com/7.x/miniavs/svg?seed=3",
519
+ variant="shadow"
520
+ ),
521
+ bot_config=ChatbotBotConfig(
522
+ header='Xinyuan',
523
+ avatar="./xinyuan.png",
524
+ actions=["copy", "retry"],
525
+ variant="shadow"
526
+ ),
527
+ )
528
+
529
+ # 发送器组件
530
+ with antdx.Sender() as sender:
531
+ with ms.Slot("prefix"):
532
+ with antd.Button(value=None, color="default", variant="text") as clear_btn:
533
+ with ms.Slot("icon"):
534
+ antd.Icon("ClearOutlined")
535
+
536
+ # 事件处理函数
537
+ def handle_login(username: str):
538
+ return handle_auth(username, False)
539
+
540
+ def handle_register(username: str):
541
+ return handle_auth(username, True)
542
+
543
+ def handle_logout():
544
+ return (
545
+ gr.update(visible=True), # 显示登录界面
546
+ gr.update(visible=False), # 隐藏聊天界面
547
+ gr.update(message="已退出登录", type="info", visible=True),
548
+ gr.update(value=""), # 清空用户名输入
549
+ "", # 清空用户信息显示
550
+ "" # 清空当前用户状态
551
+ )
552
+
553
+ def update_user_info(username: str) -> str:
554
+ return f"**当前用户: {username}**" if username else ""
555
+
556
+ # 绑定事件
557
+ login_btn.click(
558
+ fn=handle_login,
559
+ inputs=[username_input],
560
+ outputs=[login_container, chat_container, auth_message, current_user]
561
+ ).then(
562
+ fn=update_user_info,
563
+ inputs=[current_user],
564
+ outputs=[user_info]
565
  )
566
+
567
+ register_btn.click(
568
+ fn=handle_register,
569
+ inputs=[username_input],
570
+ outputs=[login_container, chat_container, auth_message, current_user]
571
+ ).then(
572
+ fn=update_user_info,
573
+ inputs=[current_user],
574
+ outputs=[user_info]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
  )
576
+
577
+ logout_btn.click(
578
+ fn=handle_logout,
579
+ outputs=[login_container, chat_container, auth_message, username_input, user_info, current_user]
580
+ )
581
+
582
+ # 聊天功能事件绑定
583
+ clear_btn.click(fn=clear, outputs=[chatbot])
584
+
585
+ submit_event = sender.submit(
586
+ fn=submit,
587
+ inputs=[sender, chatbot, current_user],
588
+ outputs=[sender, chatbot, clear_btn]
589
+ )
590
+
591
+ sender.cancel(
592
+ fn=cancel,
593
+ inputs=[chatbot],
594
+ outputs=[chatbot, sender, clear_btn],
595
+ cancels=[submit_event],
596
+ queue=False
597
+ )
598
+
599
+ chatbot.retry(
600
+ fn=retry,
601
+ inputs=[chatbot, current_user],
602
+ outputs=[sender, chatbot, clear_btn]
603
+ )
604
+
605
+ chatbot.welcome_prompt_select(fn=prompt_select, outputs=[sender])
606
 
607
+ return demo
608
+
609
+ def main():
610
+ """主函数"""
611
+ try:
612
+ logger.info("Starting Xinyuan Chat Application")
613
+ demo = create_interface()
614
+ demo.queue().launch()
615
+ except Exception as e:
616
+ logger.error(f"Failed to start application: {e}")
617
+ raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
 
619
  if __name__ == "__main__":
620
+ main()