Merge pull request #145 from StarCitizenToolBox/feat/ort_translate

Feat/ort translate
This commit is contained in:
xkeyC 2025-11-15 22:28:13 +08:00 committed by GitHub
commit af15d106f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 2621 additions and 472 deletions

View File

@ -49,6 +49,7 @@ abstract class AppGlobalState with _$AppGlobalState {
@Default(ThemeConf()) ThemeConf themeConf,
Locale? appLocale,
Box? appConfBox,
@Default(10) windowsVersion,
}) = _AppGlobalState;
}
@ -56,17 +57,15 @@ abstract class AppGlobalState with _$AppGlobalState {
GoRouter router(Ref ref) {
return GoRouter(
routes: [
GoRoute(
path: '/',
pageBuilder: (context, state) => myPageBuilder(context, state, const SplashUI()),
),
GoRoute(path: '/', pageBuilder: (context, state) => myPageBuilder(context, state, const SplashUI())),
GoRoute(
path: '/index',
pageBuilder: (context, state) => myPageBuilder(context, state, const IndexUI()),
routes: [
GoRoute(
path: "downloader",
pageBuilder: (context, state) => myPageBuilder(context, state, const HomeDownloaderUI())),
path: "downloader",
pageBuilder: (context, state) => myPageBuilder(context, state, const HomeDownloaderUI()),
),
GoRoute(
path: 'game_doctor',
pageBuilder: (context, state) => myPageBuilder(context, state, const HomeGameDoctorUI()),
@ -76,17 +75,19 @@ GoRouter router(Ref ref) {
pageBuilder: (context, state) => myPageBuilder(context, state, const HomePerformanceUI()),
),
GoRoute(
path: 'advanced_localization',
pageBuilder: (context, state) => myPageBuilder(context, state, const AdvancedLocalizationUI()))
path: 'advanced_localization',
pageBuilder: (context, state) => myPageBuilder(context, state, const AdvancedLocalizationUI()),
),
],
),
GoRoute(path: '/tools', builder: (_, _) => const SizedBox(), routes: [
GoRoute(
path: 'unp4kc',
pageBuilder: (context, state) => myPageBuilder(context, state, const UnP4kcUI()),
),
]),
GoRoute(path: '/guide', pageBuilder: (context, state) => myPageBuilder(context, state, const GuideUI()))
GoRoute(
path: '/tools',
builder: (_, _) => const SizedBox(),
routes: [
GoRoute(path: 'unp4kc', pageBuilder: (context, state) => myPageBuilder(context, state, const UnP4kcUI())),
],
),
GoRoute(path: '/guide', pageBuilder: (context, state) => myPageBuilder(context, state, const GuideUI())),
],
);
}
@ -94,13 +95,13 @@ GoRouter router(Ref ref) {
@riverpod
class AppGlobalModel extends _$AppGlobalModel {
static Map<Locale, String> get appLocaleSupport => {
const Locale("auto"): S.current.settings_app_language_auto,
const Locale("zh", "CN"): NoL10n.langZHS,
const Locale("zh", "TW"): NoL10n.langZHT,
const Locale("en"): NoL10n.langEn,
const Locale("ja"): NoL10n.langJa,
const Locale("ru"): NoL10n.langRU,
};
const Locale("auto"): S.current.settings_app_language_auto,
const Locale("zh", "CN"): NoL10n.langZHS,
const Locale("zh", "TW"): NoL10n.langZHT,
const Locale("en"): NoL10n.langEn,
const Locale("ja"): NoL10n.langJa,
const Locale("ru"): NoL10n.langRU,
};
@override
AppGlobalState build() {
@ -174,9 +175,9 @@ class AppGlobalModel extends _$AppGlobalModel {
await Window.initialize();
await Window.hideWindowControls();
if (windowsDeviceInfo?.productName.contains("Windows 11") ?? false) {
await Window.setEffect(
effect: WindowEffect.acrylic,
);
await Window.setEffect(effect: WindowEffect.acrylic);
state = state.copyWith(windowsVersion: 11);
dPrint("---- Windows 11 Acrylic Effect init -----");
}
}
});
@ -226,18 +227,24 @@ class AppGlobalModel extends _$AppGlobalModel {
if (state.networkVersionData == null) {
if (!context.mounted) return false;
await showToast(
context, S.current.app_common_network_error(ConstConf.appVersionDate, checkUpdateError.toString()));
context,
S.current.app_common_network_error(ConstConf.appVersionDate, checkUpdateError.toString()),
);
return false;
}
if (!Platform.isWindows) return false;
final lastVersion =
ConstConf.isMSE ? state.networkVersionData?.mSELastVersionCode : state.networkVersionData?.lastVersionCode;
final lastVersion = ConstConf.isMSE
? state.networkVersionData?.mSELastVersionCode
: state.networkVersionData?.lastVersionCode;
if ((lastVersion ?? 0) > ConstConf.appVersionCode) {
// need update
if (!context.mounted) return false;
final r =
await showDialog(dismissWithEsc: false, context: context, builder: (context) => const UpgradeDialogUI());
final r = await showDialog(
dismissWithEsc: false,
context: context,
builder: (context) => const UpgradeDialogUI(),
);
if (r != true) {
if (!context.mounted) return false;
@ -264,8 +271,10 @@ class AppGlobalModel extends _$AppGlobalModel {
dPrint("now == $now start == $startTime end == $endTime");
if (now < startTime) {
_activityThemeColorTimer =
Timer(Duration(milliseconds: startTime - now), () => checkActivityThemeColor(networkVersionData));
_activityThemeColorTimer = Timer(
Duration(milliseconds: startTime - now),
() => checkActivityThemeColor(networkVersionData),
);
dPrint("start Timer ....");
} else if (now >= startTime && now <= endTime) {
dPrint("update Color ....");
@ -280,8 +289,10 @@ class AppGlobalModel extends _$AppGlobalModel {
);
// wait for end
_activityThemeColorTimer =
Timer(Duration(milliseconds: endTime - now), () => checkActivityThemeColor(networkVersionData));
_activityThemeColorTimer = Timer(
Duration(milliseconds: endTime - now),
() => checkActivityThemeColor(networkVersionData),
);
} else {
dPrint("reset Color ....");
state = state.copyWith(
@ -302,8 +313,9 @@ class AppGlobalModel extends _$AppGlobalModel {
await appConfBox.put("app_locale", null);
return;
}
final localeCode =
value.countryCode != null ? "${value.languageCode}_${value.countryCode ?? ""}" : value.languageCode;
final localeCode = value.countryCode != null
? "${value.languageCode}_${value.countryCode ?? ""}"
: value.languageCode;
dPrint("changeLocale == $value localeCode=== $localeCode");
await appConfBox.put("app_locale", localeCode);
state = state.copyWith(appLocale: value);

View File

@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AppGlobalState {
String? get deviceUUID; String? get applicationSupportDir; String? get applicationBinaryModuleDir; AppVersionData? get networkVersionData; ThemeConf get themeConf; Locale? get appLocale; Box? get appConfBox;
String? get deviceUUID; String? get applicationSupportDir; String? get applicationBinaryModuleDir; AppVersionData? get networkVersionData; ThemeConf get themeConf; Locale? get appLocale; Box? get appConfBox; dynamic get windowsVersion;
/// Create a copy of AppGlobalState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -25,16 +25,16 @@ $AppGlobalStateCopyWith<AppGlobalState> get copyWith => _$AppGlobalStateCopyWith
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppGlobalState&&(identical(other.deviceUUID, deviceUUID) || other.deviceUUID == deviceUUID)&&(identical(other.applicationSupportDir, applicationSupportDir) || other.applicationSupportDir == applicationSupportDir)&&(identical(other.applicationBinaryModuleDir, applicationBinaryModuleDir) || other.applicationBinaryModuleDir == applicationBinaryModuleDir)&&(identical(other.networkVersionData, networkVersionData) || other.networkVersionData == networkVersionData)&&(identical(other.themeConf, themeConf) || other.themeConf == themeConf)&&(identical(other.appLocale, appLocale) || other.appLocale == appLocale)&&(identical(other.appConfBox, appConfBox) || other.appConfBox == appConfBox));
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppGlobalState&&(identical(other.deviceUUID, deviceUUID) || other.deviceUUID == deviceUUID)&&(identical(other.applicationSupportDir, applicationSupportDir) || other.applicationSupportDir == applicationSupportDir)&&(identical(other.applicationBinaryModuleDir, applicationBinaryModuleDir) || other.applicationBinaryModuleDir == applicationBinaryModuleDir)&&(identical(other.networkVersionData, networkVersionData) || other.networkVersionData == networkVersionData)&&(identical(other.themeConf, themeConf) || other.themeConf == themeConf)&&(identical(other.appLocale, appLocale) || other.appLocale == appLocale)&&(identical(other.appConfBox, appConfBox) || other.appConfBox == appConfBox)&&const DeepCollectionEquality().equals(other.windowsVersion, windowsVersion));
}
@override
int get hashCode => Object.hash(runtimeType,deviceUUID,applicationSupportDir,applicationBinaryModuleDir,networkVersionData,themeConf,appLocale,appConfBox);
int get hashCode => Object.hash(runtimeType,deviceUUID,applicationSupportDir,applicationBinaryModuleDir,networkVersionData,themeConf,appLocale,appConfBox,const DeepCollectionEquality().hash(windowsVersion));
@override
String toString() {
return 'AppGlobalState(deviceUUID: $deviceUUID, applicationSupportDir: $applicationSupportDir, applicationBinaryModuleDir: $applicationBinaryModuleDir, networkVersionData: $networkVersionData, themeConf: $themeConf, appLocale: $appLocale, appConfBox: $appConfBox)';
return 'AppGlobalState(deviceUUID: $deviceUUID, applicationSupportDir: $applicationSupportDir, applicationBinaryModuleDir: $applicationBinaryModuleDir, networkVersionData: $networkVersionData, themeConf: $themeConf, appLocale: $appLocale, appConfBox: $appConfBox, windowsVersion: $windowsVersion)';
}
@ -45,7 +45,7 @@ abstract mixin class $AppGlobalStateCopyWith<$Res> {
factory $AppGlobalStateCopyWith(AppGlobalState value, $Res Function(AppGlobalState) _then) = _$AppGlobalStateCopyWithImpl;
@useResult
$Res call({
String? deviceUUID, String? applicationSupportDir, String? applicationBinaryModuleDir, AppVersionData? networkVersionData, ThemeConf themeConf, Locale? appLocale, Box? appConfBox
String? deviceUUID, String? applicationSupportDir, String? applicationBinaryModuleDir, AppVersionData? networkVersionData, ThemeConf themeConf, Locale? appLocale, Box? appConfBox, dynamic windowsVersion
});
@ -62,7 +62,7 @@ class _$AppGlobalStateCopyWithImpl<$Res>
/// Create a copy of AppGlobalState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? deviceUUID = freezed,Object? applicationSupportDir = freezed,Object? applicationBinaryModuleDir = freezed,Object? networkVersionData = freezed,Object? themeConf = null,Object? appLocale = freezed,Object? appConfBox = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? deviceUUID = freezed,Object? applicationSupportDir = freezed,Object? applicationBinaryModuleDir = freezed,Object? networkVersionData = freezed,Object? themeConf = null,Object? appLocale = freezed,Object? appConfBox = freezed,Object? windowsVersion = freezed,}) {
return _then(_self.copyWith(
deviceUUID: freezed == deviceUUID ? _self.deviceUUID : deviceUUID // ignore: cast_nullable_to_non_nullable
as String?,applicationSupportDir: freezed == applicationSupportDir ? _self.applicationSupportDir : applicationSupportDir // ignore: cast_nullable_to_non_nullable
@ -71,7 +71,8 @@ as String?,networkVersionData: freezed == networkVersionData ? _self.networkVers
as AppVersionData?,themeConf: null == themeConf ? _self.themeConf : themeConf // ignore: cast_nullable_to_non_nullable
as ThemeConf,appLocale: freezed == appLocale ? _self.appLocale : appLocale // ignore: cast_nullable_to_non_nullable
as Locale?,appConfBox: freezed == appConfBox ? _self.appConfBox : appConfBox // ignore: cast_nullable_to_non_nullable
as Box?,
as Box?,windowsVersion: freezed == windowsVersion ? _self.windowsVersion : windowsVersion // ignore: cast_nullable_to_non_nullable
as dynamic,
));
}
/// Create a copy of AppGlobalState
@ -165,10 +166,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? deviceUUID, String? applicationSupportDir, String? applicationBinaryModuleDir, AppVersionData? networkVersionData, ThemeConf themeConf, Locale? appLocale, Box? appConfBox)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? deviceUUID, String? applicationSupportDir, String? applicationBinaryModuleDir, AppVersionData? networkVersionData, ThemeConf themeConf, Locale? appLocale, Box? appConfBox, dynamic windowsVersion)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AppGlobalState() when $default != null:
return $default(_that.deviceUUID,_that.applicationSupportDir,_that.applicationBinaryModuleDir,_that.networkVersionData,_that.themeConf,_that.appLocale,_that.appConfBox);case _:
return $default(_that.deviceUUID,_that.applicationSupportDir,_that.applicationBinaryModuleDir,_that.networkVersionData,_that.themeConf,_that.appLocale,_that.appConfBox,_that.windowsVersion);case _:
return orElse();
}
@ -186,10 +187,10 @@ return $default(_that.deviceUUID,_that.applicationSupportDir,_that.applicationBi
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? deviceUUID, String? applicationSupportDir, String? applicationBinaryModuleDir, AppVersionData? networkVersionData, ThemeConf themeConf, Locale? appLocale, Box? appConfBox) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? deviceUUID, String? applicationSupportDir, String? applicationBinaryModuleDir, AppVersionData? networkVersionData, ThemeConf themeConf, Locale? appLocale, Box? appConfBox, dynamic windowsVersion) $default,) {final _that = this;
switch (_that) {
case _AppGlobalState():
return $default(_that.deviceUUID,_that.applicationSupportDir,_that.applicationBinaryModuleDir,_that.networkVersionData,_that.themeConf,_that.appLocale,_that.appConfBox);case _:
return $default(_that.deviceUUID,_that.applicationSupportDir,_that.applicationBinaryModuleDir,_that.networkVersionData,_that.themeConf,_that.appLocale,_that.appConfBox,_that.windowsVersion);case _:
throw StateError('Unexpected subclass');
}
@ -206,10 +207,10 @@ return $default(_that.deviceUUID,_that.applicationSupportDir,_that.applicationBi
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? deviceUUID, String? applicationSupportDir, String? applicationBinaryModuleDir, AppVersionData? networkVersionData, ThemeConf themeConf, Locale? appLocale, Box? appConfBox)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? deviceUUID, String? applicationSupportDir, String? applicationBinaryModuleDir, AppVersionData? networkVersionData, ThemeConf themeConf, Locale? appLocale, Box? appConfBox, dynamic windowsVersion)? $default,) {final _that = this;
switch (_that) {
case _AppGlobalState() when $default != null:
return $default(_that.deviceUUID,_that.applicationSupportDir,_that.applicationBinaryModuleDir,_that.networkVersionData,_that.themeConf,_that.appLocale,_that.appConfBox);case _:
return $default(_that.deviceUUID,_that.applicationSupportDir,_that.applicationBinaryModuleDir,_that.networkVersionData,_that.themeConf,_that.appLocale,_that.appConfBox,_that.windowsVersion);case _:
return null;
}
@ -221,7 +222,7 @@ return $default(_that.deviceUUID,_that.applicationSupportDir,_that.applicationBi
class _AppGlobalState implements AppGlobalState {
const _AppGlobalState({this.deviceUUID, this.applicationSupportDir, this.applicationBinaryModuleDir, this.networkVersionData, this.themeConf = const ThemeConf(), this.appLocale, this.appConfBox});
const _AppGlobalState({this.deviceUUID, this.applicationSupportDir, this.applicationBinaryModuleDir, this.networkVersionData, this.themeConf = const ThemeConf(), this.appLocale, this.appConfBox, this.windowsVersion = 10});
@override final String? deviceUUID;
@ -231,6 +232,7 @@ class _AppGlobalState implements AppGlobalState {
@override@JsonKey() final ThemeConf themeConf;
@override final Locale? appLocale;
@override final Box? appConfBox;
@override@JsonKey() final dynamic windowsVersion;
/// Create a copy of AppGlobalState
/// with the given fields replaced by the non-null parameter values.
@ -242,16 +244,16 @@ _$AppGlobalStateCopyWith<_AppGlobalState> get copyWith => __$AppGlobalStateCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppGlobalState&&(identical(other.deviceUUID, deviceUUID) || other.deviceUUID == deviceUUID)&&(identical(other.applicationSupportDir, applicationSupportDir) || other.applicationSupportDir == applicationSupportDir)&&(identical(other.applicationBinaryModuleDir, applicationBinaryModuleDir) || other.applicationBinaryModuleDir == applicationBinaryModuleDir)&&(identical(other.networkVersionData, networkVersionData) || other.networkVersionData == networkVersionData)&&(identical(other.themeConf, themeConf) || other.themeConf == themeConf)&&(identical(other.appLocale, appLocale) || other.appLocale == appLocale)&&(identical(other.appConfBox, appConfBox) || other.appConfBox == appConfBox));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppGlobalState&&(identical(other.deviceUUID, deviceUUID) || other.deviceUUID == deviceUUID)&&(identical(other.applicationSupportDir, applicationSupportDir) || other.applicationSupportDir == applicationSupportDir)&&(identical(other.applicationBinaryModuleDir, applicationBinaryModuleDir) || other.applicationBinaryModuleDir == applicationBinaryModuleDir)&&(identical(other.networkVersionData, networkVersionData) || other.networkVersionData == networkVersionData)&&(identical(other.themeConf, themeConf) || other.themeConf == themeConf)&&(identical(other.appLocale, appLocale) || other.appLocale == appLocale)&&(identical(other.appConfBox, appConfBox) || other.appConfBox == appConfBox)&&const DeepCollectionEquality().equals(other.windowsVersion, windowsVersion));
}
@override
int get hashCode => Object.hash(runtimeType,deviceUUID,applicationSupportDir,applicationBinaryModuleDir,networkVersionData,themeConf,appLocale,appConfBox);
int get hashCode => Object.hash(runtimeType,deviceUUID,applicationSupportDir,applicationBinaryModuleDir,networkVersionData,themeConf,appLocale,appConfBox,const DeepCollectionEquality().hash(windowsVersion));
@override
String toString() {
return 'AppGlobalState(deviceUUID: $deviceUUID, applicationSupportDir: $applicationSupportDir, applicationBinaryModuleDir: $applicationBinaryModuleDir, networkVersionData: $networkVersionData, themeConf: $themeConf, appLocale: $appLocale, appConfBox: $appConfBox)';
return 'AppGlobalState(deviceUUID: $deviceUUID, applicationSupportDir: $applicationSupportDir, applicationBinaryModuleDir: $applicationBinaryModuleDir, networkVersionData: $networkVersionData, themeConf: $themeConf, appLocale: $appLocale, appConfBox: $appConfBox, windowsVersion: $windowsVersion)';
}
@ -262,7 +264,7 @@ abstract mixin class _$AppGlobalStateCopyWith<$Res> implements $AppGlobalStateCo
factory _$AppGlobalStateCopyWith(_AppGlobalState value, $Res Function(_AppGlobalState) _then) = __$AppGlobalStateCopyWithImpl;
@override @useResult
$Res call({
String? deviceUUID, String? applicationSupportDir, String? applicationBinaryModuleDir, AppVersionData? networkVersionData, ThemeConf themeConf, Locale? appLocale, Box? appConfBox
String? deviceUUID, String? applicationSupportDir, String? applicationBinaryModuleDir, AppVersionData? networkVersionData, ThemeConf themeConf, Locale? appLocale, Box? appConfBox, dynamic windowsVersion
});
@ -279,7 +281,7 @@ class __$AppGlobalStateCopyWithImpl<$Res>
/// Create a copy of AppGlobalState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? deviceUUID = freezed,Object? applicationSupportDir = freezed,Object? applicationBinaryModuleDir = freezed,Object? networkVersionData = freezed,Object? themeConf = null,Object? appLocale = freezed,Object? appConfBox = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? deviceUUID = freezed,Object? applicationSupportDir = freezed,Object? applicationBinaryModuleDir = freezed,Object? networkVersionData = freezed,Object? themeConf = null,Object? appLocale = freezed,Object? appConfBox = freezed,Object? windowsVersion = freezed,}) {
return _then(_AppGlobalState(
deviceUUID: freezed == deviceUUID ? _self.deviceUUID : deviceUUID // ignore: cast_nullable_to_non_nullable
as String?,applicationSupportDir: freezed == applicationSupportDir ? _self.applicationSupportDir : applicationSupportDir // ignore: cast_nullable_to_non_nullable
@ -288,7 +290,8 @@ as String?,networkVersionData: freezed == networkVersionData ? _self.networkVers
as AppVersionData?,themeConf: null == themeConf ? _self.themeConf : themeConf // ignore: cast_nullable_to_non_nullable
as ThemeConf,appLocale: freezed == appLocale ? _self.appLocale : appLocale // ignore: cast_nullable_to_non_nullable
as Locale?,appConfBox: freezed == appConfBox ? _self.appConfBox : appConfBox // ignore: cast_nullable_to_non_nullable
as Box?,
as Box?,windowsVersion: freezed == windowsVersion ? _self.windowsVersion : windowsVersion // ignore: cast_nullable_to_non_nullable
as dynamic,
));
}

