jarvis-ai-assistant 0.3.23__py3-none-any.whl → 0.3.25__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.
Files changed (43) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +96 -13
  3. jarvis/jarvis_agent/agent_manager.py +0 -3
  4. jarvis/jarvis_agent/jarvis.py +19 -34
  5. jarvis/jarvis_agent/main.py +2 -8
  6. jarvis/jarvis_code_agent/code_agent.py +5 -11
  7. jarvis/jarvis_code_analysis/code_review.py +12 -40
  8. jarvis/jarvis_data/config_schema.json +11 -18
  9. jarvis/jarvis_git_utils/git_commiter.py +11 -25
  10. jarvis/jarvis_mcp/sse_mcp_client.py +4 -3
  11. jarvis/jarvis_mcp/streamable_mcp_client.py +9 -8
  12. jarvis/jarvis_memory_organizer/memory_organizer.py +46 -53
  13. jarvis/jarvis_methodology/main.py +4 -2
  14. jarvis/jarvis_platform/base.py +90 -21
  15. jarvis/jarvis_platform/kimi.py +16 -22
  16. jarvis/jarvis_platform/registry.py +7 -14
  17. jarvis/jarvis_platform/tongyi.py +21 -32
  18. jarvis/jarvis_platform/yuanbao.py +15 -17
  19. jarvis/jarvis_platform_manager/main.py +14 -51
  20. jarvis/jarvis_rag/cli.py +21 -13
  21. jarvis/jarvis_rag/embedding_manager.py +138 -6
  22. jarvis/jarvis_rag/llm_interface.py +0 -2
  23. jarvis/jarvis_rag/rag_pipeline.py +41 -17
  24. jarvis/jarvis_rag/reranker.py +24 -2
  25. jarvis/jarvis_rag/retriever.py +21 -23
  26. jarvis/jarvis_smart_shell/main.py +1 -10
  27. jarvis/jarvis_tools/cli/main.py +22 -15
  28. jarvis/jarvis_tools/edit_file.py +6 -6
  29. jarvis/jarvis_tools/execute_script.py +1 -2
  30. jarvis/jarvis_tools/file_analyzer.py +12 -6
  31. jarvis/jarvis_tools/registry.py +13 -10
  32. jarvis/jarvis_tools/sub_agent.py +5 -8
  33. jarvis/jarvis_tools/sub_code_agent.py +5 -5
  34. jarvis/jarvis_utils/config.py +24 -10
  35. jarvis/jarvis_utils/input.py +8 -5
  36. jarvis/jarvis_utils/methodology.py +11 -6
  37. jarvis/jarvis_utils/utils.py +29 -12
  38. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/METADATA +10 -3
  39. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/RECORD +43 -43
  40. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/WHEEL +0 -0
  41. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/entry_points.txt +0 -0
  42. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/licenses/LICENSE +0 -0
  43. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/top_level.txt +0 -0
@@ -158,6 +158,8 @@ class StreamableMcpClient(McpClient):
158
158
 
159
159
  # 处理流式响应
160
160
  result = None
161
+ warning_lines = []
162
+ error_lines = []
161
163
  for line in response.iter_lines(decode_unicode=True):
162
164
  if line:
163
165
  try:
@@ -175,20 +177,19 @@ class StreamableMcpClient(McpClient):
175
177
  notify_method = data.get("method", "")
176
178
  params = data.get("params", {})
177
179
  if notify_method in self.notification_handlers:
178
- for handler in self.notification_handlers[
179
- notify_method
180
- ]:
180
+ for handler in self.notification_handlers[notify_method]:
181
181
  try:
182
182
  handler(params)
183
183
  except Exception as e:
184
- PrettyOutput.print(
185
- f"处理通知时出错 ({method}): {e}",
186
- OutputType.ERROR,
187
- )
184
+ error_lines.append(f"处理通知时出错 ({notify_method}): {e}")
188
185
  except json.JSONDecodeError:
