maque 0.2.1__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 (143) hide show
  1. maque/__init__.py +30 -0
  2. maque/__main__.py +926 -0
  3. maque/ai_platform/__init__.py +0 -0
  4. maque/ai_platform/crawl.py +45 -0
  5. maque/ai_platform/metrics.py +258 -0
  6. maque/ai_platform/nlp_preprocess.py +67 -0
  7. maque/ai_platform/webpage_screen_shot.py +195 -0
  8. maque/algorithms/__init__.py +78 -0
  9. maque/algorithms/bezier.py +15 -0
  10. maque/algorithms/bktree.py +117 -0
  11. maque/algorithms/core.py +104 -0
  12. maque/algorithms/hilbert.py +16 -0
  13. maque/algorithms/rate_function.py +92 -0
  14. maque/algorithms/transform.py +27 -0
  15. maque/algorithms/trie.py +272 -0
  16. maque/algorithms/utils.py +63 -0
  17. maque/algorithms/video.py +587 -0
  18. maque/api/__init__.py +1 -0
  19. maque/api/common.py +110 -0
  20. maque/api/fetch.py +26 -0
  21. maque/api/static/icon.png +0 -0
  22. maque/api/static/redoc.standalone.js +1782 -0
  23. maque/api/static/swagger-ui-bundle.js +3 -0
  24. maque/api/static/swagger-ui.css +3 -0
  25. maque/cli/__init__.py +1 -0
  26. maque/cli/clean_invisible_chars.py +324 -0
  27. maque/cli/core.py +34 -0
  28. maque/cli/groups/__init__.py +26 -0
  29. maque/cli/groups/config.py +205 -0
  30. maque/cli/groups/data.py +615 -0
  31. maque/cli/groups/doctor.py +259 -0
  32. maque/cli/groups/embedding.py +222 -0
  33. maque/cli/groups/git.py +29 -0
  34. maque/cli/groups/help.py +410 -0
  35. maque/cli/groups/llm.py +223 -0
  36. maque/cli/groups/mcp.py +241 -0
  37. maque/cli/groups/mllm.py +1795 -0
  38. maque/cli/groups/mllm_simple.py +60 -0
  39. maque/cli/groups/quant.py +210 -0
  40. maque/cli/groups/service.py +490 -0
  41. maque/cli/groups/system.py +570 -0
  42. maque/cli/mllm_run.py +1451 -0
  43. maque/cli/script.py +52 -0
  44. maque/cli/tree.py +49 -0
  45. maque/clustering/__init__.py +52 -0
  46. maque/clustering/analyzer.py +347 -0
  47. maque/clustering/clusterers.py +464 -0
  48. maque/clustering/sampler.py +134 -0
  49. maque/clustering/visualizer.py +205 -0
  50. maque/constant.py +13 -0
  51. maque/core.py +133 -0
  52. maque/cv/__init__.py +1 -0
  53. maque/cv/image.py +219 -0
  54. maque/cv/utils.py +68 -0
  55. maque/cv/video/__init__.py +3 -0
  56. maque/cv/video/keyframe_extractor.py +368 -0
  57. maque/embedding/__init__.py +43 -0
  58. maque/embedding/base.py +56 -0
  59. maque/embedding/multimodal.py +308 -0
  60. maque/embedding/server.py +523 -0
  61. maque/embedding/text.py +311 -0
  62. maque/git/__init__.py +24 -0
  63. maque/git/pure_git.py +912 -0
  64. maque/io/__init__.py +29 -0
  65. maque/io/core.py +38 -0
  66. maque/io/ops.py +194 -0
  67. maque/llm/__init__.py +111 -0
  68. maque/llm/backend.py +416 -0
  69. maque/llm/base.py +411 -0
  70. maque/llm/server.py +366 -0
  71. maque/mcp_server.py +1096 -0
  72. maque/mllm_data_processor_pipeline/__init__.py +17 -0
  73. maque/mllm_data_processor_pipeline/core.py +341 -0
  74. maque/mllm_data_processor_pipeline/example.py +291 -0
  75. maque/mllm_data_processor_pipeline/steps/__init__.py +56 -0
  76. maque/mllm_data_processor_pipeline/steps/data_alignment.py +267 -0
  77. maque/mllm_data_processor_pipeline/steps/data_loader.py +172 -0
  78. maque/mllm_data_processor_pipeline/steps/data_validation.py +304 -0
  79. maque/mllm_data_processor_pipeline/steps/format_conversion.py +411 -0
  80. maque/mllm_data_processor_pipeline/steps/mllm_annotation.py +331 -0
  81. maque/mllm_data_processor_pipeline/steps/mllm_refinement.py +446 -0
  82. maque/mllm_data_processor_pipeline/steps/result_validation.py +501 -0
  83. maque/mllm_data_processor_pipeline/web_app.py +317 -0
  84. maque/nlp/__init__.py +14 -0
  85. maque/nlp/ngram.py +9 -0
  86. maque/nlp/parser.py +63 -0
  87. maque/nlp/risk_matcher.py +543 -0
  88. maque/nlp/sentence_splitter.py +202 -0
  89. maque/nlp/simple_tradition_cvt.py +31 -0
  90. maque/performance/__init__.py +21 -0
  91. maque/performance/_measure_time.py +70 -0
  92. maque/performance/_profiler.py +367 -0
  93. maque/performance/_stat_memory.py +51 -0
  94. maque/pipelines/__init__.py +15 -0
  95. maque/pipelines/clustering.py +252 -0
  96. maque/quantization/__init__.py +42 -0
  97. maque/quantization/auto_round.py +120 -0
  98. maque/quantization/base.py +145 -0
  99. maque/quantization/bitsandbytes.py +127 -0
  100. maque/quantization/llm_compressor.py +102 -0
  101. maque/retriever/__init__.py +35 -0
  102. maque/retriever/chroma.py +654 -0
  103. maque/retriever/document.py +140 -0
  104. maque/retriever/milvus.py +1140 -0
  105. maque/table_ops/__init__.py +1 -0
  106. maque/table_ops/core.py +133 -0
  107. maque/table_viewer/__init__.py +4 -0
  108. maque/table_viewer/download_assets.py +57 -0
  109. maque/table_viewer/server.py +698 -0
  110. maque/table_viewer/static/element-plus-icons.js +5791 -0
  111. maque/table_viewer/static/element-plus.css +1 -0
  112. maque/table_viewer/static/element-plus.js +65236 -0
  113. maque/table_viewer/static/main.css +268 -0
  114. maque/table_viewer/static/main.js +669 -0
  115. maque/table_viewer/static/vue.global.js +18227 -0
  116. maque/table_viewer/templates/index.html +401 -0
  117. maque/utils/__init__.py +56 -0
  118. maque/utils/color.py +68 -0
  119. maque/utils/color_string.py +45 -0
  120. maque/utils/compress.py +66 -0
  121. maque/utils/constant.py +183 -0
  122. maque/utils/core.py +261 -0
  123. maque/utils/cursor.py +143 -0
  124. maque/utils/distance.py +58 -0
  125. maque/utils/docker.py +96 -0
  126. maque/utils/downloads.py +51 -0
  127. maque/utils/excel_helper.py +542 -0
  128. maque/utils/helper_metrics.py +121 -0
  129. maque/utils/helper_parser.py +168 -0
  130. maque/utils/net.py +64 -0
  131. maque/utils/nvidia_stat.py +140 -0
  132. maque/utils/ops.py +53 -0
  133. maque/utils/packages.py +31 -0
  134. maque/utils/path.py +57 -0
  135. maque/utils/tar.py +260 -0
  136. maque/utils/untar.py +129 -0
  137. maque/web/__init__.py +0 -0
  138. maque/web/image_downloader.py +1410 -0
  139. maque-0.2.1.dist-info/METADATA +450 -0
  140. maque-0.2.1.dist-info/RECORD +143 -0
  141. maque-0.2.1.dist-info/WHEEL +4 -0
  142. maque-0.2.1.dist-info/entry_points.txt +3 -0
  143. maque-0.2.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,324 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 文件不可见字符清理工具
