pysfi 0.1.13__py3-none-any.whl → 0.1.15__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.
sfi/docscan/docscan.py CHANGED
@@ -91,7 +91,9 @@ def t(key: str, **kwargs) -> str:
91
91
  Returns:
92
92
  Translated text
93
93
  """
94
- text = ZH_TRANSLATIONS.get(key, key) if USE_CHINESE else EN_TRANSLATIONS.get(key, key)
94
+ text = (
95
+ ZH_TRANSLATIONS.get(key, key) if USE_CHINESE else EN_TRANSLATIONS.get(key, key)
96
+ )
95
97
 
96
98
  # Format with kwargs if provided
97
99
  if kwargs:
@@ -123,7 +125,9 @@ class Rule:
123
125
  # Use re.ASCII for faster matching when possible
124
126
  self.compiled_pattern = re.compile(self.pattern, flags | re.ASCII)
125
127
  except re.error as e:
126
- logger.warning(t("invalid_regex_pattern", pattern=self.pattern, error=e))
128
+ logger.warning(
129
+ t("invalid_regex_pattern", pattern=self.pattern, error=e)
130
+ )
127
131
  self.compiled_pattern = None
128
132
  else:
129
133
  self.compiled_pattern = None
@@ -274,13 +278,18 @@ class DocumentScanner:
274
278
  "use_pdf_ocr": self.use_pdf_ocr,
275
279
  "use_process_pool": self.use_process_pool,
276
280
  },
277
- "rules": [{"name": r.name, "pattern": r.pattern, "is_regex": r.is_regex} for r in self.rules],
281
+ "rules": [
282
+ {"name": r.name, "pattern": r.pattern, "is_regex": r.is_regex}
283
+ for r in self.rules
284
+ ],
278
285
  "matches": [],
279
286
  }
280
287
 
281
288
  # Scan files in parallel
282
289
  processed = 0
283
- executor_class = ProcessPoolExecutor if self.use_process_pool else ThreadPoolExecutor
290
+ executor_class = (
291
+ ProcessPoolExecutor if self.use_process_pool else ThreadPoolExecutor
292
+ )
284
293
  executor = executor_class(max_workers=threads)
285
294
  self._executor = executor # Keep reference for forced shutdown
286
295
 
@@ -350,10 +359,17 @@ class DocumentScanner:
350
359
  break
351
360
 
352
361
  try:
353
- file_result = future.result(timeout=1.0) # Short timeout to allow quick stop
362
+ file_result = future.result(
363
+ timeout=1.0
364
+ ) # Short timeout to allow quick stop
354
365
  if file_result and file_result["matches"]:
355
366
  results["matches"].append(file_result)
356
- logger.info(t("found_matches_in_file", file_name=Path(file_result.get("file_path", "")).name))
367
+ logger.info(
368
+ t(
369
+ "found_matches_in_file",
370
+ file_name=Path(file_result.get("file_path", "")).name,
371
+ )
372
+ )
357
373
  except TimeoutError:
358
374
  logger.warning(t("task_timeout_scan_may_be_stopping"))
359
375
  if self.stopped:
@@ -366,7 +382,9 @@ class DocumentScanner:
366
382
 
367
383
  # Report progress
368
384
  if show_progress and processed % 10 == 0:
369
- logger.info(t("progress_report", processed=processed, total=len(files)))
385
+ logger.info(
386
+ t("progress_report", processed=processed, total=len(files))
387
+ )
370
388
 
371
389
  # Call progress callback if set
372
390
  if self._progress_callback:
@@ -391,7 +409,9 @@ class DocumentScanner:
391
409
  if self.stopped:
392
410
  logger.info(t("scan_stopped_processed_files", processed=processed))
393
411
  else:
394
- logger.info(t("scan_complete_found_matches", matches_count=len(results["matches"])))
412
+ logger.info(
413
+ t("scan_complete_found_matches", matches_count=len(results["matches"]))
414
+ )
395
415
 
396
416
  return results
397
417
 
@@ -493,7 +513,9 @@ class DocumentScanner:
493
513
  return {}
494
514
 
495
515
  except Exception as e:
496
- logger.warning(t("could_not_extract_text_from_file", file_path=file_path, error=e))
516
+ logger.warning(
517
+ t("could_not_extract_text_from_file", file_path=file_path, error=e)
518
+ )
497
519
  return {}
498
520
 
499
521
  processing_time = time.perf_counter() - file_start_time
@@ -549,14 +571,18 @@ class DocumentScanner:
549
571
  try:
550
572
  return self._extract_pdf_fitz(file_path)
551
573
  except Exception as e:
552
- logger.warning(t("pymupdf_failed_for_file", file_name=file_path.name, error=e))
574
+ logger.warning(
575
+ t("pymupdf_failed_for_file", file_name=file_path.name, error=e)
576
+ )
553
577
 
554
578
  # Fallback to pypdf
555
579
  if pypdf is not None:
556
580
  try:
557
581
  return self._extract_pdf_pypdf(file_path)
558
582
  except Exception as e:
559
- logger.error(t("pypdf_also_failed_for_file", file_name=file_path.name, error=e))
583
+ logger.error(
584
+ t("pypdf_also_failed_for_file", file_name=file_path.name, error=e)
585
+ )
560
586
  return "", {}
561
587
 
562
588
  logger.warning(t("no_pdf_library_installed"))
@@ -632,7 +658,9 @@ class DocumentScanner:
632
658
  except Exception as e:
633
659
  if doc:
634
660
  doc.close()
635
- logger.warning(t("pymupdf_error_trying_fallback", file_path=file_path, error=e))
661
+ logger.warning(
662
+ t("pymupdf_error_trying_fallback", file_path=file_path, error=e)
663
+ )
636
664
  # Re-raise to trigger fallback to pypdf
637
665
  raise
638
666
 
@@ -764,8 +792,12 @@ class DocumentScanner:
764
792
  text_parts.append(text)
765
793
 
766
794
  metadata = {
767
- "title": book.get_metadata("DC", "title")[0][0] if book.get_metadata("DC", "title") else "", # pyright: ignore[reportAttributeAccessIssue]
768
- "author": book.get_metadata("DC", "creator")[0][0] if book.get_metadata("DC", "creator") else "", # pyright: ignore[reportAttributeAccessIssue]
795
+ "title": book.get_metadata("DC", "title")[0][0]
796
+ if book.get_metadata("DC", "title")
797
+ else "", # pyright: ignore[reportAttributeAccessIssue]
798
+ "author": book.get_metadata("DC", "creator")[0][0]
799
+ if book.get_metadata("DC", "creator")
800
+ else "", # pyright: ignore[reportAttributeAccessIssue]
769
801
  "format": "EPUB",
770
802
  }
771
803
 
@@ -810,7 +842,9 @@ class DocumentScanner:
810
842
  root = tree.getroot()
811
843
 
812
844
  # Extract all text content
813
- text_parts = [elem.text for elem in root.iter() if elem.text and elem.text.strip()]
845
+ text_parts = [
846
+ elem.text for elem in root.iter() if elem.text and elem.text.strip()
847
+ ]
814
848
  text = "\n".join(text_parts)
815
849
 
816
850
  metadata = {
@@ -954,7 +988,9 @@ class DocumentScanner:
954
988
  wb.close()
955
989
  return "", {}
956
990
 
957
- row_text = " | ".join(str(cell) if cell is not None else "" for cell in row)
991
+ row_text = " | ".join(
992
+ str(cell) if cell is not None else "" for cell in row
993
+ )
958
994
  if row_text.strip():
959
995
  text_parts.append(row_text)
960
996
 
@@ -1017,7 +1053,9 @@ class DocumentScanner:
1017
1053
 
1018
1054
  return text, metadata
1019
1055
  except Exception as e:
1020
- logger.warning(t("could_not_perform_ocr_on_file", file_path=file_path, error=e))
1056
+ logger.warning(
1057
+ t("could_not_perform_ocr_on_file", file_path=file_path, error=e)
1058
+ )
1021
1059
  return "", {}
1022
1060
 
1023
1061
  def _extract_text(self, file_path: Path) -> tuple[str, dict[str, Any]]:
@@ -1047,8 +1085,12 @@ def main():
1047
1085
  USE_CHINESE = temp_args.lang == "zh"
1048
1086
 
1049
1087
  parser = argparse.ArgumentParser(description=t("document_scanner_description"))
1050
- parser.add_argument("input", type=str, nargs="?", default=str(cwd), help=t("input_directory_help"))
1051
- parser.add_argument("-r", "--rules", type=str, default="rules.json", help=t("rules_file_help"))
1088
+ parser.add_argument(
1089
+ "input", type=str, nargs="?", default=str(cwd), help=t("input_directory_help")
1090
+ )
1091
+ parser.add_argument(
1092
+ "-r", "--rules", type=str, default="rules.json", help=t("rules_file_help")
1093
+ )
1052
1094
  parser.add_argument("--recursive", action="store_true", help=t("recursive_help"))
1053
1095
  parser.add_argument(
1054
1096
  "-f",
@@ -1056,7 +1098,9 @@ def main():
1056
1098
  help=t("file_types_help"),
1057
1099
  default="pdf,docx,xlsx,pptx,txt,odt,rtf,epub,csv,xml,html,md,jpg,jpeg,png,gif,bmp,tiff",
1058
1100
  )
1059
- parser.add_argument("--use-pdf-ocr", help=t("use_pdf_ocr_help"), action="store_true")
1101
+ parser.add_argument(
1102
+ "--use-pdf-ocr", help=t("use_pdf_ocr_help"), action="store_true"
1103
+ )
1060
1104
  parser.add_argument(
1061
1105
  "--use-process-pool",
1062
1106
  help=t("use_process_pool_help"),
@@ -1074,7 +1118,9 @@ def main():
1074
1118
  parser.add_argument("-v", "--verbose", help=t("verbose_help"), action="store_true")
1075
1119
 
1076
1120
  # 添加语言参数
1077
- parser.add_argument("--lang", help=t("language_help"), choices=["en", "zh"], default="zh")
1121
+ parser.add_argument(
1122
+ "--lang", help=t("language_help"), choices=["en", "zh"], default="zh"
1123
+ )
1078
1124
 
1079
1125
  args = parser.parse_args()
1080
1126
 
@@ -1129,11 +1175,20 @@ def main():
1129
1175
  file_types = [ft.strip() for ft in args.file_types.split(",")]
1130
1176
 
1131
1177
  # Create scanner and run scan
1132
- scanner = DocumentScanner(input_dir, rules, file_types, args.use_pdf_ocr, args.use_process_pool, args.batch_size)
1178
+ scanner = DocumentScanner(
1179
+ input_dir,
1180
+ rules,
1181
+ file_types,
1182
+ args.use_pdf_ocr,
1183
+ args.use_process_pool,
1184
+ args.batch_size,
1185
+ )
1133
1186
  results = scanner.scan(threads=args.threads, show_progress=args.progress)
1134
1187
 
1135
1188
  # Save results to JSON file in input directory
1136
- output_file = input_dir / f"scan_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
1189
+ output_file = (
1190
+ input_dir / f"scan_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
1191
+ )
1137
1192
  with open(output_file, "w", encoding="utf-8") as f:
1138
1193
  json.dump(results, f, indent=2, ensure_ascii=False)
1139
1194
 
@@ -45,14 +45,14 @@ except ImportError:
45
45
  try:
46
46
  from sfi.docscan.docscan import DocumentScanner, Rule
47
47
  except ImportError:
48
- from src.docscan.docscan import DocumentScanner, Rule
48
+ from docscan.docscan import DocumentScanner, Rule
49
49
 
50
50
  # Import translations
51
51
  try:
52
52
  from sfi.docscan.lang.zhcn import TRANSLATIONS
53
53
  except ImportError:
54
54
  try:
55
- from src.docscan.lang.zhcn import TRANSLATIONS
55
+ from docscan.lang.zhcn import TRANSLATIONS
56
56
  except ImportError:
57
57
  TRANSLATIONS = {}
58
58
 
@@ -413,15 +413,15 @@ class SettingsDialog(QDialog):
413
413
  if item.widget() and isinstance(item.widget(), QGroupBox):
414
414
  group_box = item.widget()
415
415
  if "Language" in group_box.title(): # type: ignore
416
- group_box.setTitle(
416
+ group_box.setWindowTitle(
417
417
  t("language_settings", default="Language Settings")
418
418
  ) # type: ignore
419
419
  elif "Processing" in group_box.title(): # type: ignore
420
- group_box.setTitle(
420
+ group_box.setWindowTitle(
421
421
  t("processing_options", default="Processing Options")
422
422
  ) # type: ignore
423
423
  elif "Performance" in group_box.title(): # type: ignore
424
- group_box.setTitle(
424
+ group_box.setWindowTitle(
425
425
  t("performance_settings", default="Performance Settings")
426
426
  ) # type: ignore
427
427
 
sfi/filedate/filedate.py CHANGED
@@ -11,11 +11,18 @@ import time
11
11
  from dataclasses import dataclass
12
12
  from functools import cached_property, lru_cache
13
13
  from pathlib import Path
14
-
15
- DETECT_SEPARATORS: str = "-_#.~"
16
- SEP: str = "_"
17
- MAX_RETRY: int = 100
18
- DATE_PATTERN = re.compile(r"(20|19)\d{2}((0[1-9])|(1[012]))((0[1-9])|([12]\d)|(3[01]))")
14
+ from re import Pattern
15
+ from typing import Final
16
+
17
+ # Configuration constants
18
+ DETECT_SEPARATORS: Final[str] = "-_#.~"
19
+ SEP: Final[str] = "_"
20
+ MAX_RETRY: Final[int] = 100
21
+
22
+ # Date pattern for detection
23
+ DATE_PATTERN: Final[Pattern[str]] = re.compile(
24
+ r"(20|19)\d{2}((0[1-9])|(1[012]))((0[1-9])|([12]\d)|(3[01]))"
25
+ )
19
26
 
20
27
  logging.basicConfig(level=logging.INFO, format="%(message)s")
21
28
  logger = logging.getLogger(__name__)
sfi/img2pdf/img2pdf.py CHANGED
@@ -33,7 +33,7 @@ class ImageToPdfConfig:
33
33
  """Configuration for image to PDF conversion."""
34
34
 
35
35
  DPI: int = 300
36
- EXTENSIONS: set[str] = None
36
+ EXTENSIONS: set[str] | None = None
37
37
 
38
38
  def __post_init__(self) -> None:
39
39
  # Initialize default extensions if not provided
@@ -68,7 +68,7 @@ class ImageToPdfConfig:
68
68
  def save(self) -> None:
69
69
  """Save current configuration to file."""
70
70
  CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
71
- config_dict = {"DPI": self.DPI, "EXTENSIONS": list(self.EXTENSIONS)}
71
+ config_dict = {"DPI": self.DPI, "EXTENSIONS": list(self.EXTENSIONS or set())}
72
72
  CONFIG_FILE.write_text(json.dumps(config_dict, indent=4), encoding="utf-8")
73
73
 
74
74
 
@@ -111,7 +111,7 @@ def is_valid_image(file_path: Path) -> bool:
111
111
 
112
112
  # Extension validation.
113
113
  ext = file_path.suffix.lower()
114
- if ext not in conf.EXTENSIONS:
114
+ if not conf.EXTENSIONS or ext not in conf.EXTENSIONS:
115
115
  logger.debug(f"Invalid image extension: {ext}, {file_path}")
116
116
  return False
117
117
 
@@ -337,8 +337,8 @@ class ImageToPDFRunner:
337
337
  del image
338
338
  if "rgb_img" in locals():
339
339
  del rgb_img
340
- except:
341
- pass # Ignore cleanup errors
340
+ except Exception:
341
+ logger.error(f"Cleanup image: {filepath} failed")
342
342
  return None
343
343
 
344
344
  def _auto_rotate_image(self, image: Image.Image) -> Image.Image:
@@ -31,9 +31,14 @@ from PySide2.QtWidgets import (
31
31
 
32
32
  CONFIG_FILE = Path.home() / ".pysfi" / "llmquantize.json"
33
33
 
34
- logging.basicConfig(level=logging.INFO)
34
+ logging.basicConfig(
35
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
36
+ )
35
37
  logger = logging.getLogger(__name__)
36
38
 
39
+ __version__ = "1.0.0"
40
+ __build__ = "20260204"
41
+
37
42
 
38
43
  @dataclass
39
44
  class QuantizerConfig:
@@ -44,9 +49,9 @@ class QuantizerConfig:
44
49
  WIN_POS: list[int] = None
45
50
  LAST_INPUT_FILE: str = ""
46
51
  SELECTED_QUANTS: list[str] = None
47
- _loaded_from_file: bool = False
48
52
 
49
53
  def __post_init__(self) -> None:
54
+ """初始化默认值并加载配置文件."""
50
55
  # 初始化默认值
51
56
  if self.WIN_SIZE is None:
52
57
  self.WIN_SIZE = [600, 500]
@@ -62,12 +67,7 @@ class QuantizerConfig:
62
67
  # 更新实例属性,只更新存在的属性
63
68
  for key, value in config_data.items():
64
69
  if hasattr(self, key):
65
- if key in ["WIN_SIZE", "WIN_POS", "SELECTED_QUANTS"]:
66
- # 对于列表类型,需要特别处理
67
- setattr(self, key, value)
68
- else:
69
- setattr(self, key, value)
70
- self._loaded_from_file = True
70
+ setattr(self, key, value)
71
71
  except (json.JSONDecodeError, TypeError, AttributeError) as e:
72
72
  logger.warning("Failed to load configuration: %s", e)
73
73
  logger.info("Using default configuration")
@@ -75,7 +75,7 @@ class QuantizerConfig:
75
75
  logger.info("Using default configuration")
76
76
 
77
77
  def save(self) -> None:
78
- """保存配置."""
78
+ """保存配置到文件."""
79
79
  CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
80
80
  # 将数据类转换为字典进行JSON序列化
81
81
  config_dict = {}
@@ -97,8 +97,11 @@ atexit.register(conf.save)
97
97
  def _process_gguf_stem(filename: str) -> str:
98
98
  """处理文件名, 移除可能的F16后缀.