View File

@ -82,7 +82,7 @@ final class AppGlobalModelProvider
}
}
String _$appGlobalModelHash() => r'53dd9ed5e197333b509d282eb01073f15572820c';
String _$appGlobalModelHash() => r'51f72c5d8538e2a4f11d256802b1a1f2e04d03be';
abstract class _$AppGlobalModel extends $Notifier<AppGlobalState> {
AppGlobalState build();

View File

@ -1,7 +1,7 @@
class ConstConf {
static const String appVersion = "2.15.0";
static const int appVersionCode = 70;
static const String appVersionDate = "2025-11-8";
static const String appVersion = "2.15.1 Beta";
static const int appVersionCode = 71;
static const String appVersionDate = "2025-11-15";
static const _gameChannels = [
"LIVE",
"4.0_PREVIEW",

View File

@ -0,0 +1,72 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
/// ONNX
///
/// # Arguments
/// * `model_path` -
/// * `model_key` - "zh-en"
/// * `quantization_suffix` - "_q4", "_q8"使
///
Future<void> loadTranslationModel({
required String modelPath,
required String modelKey,
required String quantizationSuffix,
}) => RustLib.instance.api.crateApiOrtApiLoadTranslationModel(
modelPath: modelPath,
modelKey: modelKey,
quantizationSuffix: quantizationSuffix,
);
///
///
/// # Arguments
/// * `model_key` - "zh-en"
/// * `text` -
///
/// # Returns
/// * `Result<String>` -
Future<String> translateText({
required String modelKey,
required String text,
}) => RustLib.instance.api.crateApiOrtApiTranslateText(
modelKey: modelKey,
text: text,
);
///
///
/// # Arguments
/// * `model_key` - "zh-en"
/// * `texts` -
///
/// # Returns
/// * `Result<Vec<String>>` -
Future<List<String>> translateTextBatch({
required String modelKey,
required List<String> texts,
}) => RustLib.instance.api.crateApiOrtApiTranslateTextBatch(
modelKey: modelKey,
texts: texts,
);
///
///
/// # Arguments
/// * `model_key` - "zh-en"
///
Future<void> unloadTranslationModel({required String modelKey}) => RustLib
.instance
.api
.crateApiOrtApiUnloadTranslationModel(modelKey: modelKey);
///
///
/// # Returns
Future<void> clearAllModels() =>
RustLib.instance.api.crateApiOrtApiClearAllModels();

View File

@ -5,6 +5,7 @@
import 'api/asar_api.dart';
import 'api/http_api.dart';
import 'api/ort_api.dart';
import 'api/rs_process.dart';
import 'api/win32_api.dart';
import 'dart:async';
@ -68,7 +69,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
String get codegenVersion => '2.11.1';
@override
int get rustContentHash => 1832496273;
int get rustContentHash => -706588047;
static const kDefaultExternalLibraryLoaderConfig =
ExternalLibraryLoaderConfig(
@ -79,6 +80,8 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
}
abstract class RustLibApi extends BaseApi {
Future<void> crateApiOrtApiClearAllModels();
Future<List<String>> crateApiHttpApiDnsLookupIps({required String host});
Future<List<String>> crateApiHttpApiDnsLookupTxt({required String host});
@ -96,6 +99,12 @@ abstract class RustLibApi extends BaseApi {
required String asarPath,
});
Future<void> crateApiOrtApiLoadTranslationModel({
required String modelPath,
required String modelKey,
required String quantizationSuffix,
});
Future<void> crateApiAsarApiRsiLauncherAsarDataWriteMainJs({
required RsiLauncherAsarData that,
required List<int> content,
@ -122,6 +131,18 @@ abstract class RustLibApi extends BaseApi {
required String workingDirectory,
});
Future<String> crateApiOrtApiTranslateText({
required String modelKey,
required String text,
});
Future<List<String>> crateApiOrtApiTranslateTextBatch({
required String modelKey,
required List<String> texts,
});
Future<void> crateApiOrtApiUnloadTranslationModel({required String modelKey});
Future<void> crateApiRsProcessWrite({
required int rsPid,
required String data,
@ -136,6 +157,27 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
required super.portManager,
});
@override
Future<void> crateApiOrtApiClearAllModels() {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
return wire.wire__crate__api__ort_api__clear_all_models(port_);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_unit,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiOrtApiClearAllModelsConstMeta,
argValues: [],
apiImpl: this,
),
);
}
TaskConstMeta get kCrateApiOrtApiClearAllModelsConstMeta =>
const TaskConstMeta(debugName: "clear_all_models", argNames: []);
@override
Future<List<String>> crateApiHttpApiDnsLookupIps({required String host}) {
return handler.executeNormal(
@ -268,6 +310,42 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: ["asarPath"],
);
@override
Future<void> crateApiOrtApiLoadTranslationModel({
required String modelPath,
required String modelKey,
required String quantizationSuffix,
}) {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(modelPath);
var arg1 = cst_encode_String(modelKey);
var arg2 = cst_encode_String(quantizationSuffix);
return wire.wire__crate__api__ort_api__load_translation_model(
port_,
arg0,
arg1,
arg2,
);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_unit,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiOrtApiLoadTranslationModelConstMeta,
argValues: [modelPath, modelKey, quantizationSuffix],
apiImpl: this,
),
);
}
TaskConstMeta get kCrateApiOrtApiLoadTranslationModelConstMeta =>
const TaskConstMeta(
debugName: "load_translation_model",
argNames: ["modelPath", "modelKey", "quantizationSuffix"],
);
@override
Future<void> crateApiAsarApiRsiLauncherAsarDataWriteMainJs({
required RsiLauncherAsarData that,
@ -443,6 +521,102 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: ["executable", "arguments", "workingDirectory", "streamSink"],
);
@override
Future<String> crateApiOrtApiTranslateText({
required String modelKey,
required String text,
}) {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(modelKey);
var arg1 = cst_encode_String(text);
return wire.wire__crate__api__ort_api__translate_text(
port_,
arg0,
arg1,
);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_String,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiOrtApiTranslateTextConstMeta,
argValues: [modelKey, text],
apiImpl: this,
),
);
}
TaskConstMeta get kCrateApiOrtApiTranslateTextConstMeta =>
const TaskConstMeta(
debugName: "translate_text",
argNames: ["modelKey", "text"],
);
@override
Future<List<String>> crateApiOrtApiTranslateTextBatch({
required String modelKey,
required List<String> texts,
}) {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(modelKey);
var arg1 = cst_encode_list_String(texts);
return wire.wire__crate__api__ort_api__translate_text_batch(
port_,
arg0,
arg1,
);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_list_String,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiOrtApiTranslateTextBatchConstMeta,
argValues: [modelKey, texts],
apiImpl: this,
),
);
}
TaskConstMeta get kCrateApiOrtApiTranslateTextBatchConstMeta =>
const TaskConstMeta(
debugName: "translate_text_batch",
argNames: ["modelKey", "texts"],
);
@override
Future<void> crateApiOrtApiUnloadTranslationModel({
required String modelKey,
}) {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(modelKey);
return wire.wire__crate__api__ort_api__unload_translation_model(
port_,
arg0,
);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_unit,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiOrtApiUnloadTranslationModelConstMeta,
argValues: [modelKey],
apiImpl: this,
),
);
}
TaskConstMeta get kCrateApiOrtApiUnloadTranslationModelConstMeta =>
const TaskConstMeta(
debugName: "unload_translation_model",
argNames: ["modelKey"],
);
@override
Future<void> crateApiRsProcessWrite({
required int rsPid,

View File

@ -5,6 +5,7 @@
import 'api/asar_api.dart';
import 'api/http_api.dart';
import 'api/ort_api.dart';
import 'api/rs_process.dart';
import 'api/win32_api.dart';
import 'dart:async';
@ -583,7 +584,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.
// ignore_for_file: type=lint
// ignore_for_file: type=lint, unused_import
/// generated by flutter_rust_bridge
class RustLibWire implements BaseWire {
@ -614,6 +615,18 @@ class RustLibWire implements BaseWire {
late final _store_dart_post_cobject = _store_dart_post_cobjectPtr
.asFunction<void Function(DartPostCObjectFnType)>();
void wire__crate__api__ort_api__clear_all_models(int port_) {
return _wire__crate__api__ort_api__clear_all_models(port_);
}
late final _wire__crate__api__ort_api__clear_all_modelsPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'frbgen_starcitizen_doctor_wire__crate__api__ort_api__clear_all_models',
);
late final _wire__crate__api__ort_api__clear_all_models =
_wire__crate__api__ort_api__clear_all_modelsPtr
.asFunction<void Function(int)>();
void wire__crate__api__http_api__dns_lookup_ips(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> host,
@ -733,6 +746,44 @@ class RustLibWire implements BaseWire {
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)
>();
void wire__crate__api__ort_api__load_translation_model(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> model_path,
ffi.Pointer<wire_cst_list_prim_u_8_strict> model_key,
ffi.Pointer<wire_cst_list_prim_u_8_strict> quantization_suffix,
) {
return _wire__crate__api__ort_api__load_translation_model(
port_,
model_path,
model_key,
quantization_suffix,
);
}
late final _wire__crate__api__ort_api__load_translation_modelPtr =
_lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>
>(
'frbgen_starcitizen_doctor_wire__crate__api__ort_api__load_translation_model',
);
late final _wire__crate__api__ort_api__load_translation_model =
_wire__crate__api__ort_api__load_translation_modelPtr
.asFunction<
void Function(
int,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>();
void wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_js(
int port_,
ffi.Pointer<wire_cst_rsi_launcher_asar_data> that,
@ -898,6 +949,95 @@ class RustLibWire implements BaseWire {
)
>();
void wire__crate__api__ort_api__translate_text(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> model_key,
ffi.Pointer<wire_cst_list_prim_u_8_strict> text,
) {
return _wire__crate__api__ort_api__translate_text(port_, model_key, text);
}
late final _wire__crate__api__ort_api__translate_textPtr =
_lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>
>('frbgen_starcitizen_doctor_wire__crate__api__ort_api__translate_text');
late final _wire__crate__api__ort_api__translate_text =
_wire__crate__api__ort_api__translate_textPtr
.asFunction<
void Function(
int,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>();
void wire__crate__api__ort_api__translate_text_batch(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> model_key,
ffi.Pointer<wire_cst_list_String> texts,
) {
return _wire__crate__api__ort_api__translate_text_batch(
port_,
model_key,
texts,
);
}
late final _wire__crate__api__ort_api__translate_text_batchPtr =
_lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_String>,
)
>
>(
'frbgen_starcitizen_doctor_wire__crate__api__ort_api__translate_text_batch',
);
late final _wire__crate__api__ort_api__translate_text_batch =
_wire__crate__api__ort_api__translate_text_batchPtr
.asFunction<
void Function(
int,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_String>,
)
>();
void wire__crate__api__ort_api__unload_translation_model(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> model_key,
) {
return _wire__crate__api__ort_api__unload_translation_model(
port_,
model_key,
);
}
late final _wire__crate__api__ort_api__unload_translation_modelPtr =
_lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>
>(
'frbgen_starcitizen_doctor_wire__crate__api__ort_api__unload_translation_model',
);
late final _wire__crate__api__ort_api__unload_translation_model =
_wire__crate__api__ort_api__unload_translation_modelPtr
.asFunction<
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)
>();
void wire__crate__api__rs_process__write(
int port_,
int rs_pid,

View File

@ -2,6 +2,8 @@ import 'dart:convert';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -11,6 +13,7 @@ import 'package:starcitizen_doctor/common/conf/conf.dart';
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
import 'package:starcitizen_doctor/generated/l10n.dart';
import 'package:starcitizen_doctor/ui/tools/log_analyze_ui/log_analyze_ui.dart';
import 'package:window_manager/window_manager.dart';
import 'base_utils.dart';
@ -18,6 +21,15 @@ part 'multi_window_manager.freezed.dart';
part 'multi_window_manager.g.dart';
/// Window type definitions for multi-window support
class WindowTypes {
/// Main application window
static const String main = 'main';
/// Log analyzer window
static const String logAnalyze = 'log_analyze';
}
@freezed
abstract class MultiWindowAppState with _$MultiWindowAppState {
const factory MultiWindowAppState({
@ -27,35 +39,49 @@ abstract class MultiWindowAppState with _$MultiWindowAppState {
required List<String> gameInstallPaths,
String? languageCode,
String? countryCode,
@Default(10) windowsVersion,
}) = _MultiWindowAppState;
factory MultiWindowAppState.fromJson(Map<String, dynamic> json) => _$MultiWindowAppStateFromJson(json);
}
class MultiWindowManager {
static Future<void> launchSubWindow(String type, String title, AppGlobalState appGlobalState) async {
final gameInstallPaths = await SCLoggerHelper.getGameInstallPath(await SCLoggerHelper.getLauncherLogList() ?? [],
checkExists: true, withVersion: AppConf.gameChannels);
final window = await DesktopMultiWindow.createWindow(jsonEncode({
'window_type': type,
'app_state': _appStateToWindowState(
appGlobalState,
gameInstallPaths: gameInstallPaths,
).toJson(),
}));
window.setFrame(const Rect.fromLTWH(0, 0, 900, 1200));
window.setTitle(title);
await window.center();
await window.show();
// sendAppStateBroadcast(appGlobalState);
/// Parse window type from arguments string
static String parseWindowType(String arguments) {
if (arguments.isEmpty) {
return WindowTypes.main;
}
try {
final Map<String, dynamic> argument = jsonDecode(arguments);
return argument['window_type'] ?? WindowTypes.main;
} catch (e) {
return WindowTypes.main;
}
}
static void sendAppStateBroadcast(AppGlobalState appGlobalState) {
DesktopMultiWindow.invokeMethod(
0,
'app_state_broadcast',
_appStateToWindowState(appGlobalState).toJson(),
/// Launch a sub-window with specified type and title
static Future<void> launchSubWindow(String type, String title, AppGlobalState appGlobalState) async {
final gameInstallPaths = await SCLoggerHelper.getGameInstallPath(
await SCLoggerHelper.getLauncherLogList() ?? [],
checkExists: true,
withVersion: AppConf.gameChannels,
);
final controller = await WindowController.create(
WindowConfiguration(
hiddenAtLaunch: true,
arguments: jsonEncode({
'window_type': type,
'app_state': _appStateToWindowState(appGlobalState, gameInstallPaths: gameInstallPaths).toJson(),
}),
),
);
await Future.delayed(Duration(milliseconds: 500)).then((_) async {
await controller.setFrame(const Rect.fromLTWH(0, 0, 800, 1200));
await controller.setTitle(title);
await controller.center();
await controller.show();
});
}
static MultiWindowAppState _appStateToWindowState(AppGlobalState appGlobalState, {List<String>? gameInstallPaths}) {
@ -66,53 +92,147 @@ class MultiWindowManager {
languageCode: appGlobalState.appLocale?.languageCode,
countryCode: appGlobalState.appLocale?.countryCode,
gameInstallPaths: gameInstallPaths ?? [],
windowsVersion: appGlobalState.windowsVersion,
);
}
static void runSubWindowApp(List<String> args) {
final argument = args[2].isEmpty ? const {} : jsonDecode(args[2]) as Map<String, dynamic>;
/// Run sub-window app with parsed arguments
static Future<void> runSubWindowApp(String arguments, String windowType) async {
final Map<String, dynamic> argument = arguments.isEmpty ? const {} : jsonDecode(arguments);
final windowAppState = MultiWindowAppState.fromJson(argument['app_state'] ?? {});
Widget? windowWidget;
switch (argument["window_type"]) {
case "log_analyze":
switch (windowType) {
case WindowTypes.logAnalyze:
windowWidget = ToolsLogAnalyzeDialogUI(appState: windowAppState);
break;
default:
throw Exception('Unknown window type');
throw Exception('Unknown window type: $windowType');
}
return runApp(ProviderScope(
child: FluentApp(
title: "StarCitizenToolBox",
restorationScopeId: "StarCitizenToolBox",
themeMode: ThemeMode.dark,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
FluentLocalizations.delegate,
S.delegate,
],
supportedLocales: S.delegate.supportedLocales,
home: windowWidget,
theme: FluentThemeData(
await Window.initialize();
if (windowAppState.windowsVersion >= 10) {
await Window.setEffect(effect: WindowEffect.acrylic);
}
final backgroundColor = HexColor(windowAppState.backgroundColor).withValues(alpha: .1);
return runApp(
ProviderScope(
child: FluentApp(
title: "StarCitizenToolBox",
restorationScopeId: "StarCitizenToolBox",
themeMode: ThemeMode.dark,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
FluentLocalizations.delegate,
S.delegate,
],
supportedLocales: S.delegate.supportedLocales,
home: windowWidget,
theme: FluentThemeData(
brightness: Brightness.dark,
fontFamily: "SourceHanSansCN-Regular",
navigationPaneTheme: NavigationPaneThemeData(
backgroundColor: HexColor(windowAppState.backgroundColor),
),
navigationPaneTheme: NavigationPaneThemeData(backgroundColor: backgroundColor),
menuColor: HexColor(windowAppState.menuColor),
micaBackgroundColor: HexColor(windowAppState.micaColor),
scaffoldBackgroundColor: backgroundColor,
buttonTheme: ButtonThemeData(
defaultButtonStyle: ButtonStyle(
shape: WidgetStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
side: BorderSide(color: Colors.white.withValues(alpha: .01)))),
))),
locale: windowAppState.languageCode != null
? Locale(windowAppState.languageCode!, windowAppState.countryCode)
: null,
debugShowCheckedModeBanner: false,
defaultButtonStyle: ButtonStyle(
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
side: BorderSide(color: Colors.white.withValues(alpha: .01)),
),
),
),
),
),
locale: windowAppState.languageCode != null
? Locale(windowAppState.languageCode!, windowAppState.countryCode)
: null,
debugShowCheckedModeBanner: false,
),
),
));
);
}
}
/// Extension methods for WindowController to add custom functionality
extension WindowControllerExtension on WindowController {
/// Initialize custom window method handlers
Future<void> doCustomInitialize() async {
windowManager.ensureInitialized();
return await setWindowMethodHandler((call) async {
switch (call.method) {
case 'window_center':
return await windowManager.center();
case 'window_close':
return await windowManager.close();
case 'window_show':
return await windowManager.show();
case 'window_hide':
return await windowManager.hide();
case 'window_focus':
return await windowManager.focus();
case 'window_set_frame':
final args = call.arguments as Map;
return await windowManager.setBounds(
Rect.fromLTWH(
args['left'] as double,
args['top'] as double,
args['width'] as double,
args['height'] as double,
),
);
case 'window_set_title':
return await windowManager.setTitle(call.arguments as String);
default:
throw MissingPluginException('Not implemented: ${call.method}');
}
});
}
/// Center the window
Future<void> center() {
return invokeMethod('window_center');
}
/// Close the window
void close() async {
await invokeMethod('window_close');
}
/// Show the window
Future<void> show() {
return invokeMethod('window_show');
}
/// Hide the window
Future<void> hide() {
return invokeMethod('window_hide');
}
/// Focus the window
Future<void> focus() {
return invokeMethod('window_focus');
}
/// Set window frame (position and size)
Future<void> setFrame(Rect frame) {
return invokeMethod('window_set_frame', {
'left': frame.left,
'top': frame.top,
'width': frame.width,
'height': frame.height,
});
}
/// Set window title
Future<void> setTitle(String title) {
return invokeMethod('window_set_title', title);
}
}

View File

@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$MultiWindowAppState {
String get backgroundColor; String get menuColor; String get micaColor; List<String> get gameInstallPaths; String? get languageCode; String? get countryCode;
String get backgroundColor; String get menuColor; String get micaColor; List<String> get gameInstallPaths; String? get languageCode; String? get countryCode; dynamic get windowsVersion;
/// Create a copy of MultiWindowAppState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -28,16 +28,16 @@ $MultiWindowAppStateCopyWith<MultiWindowAppState> get copyWith => _$MultiWindowA
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is MultiWindowAppState&&(identical(other.backgroundColor, backgroundColor) || other.backgroundColor == backgroundColor)&&(identical(other.menuColor, menuColor) || other.menuColor == menuColor)&&(identical(other.micaColor, micaColor) || other.micaColor == micaColor)&&const DeepCollectionEquality().equals(other.gameInstallPaths, gameInstallPaths)&&(identical(other.languageCode, languageCode) || other.languageCode == languageCode)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode));
return identical(this, other) || (other.runtimeType == runtimeType&&other is MultiWindowAppState&&(identical(other.backgroundColor, backgroundColor) || other.backgroundColor == backgroundColor)&&(identical(other.menuColor, menuColor) || other.menuColor == menuColor)&&(identical(other.micaColor, micaColor) || other.micaColor == micaColor)&&const DeepCollectionEquality().equals(other.gameInstallPaths, gameInstallPaths)&&(identical(other.languageCode, languageCode) || other.languageCode == languageCode)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&const DeepCollectionEquality().equals(other.windowsVersion, windowsVersion));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,backgroundColor,menuColor,micaColor,const DeepCollectionEquality().hash(gameInstallPaths),languageCode,countryCode);
int get hashCode => Object.hash(runtimeType,backgroundColor,menuColor,micaColor,const DeepCollectionEquality().hash(gameInstallPaths),languageCode,countryCode,const DeepCollectionEquality().hash(windowsVersion));
@override
String toString() {
return 'MultiWindowAppState(backgroundColor: $backgroundColor, menuColor: $menuColor, micaColor: $micaColor, gameInstallPaths: $gameInstallPaths, languageCode: $languageCode, countryCode: $countryCode)';
return 'MultiWindowAppState(backgroundColor: $backgroundColor, menuColor: $menuColor, micaColor: $micaColor, gameInstallPaths: $gameInstallPaths, languageCode: $languageCode, countryCode: $countryCode, windowsVersion: $windowsVersion)';
}
@ -48,7 +48,7 @@ abstract mixin class $MultiWindowAppStateCopyWith<$Res> {
factory $MultiWindowAppStateCopyWith(MultiWindowAppState value, $Res Function(MultiWindowAppState) _then) = _$MultiWindowAppStateCopyWithImpl;
@useResult
$Res call({
String backgroundColor, String menuColor, String micaColor, List<String> gameInstallPaths, String? languageCode, String? countryCode
String backgroundColor, String menuColor, String micaColor, List<String> gameInstallPaths, String? languageCode, String? countryCode, dynamic windowsVersion
});
@ -65,7 +65,7 @@ class _$MultiWindowAppStateCopyWithImpl<$Res>
/// Create a copy of MultiWindowAppState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? backgroundColor = null,Object? menuColor = null,Object? micaColor = null,Object? gameInstallPaths = null,Object? languageCode = freezed,Object? countryCode = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? backgroundColor = null,Object? menuColor = null,Object? micaColor = null,Object? gameInstallPaths = null,Object? languageCode = freezed,Object? countryCode = freezed,Object? windowsVersion = freezed,}) {
return _then(_self.copyWith(
backgroundColor: null == backgroundColor ? _self.backgroundColor : backgroundColor // ignore: cast_nullable_to_non_nullable
as String,menuColor: null == menuColor ? _self.menuColor : menuColor // ignore: cast_nullable_to_non_nullable
@ -73,7 +73,8 @@ as String,micaColor: null == micaColor ? _self.micaColor : micaColor // ignore:
as String,gameInstallPaths: null == gameInstallPaths ? _self.gameInstallPaths : gameInstallPaths // ignore: cast_nullable_to_non_nullable
as List<String>,languageCode: freezed == languageCode ? _self.languageCode : languageCode // ignore: cast_nullable_to_non_nullable
as String?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
as String?,
as String?,windowsVersion: freezed == windowsVersion ? _self.windowsVersion : windowsVersion // ignore: cast_nullable_to_non_nullable
as dynamic,
));
}
@ -158,10 +159,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String backgroundColor, String menuColor, String micaColor, List<String> gameInstallPaths, String? languageCode, String? countryCode)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String backgroundColor, String menuColor, String micaColor, List<String> gameInstallPaths, String? languageCode, String? countryCode, dynamic windowsVersion)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _MultiWindowAppState() when $default != null:
return $default(_that.backgroundColor,_that.menuColor,_that.micaColor,_that.gameInstallPaths,_that.languageCode,_that.countryCode);case _:
return $default(_that.backgroundColor,_that.menuColor,_that.micaColor,_that.gameInstallPaths,_that.languageCode,_that.countryCode,_that.windowsVersion);case _:
return orElse();
}
@ -179,10 +180,10 @@ return $default(_that.backgroundColor,_that.menuColor,_that.micaColor,_that.game
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String backgroundColor, String menuColor, String micaColor, List<String> gameInstallPaths, String? languageCode, String? countryCode) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String backgroundColor, String menuColor, String micaColor, List<String> gameInstallPaths, String? languageCode, String? countryCode, dynamic windowsVersion) $default,) {final _that = this;
switch (_that) {
case _MultiWindowAppState():
return $default(_that.backgroundColor,_that.menuColor,_that.micaColor,_that.gameInstallPaths,_that.languageCode,_that.countryCode);case _:
return $default(_that.backgroundColor,_that.menuColor,_that.micaColor,_that.gameInstallPaths,_that.languageCode,_that.countryCode,_that.windowsVersion);case _:
throw StateError('Unexpected subclass');
}
@ -199,10 +200,10 @@ return $default(_that.backgroundColor,_that.menuColor,_that.micaColor,_that.game
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String backgroundColor, String menuColor, String micaColor, List<String> gameInstallPaths, String? languageCode, String? countryCode)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String backgroundColor, String menuColor, String micaColor, List<String> gameInstallPaths, String? languageCode, String? countryCode, dynamic windowsVersion)? $default,) {final _that = this;
switch (_that) {
case _MultiWindowAppState() when $default != null:
return $default(_that.backgroundColor,_that.menuColor,_that.micaColor,_that.gameInstallPaths,_that.languageCode,_that.countryCode);case _:
return $default(_that.backgroundColor,_that.menuColor,_that.micaColor,_that.gameInstallPaths,_that.languageCode,_that.countryCode,_that.windowsVersion);case _:
return null;
}
@ -214,7 +215,7 @@ return $default(_that.backgroundColor,_that.menuColor,_that.micaColor,_that.game
@JsonSerializable()
class _MultiWindowAppState implements MultiWindowAppState {
const _MultiWindowAppState({required this.backgroundColor, required this.menuColor, required this.micaColor, required final List<String> gameInstallPaths, this.languageCode, this.countryCode}): _gameInstallPaths = gameInstallPaths;
const _MultiWindowAppState({required this.backgroundColor, required this.menuColor, required this.micaColor, required final List<String> gameInstallPaths, this.languageCode, this.countryCode, this.windowsVersion = 10}): _gameInstallPaths = gameInstallPaths;
factory _MultiWindowAppState.fromJson(Map<String, dynamic> json) => _$MultiWindowAppStateFromJson(json);
@override final String backgroundColor;
@ -229,6 +230,7 @@ class _MultiWindowAppState implements MultiWindowAppState {
@override final String? languageCode;
@override final String? countryCode;
@override@JsonKey() final dynamic windowsVersion;
/// Create a copy of MultiWindowAppState
/// with the given fields replaced by the non-null parameter values.
@ -243,16 +245,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MultiWindowAppState&&(identical(other.backgroundColor, backgroundColor) || other.backgroundColor == backgroundColor)&&(identical(other.menuColor, menuColor) || other.menuColor == menuColor)&&(identical(other.micaColor, micaColor) || other.micaColor == micaColor)&&const DeepCollectionEquality().equals(other._gameInstallPaths, _gameInstallPaths)&&(identical(other.languageCode, languageCode) || other.languageCode == languageCode)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MultiWindowAppState&&(identical(other.backgroundColor, backgroundColor) || other.backgroundColor == backgroundColor)&&(identical(other.menuColor, menuColor) || other.menuColor == menuColor)&&(identical(other.micaColor, micaColor) || other.micaColor == micaColor)&&const DeepCollectionEquality().equals(other._gameInstallPaths, _gameInstallPaths)&&(identical(other.languageCode, languageCode) || other.languageCode == languageCode)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&const DeepCollectionEquality().equals(other.windowsVersion, windowsVersion));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,backgroundColor,menuColor,micaColor,const DeepCollectionEquality().hash(_gameInstallPaths),languageCode,countryCode);
int get hashCode => Object.hash(runtimeType,backgroundColor,menuColor,micaColor,const DeepCollectionEquality().hash(_gameInstallPaths),languageCode,countryCode,const DeepCollectionEquality().hash(windowsVersion));
@override
String toString() {
return 'MultiWindowAppState(backgroundColor: $backgroundColor, menuColor: $menuColor, micaColor: $micaColor, gameInstallPaths: $gameInstallPaths, languageCode: $languageCode, countryCode: $countryCode)';
return 'MultiWindowAppState(backgroundColor: $backgroundColor, menuColor: $menuColor, micaColor: $micaColor, gameInstallPaths: $gameInstallPaths, languageCode: $languageCode, countryCode: $countryCode, windowsVersion: $windowsVersion)';
}
@ -263,7 +265,7 @@ abstract mixin class _$MultiWindowAppStateCopyWith<$Res> implements $MultiWindow
factory _$MultiWindowAppStateCopyWith(_MultiWindowAppState value, $Res Function(_MultiWindowAppState) _then) = __$MultiWindowAppStateCopyWithImpl;
@override @useResult
$Res call({
String backgroundColor, String menuColor, String micaColor, List<String> gameInstallPaths, String? languageCode, String? countryCode
String backgroundColor, String menuColor, String micaColor, List<String> gameInstallPaths, String? languageCode, String? countryCode, dynamic windowsVersion
});
@ -280,7 +282,7 @@ class __$MultiWindowAppStateCopyWithImpl<$Res>
/// Create a copy of MultiWindowAppState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? backgroundColor = null,Object? menuColor = null,Object? micaColor = null,Object? gameInstallPaths = null,Object? languageCode = freezed,Object? countryCode = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? backgroundColor = null,Object? menuColor = null,Object? micaColor = null,Object? gameInstallPaths = null,Object? languageCode = freezed,Object? countryCode = freezed,Object? windowsVersion = freezed,}) {
return _then(_MultiWindowAppState(
backgroundColor: null == backgroundColor ? _self.backgroundColor : backgroundColor // ignore: cast_nullable_to_non_nullable
as String,menuColor: null == menuColor ? _self.menuColor : menuColor // ignore: cast_nullable_to_non_nullable
@ -288,7 +290,8 @@ as String,micaColor: null == micaColor ? _self.micaColor : micaColor // ignore:
as String,gameInstallPaths: null == gameInstallPaths ? _self._gameInstallPaths : gameInstallPaths // ignore: cast_nullable_to_non_nullable
as List<String>,languageCode: freezed == languageCode ? _self.languageCode : languageCode // ignore: cast_nullable_to_non_nullable
as String?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
as String?,
as String?,windowsVersion: freezed == windowsVersion ? _self.windowsVersion : windowsVersion // ignore: cast_nullable_to_non_nullable
as dynamic,
));
}

View File

@ -16,6 +16,7 @@ _MultiWindowAppState _$MultiWindowAppStateFromJson(Map<String, dynamic> json) =>
.toList(),
languageCode: json['languageCode'] as String?,
countryCode: json['countryCode'] as String?,
windowsVersion: json['windowsVersion'] ?? 10,
);
Map<String, dynamic> _$MultiWindowAppStateToJson(
@ -27,4 +28,5 @@ Map<String, dynamic> _$MultiWindowAppStateToJson(
'gameInstallPaths': instance.gameInstallPaths,
'languageCode': instance.languageCode,
'countryCode': instance.countryCode,
'windowsVersion': instance.windowsVersion,
};

View File

@ -11,28 +11,42 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'app.dart';
import 'common/utils/multi_window_manager.dart';
void main(List<String> args) async {
Future<void> main(List<String> args) async {
// webview window
if (runWebViewTitleBarWidget(args,
backgroundColor: const Color.fromRGBO(19, 36, 49, 1), builder: _defaultWebviewTitleBar)) {
return;
}
if (args.firstOrNull == 'multi_window') {
MultiWindowManager.runSubWindowApp(args);
if (runWebViewTitleBarWidget(
args,
backgroundColor: const Color.fromRGBO(19, 36, 49, 1),
builder: _defaultWebviewTitleBar,
)) {
return;
}
WidgetsFlutterBinding.ensureInitialized();
await _initWindow();
// run app
runApp(const ProviderScope(child: App()));
await windowManager.ensureInitialized();
// Get the current window controller
final windowController = await WindowController.fromCurrentEngine();
// Parse window arguments to determine which window to show
final windowType = MultiWindowManager.parseWindowType(windowController.arguments);
// Initialize window-specific handlers for sub-windows
if (windowType != WindowTypes.main) {
await windowController.doCustomInitialize();
}
// Run different apps based on the window type
switch (windowType) {
case WindowTypes.main:
await _initWindow();
runApp(const ProviderScope(child: App()));
default:
MultiWindowManager.runSubWindowApp(windowController.arguments, windowType);
}
}
Future<void> _initWindow() async {
await windowManager.ensureInitialized();
await windowManager.setTitleBarStyle(
TitleBarStyle.hidden,
windowButtonVisibility: false,
);
await windowManager.setTitleBarStyle(TitleBarStyle.hidden, windowButtonVisibility: false);
await windowManager.setSize(const Size(1280, 810));
await windowManager.setMinimumSize(const Size(1280, 810));
await windowManager.center(animate: true);
@ -73,18 +87,22 @@ class App extends HookConsumerWidget with WindowListener {
);
},
theme: FluentThemeData(
brightness: Brightness.dark,
fontFamily: "SourceHanSansCN-Regular",
navigationPaneTheme: NavigationPaneThemeData(
backgroundColor: appState.themeConf.backgroundColor,
brightness: Brightness.dark,
fontFamily: "SourceHanSansCN-Regular",
navigationPaneTheme: NavigationPaneThemeData(backgroundColor: appState.themeConf.backgroundColor),
menuColor: appState.themeConf.menuColor,
micaBackgroundColor: appState.themeConf.micaColor,
buttonTheme: ButtonThemeData(
defaultButtonStyle: ButtonStyle(
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
side: BorderSide(color: Colors.white.withValues(alpha: .01)),
),
),
),
menuColor: appState.themeConf.menuColor,
micaBackgroundColor: appState.themeConf.micaColor,
buttonTheme: ButtonThemeData(
defaultButtonStyle: ButtonStyle(
shape: WidgetStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4), side: BorderSide(color: Colors.white.withValues(alpha: .01)))),
))),
),
),
locale: appState.appLocale,
debugShowCheckedModeBanner: false,
routeInformationParser: router.routeInformationParser,
@ -97,11 +115,19 @@ class App extends HookConsumerWidget with WindowListener {
Future<void> onWindowClose() async {
debugPrint("onWindowClose");
if (await windowManager.isPreventClose()) {
final windows = await DesktopMultiWindow.getAllSubWindowIds();
for (final id in windows) {
await WindowController.fromWindowId(id).close();
final mainWindow = await WindowController.fromCurrentEngine();
final windows = await WindowController.getAll();
for (final controller in windows) {
if (controller.windowId != mainWindow.windowId) {
try {
controller.close();
} catch (e) {
debugPrint("Error closing window ${controller.windowId}: $e");
}
}
}
await windowManager.destroy();
await windowManager.setPreventClose(false);
await windowManager.close();
exit(0);
}
super.onWindowClose();
@ -112,42 +138,28 @@ Widget _defaultWebviewTitleBar(BuildContext context) {
final state = TitleBarWebViewState.of(context);
final controller = TitleBarWebViewController.of(context);
return FluentTheme(
data: FluentThemeData.dark(),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (Platform.isMacOS) const SizedBox(width: 96),
IconButton(
onPressed: !state.canGoBack ? null : controller.back,
icon: const Icon(FluentIcons.chevron_left),
),
const SizedBox(width: 12),
IconButton(
onPressed: !state.canGoForward ? null : controller.forward,
icon: const Icon(FluentIcons.chevron_right),
),
const SizedBox(width: 12),
if (state.isLoading)
IconButton(
onPressed: controller.stop,
icon: const Icon(FluentIcons.chrome_close),
)
else
IconButton(
onPressed: controller.reload,
icon: const Icon(FluentIcons.refresh),
),
const SizedBox(width: 12),
(state.isLoading)
? const SizedBox(
width: 24,
height: 24,
child: ProgressRing(),
)
: const SizedBox(width: 24),
const SizedBox(width: 12),
SelectableText(state.url ?? ""),
const Spacer()
],
));
data: FluentThemeData.dark(),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (Platform.isMacOS) const SizedBox(width: 96),
IconButton(onPressed: !state.canGoBack ? null : controller.back, icon: const Icon(FluentIcons.chevron_left)),
const SizedBox(width: 12),
IconButton(
onPressed: !state.canGoForward ? null : controller.forward,
icon: const Icon(FluentIcons.chevron_right),
),
const SizedBox(width: 12),
if (state.isLoading)
IconButton(onPressed: controller.stop, icon: const Icon(FluentIcons.chrome_close))
else
IconButton(onPressed: controller.reload, icon: const Icon(FluentIcons.refresh)),
const SizedBox(width: 12),
(state.isLoading) ? const SizedBox(width: 24, height: 24, child: ProgressRing()) : const SizedBox(width: 24),
const SizedBox(width: 12),
SelectableText(state.url ?? ""),
const Spacer(),
],
),
);
}

