jarvis-ai-assistant 0.1.212__py3-none-any.whl → 0.1.214__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,10 +2,12 @@ import mimetypes
2
2
  import os
3
3
  from typing import Dict, Generator, List, Tuple
4
4
  from jarvis.jarvis_platform.base import BasePlatform
5
- import requests
6
5
  import json
7
6
 
7
+ from jarvis.jarvis_utils import http
8
8
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
9
+ from jarvis.jarvis_utils.utils import while_success
10
+
9
11
 
10
12
  class OyiModel(BasePlatform):
11
13
  """Oyi model implementation"""
@@ -16,7 +18,7 @@ class OyiModel(BasePlatform):
16
18
  def get_model_list(self) -> List[Tuple[str, str]]:
17
19
  """Get model list"""
18
20
  self.get_available_models()
19
- return [(name,info['desc']) for name,info in self.models.items()]
21
+ return [(name, info["desc"]) for name, info in self.models.items()]
20
22
 
21
23
  def __init__(self):
22
24
  """Initialize model"""
@@ -33,23 +35,23 @@ class OyiModel(BasePlatform):
33
35
 
34
36
  self.model_name = os.getenv("JARVIS_MODEL") or "deepseek-chat"
35
37
  if self.model_name not in [m.split()[0] for m in self.get_available_models()]:
36
- PrettyOutput.print(f"警告: 选择的模型 {self.model_name} 不在可用列表中", OutputType.WARNING)
37
-
38
+ PrettyOutput.print(
39
+ f"警告: 选择的模型 {self.model_name} 不在可用列表中", OutputType.WARNING
40
+ )
38
41
 
39
42
  def set_model_name(self, model_name: str):
40
43
  """Set model name"""
41
44
 
42
45
  self.model_name = model_name
43
46
 
44
-
45
47
  def create_conversation(self) -> bool:
46
48
  """Create a new conversation"""
47
49
  try:
48
50
  headers = {
49
- 'Authorization': f'Bearer {self.token}',
50
- 'Content-Type': 'application/json',
51
- 'Accept': 'application/json',
52
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
51
+ "Authorization": f"Bearer {self.token}",
52
+ "Content-Type": "application/json",
53
+ "Accept": "application/json",
54
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
53
55
  }
54
56
 
55
57
  payload = {
@@ -58,35 +60,34 @@ class OyiModel(BasePlatform):
58
60
  "title": "New conversation",
59
61
  "isLock": False,
60
62
  "systemMessage": "",
61
- "params": json.dumps({
62
- "model": self.model_name,
63
- "is_webSearch": True,
64
- "message": [],
65
- "systemMessage": None,
66
- "requestMsgCount": 65536,
67
- "temperature": 0.8,
68
- "speechVoice": "Alloy",
69
- "max_tokens": 8192,
70
- "chatPluginIds": []
71
- })
63
+ "params": json.dumps(
64
+ {
65
+ "model": self.model_name,
66
+ "is_webSearch": True,
67
+ "message": [],
68
+ "systemMessage": None,
69
+ "requestMsgCount": 65536,
70
+ "temperature": 0.8,
71
+ "speechVoice": "Alloy",
72
+ "max_tokens": 8192,
73
+ "chatPluginIds": [],
74
+ }
75
+ ),
72
76
  }
73
77
 