4
+
5
+ 功能:
6
+ - 清理文件中的不间断空格(U+00A0)和其他常见不可见字符
7
+ - 支持单个文件或批量处理
8
+ - 自动备份原文件
9
+ - 提供详细的处理报告
10
+
11
+ 使用方法:
12
+ python clean_invisible_chars.py file.py
13
+ python clean_invisible_chars.py *.py
14
+ python clean_invisible_chars.py --dir /path/to/directory --pattern "*.py"
15
+ """
16
+
17
+ import argparse
18
+ import glob
19
+ import os
20
+ import shutil
21
+ from pathlib import Path
22
+ from typing import List, Tuple
23
+
24
+
25
+ class InvisibleCharCleaner:
26
+ """不可见字符清理器"""
27
+
28
+ # 常见的需要清理的不可见字符映射
29
+ CHAR_REPLACEMENTS = {
30
+ "\u00a0": " ", # 不间断空格 -> 普通空格
31
+ "\u2000": " ", # en quad -> 普通空格
32
+ "\u2001": " ", # em quad -> 普通空格
33
+ "\u2002": " ", # en space -> 普通空格
34
+ "\u2003": " ", # em space -> 普通空格
35
+ "\u2004": " ", # three-per-em space -> 普通空格
36
+ "\u2005": " ", # four-per-em space -> 普通空格
37
+ "\u2006": " ", # six-per-em space -> 普通空格
38
+ "\u2007": " ", # figure space -> 普通空格
39
+ "\u2008": " ", # punctuation space -> 普通空格
40
+ "\u2009": " ", # thin space -> 普通空格
41
+ "\u200a": " ", # hair space -> 普通空格
42
+ "\u200b": "", # 零宽空格 -> 删除
43
+ "\u200c": "", # 零宽非连接符 -> 删除
44
+ "\u200d": "", # 零宽连接符 -> 删除
45
+ "\u2060": "", # 字间连接符 -> 删除
46
+ "\ufeff": "", # 字节顺序标记(BOM) -> 删除
47
+ }
48
+
49
+ def __init__(self, backup=True, verbose=True):
50
+ """
51
+ 初始化清理器
52
+
53
+ Args:
54
+ backup: 是否备份原文件
55
+ verbose: 是否显示详细信息
56
+ """
57
+ self.backup = backup
58
+ self.verbose = verbose
59
+ self.stats = {
60
+ "files_processed": 0,
61
+ "files_modified": 0,
62
+ "chars_replaced": 0,
63
+ "backup_created": 0,
64
+ }
65
+
66
+ def detect_invisible_chars(self, content: str) -> List[Tuple[str, int, str]]:
67
+ """
68
+ 检测文件中的不可见字符
69
+
70
+ Args:
71
+ content: 文件内容
72
+
73
+ Returns:
74
+ 检测到的不可见字符列表: [(字符, 数量, 描述)]
75
+ """
76
+ detected = []
77
+
78
+ char_descriptions = {
79
+ "\u00a0": "不间断空格",
80
+ "\u2000": "en quad",
81
+ "\u2001": "em quad",
82
+ "\u2002": "en space",
83
+ "\u2003": "em space",
84
+ "\u2004": "three-per-em space",
85
+ "\u2005": "four-per-em space",
86
+ "\u2006": "six-per-em space",
87
+ "\u2007": "figure space",
88
+ "\u2008": "punctuation space",
89
+ "\u2009": "thin space",
90
+ "\u200a": "hair space",
91
+ "\u200b": "零宽空格",
92
+ "\u200c": "零宽非连接符",
93
+ "\u200d": "零宽连接符",
94
+ "\u2060": "字间连接符",
95
+ "\ufeff": "字节顺序标记(BOM)",
96
+ }
97
+
98
+ for char, description in char_descriptions.items():
99
+ count = content.count(char)
100
+ if count > 0:
101
+ detected.append((char, count, description))
102
+
103
+ return detected
104
+
105
+ def clean_content(self, content: str) -> Tuple[str, int]:
106
+ """
107
+ 清理文本内容中的不可见字符
108
+
109
+ Args:
110
+ content: 原始内容
111
+
112
+ Returns:
113
+ (清理后的内容, 替换的字符数量)
114
+ """
115
+ cleaned_content = content
116
+ total_replacements = 0
117
+
118
+ for old_char, new_char in self.CHAR_REPLACEMENTS.items():
119
+ count = cleaned_content.count(old_char)
120
+ if count > 0:
121
+ cleaned_content = cleaned_content.replace(old_char, new_char)
122
+ total_replacements += count
123
+
124
+ return cleaned_content, total_replacements
125
+
126
+ def backup_file(self, file_path: Path) -> Path:
127
+ """
128
+ 备份文件
129
+
130
+ Args:
131
+ file_path: 原文件路径
132
+
133
+ Returns:
134
+ 备份文件路径
135
+ """
136
+ backup_path = file_path.with_suffix(file_path.suffix + ".backup")
137
+ shutil.copy2(file_path, backup_path)
138
+ self.stats["backup_created"] += 1
139
+ return backup_path
140
+
141
+ def clean_file(self, file_path: Path) -> bool:
142
+ """
143
+ 清理单个文件
144
+
145
+ Args:
146
+ file_path: 文件路径
147
+
148
+ Returns:
149
+ 是否有修改
150
+ """
151
+ try:
152
+ # 读取文件
153
+ with open(file_path, "r", encoding="utf-8") as f:
154
+ original_content = f.read()
155
+
156
+ # 检测不可见字符
157
+ detected_chars = self.detect_invisible_chars(original_content)
158
+
159
+ if not detected_chars:
160
+ if self.verbose:
161
+ print(f"✓ {file_path}: 未发现不可见字符")
162
+ return False
163
+
164
+ # 显示检测结果
165
+ if self.verbose:
166
+ print(f"\n📁 处理文件: {file_path}")
167
+ print("🔍 检测到的不可见字符:")
168
+ for char, count, desc in detected_chars:
169
+ hex_code = f"U+{ord(char):04X}"
170
+ print(f" - {desc} ({hex_code}): {count} 个")
171
+
172
+ # 备份原文件
173
+ if self.backup:
174
+ backup_path = self.backup_file(file_path)
175
+ if self.verbose:
176
+ print(f"💾 已备份到: {backup_path}")
177
+
178
+ # 清理内容
179
+ cleaned_content, replacements = self.clean_content(original_content)
180
+
181
+ # 写入清理后的内容
182
+ with open(file_path, "w", encoding="utf-8") as f:
183
+ f.write(cleaned_content)
184
+
185
+ # 更新统计
186
+ self.stats["files_modified"] += 1
187
+ self.stats["chars_replaced"] += replacements
188
+
189
+ if self.verbose:
190
+ print(f"✅ 已清理 {replacements} 个不可见字符")
191
+
192
+ return True
193
+
194
+ except Exception as e:
195
+ print(f"❌ 处理文件 {file_path} 时出错: {e}")
196
+ return False
197
+ finally:
198
+ self.stats["files_processed"] += 1
199
+
200
+ def clean_files(self, file_paths: List[Path]) -> None:
201
+ """
202
+ 批量清理文件
203
+
204
+ Args:
205
+ file_paths: 文件路径列表
206
+ """
207
+ print(f"🚀 开始处理 {len(file_paths)} 个文件...")
208
+ print("=" * 60)
209
+
210
+ for file_path in file_paths:
211
+ if file_path.is_file():
212
+ self.clean_file(file_path)
213
+ else:
214
+ print(f"⚠️ 跳过非文件: {file_path}")
215
+
216
+ # 显示统计信息
217
+ self.print_summary()
218
+
219
+ def print_summary(self) -> None:
220
+ """打印处理摘要"""
221
+ print("\n" + "=" * 60)
222
+ print("📊 处理摘要")
223
+ print("=" * 60)
224
+ print(f"处理文件数: {self.stats['files_processed']}")
225
+ print(f"修改文件数: {self.stats['files_modified']}")
226
+ print(f"清理字符数: {self.stats['chars_replaced']}")
227
+ if self.backup:
228
+ print(f"创建备份数: {self.stats['backup_created']}")
229
+ print("✨ 处理完成!")
230
+
231
+
232
+ def find_files_by_pattern(directory: str, pattern: str) -> List[Path]:
233
+ """
234
+ 根据模式查找文件
235
+
236
+ Args:
237
+ directory: 目录路径
238
+ pattern: 文件模式 (如 "*.py")
239
+
240
+ Returns:
241
+ 匹配的文件路径列表
242
+ """
243
+ search_pattern = os.path.join(directory, "**", pattern)
244
+ file_paths = []
245
+
246
+ for path_str in glob.glob(search_pattern, recursive=True):
247
+ path = Path(path_str)
248
+ if path.is_file():
249
+ file_paths.append(path)
250
+
251
+ return sorted(file_paths)
252
+
253
+
254
+ def main():
255
+ """主函数"""
256
+ parser = argparse.ArgumentParser(
257
+ description="清理文件中的不可见字符",
258
+ formatter_class=argparse.RawDescriptionHelpFormatter,
259
+ epilog="""
260
+ 使用示例:
261
+ %(prog)s file.py # 清理单个文件
262
+ %(prog)s *.py # 清理当前目录下所有Python文件
263
+ %(prog)s file1.py file2.py # 清理多个文件
264
+ %(prog)s --dir /path --pattern "*.py" # 递归清理目录下的Python文件
265
+ %(prog)s --no-backup file.py # 清理时不创建备份
266
+ %(prog)s --quiet file.py # 静默模式
267
+ """,
268
+ )
269
+
270
+ parser.add_argument("files", nargs="*", help="要处理的文件路径")
271
+ parser.add_argument("--dir", "-d", help="要处理的目录路径")
272
+ parser.add_argument("--pattern", "-p", default="*", help='文件匹配模式 (如 "*.py")')
273
+ parser.add_argument("--no-backup", action="store_true", help="不创建备份文件")
274
+ parser.add_argument("--quiet", "-q", action="store_true", help="静默模式")
275
+
276
+ args = parser.parse_args()
277
+
278
+ # 收集要处理的文件
279
+ file_paths = []
280
+
281
+ if args.dir:
282
+ # 目录模式
283
+ if not os.path.isdir(args.dir):
284
+ print(f"❌ 目录不存在: {args.dir}")
285
+ return 1
286
+ file_paths = find_files_by_pattern(args.dir, args.pattern)
287
+ if not file_paths:
288
+ print(f"❌ 在目录 {args.dir} 中未找到匹配 {args.pattern} 的文件")
289
+ return 1
290
+ elif args.files:
291
+ # 文件列表模式
292
+ for file_pattern in args.files:
293
+ if "*" in file_pattern or "?" in file_pattern:
294
+ # 通配符模式
295
+ matched_files = glob.glob(file_pattern)
296
+ if matched_files:
297
+ file_paths.extend([Path(f) for f in matched_files])
298
+ else:
299
+ print(f"⚠️ 未找到匹配 {file_pattern} 的文件")
300
+ else:
301
+ # 直接文件路径
302
+ file_path = Path(file_pattern)
303
+ if file_path.exists():
304
+ file_paths.append(file_path)
305
+ else:
306
+ print(f"⚠️ 文件不存在: {file_pattern}")
307
+ else:
308
+ # 没有指定文件或目录
309
+ parser.print_help()
310
+ return 1
311
+
312
+ if not file_paths:
313
+ print("❌ 没有找到要处理的文件")
314
+ return 1
315
+
316
+ # 创建清理器并处理文件
317
+ cleaner = InvisibleCharCleaner(backup=not args.no_backup, verbose=not args.quiet)
318
+
319
+ cleaner.clean_files(file_paths)
320
+ return 0
321
+
322
+
323
+ if __name__ == "__main__":
324
+ exit(main())
maque/cli/core.py ADDED
@@ -0,0 +1,34 @@
1
+ import time
2
+ import subprocess
3
+ import io
4
+ import csv
5
+ import collections
6
+
7
+
8
+ def run_job(job, num: int, interval="second"):
9
+ import schedule
10
+
11
+ if interval == "second":
12
+ schedule.every(num).seconds.do(job)
13
+ elif interval == "minute":
14
+ schedule.every(num).minutes.do(job)
15
+ elif interval == "hour":
16
+ schedule.every(num).hours.do(job)
17
+ elif interval == "day":
18
+ schedule.every(num).days.do(job)
19
+ while True:
20
+ schedule.run_pending()
21
+ time.sleep(1)
22
+
23
+
24
+ def commandexists(shellcommand):
25
+ status, output = subprocess.getstatusoutput(shellcommand)
26
+ exists = status == 0
27
+ if not exists:
28
+ print("Could not execute: {0}".format(shellcommand))
29
+ return exists
30
+
31
+ def command(args):
32
+ # subprocess.call(args)
33
+ # subprocess.getoutput(cmd)
34
+ return subprocess.check_output(args).decode()
@@ -0,0 +1,26 @@
1
+ # CLI命令组模块
2
+ from .config import ConfigGroup
3
+ from .mllm import MllmGroup
4
+ from .data import DataGroup
5
+ from .service import ServiceGroup
6
+ from .doctor import DoctorGroup
7
+ from .help import HelpGroup
8
+ from .embedding import EmbeddingGroup
9
+ from .git import GitGroup
10
+ from .system import SystemGroup
11
+ from .mcp import MCPGroup
12
+ from .quant import QuantGroup
13
+
14
+ __all__ = [
15
+ 'ConfigGroup',
16
+ 'MllmGroup',
17
+ 'DataGroup',
18
+ 'ServiceGroup',
19
+ 'DoctorGroup',
20
+ 'HelpGroup',
21
+ 'EmbeddingGroup',
22
+ 'GitGroup',
23
+ 'SystemGroup',
24
+ 'MCPGroup',
25
+ 'QuantGroup',
26
+ ]
@@ -0,0 +1,205 @@
1
+ """配置管理命令组"""
2
+ import os
3
+ import yaml
4
+ from pathlib import Path
5
+ from typing import Optional
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+ from rich import print
9
+
10
+
11
+ class ConfigGroup:
12
+ """配置管理命令组"""
13
+
14
+ def __init__(self, cli_instance):
15
+ self.cli = cli_instance
16
+ self.console = Console()
17
+
18
+ def show(self):
19
+ """显示当前配置"""
20
+ config = self.cli.maque_config
21
+
22
+ print("[bold blue]Sparrow 配置信息[/bold blue]\n")
23
+
24
+ # 显示配置来源
25
+ config_paths = self.cli._get_config_search_paths()
26
+ for path in config_paths:
27
+ if path.exists():
28
+ print(f"[green]✓[/green] 配置文件: {path}")
29
+ break
30
+ else:
31
+ print("[yellow]⚠[/yellow] 未找到配置文件,使用默认配置")
32
+
33
+ print("\n[bold]当前配置:[/bold]")
34
+ self._print_config_dict(config, indent=0)
35
+
36
+ def _print_config_dict(self, config, indent=0):
37
+ """递归打印配置字典"""
38
+ prefix = " " * indent
39
+ for key, value in config.items():
40
+ if isinstance(value, dict):
41
+ print(f"{prefix}[cyan]{key}[/cyan]:")
42
+ self._print_config_dict(value, indent + 1)
43
+ else:
44
+ print(f"{prefix}[cyan]{key}[/cyan]: [green]{value}[/green]")
45
+
46
+ def edit(self, editor: str = None):
47
+ """交互式编辑配置文件
48
+
49
+ Args:
50
+ editor: 使用的编辑器,默认使用系统默认编辑器
51
+ """
52
+ config_paths = self.cli._get_config_search_paths()
53
+
54
+ # 寻找现有配置文件
55
+ config_file = None
56
+ for path in config_paths:
57
+ if path.exists():
58
+ config_file = path
59
+ break
60
+
61
+ # 如果没有配置文件,创建一个
62
+ if not config_file:
63
+ config_file = self.cli._get_default_config_path()
64
+ if not config_file.exists():
65
+ self.cli.init_config()
66
+
67
+ # 使用编辑器打开
68
+ editor_cmd = editor or os.environ.get('EDITOR', 'notepad' if os.name == 'nt' else 'nano')
69
+ os.system(f'{editor_cmd} "{config_file}"')
70
+
71
+ print(f"[green]配置文件已编辑: {config_file}[/green]")
72
+ print("重新启动maque以应用新配置")
73
+
74
+ def validate(self):
75
+ """验证配置文件"""
76
+ print("[bold blue]验证配置文件...[/bold blue]\n")
77
+
78
+ config_paths = self.cli._get_config_search_paths()
79
+ errors = []
80
+
81
+ for path in config_paths:
82
+ if path.exists():
83
+ try:
84
+ with open(path, 'r', encoding='utf-8') as f:
85
+ yaml.safe_load(f)
86
+ print(f"[green]✓[/green] {path} - 语法正确")
87
+
88
+ # 验证配置内容
89
+ config = self.cli._load_maque_config()
90
+ self._validate_config_content(config, errors)
91
+ break
92
+
93
+ except yaml.YAMLError as e:
94
+ errors.append(f"{path}: YAML语法错误 - {e}")
95
+ except Exception as e:
96
+ errors.append(f"{path}: 读取错误 - {e}")
97
+
98
+ if errors:
99
+ print("\n[bold red]发现配置错误:[/bold red]")
100
+ for error in errors:
101
+ print(f"[red]✗[/red] {error}")
102
+ return False
103
+ else:
104
+ print("\n[green]✓ 配置验证通过[/green]")
105
+ return True
106
+
107
+ def _validate_config_content(self, config, errors):
108
+ """验证配置内容的合理性"""
109
+ # 验证MLLM配置
110
+ if 'mllm' in config:
111
+ mllm_config = config['mllm']
112
+ if 'base_url' in mllm_config:
113
+ url = mllm_config['base_url']
114
+ if not (url.startswith('http://') or url.startswith('https://')):
115
+ errors.append("mllm.base_url 应该以 http:// 或 https:// 开头")
116
+
117
+ def set(self, key: str, value: str, config_file: str = None):
118
+ """设置配置值
119
+
120
+ Args:
121
+ key: 配置键,支持点号分隔如 'mllm.model'
122
+ value: 配置值
123
+ config_file: 指定配置文件路径,默认使用 ~/.maque/config.yaml
124
+ """
125
+ if config_file is None:
126
+ config_path = self.cli._get_default_config_path()
127
+ else:
128
+ config_path = Path(config_file)
129
+
130
+ # 加载现有配置
131
+ if config_path.exists():
132
+ with open(config_path, 'r', encoding='utf-8') as f:
133
+ config = yaml.safe_load(f) or {}
134
+ else:
135
+ config = {}
136
+
137
+ # 设置嵌套键值
138
+ keys = key.split('.')
139
+ current = config
140
+ for k in keys[:-1]:
141
+ if k not in current:
142
+ current[k] = {}
143
+ current = current[k]
144
+
145
+ # 尝试转换值的类型
146
+ try:
147
+ if value.lower() in ('true', 'false'):
148
+ value = value.lower() == 'true'
149
+ elif value.isdigit():
150
+ value = int(value)
151
+ elif value.replace('.', '').isdigit():
152
+ value = float(value)
153
+ except:
154
+ pass # 保持字符串类型
155
+
156
+ current[keys[-1]] = value
157
+
158
+ # 保存配置
159
+ with open(config_path, 'w', encoding='utf-8') as f:
160
+ yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
161
+
162
+ print(f"[green]✓[/green] 已设置 {key} = {value}")
163
+ print(f"配置保存到: {config_path.resolve()}")
164
+
165
+ def get(self, key: str = None):
166
+ """获取配置值
167
+
168
+ Args:
169
+ key: 配置键,支持点号分隔如 'mllm.model',为空则显示所有配置
170
+ """
171
+ if key is None:
172
+ return self.show()
173
+
174
+ value = self.cli.get_config(key)
175
+ if value is not None:
176
+ print(f"[cyan]{key}[/cyan]: [green]{value}[/green]")
177
+ else:
178
+ print(f"[yellow]配置项 '{key}' 不存在[/yellow]")
179
+
180
+ return value
181
+
182
+ def reset(self, confirm: bool = False):
183
+ """重置配置到默认值
184
+
185
+ Args:
186
+ confirm: 确认重置,为False时会提示确认
187
+ """
188
+ if not confirm:
189
+ response = input("确定要重置配置到默认值吗?这会覆盖现有配置 (y/N): ")
190
+ if response.lower() != 'y':
191
+ print("操作已取消")
192
+ return
193
+
194
+ config_paths = self.cli._get_config_search_paths()
195
+ for path in config_paths:
196
+ if path.exists():
197
+ # 备份现有配置
198
+ backup_path = path.with_suffix(path.suffix + '.backup')
199
+ path.rename(backup_path)
200
+ print(f"原配置已备份到: {backup_path}")
201
+ break
202
+
203
+ # 创建新的默认配置
204
+ self.cli.init_config()
205
+ print("[green]配置已重置为默认值[/green]")