View File

@ -8,11 +8,11 @@ import 'package:flutter/foundation.dart';
import 'package:hive_ce/hive.dart';
import 'package:starcitizen_doctor/api/api.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/rust/api/rs_process.dart'
as rs_process;
import 'package:starcitizen_doctor/common/rust/api/rs_process.dart' as rs_process;
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/ui/home/downloader/home_downloader_ui_model.dart';
part 'aria2c.g.dart';
@ -20,11 +20,8 @@ part 'aria2c.freezed.dart';
@freezed
abstract class Aria2cModelState with _$Aria2cModelState {
const factory Aria2cModelState({
required String aria2cDir,
Aria2c? aria2c,
Aria2GlobalStat? aria2globalStat,
}) = _Aria2cModelState;
const factory Aria2cModelState({required String aria2cDir, Aria2c? aria2c, Aria2GlobalStat? aria2globalStat}) =
_Aria2cModelState;
}
extension Aria2cModelExt on Aria2cModelState {
@ -32,10 +29,8 @@ extension Aria2cModelExt on Aria2cModelState {
bool get hasDownloadTask => aria2globalStat != null && aria2TotalTaskNum > 0;
int get aria2TotalTaskNum => aria2globalStat == null
? 0
: ((aria2globalStat!.numActive ?? 0) +
(aria2globalStat!.numWaiting ?? 0));
int get aria2TotalTaskNum =>
aria2globalStat == null ? 0 : ((aria2globalStat!.numActive ?? 0) + (aria2globalStat!.numWaiting ?? 0));
}
@riverpod
@ -57,16 +52,16 @@ class Aria2cModel extends _$Aria2cModel {
try {
final sessionFile = File("$aria2cDir\\aria2.session");
//
if (await sessionFile.exists() &&
(await sessionFile.readAsString()).trim().isNotEmpty) {
dPrint("launch Aria2c daemon");
await launchDaemon(appGlobalState.applicationBinaryModuleDir!);
} else {
dPrint("LazyLoad Aria2c daemon");
}
} catch (e) {
dPrint("Aria2cManager.checkLazyLoad Error:$e");
}
if (await sessionFile.exists ()
&& (await sessionFile.readAsString()).trim().isNotEmpty) {
dPrint("launch Aria2c daemon");
await launchDaemon(appGlobalState.applicationBinaryModuleDir!);
} else {
dPrint("LazyLoad Aria2c daemon");
}
} catch (e) {
dPrint("Aria2cManager.checkLazyLoad Error:$e");
}
}();
return Aria2cModelState(aria2cDir: aria2cDir);
@ -74,8 +69,7 @@ class Aria2cModel extends _$Aria2cModel {
Future launchDaemon(String applicationBinaryModuleDir) async {
if (state.aria2c != null) return;
await BinaryModuleConf.extractModule(
["aria2c"], applicationBinaryModuleDir);
await BinaryModuleConf.extractModule(["aria2c"], applicationBinaryModuleDir);
/// skip for debug hot reload
if (kDebugMode) {
@ -99,30 +93,30 @@ class Aria2cModel extends _$Aria2cModel {
dPrint("Aria2cManager .----- aria2c start $port------");
final stream = rs_process.start(
executable: exePath,
arguments: [
"-V",
"-c",
"-x 16",
"--dir=${state.aria2cDir}\\downloads",
"--disable-ipv6",
"--enable-rpc",
"--pause",
"--rpc-listen-port=$port",
"--rpc-secret=$pwd",
"--input-file=${sessionFile.absolute.path.trim()}",
"--save-session=${sessionFile.absolute.path.trim()}",
"--save-session-interval=60",
"--file-allocation=trunc",
"--seed-time=0",
],
workingDirectory: state.aria2cDir);
executable: exePath,
arguments: [
"-V",
"-c",
"-x 16",
"--dir=${state.aria2cDir}\\downloads",
"--disable-ipv6",
"--enable-rpc",
"--pause",
"--rpc-listen-port=$port",
"--rpc-secret=$pwd",
"--input-file=${sessionFile.absolute.path.trim()}",
"--save-session=${sessionFile.absolute.path.trim()}",
"--save-session-interval=60",
"--file-allocation=trunc",
"--seed-time=0",
],
workingDirectory: state.aria2cDir,
);
String launchError = "";
stream.listen((event) {
dPrint(
"Aria2cManager.rs_process event === [${event.rsPid}] ${event.dataType} >> ${event.data}");
dPrint("Aria2cManager.rs_process event === [${event.rsPid}] ${event.dataType} >> ${event.data}");
switch (event.dataType) {
case rs_process.RsProcessStreamDataType.output:
if (event.data.contains("IPv4 RPC: listening on TCP port")) {
@ -155,8 +149,7 @@ class Aria2cModel extends _$Aria2cModel {
}
String generateRandomPassword(int length) {
const String charset =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const String charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = Random();
StringBuffer buffer = StringBuffer();
for (int i = 0; i < length; i++) {
@ -190,12 +183,12 @@ class Aria2cModel extends _$Aria2cModel {
_listenState(aria2c);
});
final box = await Hive.openBox("app_conf");
aria2c.changeGlobalOption(Aria2Option()
..maxOverallUploadLimit =
textToByte(box.get("downloader_up_limit", defaultValue: "0"))
..maxOverallDownloadLimit =
textToByte(box.get("downloader_down_limit", defaultValue: "0"))
..btTracker = trackerList);
aria2c.changeGlobalOption(
Aria2Option()
..maxOverallUploadLimit = textToByte(box.get("downloader_up_limit", defaultValue: "0"))
..maxOverallDownloadLimit = textToByte(box.get("downloader_down_limit", defaultValue: "0"))
..btTracker = trackerList,
);
}
Future<void> _listenState(Aria2c aria2c) async {
@ -214,4 +207,16 @@ class Aria2cModel extends _$Aria2cModel {
await Future.delayed(const Duration(seconds: 1));
}
}
Future<bool> isNameInTask(String name) async {
final aria2c = state.aria2c;
if (aria2c == null) return false;
for (var value in [...await aria2c.tellActive(), ...await aria2c.tellWaiting(0, 100000)]) {
final t = HomeDownloaderUIModel.getTaskTypeAndName(value);
if (t.key == "torrent" && t.value.contains(name)) {
return true;
}
}
return false;
}
}

View File

@ -41,7 +41,7 @@ final class Aria2cModelProvider
}
}
String _$aria2cModelHash() => r'3d51aeefd92e5291dca1f01db961f9c5496ec24f';
String _$aria2cModelHash() => r'17956c60a79c68ae13b8b8e700ebbafb70e93194';
abstract class _$Aria2cModel extends $Notifier<Aria2cModelState> {
Aria2cModelState build();

View File

@ -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());

View File

@ -42,7 +42,7 @@ final class HomeDownloaderUIModelProvider
}
String _$homeDownloaderUIModelHash() =>
r'5b410cd38315d94279b18f147903eca4b09bd445';
r'cb5d0973d56bbf40673afc2a734b49f5d034ab98';
abstract class _$HomeDownloaderUIModel
extends $Notifier<HomeDownloaderUIState> {

View File

@ -41,7 +41,7 @@ final class HomeUIModelProvider
}
}
String _$homeUIModelHash() => r'9dc8191f358c2d8e21ed931b3755e08ce394558e';
String _$homeUIModelHash() => r'7dfe73383f7be2e520a42d176e199a8db208f008';
abstract class _$HomeUIModel extends $Notifier<HomeUIModelState> {
HomeUIModelState build();

View File

@ -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);
}
}