99
99
 
100
+ Args:
101
+ filename: 输入的文件名(不含扩展名)
102
+
100
103
  Returns:
101
- str: 处理后的文件名
104
+ str: 处理后的文件名, 移除了F16后缀(如果存在)
102
105
  """
103
106
  if filename.upper().endswith("-F16"):
104
107
  filename = filename[:-4] # 移除-F16后缀
@@ -106,7 +109,13 @@ def _process_gguf_stem(filename: str) -> str:
106
109
 
107
110
 
108
111
  class QuantizationWorker(QThread):
109
- """量化执行线程Worker."""
112
+ """量化执行线程Worker.
113
+
114
+ Attributes:
115
+ progress_msg_updated: 进度消息更新信号
116
+ progress_count_updated: 进度数值更新信号
117
+ is_finished: 完成信号
118
+ """
110
119
 
111
120
  progress_msg_updated = Signal(str)
112
121
  progress_count_updated = Signal(int)
@@ -117,6 +126,12 @@ class QuantizationWorker(QThread):
117
126
  input_file: pathlib.Path,
118
127
  quant_types: list[str],
119
128
  ) -> None:
129
+ """初始化量化Worker.
130
+
131
+ Args:
132
+ input_file: 输入的F16 GGUF文件路径
133
+ quant_types: 需要转换的量化类型列表
134
+ """
120
135
  super().__init__()
121
136
 
122
137
  self.input_file = input_file
@@ -139,7 +154,7 @@ class QuantizationWorker(QThread):
139
154
  f"正在转换到 {quant_type} 格式...",
140
155
  )
141
156
 
142
- # 构建命令行参数(确保所有参数都是字符串)
157
+ # 构建命令行参数
143
158
  cmd = [
144
159
  "llama-quantize",
145
160
  str(self.input_file.name),
@@ -147,7 +162,7 @@ class QuantizationWorker(QThread):
147
162
  quant_type,
148
163
  ]
149
164
 
150
- # 执行转换命令(使用 cwd 参数避免全局目录变更)
165
+ # 执行转换命令
151
166
  try:
152
167
  process = subprocess.Popen(
153
168
  cmd,
@@ -312,7 +327,7 @@ class GGUFQuantizerGUI(QMainWindow):
312
327
  self.setCentralWidget(main_widget)
313
328
 
314
329
  def select_file(self) -> None:
315
- """选择文件."""
330
+ """选择F16 GGUF文件."""
316
331
  # 使用上次选择的目录作为初始目录
317
332
  initial_dir = ""
318
333
  if conf.LAST_INPUT_FILE and pathlib.Path(conf.LAST_INPUT_FILE).exists():
@@ -361,21 +376,25 @@ class GGUFQuantizerGUI(QMainWindow):
361
376
  filename = f"{_process_gguf_stem(self.input_file.stem)}-{quant_type}.gguf"
362
377
  expected_file = dir_path / filename
363
378
  if expected_file.exists():
364
- # 文件已存在,标记但不禁用,允许用户选择重新生成
379
+ # 文件已存在,标记并禁用,防止重复生成
365
380
  self.quant_checks[quant_type].setText(
366
381
  f"{self.quant_types[quant_type]} (已存在)",
367
382
  )
368
383
  self.quant_checks[quant_type].setStyleSheet("color: orange;")
384
+ self.quant_checks[quant_type].setChecked(True)
385
+ self.quant_checks[quant_type].setEnabled(False)
369
386
  else:
370
387
  self.quant_checks[quant_type].setText(
371
388
  self.quant_types[quant_type],
372
389
  )
373
390
  self.quant_checks[quant_type].setStyleSheet("")
391
+ self.quant_checks[quant_type].setEnabled(True)
374
392
 
375
393
  def _scroll_to_bottom(self) -> None:
376
394
  """滚动输出框到底部."""
377
395
  scrollbar = self.output_text.verticalScrollBar()
378
- scrollbar.setValue(scrollbar.maximum())
396
+ if scrollbar:
397
+ scrollbar.setValue(scrollbar.maximum())
379
398
 
380
399
  def on_quant_type_changed(self, _state: int) -> None:
381
400
  """量化类型变更时保存配置."""
@@ -397,7 +416,7 @@ class GGUFQuantizerGUI(QMainWindow):
397
416
  return super().resizeEvent(event)
398
417
 
399
418
  def start_conversion(self) -> None:
400
- """开始转换."""
419
+ """开始转换量化任务."""
401
420
  # 检查是否已有任务在运行
402
421
  if self.worker and self.worker.isRunning():
403
422
  self.output_text.append("已有转换任务正在进行, 请等待完成")
@@ -405,7 +424,9 @@ class GGUFQuantizerGUI(QMainWindow):
405
424
  return
406
425
 
407
426
  selected_quants: list[str] = [
408
- q for q, check in self.quant_checks.items() if check.isChecked()
427
+ q
428
+ for q, check in self.quant_checks.items()
429
+ if check.isChecked() and check.isEnabled()
409
430
  ]
410
431
 
411
432
  if not selected_quants:
@@ -418,19 +439,8 @@ class GGUFQuantizerGUI(QMainWindow):
418
439
  self._scroll_to_bottom()
419
440
  return
420
441
 
421
- # 检查是否有已存在的文件将被覆盖
422
- existing_files = []
423
- for quant_type in selected_quants:
424
- filename = f"{_process_gguf_stem(self.input_file.stem)}-{quant_type}.gguf"
425
- expected_file = self.input_file.parent / filename
426
- if expected_file.exists():
427
- existing_files.append(filename)
428
-
429
- if existing_files:
430
- self.output_text.append("警告: 将覆盖以下已存在文件:")
431
- for existing_file in existing_files:
432
- self.output_text.append(f" - {existing_file}")
433
- self._scroll_to_bottom()
442
+ # 注意:由于selected_quants只包含启用的复选框,
443
+ # 所以不会包含已存在的禁用文件,无需额外检查覆盖
434
444
 
435
445
  self.convert_btn.setEnabled(False)
436
446
  self.progress_bar.setValue(0)
@@ -487,6 +497,7 @@ class GGUFQuantizerGUI(QMainWindow):
487
497
 
488
498
 
489
499
  def main() -> None:
500
+ """主程序入口."""
490
501
  app = QApplication(sys.argv)
491
502
 
492
503
  # 检查是否安装了llama.cpp
@@ -497,8 +508,8 @@ def main() -> None:
497
508
  check=False,
498
509
  )
499
510
  except FileNotFoundError:
500
- logger.exception("错误: 未找到llama.cpp/quantize工具")
501
- logger.exception(
511
+ logger.error("错误: 未找到llama.cpp/quantize工具")
512
+ logger.error(
502
513
  "请确保已编译llama.cpp并将quantize工具放在llama.cpp/目录下",
503
514
  )
504
515
  sys.exit(1)
sfi/llmserver/__init__.py CHANGED
@@ -1 +1 @@
1
-
1
+