189
- PrettyOutput.print(f"无法解析响应: {line}", OutputType.WARNING)
186
+ warning_lines.append(f"无法解析响应: {line}")
190
187
  continue
191
188
 
189
+ if warning_lines:
190
+ PrettyOutput.print("\n".join(warning_lines), OutputType.WARNING)
191
+ if error_lines:
192
+ PrettyOutput.print("\n".join(error_lines), OutputType.ERROR)
192
193
  # Ensure response is closed after streaming
193
194
  response.close()
194
195
  if result is None:
@@ -20,8 +20,6 @@ from jarvis.jarvis_utils.config import (
20
20
  get_data_dir,
21
21
  get_normal_platform_name,
22
22
  get_normal_model_name,
23
- get_thinking_platform_name,
24
- get_thinking_model_name,
25
23
  )
26
24
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
27
25
  from jarvis.jarvis_platform.registry import PlatformRegistry
@@ -31,18 +29,14 @@ from jarvis.jarvis_utils.utils import init_env
31
29
  class MemoryOrganizer:
32
30
  """记忆整理器,用于合并具有相似标签的记忆"""
33
31
 
34
- def __init__(self, llm_group: Optional[str] = None, llm_type: Optional[str] = None):
32
+ def __init__(self, llm_group: Optional[str] = None):
35
33
  """初始化记忆整理器"""
36
34
  self.project_memory_dir = Path(".jarvis/memory")
37
35
  self.global_memory_dir = Path(get_data_dir()) / "memory"
38
36
 
39
- # 根据 llm_type 选择对应的平台和模型获取函数
40
- if llm_type == "thinking":
41
- platform_name_func = get_thinking_platform_name
42
- model_name_func = get_thinking_model_name
43
- else:
44
- platform_name_func = get_normal_platform_name
45
- model_name_func = get_normal_model_name
37
+ # 统一使用 normal 平台与模型
38
+ platform_name_func = get_normal_platform_name
39
+ model_name_func = get_normal_model_name
46
40
 
47
41
  # 确定平台和模型
48
42
  platform_name = platform_name_func(model_group_override=llm_group)
@@ -72,6 +66,7 @@ class MemoryOrganizer:
72
66
  """加载指定类型的所有记忆"""
73
67
  memories = []
74
68
  memory_files = self._get_memory_files(memory_type)
69
+ error_lines: list[str] = []
75
70
 
76
71
  for memory_file in memory_files:
77
72
  try:
@@ -80,9 +75,10 @@ class MemoryOrganizer:
80
75
  memory_data["file_path"] = str(memory_file)
81
76
  memories.append(memory_data)
82
77
  except Exception as e:
83
- PrettyOutput.print(
84
- f"读取记忆文件 {memory_file} 失败: {str(e)}", OutputType.WARNING
85
- )
78
+ error_lines.append(f"读取记忆文件 {memory_file} 失败: {str(e)}")
79
+
80
+ if error_lines:
81
+ PrettyOutput.print("\n".join(error_lines), OutputType.WARNING)
86
82
 
87
83
  return memories
88
84
 
@@ -314,16 +310,14 @@ tags:
314
310
 
315
311
  group_memories = [memories[i] for i in original_indices]
316
312
 
317
- # 显示将要合并的记忆
318
- PrettyOutput.print(
319
- f"\n准备合并 {len(group_memories)} 个记忆:", OutputType.INFO
320
- )
313
+ # 显示将要合并的记忆(先拼接后统一打印,避免循环逐条输出)
314
+ lines = [f"", f"准备合并 {len(group_memories)} 个记忆:"]
321
315
  for mem in group_memories:
322
- PrettyOutput.print(
316
+ lines.append(
323
317
  f" - ID: {mem.get('id', '未知')}, "
324
- f"标签: {', '.join(mem.get('tags', []))[:50]}...",
325
- OutputType.INFO,
318
+ f"标签: {', '.join(mem.get('tags', []))[:50]}..."
326
319
  )
320
+ PrettyOutput.print("\n".join(lines), OutputType.INFO)
327
321
 
328
322
  if not dry_run:
329
323
  # 合并记忆
@@ -396,27 +390,28 @@ tags:
396
390
  OutputType.SUCCESS,
397
391
  )