View File

@ -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 ? 150 : 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");
}
}

View File

@ -43,7 +43,7 @@ final class InputMethodDialogUIModelProvider
}
String _$inputMethodDialogUIModelHash() =>
r'c07ef2474866bdb3944892460879121e0f90591f';
r'f216c1a5b6d68b3924af7b351314c618dcac80b5';
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'4f3dc0e361dca2d6b00f557496bdf006cc6c235c';
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);
}
}

View File

@ -47,7 +47,7 @@ final class AdvancedLocalizationUIModelProvider
}
String _$advancedLocalizationUIModelHash() =>
r'2f890c854bc56e506c441acabc2014438a163617';
r'c7cca8935ac7df2281e83297b11b6b82d94f7a59';
abstract class _$AdvancedLocalizationUIModel
extends $Notifier<AdvancedLocalizationUIState> {

View File

@ -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);

View File

@ -42,7 +42,7 @@ final class LocalizationUIModelProvider
}
String _$localizationUIModelHash() =>
r'd3797a7ff3d31dd1d4b05aed4a9969f4be6853c5';
r'3d3f0ed7fa3631eca4e10d456c437f6fca8eedff';
abstract class _$LocalizationUIModel extends $Notifier<LocalizationUIState> {
LocalizationUIState build();

View File

@ -42,7 +42,7 @@ final class HomePerformanceUIModelProvider
}
String _$homePerformanceUIModelHash() =>
r'c3c55c0470ef8c8be4915a1878deba332653ecde';
r'4c5c33fe7d85dc8f6bf0d019c1b870d285d594ff';
abstract class _$HomePerformanceUIModel
extends $Notifier<HomePerformanceUIState> {

View File

@ -19,7 +19,6 @@ import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/multi_window_manager.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/provider/aria2c.dart';
import 'package:starcitizen_doctor/ui/home/downloader/home_downloader_ui_model.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:xml/xml.dart';
@ -176,7 +175,8 @@ class ToolsUIModel extends _$ToolsUIModel {
"remove_nvme_settings",
S.current.tools_action_remove_nvme_registry_patch,
S.current.tools_action_info_nvme_patch_issue(
nvmePatchStatus ? S.current.localization_info_installed : S.current.tools_action_info_not_installed),
nvmePatchStatus ? S.current.localization_info_installed : S.current.tools_action_info_not_installed,
),
const Icon(FluentIcons.hard_drive, size: 24),
onTap: nvmePatchStatus
? () async {
@ -208,7 +208,7 @@ class ToolsUIModel extends _$ToolsUIModel {
state = state.copyWith(working: false);
loadToolsCard(context, skipPathScan: true);
},
)
),
];
}
@ -266,8 +266,11 @@ class ToolsUIModel extends _$ToolsUIModel {
if (listData == null) {
return;
}
scInstallPaths = await SCLoggerHelper.getGameInstallPath(listData,
checkExists: checkActive, withVersion: AppConf.gameChannels);
scInstallPaths = await SCLoggerHelper.getGameInstallPath(
listData,
checkExists: checkActive,
withVersion: AppConf.gameChannels,
);
if (scInstallPaths.isNotEmpty) {
scInstalledPath = scInstallPaths.first;
}
@ -337,11 +340,12 @@ class ToolsUIModel extends _$ToolsUIModel {
Future<String> getSystemInfo() async {
return S.current.tools_action_info_system_info_content(
await SystemHelper.getSystemName(),
await SystemHelper.getCpuName(),
await SystemHelper.getSystemMemorySizeGB(),
await SystemHelper.getGpuInfo(),
await SystemHelper.getDiskInfo());
await SystemHelper.getSystemName(),
await SystemHelper.getCpuName(),
await SystemHelper.getSystemMemorySizeGB(),
await SystemHelper.getGpuInfo(),
await SystemHelper.getDiskInfo(),
);
}
/// RSI
@ -365,9 +369,7 @@ class ToolsUIModel extends _$ToolsUIModel {
builder: (context) => ContentDialog(
title: Text(S.current.tools_action_info_system_info_title),
content: Text(systemInfo),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .65,
),
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .65),
actions: [
FilledButton(
child: Padding(
@ -404,8 +406,11 @@ class ToolsUIModel extends _$ToolsUIModel {
if ((await SystemHelper.getPID("\"RSI Launcher\"")).isNotEmpty) {
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_rsi_launcher_running_warning,
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .35));
showToast(
context,
S.current.tools_action_info_rsi_launcher_running_warning,
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .35),
);
return;
}
@ -423,14 +428,11 @@ class ToolsUIModel extends _$ToolsUIModel {
final aria2c = ref.read(aria2cModelProvider).aria2c!;
// check download task list
for (var value in [...await aria2c.tellActive(), ...await aria2c.tellWaiting(0, 100000)]) {
final t = HomeDownloaderUIModel.getTaskTypeAndName(value);
if (t.key == "torrent" && t.value.contains("Data.p4k")) {
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_p4k_download_in_progress);
state = state.copyWith(working: false);
return;
}
if (await aria2cManager.isNameInTask("Data.p4k")) {
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_p4k_download_in_progress);
state = state.copyWith(working: false);
return;
}
if (torrentUrl == "") {
@ -440,8 +442,11 @@ class ToolsUIModel extends _$ToolsUIModel {
return;
}
final userSelect =
await FilePicker.platform.saveFile(initialDirectory: savePath, fileName: fileName, lockParentWindow: true);
final userSelect = await FilePicker.platform.saveFile(
initialDirectory: savePath,
fileName: fileName,
lockParentWindow: true,
);
if (userSelect == null) {
state = state.copyWith(working: false);
return;
@ -550,16 +555,18 @@ class ToolsUIModel extends _$ToolsUIModel {
static Future<void> rsiEnhance(BuildContext context, {bool showNotGameInstallMsg = false}) async {
if ((await SystemHelper.getPID("\"RSI Launcher\"")).isNotEmpty) {
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_rsi_launcher_running_warning,
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .35));
showToast(
context,
S.current.tools_action_info_rsi_launcher_running_warning,
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .35),
);
return;
}
if (!context.mounted) return;
showDialog(
context: context,
builder: (BuildContext context) => RsiLauncherEnhanceDialogUI(
showNotGameInstallMsg: showNotGameInstallMsg,
));
context: context,
builder: (BuildContext context) => RsiLauncherEnhanceDialogUI(showNotGameInstallMsg: showNotGameInstallMsg),
);
}
Future<void> _showLogAnalyze(BuildContext context) async {
@ -568,6 +575,10 @@ class ToolsUIModel extends _$ToolsUIModel {
return;
}
if (!context.mounted) return;
await MultiWindowManager.launchSubWindow("log_analyze", S.current.log_analyzer_window_title, appGlobalState);
await MultiWindowManager.launchSubWindow(
WindowTypes.logAnalyze,
S.current.log_analyzer_window_title,
appGlobalState,
);
}
}

