JJ_MKVMaker/lib/ui/ai_subtitle_models.dart

94 lines
3.3 KiB
Dart

import 'package:flutter/material.dart';
import '../app/ai_local.dart' show AiStore;
import '../app/app_controller.dart';
import '../l10n/tr.dart';
import '../services/model_store.dart';
import 'confirm.dart';
import 'setting_tile.dart';
import 'theme.dart';
/// 260: 받은 AI 자막 모델 (Whisper · NLLB) - 차지한 크기 · [지우기]. 다시 쓰면 그때 다시 받는다
class AiSubtitleModels extends StatefulWidget {
final AppController c;
const AiSubtitleModels({super.key, required this.c});
@override
State<AiSubtitleModels> createState() => _AiSubtitleModelsState();
}
class _AiSubtitleModelsState extends State<AiSubtitleModels> {
/// 모델 id → 차지한 바이트 (0 이면 받지 않음)
Map<String, int>? _used;
/// 다 받은 모델 (나머지 중 크기가 있으면 받다 만 조각)
final _installed = <String>{};
String? _error;
ModelStore get _store => widget.c.services.models;
@override
void initState() {
super.initState();
_refresh();
}
Future<void> _refresh() async {
final used = {for (final m in [...whisperModels, nllbModel]) m.id: await _store.usedBytes(m)};
_installed
..clear()
..addAll([for (final m in [...whisperModels, nllbModel]) if (await _store.isInstalled(m)) m.id]);
if (mounted) setState(() => _used = used);
}
Future<void> _remove(ModelSpec m) async {
final ok = await confirmAction(context,
title: trf('{0} 을(를) 지울까요?', [m.label]), body: tr('다시 쓰려면 다시 받아야 합니다.'), ok: tr('지우기'));
if (!ok) return;
try {
await _store.remove(m);
_error = null;
} catch (e) {
_error = trf('지우지 못했습니다: {0}', [e]);
}
await _refresh();
}
@override
Widget build(BuildContext context) {
final used = _used;
if (used == null) return const SizedBox();
return ListenableBuilder(
listenable: widget.c,
builder: (context, _) {
final inUse = widget.c.aiModelsInUse;
return Column(mainAxisSize: MainAxisSize.min, children: [
for (final m in [...whisperModels, nllbModel])
SettingTile(
dense: true,
leading: Icon(used[m.id]! > 0 ? Icons.check_circle : Icons.cloud_download_outlined,
color: used[m.id]! > 0 ? JjColors.success : JjColors.textDim, size: 20),
title: Text(m.label),
subtitle: Text(used[m.id]! > 0
? trf(_installed.contains(m.id) ? '받음 · {0}' : '받다 만 조각 · {0}', [AiStore.sizeText(used[m.id]!)])
: trf('받지 않음 (처음 쓸 때 내려받기 {0})', [m.sizeLabel])),
trailing: used[m.id]! > 0
? inUse
? Tooltip(
message: tr('AI 자막 · 번역 작업이 끝난 뒤 지울 수 있습니다'),
child: TextButton(onPressed: null, child: Text(tr('지우기'))),
)
: TextButton(onPressed: () => _remove(m), child: Text(tr('지우기')))
: null,
),
if (_error != null)
Padding(
padding: const EdgeInsets.fromLTRB(16, 4, 16, 0),
child: Text(_error!, style: const TextStyle(fontSize: 12, color: JjColors.danger)),
),
]);
},
);
}
}