398
392
 
399
- # 删除原始记忆文件
393
+ # 删除原始记忆文件(先汇总日志,最后统一打印)
394
+ info_lines: list[str] = []
395
+ warn_lines: list[str] = []
400
396
  for orig_memory in original_memories:
401
397
  if "file_path" in orig_memory:
402
398
  try:
403
399
  file_path = Path(orig_memory["file_path"])
404
400
  if file_path.exists():
405
401
  file_path.unlink()
406
- PrettyOutput.print(
407
- f"删除原始记忆: {orig_memory.get('id', '未知')}",
408
- OutputType.INFO,
409
- )
402
+ info_lines.append(f"删除原始记忆: {orig_memory.get('id', '未知')}")
410
403
  else:
411
- PrettyOutput.print(
412
- f"原始记忆文件已不存在,跳过删除: {orig_memory.get('id', '未知')}",
413
- OutputType.INFO,
404
+ info_lines.append(
405
+ f"原始记忆文件已不存在,跳过删除: {orig_memory.get('id', '未知')}"
414
406
  )
415
407
  except Exception as e:
416
- PrettyOutput.print(
417
- f"删除记忆文件失败 {orig_memory['file_path']}: {str(e)}",
418
- OutputType.WARNING,
408
+ warn_lines.append(
409
+ f"删除记忆文件失败 {orig_memory.get('file_path', '')}: {str(e)}"
419
410
  )
411
+ if info_lines:
412
+ PrettyOutput.print("\n".join(info_lines), OutputType.INFO)
413
+ if warn_lines:
414
+ PrettyOutput.print("\n".join(warn_lines), OutputType.WARNING)
420
415
 
421
416
  def export_memories(
422
417
  self,
@@ -436,9 +431,10 @@ tags:
436
431
  导出的记忆数量
437
432
  """
438
433
  all_memories = []
434
+ progress_lines: list[str] = []
439
435
 
440
436
  for memory_type in memory_types:
441
- PrettyOutput.print(f"正在导出 {memory_type} 类型的记忆...", OutputType.INFO)
437
+ progress_lines.append(f"正在导出 {memory_type} 类型的记忆...")
442
438
  memories = self._load_memories(memory_type)
443
439
 
444
440
  # 如果指定了标签,进行过滤
@@ -456,9 +452,11 @@ tags:
456
452
  memory.pop("file_path", None)
457
453
 
458
454
  all_memories.extend(memories)
459
- PrettyOutput.print(
460
- f"从 {memory_type} 导出了 {len(memories)} 个记忆", OutputType.INFO
461
- )
455
+ progress_lines.append(f"从 {memory_type} 导出了 {len(memories)} 个记忆")
456
+
457
+ # 统一展示导出进度日志
458
+ if progress_lines:
459
+ PrettyOutput.print("\n".join(progress_lines), OutputType.INFO)
462
460
 
463
461
  # 保存到文件
464
462
  output_file.parent.mkdir(parents=True, exist_ok=True)
@@ -505,10 +503,6 @@ tags:
505
503
  for memory in memories:
506
504
  memory_type = memory.get("memory_type", memory.get("type"))
507
505
  if not memory_type:
508
- PrettyOutput.print(
509
- f"跳过没有类型的记忆: {memory.get('id', '未知')}",
510
- OutputType.WARNING,
511
- )
512
506
  skipped_count += 1
513
507
  continue
514
508
 
@@ -560,8 +554,9 @@ tags:
560
554
 
561
555
  # 显示导入结果
562
556
  PrettyOutput.print("\n导入完成!", OutputType.SUCCESS)
563
- for memory_type, count in import_stats.items():
564
- PrettyOutput.print(f"{memory_type}: 导入了 {count} 个记忆", OutputType.INFO)
557
+ if import_stats:
558
+ lines = [f"{memory_type}: 导入了 {count} 个记忆" for memory_type, count in import_stats.items()]
559
+ PrettyOutput.print("\n".join(lines), OutputType.INFO)
565
560
 
566
561
  if skipped_count > 0:
567
562
  PrettyOutput.print(f"跳过了 {skipped_count} 个记忆", OutputType.WARNING)
@@ -592,12 +587,7 @@ def organize(
592
587
  llm_group: Optional[str] = typer.Option(
593
588
  None, "-g", "--llm-group", help="使用的模型组,覆盖配置文件中的设置"
594
589
  ),
595
- llm_type: Optional[str] = typer.Option(
596
- "normal",
597
- "-t",
598
- "--llm-type",
599
- help="使用的LLM类型,可选值:'normal'(普通)或 'thinking'(思考模式)",
600
- ),
590
+
601
591
  ):
602
592
  """
603
593
  整理和合并具有相似标签的记忆。
@@ -627,7 +617,7 @@ def organize(
627
617
 
628
618
  # 创建整理器并执行
629
619
  try:
630
- organizer = MemoryOrganizer(llm_group=llm_group, llm_type=llm_type)
620
+ organizer = MemoryOrganizer(llm_group=llm_group)
631
621
  stats = organizer.organize_memories(
632
622
  memory_type=memory_type, min_overlap=min_overlap, dry_run=dry_run
633
623
  )
@@ -681,12 +671,15 @@ def export(
681
671
  try:
682
672
  organizer = MemoryOrganizer()
683
673
 
684
- # 验证记忆类型
674
+ # 验证记忆类型(先收集无效类型,统一打印一次)
685
675
  valid_types = ["project_long_term", "global_long_term"]
686
- for mt in memory_types:
687
- if mt not in valid_types:
688
- PrettyOutput.print(f"错误:不支持的记忆类型 '{mt}'", OutputType.ERROR)
689
- raise typer.Exit(1)
676
+ invalid_types = [mt for mt in memory_types if mt not in valid_types]
677
+ if invalid_types:
678
+ PrettyOutput.print(
679
+ "错误:不支持的记忆类型: " + ", ".join(f"'{mt}'" for mt in invalid_types),
680
+ OutputType.ERROR,
681
+ )
682
+ raise typer.Exit(1)
690
683
 
691
684
  count = organizer.export_memories(
692
685
  memory_types=memory_types,
@@ -94,9 +94,11 @@ def list_methodologies():
94
94
  PrettyOutput.print("没有找到方法论", OutputType.INFO)
95
95
  return
96
96
 
97
- PrettyOutput.print("可用方法论:", OutputType.INFO)
97
+ # 先拼接再统一打印,避免在循环中逐条输出造成信息稀疏
98
+ lines = ["可用方法论:"]
98
99
  for i, (problem_type, _) in enumerate(methodologies.items(), 1):
99
- PrettyOutput.print(f"{i}. {problem_type}", OutputType.INFO)
100
+ lines.append(f"{i}. {problem_type}")
101
+ PrettyOutput.print("\n".join(lines), OutputType.INFO)
100
102
  except (OSError, json.JSONDecodeError) as e:
101
103
  PrettyOutput.print(f"列出方法论失败: {str(e)}", OutputType.ERROR)
102
104
  raise typer.Exit(code=1)
@@ -9,6 +9,7 @@ from typing_extensions import Self
9
9
  from rich import box # type: ignore
10
10
  from rich.live import Live # type: ignore
11
11
  from rich.panel import Panel # type: ignore
12
+ from rich.status import Status # type: ignore
12
13
  from rich.text import Text # type: ignore
13
14
 
14
15
  from jarvis.jarvis_utils.config import (
@@ -120,32 +121,100 @@ class BasePlatform(ABC):
120
121
  else:
121
122
  response = ""
122
123
 
123
- text_content = Text()
124
- panel = Panel(
125
- text_content,
126
- title=f"[bold cyan]{self.name()}[/bold cyan]",
127
- subtitle="[dim]思考中... (按 Ctrl+C 中断)[/dim]",
128
- border_style="bright_blue",
129
- box=box.ROUNDED,
130
- )
131
-
132
124
  if not self.suppress_output:
133
125
  if get_pretty_output():
134
- with Live(panel, refresh_per_second=10, transient=False) as live:
135
- for s in self.chat(message):
136
- response += s
137
- text_content.append(s, style="bright_white")
138
- panel.subtitle = (
139
- "[yellow]正在回答... (按 Ctrl+C 中断)[/yellow]"
140
- )
141
- live.update(panel)
126
+ chat_iterator = self.chat(message)
127
+ first_chunk = None
128
+
129
+ with Status(
130
+ f"🤔 {self.name()} 正在思考中...", spinner="dots", console=console
131
+ ):
132
+ try:
133
+ while True:
134
+ first_chunk = next(chat_iterator)
135
+ if first_chunk:
136
+ break
137
+ except StopIteration:
138
+ return ""
139
+
140
+ text_content = Text(overflow="fold")
141
+ panel = Panel(
142
+ text_content,
143
+ title=f"[bold cyan]{self.name()}[/bold cyan]",
144
+ subtitle="[yellow]正在回答... (按 Ctrl+C 中断)[/yellow]",
145
+ border_style="bright_blue",
146
+ box=box.ROUNDED,
147
+ expand=True, # 允许面板自动调整大小
148
+ )
149
+
150
+ buffer = []
151
+ buffer_count = 0
152
+ with Live(panel, refresh_per_second=4, transient=False) as live:
153
+ # Process first chunk
154
+ response += first_chunk
155
+ buffer.append(first_chunk)
156
+ buffer_count += 1
157
+
158
+ # Process rest of the chunks
159
+ for s in chat_iterator:
160
+ if not s:
161
+ continue
162
+ response += s # Accumulate the full response string
163
+ buffer.append(s)
164
+ buffer_count += 1
165
+
166
+ # 积累一定量或达到最后再更新,减少闪烁
167
+ if buffer_count >= 5 or s == "":
168
+ # Append buffered content to the Text object
169
+ text_content.append(
170
+ "".join(buffer), style="bright_white"
171
+ )
172
+ buffer.clear()
173
+ buffer_count = 0
174
+
175
+ # --- Scrolling Logic ---
176
+ # Calculate available height in the panel
177
+ max_text_height = (
178
+ console.height - 5
179
+ ) # Leave space for borders/titles
180
+ if max_text_height <= 0:
181
+ max_text_height = 1
182
+
183
+ # Get the actual number of lines the text will wrap to
184
+ lines = text_content.wrap(
185
+ console,
186
+ console.width - 4 if console.width > 4 else 1,
187
+ )
188
+
189
+ # If content overflows, truncate to show only the last few lines
190
+ if len(lines) > max_text_height:
191
+ new_text = "\n".join(
192
+ text_content.plain.splitlines()[
193
+ -max_text_height:
194
+ ]
195
+ )
196
+ text_content.plain = new_text
197
+
198
+ panel.subtitle = (
199
+ "[yellow]正在回答... (按 Ctrl+C 中断)[/yellow]"
200
+ )
201
+ live.update(panel)
202
+
142
203
  if is_immediate_abort() and get_interrupt():
143
- return response
204
+ return response # Return the partial response immediately
205
+
206
+ # Ensure any remaining content in the buffer is displayed
207
+ if buffer:
208
+ text_content.append(
209
+ "".join(buffer), style="bright_white"
210
+ )
211
+
212
+ # At the end, display the entire response
213
+ text_content.plain = response
214
+
144
215
  end_time = time.time()
145
216
  duration = end_time - start_time
146
- panel.subtitle = (
147
- f"[bold green]✓ 对话完成耗时: {duration:.2f}秒[/bold green]"
148
- )
217
+ panel.subtitle = f"[bold green]✓ 对话完成耗时: {duration:.2f}秒[/bold green]"
149
218
  live.update(panel)
150
219
  else:
151
220
  # Print a clear prefix line before streaming model output (non-pretty mode)
@@ -194,9 +194,7 @@ class KimiModel(BasePlatform):
194
194
  uploaded_files = []
195
195
  for index, file_path in enumerate(file_list, 1):
196
196
  file_name = os.path.basename(file_path)
197
- PrettyOutput.print(
198
- f"处理文件 [{index}/{len(file_list)}]: {file_name}", OutputType.INFO
199
- )
197
+ log_lines: list[str] = [f"处理文件 [{index}/{len(file_list)}]: {file_name}"]
200
198
  try:
201
199
  mime_type, _ = mimetypes.guess_type(file_path)
202
200
  action = (
@@ -204,44 +202,40 @@ class KimiModel(BasePlatform):
204
202
  )
205
203
 
206
204
  # 获取预签名URL
207
- PrettyOutput.print(f"获取上传URL: {file_name}", OutputType.INFO)
205
+ log_lines.append(f"获取上传URL: {file_name}")
208
206
  presigned_data = self._get_presigned_url(file_path, action)
209
207
 
210
208
  # 上传文件
211
- PrettyOutput.print(f"上传文件: {file_name}", OutputType.INFO)
209
+ log_lines.append(f"上传文件: {file_name}")
212
210
  if self._upload_file(file_path, presigned_data["url"]):
213
211
  # 获取文件信息
214
- PrettyOutput.print(f"获取文件信息: {file_name}", OutputType.INFO)
212
+ log_lines.append(f"获取文件信息: {file_name}")
215
213
  file_info = self._get_file_info(presigned_data, file_name, action)
216
214
 
217
215
  # 只有文件需要解析
218
216
  if action == "file":
219
- PrettyOutput.print(
220
- f"等待文件解析: {file_name}", OutputType.INFO
221
- )
217
+ log_lines.append(f"等待文件解析: {file_name}")
222
218
  if self._wait_for_parse(file_info["id"]):
223
219
  uploaded_files.append(file_info)
224
- PrettyOutput.print(
225
- f"文件处理完成: {file_name}", OutputType.SUCCESS
226
- )
220
+ log_lines.append(f"文件处理完成: {file_name}")
227
221
  else:
228
- PrettyOutput.print(
229
- f"文件解析失败: {file_name}", OutputType.ERROR
230
- )
222
+ log_lines.append(f"文件解析失败: {file_name}")
223
+ PrettyOutput.print("\n".join(log_lines), OutputType.ERROR)
231
224
  return False
232
225
  else:
233
226
  uploaded_files.append(file_info)
234
- PrettyOutput.print(
235
- f"图片处理完成: {file_name}", OutputType.SUCCESS
236
- )
227
+ log_lines.append(f"图片处理完成: {file_name}")
237
228
  else:
238
- PrettyOutput.print(f"文件上传失败: {file_name}", OutputType.ERROR)
229
+ log_lines.append(f"文件上传失败: {file_name}")
230
+ PrettyOutput.print("\n".join(log_lines), OutputType.ERROR)
239
231
  return False
240
232
 
233
+ # 成功路径统一输出本文件的处理日志
234
+ PrettyOutput.print("\n".join(log_lines), OutputType.INFO)
235
+
241
236
  except Exception as e:
242
- PrettyOutput.print(
243
- f"处理文件出错 {file_path}: {str(e)}", OutputType.ERROR
244
- )
237
+ log_lines.append(f"处理文件出错 {file_path}: {str(e)}")
238
+ PrettyOutput.print("\n".join(log_lines), OutputType.ERROR)
245
239
  return False
246
240
 
247
241
  self.uploaded_files = uploaded_files
@@ -10,8 +10,6 @@ from jarvis.jarvis_utils.config import (
10
10
  get_data_dir,
11
11
  get_normal_model_name,
12
12
  get_normal_platform_name,
13
- get_thinking_model_name,
14
- get_thinking_platform_name,
15
13
  )
16
14
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
17
15
 
@@ -113,6 +111,7 @@ class PlatformRegistry:
113
111
  if directory not in sys.path:
114
112
  sys.path.append(directory)
115
113
 
114
+ error_lines = []
116
115
  # 遍历目录下的所有.py文件
117
116
  for filename in os.listdir(directory):
118
117
  if filename.endswith(".py") and not filename.startswith("__"):
@@ -142,16 +141,15 @@ class PlatformRegistry:
142
141
  platform_name = obj.platform_name()
143
142
  platforms[platform_name] = obj
144
143
  except Exception as e:
145
- PrettyOutput.print(
146
- f"实例化或注册平台失败 {obj.__name__}: {str(e)}",
147
- OutputType.ERROR,
144
+ error_lines.append(
145
+ f"实例化或注册平台失败 {obj.__name__}: {str(e)}"
148
146
  )
149
147
  break
150
148
  except Exception as e:
151
- PrettyOutput.print(
152
- f"加载平台 {module_name} 失败: {str(e)}", OutputType.ERROR
153
- )
149
+ error_lines.append(f"加载平台 {module_name} 失败: {str(e)}")
154
150
 
151
+ if error_lines:
152
+ PrettyOutput.print("\n".join(error_lines), OutputType.ERROR)
155
153
  return platforms
156
154
 
157
155
  @staticmethod
@@ -187,12 +185,7 @@ class PlatformRegistry:
187
185
  platform.set_model_name(model_name) # type: ignore
188
186
  return platform # type: ignore
189
187
 
190
- def get_thinking_platform(self) -> BasePlatform:
191
- platform_name = get_thinking_platform_name()
192
- model_name = get_thinking_model_name()
193
- platform = self.create_platform(platform_name)
194
- platform.set_model_name(model_name) # type: ignore
195
- return platform # type: ignore
188
+
196
189
 
197
190
  def register_platform(self, name: str, platform_class: Type[BasePlatform]) -> None:
198
191
  """Register platform class
@@ -276,16 +276,19 @@ class TongyiPlatform(BasePlatform):
276
276
 
277
277
  for file_path in file_list:
278
278
  file_name = os.path.basename(file_path)
279
- PrettyOutput.print(f"上传文件 {file_name}", OutputType.INFO)
279
+ log_lines: list[str] = []
280
+ log_lines.append(f"上传文件 {file_name}")
280
281
  try:
281
282
  if not os.path.exists(file_path):
282
- PrettyOutput.print(f"文件不存在: {file_path}", OutputType.ERROR)
283
+ # 先输出已收集的日志与错误后返回
284
+ log_lines.append(f"文件不存在: {file_path}")
285
+ PrettyOutput.print("\n".join(log_lines), OutputType.ERROR)
283
286
  return False
284
287
 
285
288
  # Get file name and content type
286
289
  content_type = self._get_content_type(file_path)
287
290
 
288
- PrettyOutput.print(f"准备上传文件: {file_name}", OutputType.INFO)
291
+ log_lines.append(f"准备上传文件: {file_name}")
289
292
 
290
293
  # Prepare form data
291
294
  form_data = {
@@ -300,7 +303,7 @@ class TongyiPlatform(BasePlatform):
300
303
  # Prepare files
301
304
  files = {"file": (file_name, open(file_path, "rb"), content_type)}
302
305
 
303
- PrettyOutput.print(f"正在上传文件: {file_name}", OutputType.INFO)
306
+ log_lines.append(f"正在上传文件: {file_name}")
304
307
 
305
308
  # Upload file
306
309
  response = http.post(
@@ -308,10 +311,8 @@ class TongyiPlatform(BasePlatform):
308
311
  )
309
312
 
310
313
  if response.status_code != 200:
311
- PrettyOutput.print(
312
- f"上传失败 {file_name}: HTTP {response.status_code}",
313
- OutputType.ERROR,
314
- )
314
+ log_lines.append(f"上传失败 {file_name}: HTTP {response.status_code}")
315
+ PrettyOutput.print("\n".join(log_lines), OutputType.ERROR)
315
316
  return False
316
317
 
317
318
  # Determine file type based on extension
@@ -326,7 +327,7 @@ class TongyiPlatform(BasePlatform):
326
327
  }
327
328
  )
328
329
 
329
- PrettyOutput.print(f"获取下载链接: {file_name}", OutputType.INFO)
330
+ log_lines.append(f"获取下载链接: {file_name}")
330
331
 
331
332
  # Get download links for uploaded files
332
333
  url = "https://api.tongyi.com/dialog/downloadLink/batch"
@@ -343,26 +344,20 @@ class TongyiPlatform(BasePlatform):
343
344
 
344
345
  response = http.post(url, headers=headers, json=payload)
345
346
  if response.status_code != 200:
346
- PrettyOutput.print(
347
- f"获取下载链接失败: HTTP {response.status_code}",
348
- OutputType.ERROR,
349
- )
347
+ log_lines.append(f"获取下载链接失败: HTTP {response.status_code}")
348
+ PrettyOutput.print("\n".join(log_lines), OutputType.ERROR)
350
349
  return False
351
350
 
352
351
  result = response.json()
353
352
  if not result.get("success"):
354
- PrettyOutput.print(
355
- f"获取下载链接失败: {result.get('errorMsg')}",
356
- OutputType.ERROR,
357
- )
353
+ log_lines.append(f"获取下载链接失败: {result.get('errorMsg')}")
354
+ PrettyOutput.print("\n".join(log_lines), OutputType.ERROR)
358
355
  return False
359
356
 
360
357
  # Add files to chat
361
358
  self.uploaded_file_info = result.get("data", {}).get("results", [])
362
359
  for file_info in self.uploaded_file_info:
363
- PrettyOutput.print(
364
- f"添加文件到对话: {file_name}", OutputType.INFO
365
- )
360
+ log_lines.append(f"添加文件到对话: {file_name}")
366
361
  add_url = "https://api.tongyi.com/assistant/api/chat/file/add"
367
362
  add_payload = {
368
363
  "workSource": "chat",
@@ -385,29 +380,23 @@ class TongyiPlatform(BasePlatform):
385
380
  add_url, headers=headers, json=add_payload
386
381
  )
387
382
  if add_response.status_code != 200:
388
- PrettyOutput.print(
389
- f"添加文件到对话失败: HTTP {add_response.status_code}",
390
- OutputType.ERROR,
391
- )
383
+ log_lines.append(f"添加文件到对话失败: HTTP {add_response.status_code}")
392
384
  continue
393
385
 
394
386
  add_result = add_response.json()
395
387
  if not add_result.get("success"):
396
- PrettyOutput.print(
397
- f"添加文件到对话失败: {add_result.get('errorMsg')}",
398
- OutputType.ERROR,
399
- )
388
+ log_lines.append(f"添加文件到对话失败: {add_result.get('errorMsg')}")
400
389
  continue
401
390
 
402
391
  file_info.update(add_result.get("data", {}))
403
392
 
404
- PrettyOutput.print(f"文件 {file_name} 上传成功", OutputType.SUCCESS)
393
+ log_lines.append(f"文件 {file_name} 上传成功")
394
+ PrettyOutput.print("\n".join(log_lines), OutputType.INFO)
405
395
  time.sleep(1) # 短暂暂停以便用户看到成功状态
406
396
 
407
397
  except Exception as e:
408
- PrettyOutput.print(
409
- f"上传文件 {file_name} 时出错: {str(e)}", OutputType.ERROR
410
- )
398
+ log_lines.append(f"上传文件 {file_name} 时出错: {str(e)}")
399
+ PrettyOutput.print("\n".join(log_lines), OutputType.ERROR)
411
400
  return False
412
401
  return True
413
402