View File

@ -41,7 +41,7 @@ final class ToolsUIModelProvider
}
}
String _$toolsUIModelHash() => r'81a73aeccf978f7e620681eaf1a3d4182ff48f9e';
String _$toolsUIModelHash() => r'78732ff16e87cc9f92174bda43d0fafadba51146';
abstract class _$ToolsUIModel extends $Notifier<ToolsUIState> {
ToolsUIState build();

View File

@ -7,6 +7,8 @@
#include "flutter/generated_plugin_registrant.h"
#include "desktop_multi_window/desktop_multi_window_plugin.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
@ -59,6 +61,10 @@ static void my_application_activate(GApplication* application) {
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
desktop_multi_window_plugin_set_window_created_callback([](FlPluginRegistry* registry){
fl_register_plugins(registry);
});
gtk_widget_grab_focus(GTK_WIDGET(view));
}

View File

@ -1,5 +1,6 @@
import Cocoa
import FlutterMacOS
import desktop_multi_window
class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
@ -9,6 +10,11 @@ class MainFlutterWindow: NSWindow {
self.setFrame(windowFrame, display: true)
RegisterGeneratedPlugins(registry: flutterViewController)
FlutterMultiWindowPlugin.setOnWindowCreatedCallback { controller in
// Register the plugin which you want access from other isolate.
RegisterGeneratedPlugins(registry: controller)
}
super.awakeFromNib()
}

View File

@ -5,34 +5,34 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a
url: "https://pub.dev"
source: hosted
version: "85.0.0"
version: "88.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c
sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f"
url: "https://pub.dev"
source: hosted
version: "7.6.0"
version: "8.1.1"
analyzer_buffer:
dependency: transitive
description:
name: analyzer_buffer
sha256: f7833bee67c03c37241c67f8741b17cc501b69d9758df7a5a4a13ed6c947be43
sha256: aba2f75e63b3135fd1efaa8b6abefe1aa6e41b6bd9806221620fa48f98156033
url: "https://pub.dev"
source: hosted
version: "0.1.10"
version: "0.1.11"
analyzer_plugin:
dependency: transitive
description:
name: analyzer_plugin
sha256: a5ab7590c27b779f3d4de67f31c4109dbe13dd7339f86461a6f2a8ab2594d8ce
sha256: dd574a0ab77de88b7d9c12bc4b626109a5ca9078216a79041a5c24c3a1bd103c
url: "https://pub.dev"
source: hosted
version: "0.13.4"
version: "0.13.7"
archive:
dependency: "direct main"
description:
@ -278,42 +278,42 @@ packages:
dependency: "direct dev"
description:
name: custom_lint
sha256: "78085fbe842de7c5bef92de811ca81536968dbcbbcdac5c316711add2d15e796"
sha256: "751ee9440920f808266c3ec2553420dea56d3c7837dd2d62af76b11be3fcece5"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.8.1"
custom_lint_builder:
dependency: transitive
description:
name: custom_lint_builder
sha256: cc5532d5733d4eccfccaaec6070a1926e9f21e613d93ad0927fad020b95c9e52
sha256: "1128db6f58e71d43842f3b9be7465c83f0c47f4dd8918f878dd6ad3b72a32072"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.8.1"
custom_lint_core:
dependency: transitive
description:
name: custom_lint_core
sha256: cc4684d22ca05bf0a4a51127e19a8aea576b42079ed2bc9e956f11aaebe35dd1
sha256: "85b339346154d5646952d44d682965dfe9e12cae5febd706f0db3aa5010d6423"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.8.1"
custom_lint_visitor:
dependency: transitive
description:
name: custom_lint_visitor
sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2"
sha256: "446d68322747ec1c36797090de776aa72228818d3d80685a91ff524d163fee6d"
url: "https://pub.dev"
source: hosted
version: "1.0.0+7.7.0"
version: "1.0.0+8.1.1"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "3.1.2"
dbus:
dependency: transitive
description:
@ -326,10 +326,10 @@ packages:
dependency: "direct main"
description:
name: desktop_multi_window
sha256: "3ea2d696e50c3df696aabfddbd98c220ab4dde38f12c2ab12d1103bfe00ae79b"
sha256: "60ba38725b8887b60e44d15afdcf0c3813568b5da2ccaf1e7f6fd09a380a6e24"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
version: "0.3.0"
desktop_webview_window:
dependency: "direct main"
description:
@ -422,10 +422,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f
sha256: f8f4ea435f791ab1f817b4e338ed958cb3d04ba43d6736ffc39958d950754967
url: "https://pub.dev"
source: hosted
version: "10.3.3"
version: "10.3.6"
file_sizes:
dependency: "direct main"
description:
@ -618,10 +618,10 @@ packages:
dependency: "direct main"
description:
name: hexcolor
sha256: c07f4bbb9095df87eeca87e7c69e8c3d60f70c66102d7b8d61c4af0453add3f6
sha256: "0f237eed7db96ebacd8fda00d17f5ae262aaa84c213d53457c06b1dcbdfa81f2"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.2"
highlight:
dependency: transitive
description:
@ -666,10 +666,10 @@ packages:
dependency: "direct overridden"
description:
name: http
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
version: "1.6.0"
http_client_helper:
dependency: transitive
description:
@ -874,10 +874,10 @@ packages:
dependency: "direct main"
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.16.0"
version: "1.17.0"
mime:
dependency: transitive
description:
@ -890,10 +890,10 @@ packages:
dependency: transitive
description:
name: mockito
sha256: "2314cbe9165bcd16106513df9cf3c3224713087f09723b128928dc11a4379f99"
sha256: "4feb43bc4eb6c03e832f5fcd637d1abb44b98f9cfa245c58e27382f58859f8f6"
url: "https://pub.dev"
source: hosted
version: "5.5.0"
version: "5.5.1"
msix:
dependency: "direct dev"
description:
@ -1261,10 +1261,10 @@ packages:
dependency: transitive
description:
name: source_gen
sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3"
sha256: "9098ab86015c4f1d8af6486b547b11100e73b193e1899015033cb3e14ad20243"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "4.0.2"
source_helper:
dependency: transitive
description:
@ -1297,14 +1297,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.1"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace:
dependency: transitive
description:
@ -1373,26 +1365,26 @@ packages:
dependency: transitive
description:
name: test
sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb"
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
url: "https://pub.dev"
source: hosted
version: "1.26.2"
version: "1.26.3"
test_api:
dependency: transitive
description:
name: test_api
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.6"
version: "0.7.7"
test_core:
dependency: transitive
description:
name: test_core
sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a"
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
url: "https://pub.dev"
source: hosted
version: "0.6.11"
version: "0.6.12"
timing:
dependency: transitive
description:
@ -1485,10 +1477,10 @@ packages:
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.1"
version: "4.5.2"
vector_graphics:
dependency: transitive
description:
@ -1588,10 +1580,11 @@ packages:
window_manager:
dependency: "direct main"
description:
name: window_manager
sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd"
url: "https://pub.dev"
source: hosted
path: "packages/window_manager"
ref: "6fae92d21b4c80ce1b8f71c1190d7970cf722bd4"
resolved-ref: "6fae92d21b4c80ce1b8f71c1190d7970cf722bd4"
url: "https://github.com/boyan01/window_manager.git"
source: git
version: "0.5.1"
xdg_directories:
dependency: transitive

View File

@ -15,11 +15,15 @@ dependencies:
sdk: flutter
flutter_riverpod: ^3.0.3
riverpod_annotation: ^3.0.3
flutter_hooks: ^0.21.3+1
flutter_hooks: ^0.21.3
hooks_riverpod: ^3.0.3
json_annotation: ^4.9.0
go_router: ^17.0.0
window_manager: ^0.5.1
window_manager:
git:
url: https://github.com/boyan01/window_manager.git
path: packages/window_manager
ref: 6fae92d21b4c80ce1b8f71c1190d7970cf722bd4
fluent_ui: 4.11.3
flutter_staggered_grid_view: ^0.7.0
flutter_acrylic: ^1.1.4
@ -33,20 +37,20 @@ dependencies:
markdown_widget: ^2.3.2+8
extended_image: ^10.0.1
device_info_plus: ^12.2.0
file_picker: ^10.3.3
file_picker: ^10.3.6
file_sizes: ^1.0.6
desktop_webview_window: ^0.2.3
flutter_svg: ^2.2.2
archive: ^4.0.7
jwt_decode: ^0.3.1
uuid: ^4.5.1
uuid: ^4.5.2
flutter_tilt: ^3.3.2
card_swiper: ^3.0.1
ffi: ^2.1.4
flutter_rust_bridge: ^2.11.1
freezed_annotation: ^3.1.0
meta: ^1.16.0
hexcolor: ^3.0.1
meta: ^1.17.0
hexcolor: ^3.0.2
html: ^0.15.6
fixnum: ^1.1.1
rust_builder:
@ -54,7 +58,6 @@ dependencies:
aria2:
git: https://github.com/xkeyC/dart_aria2_rpc.git
# path: ../../xkeyC/dart_aria2_rpc
# path: ../../xkeyC/dart_aria2_rpc
intl: any
synchronized: ^3.4.0
super_sliver_list: ^0.4.1
@ -63,13 +66,13 @@ dependencies:
re_highlight: ^0.0.3
shelf: ^1.4.2
qr_flutter: ^4.1.0
desktop_multi_window: ^0.2.1
desktop_multi_window: ^0.3.0
watcher: ^1.1.4
path: ^1.9.1
crypto: ^3.0.7
xml: ^6.6.1
dependency_overrides:
http: ^1.5.0
http: ^1.6.0
intl: ^0.20.2
dev_dependencies:
@ -81,7 +84,7 @@ dev_dependencies:
freezed: ^3.2.3
json_serializable: ^6.11.1
riverpod_generator: ^3.0.3
custom_lint: ^0.8.0
custom_lint: ^0.8.1
riverpod_lint: ^3.0.3
ffigen: ^20.0.0
sct_dev_tools:

568
rust/Cargo.lock generated
View File

@ -17,6 +17,20 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"getrandom 0.3.4",
"once_cell",
"serde",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
@ -316,12 +330,24 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
[[package]]
name = "bitflags"
version = "2.10.0"
@ -389,6 +415,15 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "castaway"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
dependencies = [
"rustversion",
]
[[package]]
name = "cc"
version = "1.2.43"
@ -496,6 +531,21 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "compact_str"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"rustversion",
"ryu",
"serde",
"static_assertions",
]
[[package]]
name = "compression-codecs"
version = "0.4.31"
@ -630,6 +680,16 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
@ -655,14 +715,38 @@ dependencies = [
"typenum",
]
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core 0.20.11",
"darling_macro 0.20.11",
]
[[package]]
name = "darling"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
dependencies = [
"darling_core",
"darling_macro",
"darling_core 0.21.3",
"darling_macro 0.21.3",
]
[[package]]
name = "darling_core"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
@ -679,13 +763,24 @@ dependencies = [
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core 0.20.11",
"quote",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
dependencies = [
"darling_core",
"darling_core 0.21.3",
"quote",
"syn",
]
@ -699,6 +794,15 @@ dependencies = [
"cc",
]
[[package]]
name = "dary_heap"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04"
dependencies = [
"serde",
]
[[package]]
name = "dashmap"
version = "5.5.3"
@ -729,6 +833,16 @@ dependencies = [
"syn",
]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"pem-rfc7468",
"zeroize",
]
[[package]]
name = "deranged"
version = "0.5.4"
@ -739,6 +853,37 @@ dependencies = [
"serde_core",
]
[[package]]
name = "derive_builder"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
dependencies = [
"darling 0.20.11",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -865,6 +1010,12 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "esaxx-rs"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6"
[[package]]
name = "event-listener"
version = "5.4.1"
@ -902,6 +1053,18 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "filetime"
version = "0.2.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys 0.60.2",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
@ -1230,7 +1393,7 @@ dependencies = [
"parking_lot",
"rand",
"resolv-conf",
"smallvec",
"smallvec 1.15.1",
"thiserror 2.0.17",
"tokio",
"tracing",
@ -1293,7 +1456,7 @@ dependencies = [
"itoa",
"pin-project-lite",
"pin-utils",
"smallvec",
"smallvec 1.15.1",
"tokio",
"want",
]
@ -1337,7 +1500,7 @@ version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [
"base64",
"base64 0.22.1",
"bytes",
"futures-channel",
"futures-core",
@ -1418,7 +1581,7 @@ dependencies = [
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"smallvec 1.15.1",
"zerovec",
]
@ -1480,7 +1643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
"smallvec 1.15.1",
"utf8_iter",
]
@ -1575,6 +1738,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
@ -1603,6 +1775,17 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libredox"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
"bitflags",
"libc",
"redox_syscall",
]
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
@ -1654,6 +1837,32 @@ dependencies = [
"time",
]
[[package]]
name = "macro_rules_attribute"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520"
dependencies = [
"macro_rules_attribute-proc_macro",
"paste",
]
[[package]]
name = "macro_rules_attribute-proc_macro"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30"
[[package]]
name = "matrixmultiply"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
dependencies = [
"autocfg",
"rawpointer",
]
[[package]]
name = "md-5"
version = "0.10.6"
@ -1725,11 +1934,33 @@ dependencies = [
"parking_lot",
"portable-atomic",
"rustc_version",
"smallvec",
"smallvec 1.15.1",
"tagptr",
"uuid",
]
[[package]]
name = "monostate"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67"
dependencies = [
"monostate-impl",
"serde",
"serde_core",
]
[[package]]
name = "monostate-impl"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "native-tls"
version = "0.2.14"
@ -1747,6 +1978,36 @@ dependencies = [
"tempfile",
]
[[package]]
name = "ndarray"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
dependencies = [
"matrixmultiply",
"num-complex",
"num-integer",
"num-traits",
"portable-atomic",
"portable-atomic-util",
"rawpointer",
]
[[package]]
name = "ndarray"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7c9125e8f6f10c9da3aad044cc918cf8784fa34de857b1aa68038eb05a50a9"
dependencies = [
"matrixmultiply",
"num-complex",
"num-integer",
"num-traits",
"portable-atomic",
"portable-atomic-util",
"rawpointer",
]
[[package]]
name = "nix"
version = "0.30.1"
@ -1784,12 +2045,30 @@ dependencies = [
"zbus",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@ -1873,6 +2152,28 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "onig"
version = "6.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0"
dependencies = [
"bitflags",
"libc",
"once_cell",
"onig_sys",
]
[[package]]
name = "onig_sys"
version = "69.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "openssl"
version = "0.10.74"
@ -1927,6 +2228,31 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "ort"
version = "2.0.0-rc.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa7e49bd669d32d7bc2a15ec540a527e7764aec722a45467814005725bcd721"
dependencies = [
"ndarray 0.16.1",
"ort-sys",
"smallvec 2.0.0-alpha.10",
"tracing",
]
[[package]]
name = "ort-sys"
version = "2.0.0-rc.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2aba9f5c7c479925205799216e7e5d07cc1d4fa76ea8058c60a9a30f6a4e890"
dependencies = [
"flate2",
"pkg-config",
"sha2",
"tar",
"ureq",
]
[[package]]
name = "oslog"
version = "0.2.0"
@ -1969,10 +2295,25 @@ dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"smallvec 1.15.1",
"windows-link 0.2.1",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
dependencies = [
"base64ct",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
@ -2037,6 +2378,15 @@ version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "potential_utf"
version = "0.1.3"
@ -2203,6 +2553,43 @@ dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "rayon"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-cond"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f"
dependencies = [
"either",
"itertools 0.14.0",
"rayon",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
@ -2268,7 +2655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
dependencies = [
"async-compression",
"base64",
"base64 0.22.1",
"bytes",
"cookie",
"cookie_store",
@ -2340,10 +2727,14 @@ dependencies = [
"flutter_rust_bridge",
"futures",
"hickory-resolver",
"ndarray 0.17.1",
"notify-rust",
"once_cell",
"ort",
"reqwest",
"scopeguard",
"serde_json",
"tokenizers",
"tokio",
"url",
"walkdir",
@ -2581,7 +2972,7 @@ version = "3.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04"
dependencies = [
"base64",
"base64 0.22.1",
"chrono",
"hex",
"indexmap 1.9.3",
@ -2600,7 +2991,7 @@ version = "3.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955"
dependencies = [
"darling",
"darling 0.21.3",
"proc-macro2",
"quote",
"syn",
@ -2659,6 +3050,12 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "smallvec"
version = "2.0.0-alpha.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d44cfb396c3caf6fbfd0ab422af02631b69ddd96d2eff0b0f0724f9024051b"
[[package]]
name = "socket2"
version = "0.5.10"
@ -2679,6 +3076,29 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "socks"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b"
dependencies = [
"byteorder",
"libc",
"winapi",
]
[[package]]
name = "spm_precompiled"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326"
dependencies = [
"base64 0.13.1",
"nom",
"serde",
"unicode-segmentation",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@ -2761,6 +3181,17 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
[[package]]
name = "tar"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "tauri-winrt-notification"
version = "0.7.2"
@ -2900,6 +3331,39 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokenizers"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6475a27088c98ea96d00b39a9ddfb63780d1ad4cceb6f48374349a96ab2b7842"
dependencies = [
"ahash",
"aho-corasick",
"compact_str",
"dary_heap",
"derive_builder",
"esaxx-rs",
"getrandom 0.3.4",
"itertools 0.14.0",
"log",
"macro_rules_attribute",
"monostate",
"onig",
"paste",
"rand",
"rayon",
"rayon-cond",
"regex",
"regex-syntax",
"serde",
"serde_json",
"spm_precompiled",
"thiserror 2.0.17",
"unicode-normalization-alignments",
"unicode-segmentation",
"unicode_categories",
]
[[package]]
name = "tokio"
version = "1.48.0"
@ -3117,18 +3581,69 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
[[package]]
name = "unicode-normalization-alignments"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de"
dependencies = [
"smallvec 1.15.1",
]
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a"
dependencies = [
"base64 0.22.1",
"der",
"log",
"native-tls",
"percent-encoding",
"rustls-pki-types",
"socks",
"ureq-proto",
"utf-8",
"webpki-root-certs",
]
[[package]]
name = "ureq-proto"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2"
dependencies = [
"base64 0.22.1",
"http",
"httparse",
"log",
]
[[package]]
name = "url"
version = "2.5.7"
@ -3141,6 +3656,12 @@ dependencies = [
"serde",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8_iter"
version = "1.0.4"
@ -3309,7 +3830,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d12a78aa0bab22d2f26ed1a96df7ab58e8a93506a3e20adb47c51a93b4e1357"
dependencies = [
"const_format",
"itertools",
"itertools 0.11.0",
"nom",
"pori",
"regex",
@ -3337,6 +3858,15 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webpki-roots"
version = "1.0.3"
@ -3865,6 +4395,16 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
]
[[package]]
name = "yoke"
version = "0.8.0"

View File

@ -23,6 +23,10 @@ scopeguard = "1.2"
notify-rust = "4"
asar = "0.3.0"
walkdir = "2.5.0"
ort = { version = "2.0.0-rc.10", features = ["xnnpack", "download-binaries", "ndarray"] }
tokenizers = { version = "0.22", default-features = false, features = ["onig"] }
ndarray = "0.17"
serde_json = "1.0"
[target.'cfg(windows)'.dependencies]
windows = { version = "0.62.2", features = ["Win32_UI_WindowsAndMessaging"] }

View File

@ -5,3 +5,4 @@ pub mod http_api;
pub mod rs_process;
pub mod win32_api;
pub mod asar_api;
pub mod ort_api;

107
rust/src/api/ort_api.rs Normal file
View File

@ -0,0 +1,107 @@
use anyhow::Result;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Mutex;
use crate::ort_models::opus_mt::OpusMtModel;
/// 全局模型缓存
static MODEL_CACHE: Lazy<Mutex<HashMap<String, OpusMtModel>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
/// 加载 ONNX 翻译模型
///
/// # Arguments
/// * `model_path` - 模型文件夹路径
/// * `model_key` - 模型缓存键(用于标识模型,如 "zh-en"
/// * `quantization_suffix` - 量化后缀(如 "_q4", "_q8",空字符串表示使用默认模型)
///
pub fn load_translation_model(
model_path: String,
model_key: String,
quantization_suffix: String,
) -> Result<()> {
let model = OpusMtModel::new(&model_path, &quantization_suffix)?;
let mut cache = MODEL_CACHE
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock model cache: {}", e))?;
cache.insert(model_key, model);
Ok(())
}
/// 翻译文本
///
/// # Arguments
/// * `model_key` - 模型缓存键(如 "zh-en"
/// * `text` - 要翻译的文本
///
/// # Returns
/// * `Result<String>` - 翻译后的文本
pub fn translate_text(model_key: String, text: String) -> Result<String> {
let cache = MODEL_CACHE
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock model cache: {}", e))?;
let model = cache.get(&model_key).ok_or_else(|| {
anyhow::anyhow!(
"Model not found: {}. Please load the model first.",
model_key
)
})?;
model.translate(&text)
}
/// 批量翻译文本
///
/// # Arguments
/// * `model_key` - 模型缓存键(如 "zh-en"
/// * `texts` - 要翻译的文本列表
///
/// # Returns
/// * `Result<Vec<String>>` - 翻译后的文本列表
pub fn translate_text_batch(model_key: String, texts: Vec<String>) -> Result<Vec<String>> {
let cache = MODEL_CACHE
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock model cache: {}", e))?;
let model = cache.get(&model_key).ok_or_else(|| {
anyhow::anyhow!(
"Model not found: {}. Please load the model first.",
model_key
)
})?;
model.translate_batch(&texts)
}
/// 卸载模型
///
/// # Arguments
/// * `model_key` - 模型缓存键(如 "zh-en"
///
pub fn unload_translation_model(model_key: String) -> Result<()> {
let mut cache = MODEL_CACHE
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock model cache: {}", e))?;
cache.remove(&model_key);
Ok(())
}
/// 清空所有已加载的模型
///
/// # Returns
pub fn clear_all_models() -> Result<()> {
let mut cache = MODEL_CACHE
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock model cache: {}", e))?;
cache.clear();
Ok(())
}

View File

@ -37,7 +37,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_auto_opaque = RustAutoOpaqueNom,
);
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1";
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1832496273;
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -706588047;
// Section: executor
@ -45,6 +45,27 @@ flutter_rust_bridge::frb_generated_default_handler!();
// Section: wire_funcs
fn wire__crate__api__ort_api__clear_all_models_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "clear_all_models",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
move |context| {
transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || {
let output_ok = crate::api::ort_api::clear_all_models()?;
Ok(output_ok)
})(),
)
}
},
)
}
fn wire__crate__api__http_api__dns_lookup_ips_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
host: impl CstDecode<String>,
@ -161,6 +182,37 @@ fn wire__crate__api__asar_api__get_rsi_launcher_asar_data_impl(
},
)
}
fn wire__crate__api__ort_api__load_translation_model_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
model_path: impl CstDecode<String>,
model_key: impl CstDecode<String>,
quantization_suffix: impl CstDecode<String>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "load_translation_model",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let api_model_path = model_path.cst_decode();
let api_model_key = model_key.cst_decode();
let api_quantization_suffix = quantization_suffix.cst_decode();
move |context| {
transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || {
let output_ok = crate::api::ort_api::load_translation_model(
api_model_path,
api_model_key,
api_quantization_suffix,
)?;
Ok(output_ok)
})(),
)
}
},
)
}
fn wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_js_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
that: impl CstDecode<crate::api::asar_api::RsiLauncherAsarData>,
@ -315,6 +367,82 @@ fn wire__crate__api__rs_process__start_impl(
},
)
}
fn wire__crate__api__ort_api__translate_text_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
model_key: impl CstDecode<String>,
text: impl CstDecode<String>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "translate_text",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let api_model_key = model_key.cst_decode();
let api_text = text.cst_decode();
move |context| {
transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || {
let output_ok =
crate::api::ort_api::translate_text(api_model_key, api_text)?;
Ok(output_ok)
})(),
)
}
},
)
}
fn wire__crate__api__ort_api__translate_text_batch_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
model_key: impl CstDecode<String>,
texts: impl CstDecode<Vec<String>>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "translate_text_batch",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let api_model_key = model_key.cst_decode();
let api_texts = texts.cst_decode();
move |context| {
transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || {
let output_ok =
crate::api::ort_api::translate_text_batch(api_model_key, api_texts)?;
Ok(output_ok)
})(),
)
}
},
)
}
fn wire__crate__api__ort_api__unload_translation_model_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
model_key: impl CstDecode<String>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::DcoCodec, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "unload_translation_model",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let api_model_key = model_key.cst_decode();
move |context| {
transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || {
let output_ok =
crate::api::ort_api::unload_translation_model(api_model_key)?;
Ok(output_ok)
})(),
)
}
},
)
}
fn wire__crate__api__rs_process__write_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
rs_pid: impl CstDecode<u32>,
@ -1361,6 +1489,13 @@ mod io {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__ort_api__clear_all_models(
port_: i64,
) {
wire__crate__api__ort_api__clear_all_models_impl(port_)
}
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__http_api__dns_lookup_ips(
port_: i64,
@ -1406,6 +1541,21 @@ mod io {
wire__crate__api__asar_api__get_rsi_launcher_asar_data_impl(port_, asar_path)
}
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__ort_api__load_translation_model(
port_: i64,
model_path: *mut wire_cst_list_prim_u_8_strict,
model_key: *mut wire_cst_list_prim_u_8_strict,
quantization_suffix: *mut wire_cst_list_prim_u_8_strict,
) {
wire__crate__api__ort_api__load_translation_model_impl(
port_,
model_path,
model_key,
quantization_suffix,
)
}
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_js(
port_: i64,
@ -1459,6 +1609,32 @@ mod io {
)
}
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__ort_api__translate_text(
port_: i64,
model_key: *mut wire_cst_list_prim_u_8_strict,
text: *mut wire_cst_list_prim_u_8_strict,
) {
wire__crate__api__ort_api__translate_text_impl(port_, model_key, text)
}
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__ort_api__translate_text_batch(
port_: i64,
model_key: *mut wire_cst_list_prim_u_8_strict,
texts: *mut wire_cst_list_String,
) {
wire__crate__api__ort_api__translate_text_batch_impl(port_, model_key, texts)
}
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__ort_api__unload_translation_model(
port_: i64,
model_key: *mut wire_cst_list_prim_u_8_strict,
) {
wire__crate__api__ort_api__unload_translation_model_impl(port_, model_key)
}
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__rs_process__write(
port_: i64,

View File

@ -1,3 +1,4 @@
pub mod api;
mod frb_generated;
pub mod http_package;
pub mod ort_models;

View File

@ -0,0 +1 @@
pub mod opus_mt;

View File

@ -0,0 +1,403 @@
use anyhow::{anyhow, Context, Result};
use ndarray::{Array2, ArrayD};
use ort::{
execution_providers::XNNPACKExecutionProvider, session::builder::GraphOptimizationLevel,
session::Session, value::Value,
};
use std::path::Path;
use std::sync::Mutex;
use tokenizers::Tokenizer;
/// Opus-MT 翻译模型的推理结构
pub struct OpusMtModel {
encoder_session: Mutex<Session>,
decoder_session: Mutex<Session>,
tokenizer: Tokenizer,
config: ModelConfig,
}
/// 模型配置
#[derive(Debug, Clone)]
pub struct ModelConfig {
pub max_length: usize,
pub num_beams: usize,
pub decoder_start_token_id: i64,
pub eos_token_id: i64,
pub pad_token_id: i64,
}
impl Default for ModelConfig {
fn default() -> Self {
Self {
max_length: 512,
num_beams: 1,
decoder_start_token_id: 0,
eos_token_id: 0,
pad_token_id: 0,
}
}
}
impl OpusMtModel {
/// 从模型路径创建新的 OpusMT 模型实例
///
/// # Arguments
/// * `model_path` - 模型文件夹路径(应包含 onnx 子文件夹)
/// * `quantization_suffix` - 量化后缀,如 "_q4", "_q8",为空字符串则使用默认模型
///
/// # Returns
/// * `Result<Self>` - 成功返回模型实例,失败返回错误
pub fn new<P: AsRef<Path>>(model_path: P, quantization_suffix: &str) -> Result<Self> {
let model_path = model_path.as_ref();
// onnx-community 标准:模型在 onnx 子文件夹中
let onnx_dir = model_path.join("onnx");
// 加载 tokenizer在根目录
let tokenizer_path = model_path.join("tokenizer.json");
// 动态加载并修复 tokenizer
let tokenizer =
Self::load_tokenizer(&tokenizer_path).context("Failed to load tokenizer")?;
// 构建模型文件名
let encoder_filename = if quantization_suffix.is_empty() {
"encoder_model.onnx".to_string()
} else {
format!("encoder_model{}.onnx", quantization_suffix)
};
let decoder_filename = if quantization_suffix.is_empty() {
"decoder_model.onnx".to_string()
} else {
format!("decoder_model{}.onnx", quantization_suffix)
};
// 加载 encoder 模型(在 onnx 子目录)
let encoder_path = onnx_dir.join(&encoder_filename);
if !encoder_path.exists() {
return Err(anyhow!(
"Encoder model not found: {}",
encoder_path.display()
));
}
let encoder_session = Session::builder()
.context("Failed to create encoder session builder")?
.with_optimization_level(GraphOptimizationLevel::Level3)
.context("Failed to set optimization level")?
.with_intra_threads(4)
.context("Failed to set intra threads")?
.with_execution_providers([XNNPACKExecutionProvider::default().build()])
.context("Failed to register XNNPACK execution provider")?
.commit_from_file(&encoder_path)
.context(format!(
"Failed to load encoder model: {}",
encoder_filename
))?;
// 加载 decoder 模型(在 onnx 子目录)
let decoder_path = onnx_dir.join(&decoder_filename);
if !decoder_path.exists() {
return Err(anyhow!(
"Decoder model not found: {}",
decoder_path.display()
));
}
let decoder_session = Session::builder()
.context("Failed to create decoder session builder")?
.with_optimization_level(GraphOptimizationLevel::Level3)
.context("Failed to set optimization level")?
.with_intra_threads(4)
.context("Failed to set intra threads")?
.with_execution_providers([XNNPACKExecutionProvider::default().build()])
.context("Failed to register XNNPACK execution provider")?
.commit_from_file(&decoder_path)
.context(format!(
"Failed to load decoder model: {}",
decoder_filename
))?;
// 加载配置(如果存在,在根目录)
let config = Self::load_config(model_path)?;
Ok(Self {
encoder_session: Mutex::new(encoder_session),
decoder_session: Mutex::new(decoder_session),
tokenizer,
config,
})
}
/// 动态加载 tokenizer自动修复常见问题
fn load_tokenizer(tokenizer_path: &Path) -> Result<Tokenizer> {
use std::fs;
// 读取原始文件
let content =
fs::read_to_string(tokenizer_path).context("Failed to read tokenizer.json")?;
// 解析为 JSON
let mut json: serde_json::Value =
serde_json::from_str(&content).context("Failed to parse tokenizer.json")?;
let mut needs_fix = false;
// 修复 normalizer 中的问题
if let Some(obj) = json.as_object_mut() {
if let Some(normalizer) = obj.get("normalizer") {
let mut should_remove_normalizer = false;
if normalizer.is_null() {
// normalizer 是 null需要移除
should_remove_normalizer = true;
} else if let Some(norm_obj) = normalizer.as_object() {
// 检查是否是有问题的 Precompiled 类型
if let Some(type_val) = norm_obj.get("type") {
if type_val.as_str() == Some("Precompiled") {
// 检查 precompiled_charsmap 字段
if let Some(precompiled) = norm_obj.get("precompiled_charsmap") {
if precompiled.is_null() {
// precompiled_charsmap 是 null移除整个 normalizer
should_remove_normalizer = true;
}
} else {
// 缺少 precompiled_charsmap 字段,移除整个 normalizer
should_remove_normalizer = true;
}
}
}
}
if should_remove_normalizer {
obj.remove("normalizer");
needs_fix = true;
}
}
}
// 从修复后的 JSON 字符串加载 tokenizer
let json_str = if needs_fix {
serde_json::to_string(&json).context("Failed to serialize fixed tokenizer")?
} else {
content
};
// 从字节数组加载 tokenizer
Tokenizer::from_bytes(json_str.as_bytes())
.map_err(|e| anyhow!("Failed to load tokenizer: {}", e))
}
/// 从配置文件加载模型配置
fn load_config(model_path: &Path) -> Result<ModelConfig> {
let config_path = model_path.join("config.json");
if config_path.exists() {
let config_str =
std::fs::read_to_string(config_path).context("Failed to read config.json")?;
let config_json: serde_json::Value =
serde_json::from_str(&config_str).context("Failed to parse config.json")?;
Ok(ModelConfig {
max_length: config_json["max_length"].as_u64().unwrap_or(512) as usize,
num_beams: config_json["num_beams"].as_u64().unwrap_or(1) as usize,
decoder_start_token_id: config_json["decoder_start_token_id"].as_i64().unwrap_or(0),
eos_token_id: config_json["eos_token_id"].as_i64().unwrap_or(0),
pad_token_id: config_json["pad_token_id"].as_i64().unwrap_or(0),
})
} else {
Ok(ModelConfig::default())
}
}
/// 翻译文本
///
/// # Arguments
/// * `text` - 要翻译的文本
///
/// # Returns
/// * `Result<String>` - 翻译后的文本
pub fn translate(&self, text: &str) -> Result<String> {
// 1. Tokenize 输入文本
let encoding = self
.tokenizer
.encode(text, true)
.map_err(|e| anyhow!("Failed to encode text: {}", e))?;
let input_ids = encoding.get_ids();
let attention_mask = encoding.get_attention_mask();
// 2. 准备 encoder 输入
let batch_size = 1;
let seq_len = input_ids.len();
let input_ids_array: Array2<i64> = Array2::from_shape_vec(
(batch_size, seq_len),
input_ids.iter().map(|&id| id as i64).collect(),
)
.context("Failed to create input_ids array")?;
let attention_mask_array: Array2<i64> = Array2::from_shape_vec(
(batch_size, seq_len),
attention_mask.iter().map(|&mask| mask as i64).collect(),
)
.context("Failed to create attention_mask array")?;
// 3. 运行 encoder
let input_ids_value = Value::from_array((
input_ids_array.shape().to_vec(),
input_ids_array.into_raw_vec_and_offset().0,
))
.context("Failed to create input_ids value")?;
let attention_mask_value = Value::from_array((
attention_mask_array.shape().to_vec(),
attention_mask_array.clone().into_raw_vec_and_offset().0,
))
.context("Failed to create attention_mask value")?;
let encoder_inputs = ort::inputs![
"input_ids" => input_ids_value,
"attention_mask" => attention_mask_value,
];
let mut encoder_session = self
.encoder_session
.lock()
.map_err(|e| anyhow!("Failed to lock encoder session: {}", e))?;
let encoder_outputs = encoder_session
.run(encoder_inputs)
.context("Failed to run encoder")?;
let encoder_hidden_states = encoder_outputs["last_hidden_state"]
.try_extract_tensor::<f32>()
.context("Failed to extract encoder hidden states")?;
// 将 tensor 转换为 ArrayD
let (shape, data) = encoder_hidden_states;
let shape_vec: Vec<usize> = shape.iter().map(|&x| x as usize).collect();
let encoder_array = ArrayD::from_shape_vec(shape_vec, data.to_vec())
.context("Failed to create encoder array")?;
// 4. 贪婪解码生成输出
let output_ids = self.greedy_decode(encoder_array, &attention_mask_array)?;
// 5. Decode 输出 token IDs
let output_tokens: Vec<u32> = output_ids.iter().map(|&id| id as u32).collect();
let decoded = self
.tokenizer
.decode(&output_tokens, true)
.map_err(|e| anyhow!("Failed to decode output: {}", e))?;
Ok(decoded)
}
/// 贪婪解码
fn greedy_decode(
&self,
encoder_hidden_states: ArrayD<f32>,
encoder_attention_mask: &Array2<i64>,
) -> Result<Vec<i64>> {
let batch_size = 1;
let mut generated_ids = vec![self.config.decoder_start_token_id];
for _ in 0..self.config.max_length {
// 准备 decoder 输入
let decoder_input_len = generated_ids.len();
let decoder_input_ids: Array2<i64> =
Array2::from_shape_vec((batch_size, decoder_input_len), generated_ids.clone())
.context("Failed to create decoder input_ids")?;
// 创建 ORT Value
let decoder_input_value = Value::from_array((
decoder_input_ids.shape().to_vec(),
decoder_input_ids.into_raw_vec_and_offset().0,
))
.context("Failed to create decoder input value")?;
let encoder_hidden_value = Value::from_array((
encoder_hidden_states.shape().to_vec(),
encoder_hidden_states.clone().into_raw_vec_and_offset().0,
))
.context("Failed to create encoder hidden value")?;
let encoder_mask_value = Value::from_array((
encoder_attention_mask.shape().to_vec(),
encoder_attention_mask.clone().into_raw_vec_and_offset().0,
))
.context("Failed to create encoder mask value")?;
// 运行 decoder
let decoder_inputs = ort::inputs![
"input_ids" => decoder_input_value,
"encoder_hidden_states" => encoder_hidden_value,
"encoder_attention_mask" => encoder_mask_value,
];
let mut decoder_session = self
.decoder_session
.lock()
.map_err(|e| anyhow!("Failed to lock decoder session: {}", e))?;
let decoder_outputs = decoder_session
.run(decoder_inputs)
.context("Failed to run decoder")?;
// 获取 logits
let logits_tensor = decoder_outputs["logits"]
.try_extract_tensor::<f32>()
.context("Failed to extract logits")?;
let (logits_shape, logits_data) = logits_tensor;
let vocab_size = logits_shape[2] as usize;
// 获取最后一个 token 的 logits
let last_token_idx = decoder_input_len - 1;
let last_logits_start = last_token_idx * vocab_size;
let last_logits_end = last_logits_start + vocab_size;
let last_logits_slice = &logits_data[last_logits_start..last_logits_end];
// 找到最大概率的 token
let next_token_id = last_logits_slice
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
.map(|(idx, _)| idx as i64)
.context("Failed to find max token")?;
// 检查是否到达结束 token
if next_token_id == self.config.eos_token_id {
break;
}
generated_ids.push(next_token_id);
}
Ok(generated_ids)
}
/// 批量翻译文本
///
/// # Arguments
/// * `texts` - 要翻译的文本列表
///
/// # Returns
/// * `Result<Vec<String>>` - 翻译后的文本列表
pub fn translate_batch(&self, texts: &[String]) -> Result<Vec<String>> {
texts.iter().map(|text| self.translate(text)).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_translation() {
let model = OpusMtModel::new(
"C:\\Users\\xkeyc\\Downloads\\onnx_models\\opus-mt-zh-en",
"_q4f16",
)
.unwrap();
let result = model.translate("你好世界").unwrap();
println!("Translation: {}", result);
}
}

View File

@ -3,6 +3,7 @@
#include <optional>
#include "flutter/generated_plugin_registrant.h"
#include "desktop_multi_window/desktop_multi_window_plugin.h"
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
@ -25,6 +26,12 @@ bool FlutterWindow::OnCreate() {
return false;
}
RegisterPlugins(flutter_controller_->engine());
DesktopMultiWindowSetWindowCreatedCallback([](void *controller) {
auto *flutter_view_controller =
reinterpret_cast<flutter::FlutterViewController *>(controller);
auto *registry = flutter_view_controller->engine();
RegisterPlugins(registry);
});
SetChildContent(flutter_controller_->view()->GetNativeWindow());
flutter_controller_->engine()->SetNextFrameCallback([&]() {