74
- response = requests.post(
75
- f"{self.BASE_URL}/chatapi/chat/save",
76
- headers=headers,
77
- json=payload
78
+ response = while_success(
79
+ lambda: http.post(
80
+ f"{self.BASE_URL}/chatapi/chat/save", headers=headers, json=payload
81
+ ),
82
+ sleep_time=5,
78
83
  )
79
84
 
80
- if response.status_code == 200:
81
- data = response.json()
82
- if data['code'] == 200 and data['type'] == 'success':
83
- self.conversation = data
84
- return True
85
- else:
86
- PrettyOutput.print(f"创建会话失败: {data['message']}", OutputType.WARNING)
87
- return False
85
+ data = response.json()
86
+ if data["code"] == 200 and data["type"] == "success":
87
+ self.conversation = data
88
+ return True
88
89
  else:
89
- PrettyOutput.print(f"创建会话失败: {response.status_code}", OutputType.WARNING)
90
+ PrettyOutput.print(f"创建会话失败: {data['message']}", OutputType.WARNING)
90
91
  return False
91
92
 
92
93
  except Exception as e:
@@ -114,19 +115,21 @@ class OyiModel(BasePlatform):
114
115
 
115
116
  # 1. 发送消息
116
117
  headers = {
117
- 'Authorization': f'Bearer {self.token}',
118
- 'Content-Type': 'application/json',
119
- 'Accept': 'application/json, text/plain, */*',
120
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
121
- 'Origin': 'https://ai.rcouyi.com',
122
- 'Referer': 'https://ai.rcouyi.com/'
118
+ "Authorization": f"Bearer {self.token}",
119
+ "Content-Type": "application/json",
120
+ "Accept": "application/json, text/plain, */*",
121
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
122
+ "Origin": "https://ai.rcouyi.com",
123
+ "Referer": "https://ai.rcouyi.com/",
123
124
  }
124
125
 
125
126
  payload = {
126
- "topicId": self.conversation['result']['id'] if self.conversation else None,
127
+ "topicId": (
128
+ self.conversation["result"]["id"] if self.conversation else None
129
+ ),
127
130
  "messages": self.messages,
128
131
  "content": message,
129
- "contentFiles": []
132
+ "contentFiles": [],
130
133
  }
131
134
 
132
135
  # 如果有上传的文件,添加到请求中
@@ -138,52 +141,47 @@ class OyiModel(BasePlatform):
138
141
  self.messages.append({"role": "user", "content": message})
139
142
 
140
143
  # 发送消息
141
- response = requests.post(
142
- f"{self.BASE_URL}/chatapi/chat/message",
143
- headers=headers,
144
- json=payload
144
+ response = while_success(
145
+ lambda: http.post(
146
+ f"{self.BASE_URL}/chatapi/chat/message",
147
+ headers=headers,
148
+ json=payload,
149
+ ),
150
+ sleep_time=5,
145
151
  )
146
152
 
147
- if response.status_code != 200:
148
- error_msg = f"聊天请求失败: {response.status_code}"
149
- PrettyOutput.print(error_msg, OutputType.WARNING)
150
- raise Exception(error_msg)
151
-
152
153
  data = response.json()
153
- if data['code'] != 200 or data['type'] != 'success':
154
+ if data["code"] != 200 or data["type"] != "success":
154
155
  error_msg = f"聊天失败: {data.get('message', '未知错误')}"
155
156
  PrettyOutput.print(error_msg, OutputType.WARNING)
156
157
  raise Exception(error_msg)
157
158
 
158
- message_id = data['result'][-1]
159
+ message_id = data["result"][-1]
159
160
 
160
161
  # 获取响应内容
161
- response = requests.post(
162
- f"{self.BASE_URL}/chatapi/chat/message/{message_id}",
163
- headers=headers,
164
- stream=True
162
+ response = while_success(
163
+ lambda: http.stream_post(
164
+ f"{self.BASE_URL}/chatapi/chat/message/{message_id}",
165
+ headers=headers,
166
+ ),
167
+ sleep_time=5,
165
168
  )
166
169
 
167
- if response.status_code == 200:
168
- full_response = ""
169
- bin = b""
170
- for chunk in response.iter_content(decode_unicode=True):
171
- if chunk:
172
- bin += chunk
173
- try:
174
- text = bin.decode('utf-8')
175
- except UnicodeDecodeError:
176
- continue
177
- full_response += text
178
- bin = b""
179
- yield text
180
-
181
- self.messages.append({"role": "assistant", "content": full_response})
182
- return None
183
- else:
184
- error_msg = f"获取响应失败: {response.status_code}"
185
- PrettyOutput.print(error_msg, OutputType.WARNING)
186
- raise Exception(error_msg)
170
+ full_response = ""
171
+ bin = b""
172
+ for chunk in response:
173
+ if chunk:
174
+ bin += chunk
175
+ try:
176
+ text = bin.decode("utf-8")
177
+ except UnicodeDecodeError:
178
+ continue
179
+ full_response += text
180
+ bin = b""
181
+ yield text
182
+
183
+ self.messages.append({"role": "assistant", "content": full_response})
184
+ return None
187
185
  except Exception as e:
188
186
  PrettyOutput.print(f"聊天失败: {str(e)}", OutputType.ERROR)
189
187
  raise e
@@ -192,7 +190,6 @@ class OyiModel(BasePlatform):
192
190
  """Return model name"""
193
191
  return self.model_name
194
192
 
195
-
196
193
  def delete_chat(self) -> bool:
197
194
  """Delete current chat session"""
198
195
  try:
@@ -200,41 +197,84 @@ class OyiModel(BasePlatform):
200
197
  return True
201
198
 
202
199
  headers = {
203
- 'Authorization': f'Bearer {self.token}',
204
- 'Content-Type': 'application/json',
205
- 'Accept': 'application/json, text/plain, */*',
206
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
207
- 'Origin': 'https://ai.rcouyi.com',
208
- 'Referer': 'https://ai.rcouyi.com/'
200
+ "Authorization": f"Bearer {self.token}",
201
+ "Content-Type": "application/json",
202
+ "Accept": "application/json, text/plain, */*",
203
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
204
+ "Origin": "https://ai.rcouyi.com",
205
+ "Referer": "https://ai.rcouyi.com/",
209
206
  }
210
207
 
211
- response = requests.post(
212
- f"{self.BASE_URL}/chatapi/chat/{self.conversation['result']['id']}",
213
- headers=headers,
214
- json={}
208
+ response = while_success(
209
+ lambda: http.post(
210
+ f"{self.BASE_URL}/chatapi/chat/{self.conversation['result']['id']}", # type: ignore
211
+ headers=headers,
212
+ json={},
213
+ ),
214
+ sleep_time=5,
215
215
  )
216
216
 
217
- if response.status_code == 200:
218
- data = response.json()
219
- if data['code'] == 200 and data['type'] == 'success':
220
- self.messages = []
221
- self.conversation = None
222
- self.first_chat = True
223
- return True
224
- else:
225
- error_msg = f"删除会话失败: {data.get('message', '未知错误')}"
226
- PrettyOutput.print(error_msg, OutputType.WARNING)
227
- return False
217
+ data = response.json()
218
+ if data["code"] == 200 and data["type"] == "success":
219
+ self.messages = []
220
+ self.conversation = None
221
+ self.first_chat = True
222
+ return True
228
223
  else:
229
- error_msg = f"删除会话请求失败: {response.status_code}"
224
+ error_msg = f"删除会话失败: {data.get('message', '未知错误')}"
230
225
  PrettyOutput.print(error_msg, OutputType.WARNING)
231
226
  return False
232
227
 
233
-
234
228
  except Exception as e:
235
229
  PrettyOutput.print(f"删除会话失败: {str(e)}", OutputType.ERROR)
236
230
  return False
237
231
 
232
+ def save(self, file_path: str) -> bool:
233
+ """Save chat session to a file."""
234
+ if not self.conversation:
235
+ PrettyOutput.print("没有活动的会话可供保存", OutputType.WARNING)
236
+ return False
237
+
238
+ state = {
239
+ "conversation": self.conversation,
240
+ "messages": self.messages,
241
+ "model_name": self.model_name,
242
+ "system_prompt": self.system_prompt,
243
+ "first_chat": self.first_chat,
244
+ }
245
+
246
+ try:
247
+ with open(file_path, "w", encoding="utf-8") as f:
248
+ json.dump(state, f, ensure_ascii=False, indent=4)
249
+ self._saved = True
250
+ PrettyOutput.print(f"会话已成功保存到 {file_path}", OutputType.SUCCESS)
251
+ return True
252
+ except Exception as e:
253
+ PrettyOutput.print(f"保存会话失败: {str(e)}", OutputType.ERROR)
254
+ return False
255
+
256
+ def restore(self, file_path: str) -> bool:
257
+ """Restore chat session from a file."""
258
+ try:
259
+ with open(file_path, "r", encoding="utf-8") as f:
260
+ state = json.load(f)
261
+
262
+ self.conversation = state.get("conversation")
263
+ self.messages = state.get("messages", [])
264
+ self.model_name = state.get("model_name", "deepseek-chat")
265
+ self.system_prompt = state.get("system_prompt", "")
266
+ self.first_chat = state.get("first_chat", True)
267
+ self._saved = True
268
+
269
+ PrettyOutput.print(f"从 {file_path} 成功恢复会话", OutputType.SUCCESS)
270
+ return True
271
+ except FileNotFoundError:
272
+ PrettyOutput.print(f"会话文件未找到: {file_path}", OutputType.ERROR)
273
+ return False
274
+ except Exception as e:
275
+ PrettyOutput.print(f"恢复会话失败: {str(e)}", OutputType.ERROR)
276
+ return False
277
+
238
278
  def get_available_models(self) -> List[str]:
239
279
  """Get available model list
240
280
 
@@ -246,55 +286,53 @@ class OyiModel(BasePlatform):
246
286
  return list(self.models.keys())
247
287
 
248
288
  headers = {
249
- 'Content-Type': 'application/json',
250
- 'Accept': 'application/json, text/plain, */*',
251
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
252
- 'Origin': 'https://ai.rcouyi.com',
253
- 'Referer': 'https://ai.rcouyi.com/'
289
+ "Content-Type": "application/json",
290
+ "Accept": "application/json, text/plain, */*",
291
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
292
+ "Origin": "https://ai.rcouyi.com",
293
+ "Referer": "https://ai.rcouyi.com/",
254
294
  }
255
295
 
256
- response = requests.get(
257
- "https://ai.rcouyi.com/config/system.json",
258
- headers=headers
296
+ response = while_success(
297
+ lambda: http.get(
298
+ "https://ai.rcouyi.com/config/system.json", headers=headers
299
+ ),
300
+ sleep_time=5,
259
301
  )
260
302
 
261
- if response.status_code != 200:
262
- PrettyOutput.print(f"获取模型列表失败: {response.status_code}", OutputType.WARNING)
263
- return []
264
-
265
303
  data = response.json()
266
304
 
267
305
  # 保存模型信息
268
306
  self.models = {
269
- model['value']: model
270
- for model in data.get('model', [])
271
- if model.get('enable', False) # 只保存启用的模型
307
+ model["value"]: model
308
+ for model in data.get("model", [])
309
+ if model.get("enable", False) # 只保存启用的模型
272
310
  }
273
311
 
274
312
  # 格式化显示
275
313
  models = []
276
314
  for model in self.models.values():
277
315
  # 基本信息
278
- model_name = model['value']
279
- model_str = model['label']
316
+ model_name = model["value"]
317
+ model_str = model["label"]
280
318
 
281
319
  # 添加后缀标签
282
- suffix = model.get('suffix', [])
320
+ suffix = model.get("suffix", [])
283
321
  if suffix:
284
322
  # 处理新格式的suffix (字典列表)
285
323
  if suffix and isinstance(suffix[0], dict):
286
- suffix_str = ', '.join(s.get('tag', '') for s in suffix)
324
+ suffix_str = ", ".join(s.get("tag", "") for s in suffix)
287
325
  # 处理旧格式的suffix (字符串列表)
288
326
  else:
289
- suffix_str = ', '.join(str(s) for s in suffix)
327
+ suffix_str = ", ".join(str(s) for s in suffix)
290
328
  model_str += f" ({suffix_str})"
291
329
 
292
330
  # 添加描述或提示
293
- info = model.get('tooltip') or model.get('description', '')
331
+ info = model.get("tooltip") or model.get("description", "")
294
332
  if info:
295
333
  model_str += f" - {info}"
296
334
 
297
- model['desc'] = model_str
335
+ model["desc"] = model_str
298
336
  models.append(model_name)
299
337
 
300
338
  return sorted(models)
@@ -305,10 +343,9 @@ class OyiModel(BasePlatform):
305
343
 
306
344
  def support_upload_files(self) -> bool:
307
345
  return False
308
-
309
-
346
+
310
347
  def support_web(self) -> bool:
311
348
  return False
312
-
349
+
313
350
  def upload_files(self, file_list: List[str]) -> bool:
314
351
  return False
@@ -264,7 +264,7 @@ class TongyiPlatform(BasePlatform):
264
264
  """
265
265
  url = "https://api.tongyi.com/dialog/uploadToken"
266
266
  headers = self._get_base_headers()
267
- payload = {}
267
+ payload: Dict[str, Any] = {}
268
268
 
269
269
  try:
270
270
  response = while_success(
@@ -395,16 +395,12 @@ class TongyiPlatform(BasePlatform):
395
395
  add_url, headers=headers, json=add_payload
396
396
  )
397
397
  if add_response.status_code != 200:
398
- print(
399
- f"❌ 添加文件到对话失败: HTTP {add_response.status_code}"
400
- )
398
+ print(f"❌ 添加文件到对话失败: HTTP {add_response.status_code}")
401
399
  continue
402
400
 
403
401
  add_result = add_response.json()
404
402
  if not add_result.get("success"):
405
- print(
406
- f"❌ 添加文件到对话失败: {add_result.get('errorMsg')}"
407
- )
403
+ print(f"❌ 添加文件到对话失败: {add_result.get('errorMsg')}")
408
404
  continue
409
405
 
410
406
  file_info.update(add_result.get("data", {}))
@@ -491,6 +487,56 @@ class TongyiPlatform(BasePlatform):
491
487
  PrettyOutput.print(f"Error deleting chat: {str(e)}", OutputType.ERROR)
492
488
  return False
493
489
 
490
+ def save(self, file_path: str) -> bool:
491
+ """Save chat session to a file."""
492
+ if not self.session_id:
493
+ PrettyOutput.print("没有活动的会话可供保存", OutputType.WARNING)
494
+ return False
495
+
496
+ state = {
497
+ "session_id": self.session_id,
498
+ "request_id": self.request_id,
499
+ "msg_id": self.msg_id,
500
+ "model_name": self.model_name,
501
+ "uploaded_file_info": self.uploaded_file_info,
502
+ "system_message": self.system_message,
503
+ "first_chat": self.first_chat,
504
+ }
505
+
506
+ try:
507
+ with open(file_path, "w", encoding="utf-8") as f:
508
+ json.dump(state, f, ensure_ascii=False, indent=4)
509
+ self._saved = True
510
+ PrettyOutput.print(f"会话已成功保存到 {file_path}", OutputType.SUCCESS)
511
+ return True
512
+ except Exception as e:
513
+ PrettyOutput.print(f"保存会话失败: {str(e)}", OutputType.ERROR)
514
+ return False
515
+
516
+ def restore(self, file_path: str) -> bool:
517
+ """Restore chat session from a file."""
518
+ try:
519
+ with open(file_path, "r", encoding="utf-8") as f:
520
+ state = json.load(f)
521
+
522
+ self.session_id = state.get("session_id", "")
523
+ self.request_id = state.get("request_id", "")
524
+ self.msg_id = state.get("msg_id", "")
525
+ self.model_name = state.get("model_name", "")
526
+ self.uploaded_file_info = state.get("uploaded_file_info", [])
527
+ self.system_message = state.get("system_message", "")
528
+ self.first_chat = state.get("first_chat", True)
529
+ self._saved = True
530
+
531
+ PrettyOutput.print(f"从 {file_path} 成功恢复会话", OutputType.SUCCESS)
532
+ return True
533
+ except FileNotFoundError:
534
+ PrettyOutput.print(f"会话文件未找到: {file_path}", OutputType.ERROR)
535
+ return False
536
+ except Exception as e:
537
+ PrettyOutput.print(f"恢复会话失败: {str(e)}", OutputType.ERROR)
538
+ return False
539
+
494
540
  def set_system_prompt(self, message: str):
495
541
  """Set system message
496
542
 
@@ -104,9 +104,7 @@ class YuanbaoPlatform(BasePlatform):
104
104
  self.conversation_id = response_json["id"]
105
105
  return True
106
106
  else:
107
- PrettyOutput.print(
108
- f"错误:创建会话失败,响应: {response_json}", OutputType.ERROR
109
- )
107
+ PrettyOutput.print(f"错误:创建会话失败,响应: {response_json}", OutputType.ERROR)
110
108
  return False
111
109
  except Exception as e:
112
110
  PrettyOutput.print(f"错误:创建会话失败:{e}", OutputType.ERROR)
@@ -135,7 +133,6 @@ class YuanbaoPlatform(BasePlatform):
135
133
  file_name = os.path.basename(file_path)
136
134
  print(f"🔍 上传文件 {file_name}")
137
135
  try:
138
-
139
136
  # 1. Prepare the file information
140
137
  print(f"🔍 准备文件信息: {file_name}")
141
138
  file_size = os.path.getsize(file_path)
@@ -572,6 +569,52 @@ class YuanbaoPlatform(BasePlatform):
572
569
  PrettyOutput.print(f"删除会话时发生错误: {str(e)}", OutputType.ERROR)
573
570
  return False
574
571
 
572
+ def save(self, file_path: str) -> bool:
573
+ """Save chat session to a file."""
574
+ if not self.conversation_id:
575
+ PrettyOutput.print("没有活动的会话可供保存", OutputType.WARNING)
576
+ return False
577
+
578
+ state = {
579
+ "conversation_id": self.conversation_id,
580
+ "system_message": self.system_message,
581
+ "first_chat": self.first_chat,
582
+ "model_name": self.model_name,
583
+ "multimedia": self.multimedia,
584
+ }
585
+
586
+ try:
587
+ with open(file_path, "w", encoding="utf-8") as f:
588
+ json.dump(state, f, ensure_ascii=False, indent=4)
589
+ self._saved = True
590
+ PrettyOutput.print(f"会话已成功保存到 {file_path}", OutputType.SUCCESS)
591
+ return True
592
+ except Exception as e:
593
+ PrettyOutput.print(f"保存会话失败: {str(e)}", OutputType.ERROR)
594
+ return False
595
+
596
+ def restore(self, file_path: str) -> bool:
597
+ """Restore chat session from a file."""
598
+ try:
599
+ with open(file_path, "r", encoding="utf-8") as f:
600
+ state = json.load(f)
601
+
602
+ self.conversation_id = state.get("conversation_id", "")
603
+ self.system_message = state.get("system_message", "")
604
+ self.first_chat = state.get("first_chat", True)
605
+ self.model_name = state.get("model_name", "deep_seek_v3")
606
+ self.multimedia = state.get("multimedia", [])
607
+ self._saved = True
608
+
609
+ PrettyOutput.print(f"从 {file_path} 成功恢复会话", OutputType.SUCCESS)
610
+ return True
611
+ except FileNotFoundError:
612
+ PrettyOutput.print(f"会话文件未找到: {file_path}", OutputType.ERROR)
613
+ return False
614
+ except Exception as e:
615
+ PrettyOutput.print(f"恢复会话失败: {str(e)}", OutputType.ERROR)
616
+ return False
617
+
575
618
  def name(self) -> str:
576
619
  """模型名称"""
577
620
  return self.model_name