mirror of
https://github.com/StarCitizenToolBox/app.git
synced 2026-02-06 15:10:20 +00:00
feat: ORT Local Translate
This commit is contained in:
@@ -79,9 +79,10 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
|
||||
return;
|
||||
case "cancel_all":
|
||||
final userOK = await showConfirmDialogs(
|
||||
context,
|
||||
S.current.downloader_action_confirm_cancel_all_tasks,
|
||||
Text(S.current.downloader_info_manual_file_deletion_note));
|
||||
context,
|
||||
S.current.downloader_action_confirm_cancel_all_tasks,
|
||||
Text(S.current.downloader_info_manual_file_deletion_note),
|
||||
);
|
||||
if (userOK == true) {
|
||||
if (!aria2cState.isRunning) return;
|
||||
try {
|
||||
@@ -101,31 +102,19 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
|
||||
}
|
||||
|
||||
int getTasksLen() {
|
||||
return state.tasks.length +
|
||||
state.waitingTasks.length +
|
||||
state.stoppedTasks.length;
|
||||
return state.tasks.length + state.waitingTasks.length + state.stoppedTasks.length;
|
||||
}
|
||||
|
||||
(Aria2Task, String, bool) getTaskAndType(int index) {
|
||||
final tempList = <Aria2Task>[
|
||||
...state.tasks,
|
||||
...state.waitingTasks,
|
||||
...state.stoppedTasks
|
||||
];
|
||||
final tempList = <Aria2Task>[...state.tasks, ...state.waitingTasks, ...state.stoppedTasks];
|
||||
if (index >= 0 && index < state.tasks.length) {
|
||||
return (tempList[index], "active", index == 0);
|
||||
}
|
||||
if (index >= state.tasks.length &&
|
||||
index < state.tasks.length + state.waitingTasks.length) {
|
||||
if (index >= state.tasks.length && index < state.tasks.length + state.waitingTasks.length) {
|
||||
return (tempList[index], "waiting", index == state.tasks.length);
|
||||
}
|
||||
if (index >= state.tasks.length + state.waitingTasks.length &&
|
||||
index < tempList.length) {
|
||||
return (
|
||||
tempList[index],
|
||||
"stopped",
|
||||
index == state.tasks.length + state.waitingTasks.length
|
||||
);
|
||||
if (index >= state.tasks.length + state.waitingTasks.length && index < tempList.length) {
|
||||
return (tempList[index], "stopped", index == state.tasks.length + state.waitingTasks.length);
|
||||
}
|
||||
throw Exception("Index out of range or element is null");
|
||||
}
|
||||
@@ -148,8 +137,7 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
|
||||
|
||||
int getETA(Aria2Task task) {
|
||||
if (task.downloadSpeed == null || task.downloadSpeed == 0) return 0;
|
||||
final remainingBytes =
|
||||
(task.totalLength ?? 0) - (task.completedLength ?? 0);
|
||||
final remainingBytes = (task.totalLength ?? 0) - (task.completedLength ?? 0);
|
||||
return remainingBytes ~/ (task.downloadSpeed!);
|
||||
}
|
||||
|
||||
@@ -172,9 +160,10 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
|
||||
if (gid != null) {
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context,
|
||||
S.current.downloader_action_confirm_cancel_download,
|
||||
Text(S.current.downloader_info_manual_file_deletion_note));
|
||||
context,
|
||||
S.current.downloader_action_confirm_cancel_download,
|
||||
Text(S.current.downloader_info_manual_file_deletion_note),
|
||||
);
|
||||
if (ok == true) {
|
||||
final aria2c = ref.read(aria2cModelProvider).aria2c;
|
||||
await aria2c?.remove(gid);
|
||||
@@ -204,8 +193,8 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
|
||||
Future<void> _listenDownloader() async {
|
||||
try {
|
||||
while (true) {
|
||||
final aria2cState = ref.read(aria2cModelProvider);
|
||||
if (_disposed) return;
|
||||
final aria2cState = ref.read(aria2cModelProvider);
|
||||
if (aria2cState.isRunning) {
|
||||
final aria2c = aria2cState.aria2c!;
|
||||
final tasks = await aria2c.tellActive();
|
||||
@@ -219,12 +208,7 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
|
||||
globalStat: globalStat,
|
||||
);
|
||||
} else {
|
||||
state = state.copyWith(
|
||||
tasks: [],
|
||||
waitingTasks: [],
|
||||
stoppedTasks: [],
|
||||
globalStat: null,
|
||||
);
|
||||
state = state.copyWith(tasks: [], waitingTasks: [], stoppedTasks: [], globalStat: null);
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
@@ -236,72 +220,64 @@ class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
|
||||
Future<void> _showDownloadSpeedSettings(BuildContext context) async {
|
||||
final box = await Hive.openBox("app_conf");
|
||||
|
||||
final upCtrl = TextEditingController(
|
||||
text: box.get("downloader_up_limit", defaultValue: ""));
|
||||
final downCtrl = TextEditingController(
|
||||
text: box.get("downloader_down_limit", defaultValue: ""));
|
||||
final upCtrl = TextEditingController(text: box.get("downloader_up_limit", defaultValue: ""));
|
||||
final downCtrl = TextEditingController(text: box.get("downloader_down_limit", defaultValue: ""));
|
||||
|
||||
final ifr = FilteringTextInputFormatter.allow(RegExp(r'^\d*[km]?$'));
|
||||
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context,
|
||||
S.current.downloader_speed_limit_settings,
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.current.downloader_info_p2p_network_note,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white.withValues(alpha: .6),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(S.current.downloader_info_download_unit_input_prompt),
|
||||
const SizedBox(height: 12),
|
||||
Text(S.current.downloader_input_upload_speed_limit),
|
||||
const SizedBox(height: 6),
|
||||
TextFormBox(
|
||||
placeholder: "1、100k、10m、0",
|
||||
controller: upCtrl,
|
||||
placeholderStyle:
|
||||
TextStyle(color: Colors.white.withValues(alpha: .6)),
|
||||
inputFormatters: [ifr],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(S.current.downloader_input_download_speed_limit),
|
||||
const SizedBox(height: 6),
|
||||
TextFormBox(
|
||||
placeholder: "1、100k、10m、0",
|
||||
controller: downCtrl,
|
||||
placeholderStyle:
|
||||
TextStyle(color: Colors.white.withValues(alpha: .6)),
|
||||
inputFormatters: [ifr],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
S.current.downloader_input_info_p2p_upload_note,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white.withValues(alpha: .6),
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
context,
|
||||
S.current.downloader_speed_limit_settings,
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.current.downloader_info_p2p_network_note,
|
||||
style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(S.current.downloader_info_download_unit_input_prompt),
|
||||
const SizedBox(height: 12),
|
||||
Text(S.current.downloader_input_upload_speed_limit),
|
||||
const SizedBox(height: 6),
|
||||
TextFormBox(
|
||||
placeholder: "1、100k、10m、0",
|
||||
controller: upCtrl,
|
||||
placeholderStyle: TextStyle(color: Colors.white.withValues(alpha: .6)),
|
||||
inputFormatters: [ifr],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(S.current.downloader_input_download_speed_limit),
|
||||
const SizedBox(height: 6),
|
||||
TextFormBox(
|
||||
placeholder: "1、100k、10m、0",
|
||||
controller: downCtrl,
|
||||
placeholderStyle: TextStyle(color: Colors.white.withValues(alpha: .6)),
|
||||
inputFormatters: [ifr],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
S.current.downloader_input_info_p2p_upload_note,
|
||||
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (ok == true) {
|
||||
final aria2cState = ref.read(aria2cModelProvider);
|
||||
final aria2cModel = ref.read(aria2cModelProvider.notifier);
|
||||
await aria2cModel
|
||||
.launchDaemon(appGlobalState.applicationBinaryModuleDir!);
|
||||
await aria2cModel.launchDaemon(appGlobalState.applicationBinaryModuleDir!);
|
||||
final aria2c = aria2cState.aria2c!;
|
||||
final upByte = aria2cModel.textToByte(upCtrl.text.trim());
|
||||
final downByte = aria2cModel.textToByte(downCtrl.text.trim());
|
||||
final r = await aria2c
|
||||
.changeGlobalOption(Aria2Option()
|
||||
..maxOverallUploadLimit = upByte
|
||||
..maxOverallDownloadLimit = downByte)
|
||||
.changeGlobalOption(
|
||||
Aria2Option()
|
||||
..maxOverallUploadLimit = upByte
|
||||
..maxOverallDownloadLimit = downByte,
|
||||
)
|
||||
.unwrap();
|
||||
if (r != null) {
|
||||
await box.put('downloader_up_limit', upCtrl.text.trim());
|
||||
|
||||
@@ -42,7 +42,7 @@ final class HomeDownloaderUIModelProvider
|
||||
}
|
||||
|
||||
String _$homeDownloaderUIModelHash() =>
|
||||
r'5b410cd38315d94279b18f147903eca4b09bd445';
|
||||
r'cb5d0973d56bbf40673afc2a734b49f5d034ab98';
|
||||
|
||||
abstract class _$HomeDownloaderUIModel
|
||||
extends $Notifier<HomeDownloaderUIState> {
|
||||
|
||||
@@ -41,7 +41,7 @@ final class HomeUIModelProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$homeUIModelHash() => r'9dc8191f358c2d8e21ed931b3755e08ce394558e';
|
||||
String _$homeUIModelHash() => r'7dfe73383f7be2e520a42d176e199a8db208f008';
|
||||
|
||||
abstract class _$HomeUIModel extends $Notifier<HomeUIModelState> {
|
||||
HomeUIModelState build();
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/input_method/input_method_dialog_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/input_method/server.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
@@ -60,7 +61,9 @@ class InputMethodDialogUI extends HookConsumerWidget {
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
TextFormBox(
|
||||
placeholder: S.current.input_method_input_placeholder,
|
||||
placeholder: state.isEnableAutoTranslate
|
||||
? "${S.current.input_method_input_placeholder}\n\n本地翻译模型对中英混合处理能力较差,如有需要,建议分开发送。"
|
||||
: S.current.input_method_input_placeholder,
|
||||
controller: srcTextCtrl,
|
||||
maxLines: 5,
|
||||
placeholderStyle: TextStyle(color: Colors.white.withValues(alpha: .6)),
|
||||
@@ -68,7 +71,9 @@ class InputMethodDialogUI extends HookConsumerWidget {
|
||||
onChanged: (str) async {
|
||||
final text = model.onTextChange("src", str);
|
||||
destTextCtrl.text = text ?? "";
|
||||
if (text != null) {}
|
||||
if (text != null) {
|
||||
model.checkAutoTranslate();
|
||||
}
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
@@ -91,17 +96,23 @@ class InputMethodDialogUI extends HookConsumerWidget {
|
||||
placeholderStyle: TextStyle(color: Colors.white.withValues(alpha: .6)),
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
enabled: true,
|
||||
onChanged: (str) {
|
||||
// final text = model.onTextChange("dest", str);
|
||||
// if (text != null) {
|
||||
// srcTextCtrl.text = text;
|
||||
// }
|
||||
},
|
||||
onChanged: (str) {},
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(S.current.input_method_auto_translate),
|
||||
SizedBox(width: 6),
|
||||
ToggleSwitch(
|
||||
checked: state.isEnableAutoTranslate,
|
||||
onChanged: (b) => _onSwitchAutoTranslate(context, model, b),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(width: 24),
|
||||
Row(
|
||||
children: [
|
||||
Text(S.current.input_method_remote_input_service),
|
||||
@@ -194,4 +205,52 @@ class InputMethodDialogUI extends HookConsumerWidget {
|
||||
await serverModel.stopServer().unwrap(context: context);
|
||||
}
|
||||
}
|
||||
|
||||
void _onSwitchAutoTranslate(BuildContext context, InputMethodDialogUIModel model, bool b) async {
|
||||
if (b) {
|
||||
// 检查下载任务
|
||||
if (await model.isTranslateModelDownloading()) {
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "模型正在下载中,请稍后...");
|
||||
return;
|
||||
}
|
||||
// 打开,检查本地模型
|
||||
if (!await model.checkLocalTranslateModelAvailable()) {
|
||||
if (!context.mounted) return;
|
||||
// 询问用户是否下载模型
|
||||
final userOK = await showConfirmDialogs(
|
||||
context,
|
||||
"是否下载 AI 模型以使用翻译功能?",
|
||||
Text(
|
||||
"大约需要 200MB 的本地空间。"
|
||||
"\n\n我们使用本地模型进行翻译,您的翻译数据不会发送给任何第三方。"
|
||||
"\n\n模型未对游戏术语优化,请自行判断使用。",
|
||||
),
|
||||
);
|
||||
if (userOK) {
|
||||
try {
|
||||
final guid = await model.doDownloadTranslateModel();
|
||||
if (guid.isNotEmpty) {
|
||||
if (!context.mounted) return;
|
||||
context.go("/index/downloader");
|
||||
await Future.delayed(Duration(seconds: 1)).then((_) {
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "下载已开始,请在模型下载完成后重新启用翻译功能。");
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
dPrint("下载模型失败:$e");
|
||||
if (context.mounted) {
|
||||
showToast(context, "下载模型失败:$e");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
model.toggleAutoTranslate(b, context: context).unwrap(context: context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
// ignore_for_file: avoid_build_context_in_providers, use_build_context_synchronously
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:starcitizen_doctor/api/api.dart';
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/async.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/provider.dart';
|
||||
import 'package:starcitizen_doctor/provider/aria2c.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/localization/localization_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/rust/api/ort_api.dart' as ort;
|
||||
|
||||
part 'input_method_dialog_ui_model.g.dart';
|
||||
|
||||
@@ -44,7 +54,8 @@ class InputMethodDialogUIModel extends _$InputMethodDialogUIModel {
|
||||
final worldMaps = keyMaps?.map((key, value) => MapEntry(value.trim(), key));
|
||||
final appBox = await Hive.openBox("app_conf");
|
||||
final enableAutoCopy = appBox.get("enableAutoCopy", defaultValue: false);
|
||||
final isEnableAutoTranslate = appBox.get("isEnableAutoTranslate", defaultValue: false);
|
||||
final isEnableAutoTranslate = appBox.get("isEnableAutoTranslate_v2", defaultValue: false);
|
||||
_checkAutoTranslateOnInit();
|
||||
state = state.copyWith(
|
||||
keyMaps: keyMaps,
|
||||
worldMaps: worldMaps,
|
||||
@@ -134,14 +145,216 @@ class InputMethodDialogUIModel extends _$InputMethodDialogUIModel {
|
||||
_srcTextCtrl?.text = text;
|
||||
_destTextCtrl?.text = onTextChange("src", text) ?? "";
|
||||
if (_destTextCtrl?.text.isEmpty ?? true) return;
|
||||
checkAutoTranslate(webMessage: true);
|
||||
if (autoCopy && !state.isAutoTranslateWorking) {
|
||||
Clipboard.setData(ClipboardData(text: _destTextCtrl?.text ?? ""));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleAutoTranslate(bool b) async {
|
||||
// ignore: duplicate_ignore
|
||||
// ignore: avoid_build_context_in_providers
|
||||
Future<void> toggleAutoTranslate(bool b, {BuildContext? context}) async {
|
||||
state = state.copyWith(isEnableAutoTranslate: b);
|
||||
final appConf = await Hive.openBox("app_conf");
|
||||
await appConf.put("isEnableAutoTranslate", b);
|
||||
await appConf.put("isEnableAutoTranslate_v2", b);
|
||||
if (b) {
|
||||
mountOnnxTranslationProvider(_localTranslateModelDir, _localTranslateModelName, context: context);
|
||||
}
|
||||
}
|
||||
|
||||
Timer? _translateTimer;
|
||||
|
||||
Future<void> checkAutoTranslate({bool webMessage = false}) async {
|
||||
final sourceText = _srcTextCtrl?.text ?? "";
|
||||
final content = _destTextCtrl?.text ?? "";
|
||||
if (sourceText.trim().isEmpty) return;
|
||||
if (state.isEnableAutoTranslate) {
|
||||
if (_translateTimer != null) _translateTimer?.cancel();
|
||||
state = state.copyWith(isAutoTranslateWorking: true);
|
||||
_translateTimer = Timer(Duration(milliseconds: webMessage ? 1 : 400), () async {
|
||||
try {
|
||||
final inputText = sourceText.replaceAll("\n", " ");
|
||||
final r = await doTranslateText(inputText);
|
||||
if (r != null) {
|
||||
String resultText = r;
|
||||
// resultText 首字母大写
|
||||
if (content.isNotEmpty) {
|
||||
final firstChar = resultText.characters.first;
|
||||
resultText = resultText.replaceFirst(firstChar, firstChar.toUpperCase());
|
||||
}
|
||||
_destTextCtrl?.text = "$content \n[en] $resultText";
|
||||
if (state.enableAutoCopy || webMessage) {
|
||||
Clipboard.setData(ClipboardData(text: _destTextCtrl?.text ?? ""));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
dPrint("[InputMethodDialogUIModel] AutoTranslate error: $e");
|
||||
}
|
||||
state = state.copyWith(isAutoTranslateWorking: false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
String get _localTranslateModelName => "opus-mt-zh-en_onnx";
|
||||
|
||||
String get _localTranslateModelDir => "${appGlobalState.applicationSupportDir}/onnx_models";
|
||||
|
||||
OnnxTranslationProvider get _localTranslateModelProvider =>
|
||||
onnxTranslationProvider(_localTranslateModelDir, _localTranslateModelName);
|
||||
|
||||
void _checkAutoTranslateOnInit() {
|
||||
// 检查模型文件是否存在,不存在则关闭自动翻译
|
||||
if (state.isEnableAutoTranslate) {
|
||||
checkLocalTranslateModelAvailable().then((available) {
|
||||
if (!available) {
|
||||
toggleAutoTranslate(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> checkLocalTranslateModelAvailable() async {
|
||||
final fileCheckList = const [
|
||||
"config.json",
|
||||
"tokenizer.json",
|
||||
"vocab.json",
|
||||
"onnx/decoder_model_q4f16.onnx",
|
||||
"onnx/encoder_model_q4f16.onnx",
|
||||
];
|
||||
var allExist = true;
|
||||
for (var fileName in fileCheckList) {
|
||||
final filePath = "$_localTranslateModelDir/$_localTranslateModelName/$fileName";
|
||||
if (!await File(filePath).exists()) {
|
||||
allExist = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return allExist;
|
||||
}
|
||||
|
||||
Future<String> doDownloadTranslateModel() async {
|
||||
state = state.copyWith(isAutoTranslateWorking: true);
|
||||
try {
|
||||
final aria2cManager = ref.read(aria2cModelProvider.notifier);
|
||||
await aria2cManager.launchDaemon(appGlobalState.applicationBinaryModuleDir!);
|
||||
final aria2c = ref.read(aria2cModelProvider).aria2c!;
|
||||
|
||||
if (await aria2cManager.isNameInTask(_localTranslateModelName)) {
|
||||
throw Exception("Model is already downloading");
|
||||
}
|
||||
|
||||
final l = await Api.getAppTorrentDataList();
|
||||
final modelTorrent = l.firstWhere(
|
||||
(element) => element.name == _localTranslateModelName,
|
||||
orElse: () => throw Exception("Model torrent not found"),
|
||||
);
|
||||
final torrentUrl = modelTorrent.url;
|
||||
if (torrentUrl?.isEmpty ?? true) {
|
||||
throw Exception("Get model torrent url failed");
|
||||
}
|
||||
// get torrent Data
|
||||
final data = await RSHttp.get(torrentUrl!);
|
||||
final b64Str = base64Encode(data.data!);
|
||||
final gid = await aria2c.addTorrent(b64Str, extraParams: {"dir": _localTranslateModelDir});
|
||||
return gid;
|
||||
} catch (e) {
|
||||
dPrint("[InputMethodDialogUIModel] doDownloadTranslateModel error: $e");
|
||||
rethrow;
|
||||
} finally {
|
||||
state = state.copyWith(isAutoTranslateWorking: false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> mountOnnxTranslationProvider(
|
||||
String localTranslateModelDir,
|
||||
String localTranslateModelName, {
|
||||
BuildContext? context,
|
||||
}) async {
|
||||
if (!ref.exists(_localTranslateModelProvider)) {
|
||||
ref.listen(_localTranslateModelProvider, ((_, _) {}));
|
||||
final err = await ref.read(_localTranslateModelProvider.notifier).initModel();
|
||||
_handleTranslateModel(context, err);
|
||||
} else {
|
||||
// 重新加载
|
||||
final err = await ref.read(_localTranslateModelProvider.notifier).initModel();
|
||||
_handleTranslateModel(context, err);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleTranslateModel(BuildContext? context, String? err) async {
|
||||
if (err != null) {
|
||||
dPrint("[InputMethodDialogUIModel] mountOnnxTranslationProvider failed to init model");
|
||||
if (context != null) {
|
||||
if (!context.mounted) return;
|
||||
final userOK = await showConfirmDialogs(context, "翻译模型加载失败", Text("是否删除本地文件,稍后您可以尝试重新下载。错误信息:\n$err"));
|
||||
if (userOK) {
|
||||
// 删除文件,并禁用开关
|
||||
final dir = Directory("$_localTranslateModelDir/$_localTranslateModelName");
|
||||
if (await dir.exists()) {
|
||||
await dir.delete(recursive: true);
|
||||
dPrint("[InputMethodDialogUIModel] Deleted local translate model files.");
|
||||
toggleAutoTranslate(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 禁用开关
|
||||
toggleAutoTranslate(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> doTranslateText(String text) async {
|
||||
if (!ref.exists(_localTranslateModelProvider)) {
|
||||
await mountOnnxTranslationProvider(_localTranslateModelDir, _localTranslateModelName);
|
||||
}
|
||||
final onnxTranslationState = ref.read(_localTranslateModelProvider);
|
||||
if (!onnxTranslationState) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final result = await ort.translateText(modelKey: _localTranslateModelName, text: text);
|
||||
return result;
|
||||
} catch (e) {
|
||||
dPrint("[InputMethodDialogUIModel] doTranslateText error: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> isTranslateModelDownloading() async {
|
||||
final aria2cManager = ref.read(aria2cModelProvider.notifier);
|
||||
return await aria2cManager.isNameInTask(_localTranslateModelName);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class OnnxTranslation extends _$OnnxTranslation {
|
||||
@override
|
||||
bool build(String modelDir, String modelName) {
|
||||
dPrint("[OnnxTranslation] Build provider for model: $modelName");
|
||||
ref.onDispose(disposeModel);
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<String?> initModel() async {
|
||||
dPrint("[OnnxTranslation] Load model: $modelName from $modelDir");
|
||||
String? errorMessage;
|
||||
try {
|
||||
await ort.loadTranslationModel(
|
||||
modelPath: "$modelDir/$modelName",
|
||||
modelKey: modelName,
|
||||
quantizationSuffix: "_q4f16",
|
||||
);
|
||||
state = true;
|
||||
} catch (e) {
|
||||
dPrint("[OnnxTranslation] Load model error: $e");
|
||||
errorMessage = e.toString();
|
||||
state = false;
|
||||
}
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
Future<void> disposeModel() async {
|
||||
await ort.unloadTranslationModel(modelKey: modelName).unwrap();
|
||||
dPrint("[OnnxTranslation] Unload model: $modelName");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ final class InputMethodDialogUIModelProvider
|
||||
}
|
||||
|
||||
String _$inputMethodDialogUIModelHash() =>
|
||||
r'c07ef2474866bdb3944892460879121e0f90591f';
|
||||
r'39b7fc1446c09514b837c0f181488d34a4391751';
|
||||
|
||||
abstract class _$InputMethodDialogUIModel
|
||||
extends $Notifier<InputMethodDialogUIState> {
|
||||
@@ -65,3 +65,102 @@ abstract class _$InputMethodDialogUIModel
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(OnnxTranslation)
|
||||
const onnxTranslationProvider = OnnxTranslationFamily._();
|
||||
|
||||
final class OnnxTranslationProvider
|
||||
extends $NotifierProvider<OnnxTranslation, bool> {
|
||||
const OnnxTranslationProvider._({
|
||||
required OnnxTranslationFamily super.from,
|
||||
required (String, String) super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'onnxTranslationProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$onnxTranslationHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'onnxTranslationProvider'
|
||||
''
|
||||
'$argument';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
OnnxTranslation create() => OnnxTranslation();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(bool value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<bool>(value),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is OnnxTranslationProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$onnxTranslationHash() => r'05b7b063a1013eed1ee4daae5212b3b6c555cd82';
|
||||
|
||||
final class OnnxTranslationFamily extends $Family
|
||||
with
|
||||
$ClassFamilyOverride<
|
||||
OnnxTranslation,
|
||||
bool,
|
||||
bool,
|
||||
bool,
|
||||
(String, String)
|
||||
> {
|
||||
const OnnxTranslationFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'onnxTranslationProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
OnnxTranslationProvider call(String modelDir, String modelName) =>
|
||||
OnnxTranslationProvider._(argument: (modelDir, modelName), from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'onnxTranslationProvider';
|
||||
}
|
||||
|
||||
abstract class _$OnnxTranslation extends $Notifier<bool> {
|
||||
late final _$args = ref.$arg as (String, String);
|
||||
String get modelDir => _$args.$1;
|
||||
String get modelName => _$args.$2;
|
||||
|
||||
bool build(String modelDir, String modelName);
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build(_$args.$1, _$args.$2);
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<bool, bool>,
|
||||
bool,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ final class AdvancedLocalizationUIModelProvider
|
||||
}
|
||||
|
||||
String _$advancedLocalizationUIModelHash() =>
|
||||
r'2f890c854bc56e506c441acabc2014438a163617';
|
||||
r'c7cca8935ac7df2281e83297b11b6b82d94f7a59';
|
||||
|
||||
abstract class _$AdvancedLocalizationUIModel
|
||||
extends $Notifier<AdvancedLocalizationUIState> {
|
||||
|
||||
@@ -66,6 +66,10 @@ class LocalizationUIModel extends _$LocalizationUIModel {
|
||||
@override
|
||||
LocalizationUIState build() {
|
||||
state = LocalizationUIState(selectedLanguage: languageSupport.keys.first);
|
||||
ref.onDispose(() {
|
||||
_customizeDirListenSub?.cancel();
|
||||
_customizeDirListenSub = null;
|
||||
});
|
||||
_init();
|
||||
return state;
|
||||
}
|
||||
@@ -74,10 +78,6 @@ class LocalizationUIModel extends _$LocalizationUIModel {
|
||||
if (_scInstallPath == "not_install") {
|
||||
return;
|
||||
}
|
||||
ref.onDispose(() {
|
||||
_customizeDirListenSub?.cancel();
|
||||
_customizeDirListenSub = null;
|
||||
});
|
||||
final appConfBox = await Hive.openBox("app_conf");
|
||||
final lang = await appConfBox.get("localization_selectedLanguage", defaultValue: languageSupport.keys.first);
|
||||
state = state.copyWith(selectedLanguage: lang);
|
||||
|
||||
@@ -42,7 +42,7 @@ final class LocalizationUIModelProvider
|
||||
}
|
||||
|
||||
String _$localizationUIModelHash() =>
|
||||
r'd3797a7ff3d31dd1d4b05aed4a9969f4be6853c5';
|
||||
r'3d3f0ed7fa3631eca4e10d456c437f6fca8eedff';
|
||||
|
||||
abstract class _$LocalizationUIModel extends $Notifier<LocalizationUIState> {
|
||||
LocalizationUIState build();
|
||||
|
||||
@@ -42,7 +42,7 @@ final class HomePerformanceUIModelProvider
|
||||
}
|
||||
|
||||
String _$homePerformanceUIModelHash() =>
|
||||
r'c3c55c0470ef8c8be4915a1878deba332653ecde';
|
||||
r'4c5c33fe7d85dc8f6bf0d019c1b870d285d594ff';
|
||||
|
||||
abstract class _$HomePerformanceUIModel
|
||||
extends $Notifier<HomePerformanceUIState> {
|
||||
|
||||
Reference in New Issue
Block a user