Skip to content

Commit 996afd5

Browse files
feat(ai): pre-validate HF repo files and show required format hint
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <[email protected]>
1 parent 7ed8d52 commit 996afd5

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

lib/pages/setting/pages/ai_image_moderation.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ class _AiImageModerationPageState extends State<AiImageModerationPage> {
7676

7777
Pref.aiModelRepoUrl = url;
7878

79+
// Pre-validate repo files before opening the progress dialog.
80+
final validationError = await HfModelDownloader.validateRepoFiles(url);
81+
if (validationError != null) {
82+
SmartDialog.showToast(validationError);
83+
return;
84+
}
85+
7986
final progressNotifier = ValueNotifier<double>(0.0);
8087
final statusNotifier = ValueNotifier<String>('正在准备下载...');
8188

@@ -254,6 +261,13 @@ class _AiImageModerationPageState extends State<AiImageModerationPage> {
254261
'HuggingFace 仓库地址',
255262
style: theme.textTheme.titleMedium,
256263
),
264+
const SizedBox(height: 4),
265+
Text(
266+
'仓库需包含:tokenizer.json、vision_model.onnx/tflite、text_model.onnx/tflite',
267+
style: theme.textTheme.bodySmall?.copyWith(
268+
color: theme.colorScheme.outline,
269+
),
270+
),
257271
const SizedBox(height: 8),
258272
TextField(
259273
controller: _urlController,

lib/utils/hf_model_downloader.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,53 @@ class HfModelDownloader {
3636

3737
// ── Public entry-point ──────────────────────────────────────────────
3838

39+
/// Validates whether [repoUrl] contains the files required by this
40+
/// downloader. Returns `null` if valid, otherwise a Chinese error message
41+
/// describing what is missing.
42+
static Future<String?> validateRepoFiles(String repoUrl) async {
43+
final base = _parseBase(repoUrl);
44+
if (base == null) return '无效的 HuggingFace 地址';
45+
46+
final (host, ownerRepo) = base;
47+
final apiUrl = 'https://$host/api/models/$ownerRepo';
48+
49+
try {
50+
final response = await Dio().get(apiUrl);
51+
final data = response.data;
52+
if (data is! Map<String, dynamic>) return '无法解析仓库信息';
53+
54+
final siblings = data['siblings'] as List<dynamic>?;
55+
if (siblings == null) return '无法获取仓库文件列表';
56+
57+
final files = siblings
58+
.whereType<Map<String, dynamic>>()
59+
.map((e) => e['rfilename'] as String?)
60+
.whereType<String>()
61+
.toSet();
62+
63+
final hasTokenizer = _tokenizerPriority.any(files.contains) ||
64+
(files.contains('vocab.json') && files.contains('merges.txt'));
65+
final hasVision = _visionPriority.any(files.contains);
66+
final hasText = _textPriority.any(files.contains);
67+
68+
if (hasTokenizer && hasVision && hasText) return null;
69+
70+
final missing = <String>[];
71+
if (!hasTokenizer) missing.add('tokenizer.json(或 vocab.json + merges.txt)');
72+
if (!hasVision) missing.add('vision_model.onnx/tflite 或 image_encoder.onnx/tflite');
73+
if (!hasText) missing.add('text_model.onnx/tflite 或 text_encoder.onnx/tflite');
74+
75+
return '该仓库缺少必要文件:${missing.join('、')}';
76+
} on DioException catch (e) {
77+
if (e.response?.statusCode == 404) {
78+
return '仓库不存在或无法访问';
79+
}
80+
return '检查仓库文件失败:${e.message}';
81+
} catch (e) {
82+
return '检查仓库文件失败:$e';
83+
}
84+
}
85+
3986
/// Download all required files from a HuggingFace repo.
4087
///
4188
/// [repoUrl] can be any HuggingFace URL pattern:

0 commit comments

Comments
 (0)