add feature to clear ml downloads
This commit is contained in:
@@ -24,6 +24,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:weblibre/core/providers/app_state.dart';
|
import 'package:weblibre/core/providers/app_state.dart';
|
||||||
@@ -85,6 +86,12 @@ const List<SettingsSectionDefinition> advancedSettingsSections = [
|
|||||||
keywords: ['favicons', 'cache'],
|
keywords: ['favicons', 'cache'],
|
||||||
child: _IconCacheTile(),
|
child: _IconCacheTile(),
|
||||||
),
|
),
|
||||||
|
SettingsEntryDefinition(
|
||||||
|
title: 'ML Downloads',
|
||||||
|
subtitle: 'Downloaded AI models and runtime files',
|
||||||
|
keywords: ['ai', 'ml', 'models', 'onnx', 'cache'],
|
||||||
|
child: _MlCacheTile(),
|
||||||
|
),
|
||||||
SettingsEntryDefinition(
|
SettingsEntryDefinition(
|
||||||
title: 'Error Logs',
|
title: 'Error Logs',
|
||||||
subtitle: 'View and copy logs for issue reporting',
|
subtitle: 'View and copy logs for issue reporting',
|
||||||
@@ -294,6 +301,92 @@ class _IconCacheTile extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _MlCacheTile extends HookWidget {
|
||||||
|
const _MlCacheTile();
|
||||||
|
|
||||||
|
Future<bool> _confirmClear(BuildContext context) async {
|
||||||
|
final result = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Clear ML downloads?'),
|
||||||
|
content: const Text(
|
||||||
|
'This clears downloaded AI models and ONNX runtime files for this profile. '
|
||||||
|
'They will be downloaded again when needed. Restart WebLibre before retrying ML features.',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
child: const Text('Clear'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return result ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isClearing = useState(false);
|
||||||
|
|
||||||
|
return CustomListTile(
|
||||||
|
title: 'ML Downloads',
|
||||||
|
subtitle: 'Downloaded AI models and runtime files',
|
||||||
|
prefix: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 16.0),
|
||||||
|
child: Icon(
|
||||||
|
Icons.memory,
|
||||||
|
size: 24,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
suffix: FilledButton.icon(
|
||||||
|
onPressed: isClearing.value
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
if (!await _confirmClear(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isClearing.value = true;
|
||||||
|
try {
|
||||||
|
await GeckoMlService().clearMlCache();
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
showInfoMessage(
|
||||||
|
context,
|
||||||
|
'ML downloads cleared. Restart WebLibre before retrying.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
showErrorMessage(
|
||||||
|
context,
|
||||||
|
'Failed to clear ML downloads: $e',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) {
|
||||||
|
isClearing.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: isClearing.value
|
||||||
|
? const SizedBox.square(
|
||||||
|
dimension: 18,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.delete),
|
||||||
|
label: Text(isClearing.value ? 'Clearing' : 'Clear'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _ErrorLogsTile extends StatelessWidget {
|
class _ErrorLogsTile extends StatelessWidget {
|
||||||
const _ErrorLogsTile();
|
const _ErrorLogsTile();
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -6,8 +6,11 @@
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
createEngine,
|
createEngine,
|
||||||
|
EngineProcess,
|
||||||
FEATURES,
|
FEATURES,
|
||||||
} = ChromeUtils.importESModule("chrome://global/content/ml/EngineProcess.sys.mjs");
|
} = ChromeUtils.importESModule("chrome://global/content/ml/EngineProcess.sys.mjs");
|
||||||
|
const { ModelHub } = ChromeUtils.importESModule("chrome://global/content/ml/ModelHub.sys.mjs");
|
||||||
|
const { OPFS } = ChromeUtils.importESModule("chrome://global/content/ml/OPFS.sys.mjs");
|
||||||
|
|
||||||
const ML_TASK_FEATURE_EXTRACTION = "feature-extraction";
|
const ML_TASK_FEATURE_EXTRACTION = "feature-extraction";
|
||||||
const ML_TASK_TEXT2TEXT = "text2text-generation";
|
const ML_TASK_TEXT2TEXT = "text2text-generation";
|
||||||
@@ -154,7 +157,7 @@ async function createMlEngine(engineConfig, progressCallback) {
|
|||||||
numThreads,
|
numThreads,
|
||||||
};
|
};
|
||||||
|
|
||||||
return await createEngine(initData, progressCallback);
|
return await createEngine(initData, progressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ml = class extends ExtensionAPI {
|
this.ml = class extends ExtensionAPI {
|
||||||
@@ -234,6 +237,16 @@ this.ml = class extends ExtensionAPI {
|
|||||||
const generated = cutAtDuplicateWords((res[0]["generated_text"] || "").trim());
|
const generated = cutAtDuplicateWords((res[0]["generated_text"] || "").trim());
|
||||||
|
|
||||||
return generated;
|
return generated;
|
||||||
|
},
|
||||||
|
async clearCache() {
|
||||||
|
self.embeddingEngine = null;
|
||||||
|
self.topicEngine = null;
|
||||||
|
|
||||||
|
await EngineProcess.destroyMLEngine();
|
||||||
|
await new ModelHub().purgeDatabase();
|
||||||
|
await OPFS.remove("mlRuntimeFiles", { recursive: true });
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -56,6 +56,12 @@ port.onMessage.addListener(async (message) => {
|
|||||||
sendJsonResultForRequest(requestId)(result);
|
sendJsonResultForRequest(requestId)(result);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "clearMlCache": {
|
||||||
|
const result = await browser.experiments.ml.clearCache();
|
||||||
|
|
||||||
|
sendJsonResultForRequest(requestId)(result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported ML action: ${message["action"]}`);
|
throw new Error(`Unsupported ML action: ${message["action"]}`);
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-1
@@ -85,7 +85,14 @@
|
|||||||
"description": "Array of document titles/content"
|
"description": "Array of document titles/content"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clearCache",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Clear downloaded ML models and runtime files",
|
||||||
|
"async": true,
|
||||||
|
"parameters": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+13
-1
@@ -131,4 +131,16 @@ class GeckoMlApiImpl(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
override fun clearMlCache(callback: (Result<Unit>) -> Unit) {
|
||||||
|
MLEngineFeature.scheduleRequest("clearMlCache", JSONObject(), object : ResultConsumer<JSONObject> {
|
||||||
|
override fun success(result: JSONObject) {
|
||||||
|
callback(Result.success(Unit))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
|
||||||
|
callback(Result.failure(Exception("$errorCode $errorMessage $errorDetails")))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
+18
@@ -8315,6 +8315,7 @@ interface GeckoPrefApi {
|
|||||||
interface GeckoMlApi {
|
interface GeckoMlApi {
|
||||||
fun predictDocumentTopic(documents: List<String>, callback: (Result<String>) -> Unit)
|
fun predictDocumentTopic(documents: List<String>, callback: (Result<String>) -> Unit)
|
||||||
fun generateDocumentEmbeddings(documents: List<String>, callback: (Result<List<Any?>>) -> Unit)
|
fun generateDocumentEmbeddings(documents: List<String>, callback: (Result<List<Any?>>) -> Unit)
|
||||||
|
fun clearMlCache(callback: (Result<Unit>) -> Unit)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** The codec used by GeckoMlApi. */
|
/** The codec used by GeckoMlApi. */
|
||||||
@@ -8365,6 +8366,23 @@ interface GeckoMlApi {
|
|||||||
channel.setMessageHandler(null)
|
channel.setMessageHandler(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoMlApi.clearMlCache$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
api.clearMlCache{ result: Result<Unit> ->
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
if (error != null) {
|
||||||
|
reply.reply(GeckoPigeonUtils.wrapError(error))
|
||||||
|
} else {
|
||||||
|
reply.reply(GeckoPigeonUtils.wrapResult(null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,4 +28,8 @@ class GeckoMlService {
|
|||||||
.map((values) => (values! as List).cast<double>())
|
.map((values) => (values! as List).cast<double>())
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> clearMlCache() {
|
||||||
|
return _apiInstance.clearMlCache();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8621,6 +8621,24 @@ class GeckoMlApi {
|
|||||||
);
|
);
|
||||||
return pigeonVar_replyValue! as List<Object?>;
|
return pigeonVar_replyValue! as List<Object?>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> clearMlCache() async {
|
||||||
|
final pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.flutter_mozilla_components.GeckoMlApi.clearMlCache$pigeonVar_messageChannelSuffix';
|
||||||
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||||
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
|
_extractReplyValueOrThrow(
|
||||||
|
pigeonVar_replyList,
|
||||||
|
pigeonVar_channelName,
|
||||||
|
isNullValid: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GeckoBrowserExtensionApi {
|
class GeckoBrowserExtensionApi {
|
||||||
|
|||||||
@@ -1848,6 +1848,8 @@ abstract class GeckoMlApi {
|
|||||||
String predictDocumentTopic(List<String> documents);
|
String predictDocumentTopic(List<String> documents);
|
||||||
@async
|
@async
|
||||||
List generateDocumentEmbeddings(List<String> documents);
|
List generateDocumentEmbeddings(List<String> documents);
|
||||||
|
@async
|
||||||
|
void clearMlCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostApi()
|
@HostApi()
|
||||||
|
|||||||
Reference in New Issue
Block a user