Add resource importing (dict/voice/transformer)

This commit is contained in:
Aleksandras Kostarevas 2024-04-01 14:31:11 -05:00
parent bec40d167c
commit ff29bf5802
12 changed files with 506 additions and 61 deletions

View File

@ -114,6 +114,33 @@
</intent-filter>
</activity>
<activity android:name=".uix.ImportResourceActivity"
android:label="Import dictionary or model"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:launchMode="singleTask"
android:noHistory="false"
android:configChanges="orientation|screenLayout|screenSize|keyboardHidden|keyboard|uiMode|density"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="content"/>
<data android:host="*"/>
<data android:mimeType="application/octet-stream"/>
<data android:mimeType="application/dict"/>
<data android:mimeType="application/bin"/>
<data android:mimeType="application/gguf"/>
</intent-filter>
</activity>
<activity
android:name=".uix.voiceinput.downloader.DownloadActivity"
android:exported="false"

View File

@ -41,4 +41,5 @@
<string name="blacklist_from_suggestions">Blacklist \"%1$s\" from being suggested?</string>
<string name="disable_emoji">Disable emoji suggestions</string>
<string name="try_typing">Try typing here…</string>
<string name="externally_imported_model">Externally imported model</string>
</resources>

View File

@ -240,7 +240,7 @@ final public class BinaryDictionaryGetter {
* @return The list of addresses of valid dictionary files, or null.
*/
public static ArrayList<AssetFileAddress> getDictionaryFiles(final Locale locale,
final Context context, boolean notifyDictionaryPackForUpdates) {
final Context context, boolean notifyDictionaryPackForUpdates, boolean fallback) {
if (notifyDictionaryPackForUpdates) {
final boolean hasDefaultWordList = DictionaryInfoUtils.isDictionaryAvailable(
context, locale);
@ -280,9 +280,12 @@ final public class BinaryDictionaryGetter {
if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) {
final int fallbackResId =
DictionaryInfoUtils.getMainDictionaryResourceId(context.getResources(), locale);
final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId);
if (null != fallbackAsset) {
fileList.add(fallbackAsset);
if(fallback || fallbackResId != R.raw.main) {
final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId);
if (null != fallbackAsset) {
fileList.add(fallbackAsset);
}
}
}

View File

@ -21,6 +21,7 @@ import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.util.Log;
import org.futo.inputmethod.latin.uix.ResourceHelper;
import org.futo.inputmethod.latin.utils.DictionaryInfoUtils;
import java.io.File;
@ -52,19 +53,26 @@ public final class DictionaryFactory {
}
final LinkedList<Dictionary> dictList = new LinkedList<>();
final ArrayList<AssetFileAddress> assetFileList =
BinaryDictionaryGetter.getDictionaryFiles(locale, context, true);
if (null != assetFileList) {
for (final AssetFileAddress f : assetFileList) {
final ReadOnlyBinaryDictionary readOnlyBinaryDictionary =
new ReadOnlyBinaryDictionary(f.mFilename, f.mOffset, f.mLength,
false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
if (readOnlyBinaryDictionary.isValidDictionary()) {
dictList.add(readOnlyBinaryDictionary);
} else {
readOnlyBinaryDictionary.close();
// Prevent this dictionary to do any further harm.
killDictionary(context, f);
ReadOnlyBinaryDictionary customDict =
ResourceHelper.INSTANCE.tryOpeningCustomMainDictionaryForLocale(context, locale);
if(customDict != null) {
dictList.add(customDict);
} else {
final ArrayList<AssetFileAddress> assetFileList =
BinaryDictionaryGetter.getDictionaryFiles(locale, context, true, true);
if (null != assetFileList) {
for (final AssetFileAddress f : assetFileList) {
final ReadOnlyBinaryDictionary readOnlyBinaryDictionary =
new ReadOnlyBinaryDictionary(f.mFilename, f.mOffset, f.mLength,
false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
if (readOnlyBinaryDictionary.isValidDictionary()) {
dictList.add(readOnlyBinaryDictionary);
} else {
readOnlyBinaryDictionary.close();
// Prevent this dictionary to do any further harm.
killDictionary(context, f);
}
}
}
}

View File

@ -2049,6 +2049,10 @@ public class LatinIMELegacy implements KeyboardActionListener,
return ((LatinIME)(mInputMethodService)).getLanguageModelFacilitator();
}
public Locale getLocale() {
return mLocale;
}
public void onCodePointDeleted(String textBeforeCursor) {
((LatinIME)(mInputMethodService)).onEmojiDeleted(textBeforeCursor);
}

View File

@ -7,6 +7,7 @@ import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.lifecycle.LifecycleCoroutineScope
import org.futo.inputmethod.latin.uix.theme.ThemeOption
import java.util.Locale
interface ActionInputTransaction {
fun updatePartial(text: String)
@ -38,6 +39,7 @@ interface KeyboardManagerForAction {
fun cursorRight(steps: Int, stepOverWords: Boolean, select: Boolean)
fun performHapticAndAudioFeedback(code: Int, view: View)
fun getActiveLocale(): Locale
}
interface ActionWindow {

View File

@ -0,0 +1,326 @@
package org.futo.inputmethod.latin.uix
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.view.inputmethod.InputMethodSubtype
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.futo.inputmethod.latin.Dictionary
import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.ReadOnlyBinaryDictionary
import org.futo.inputmethod.latin.RichInputMethodManager
import org.futo.inputmethod.latin.uix.settings.DataStoreItem
import org.futo.inputmethod.latin.uix.settings.NavigationItem
import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle
import org.futo.inputmethod.latin.uix.settings.ScreenTitle
import org.futo.inputmethod.latin.uix.settings.ScrollableList
import org.futo.inputmethod.latin.uix.settings.useDataStore
import org.futo.inputmethod.latin.uix.theme.StatusBarColorSetter
import org.futo.inputmethod.latin.uix.theme.ThemeOption
import org.futo.inputmethod.latin.uix.theme.ThemeOptions
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
import org.futo.inputmethod.latin.utils.SubtypeLocaleUtils
import org.futo.inputmethod.latin.xlm.ModelPaths
import org.futo.voiceinput.shared.BUILTIN_ENGLISH_MODEL
import org.futo.voiceinput.shared.types.ModelFileFile
import org.futo.voiceinput.shared.types.ModelLoader
import java.io.File
import java.nio.ByteBuffer
import java.util.Locale
data class InputLanguage(
val tag: String,
val name: String,
val inputMethodSubtype: InputMethodSubtype
)
fun getActiveLanguages(context: Context): List<InputLanguage> {
RichInputMethodManager.init(context)
return RichInputMethodManager.getInstance().getMyEnabledInputMethodSubtypeList(true).map {
val name = SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(it)
InputLanguage(it.locale, name, it)
}.toList()
}
fun FileKind.preferencesKeyFor(locale: String): Preferences.Key<String> {
assert(this != FileKind.Invalid)
return stringPreferencesKey("resource_${name}_${locale}")
}
@Composable
fun resourceOption(language: InputMethodSubtype, kind: FileKind): DataStoreItem<String> {
return useDataStore(key = kind.preferencesKeyFor(language.locale), default = "")
}
@Composable
fun ImportScreen(fileKind: FileKind, file: String?, onApply: (FileKind, InputMethodSubtype) -> Unit, onCancel: () -> Unit) {
val context = LocalContext.current
val importing = remember { mutableStateOf(false) }
ScrollableList {
ScreenTitle(title = "Resource Importer")
if(fileKind == FileKind.Invalid) {
Text("This file does not appear to be a dictionary, voice input or transformer model. It may be an invalid file or corrupted. Please try a different file.")
NavigationItem(
title = "Close",
style = NavigationItemStyle.MiscNoArrow,
navigate = {
onCancel()
}
)
} else {
Text("You are importing a ${fileKind.youAreImporting()}.", modifier = Modifier.padding(8.dp))
Spacer(modifier = Modifier.height(32.dp))
if(importing.value) {
Box(modifier = Modifier
.fillMaxWidth()
.padding(32.dp)) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
} else {
Text(
"Which language would you like to set the ${fileKind.youAreImporting()} for?",
modifier = Modifier.padding(8.dp)
)
getActiveLanguages(context).forEach {
NavigationItem(
title = "${it.name} (${it.tag})",
style = NavigationItemStyle.MiscNoArrow,
navigate = {
importing.value = true
onApply(fileKind, it.inputMethodSubtype)
}
)
}
}
}
}
}
enum class FileKind {
VoiceInput,
Transformer,
Dictionary,
Invalid
}
fun FileKind.youAreImporting(): String {
return when(this) {
FileKind.VoiceInput -> "voice input model"
FileKind.Transformer -> "transformer model"
FileKind.Dictionary -> "dictionary"
FileKind.Invalid -> "invalid file"
}
}
fun FileKind.extension(): String {
return when(this) {
FileKind.VoiceInput -> ".bin"
FileKind.Transformer -> ".gguf"
FileKind.Dictionary -> ".dict"
FileKind.Invalid -> ""
}
}
fun determineFileKind(context: Context, file: Uri): FileKind {
val contentResolver = context.contentResolver
return contentResolver.openInputStream(file)?.use { inputStream ->
val array = ByteArray(4)
inputStream.read(array)
val voiceInputMagic = 0x6c6d6767.toUInt()
val transformerMagic = 0x47475546.toUInt()
val dictionaryMagic = 0x9bc13afe.toUInt()
val magic = ByteBuffer.wrap(array).getInt().toUInt()
when(magic) {
voiceInputMagic -> FileKind.VoiceInput
transformerMagic -> FileKind.Transformer
dictionaryMagic -> FileKind.Dictionary
else -> FileKind.Invalid
}
} ?: FileKind.Invalid
}
object ResourceHelper {
suspend fun findFileForKind(context: Context, locale: Locale, kind: FileKind): File? {
val keysToTry = listOf(
locale.language,
"${locale.language}_${locale.country}",
"${locale.language.lowercase()}_${locale.country.uppercase()}",
)
val settingValue: String = keysToTry.firstNotNullOfOrNull { key ->
context.getSetting(kind.preferencesKeyFor(key), "").ifEmpty { null }
} ?: return null
val file = File(context.getExternalFilesDir(null), settingValue)
if(!file.exists()) {
return null
}
return file
}
fun tryFindingVoiceInputModelForLocale(context: Context, locale: Locale): ModelLoader? {
val file = runBlocking { findFileForKind(context, locale, FileKind.VoiceInput) }
?: return if(locale.language == "en") {
BUILTIN_ENGLISH_MODEL
} else {
null
}
return ModelFileFile(R.string.externally_imported_model, file)
}
fun tryOpeningCustomMainDictionaryForLocale(context: Context, locale: Locale): ReadOnlyBinaryDictionary? {
val file = runBlocking { findFileForKind(context, locale, FileKind.Dictionary) } ?: return null
return ReadOnlyBinaryDictionary(
file.absolutePath,
0,
file.length(),
false,
locale,
Dictionary.TYPE_MAIN
)
}
}
class ImportResourceActivity : ComponentActivity() {
private val themeOption: MutableState<ThemeOption?> = mutableStateOf(null)
private val fileBeingImported: MutableState<String?> = mutableStateOf(null)
private val fileKind: MutableState<FileKind> = mutableStateOf(FileKind.Invalid)
private var uri: Uri? = null
private fun applySetting(fileKind: FileKind, inputMethodSubtype: InputMethodSubtype) {
val outputFileName = "${fileKind.name.lowercase()}_${inputMethodSubtype.locale}${fileKind.extension()}"
lifecycleScope.launch {
withContext(Dispatchers.IO) {
// This is a special case for now
if (fileKind == FileKind.Transformer) {
// 1. Copy file
val contentResolver = applicationContext.contentResolver
val outDirectory = ModelPaths.getModelDirectory(applicationContext)
val outputFile = File(outDirectory, outputFileName)
contentResolver.openInputStream(uri!!)!!.use { inputStream ->
outputFile.outputStream().use { outputStream ->
inputStream.copyTo(outputStream, 1024)
}
}
// 2. Update reference
val language = inputMethodSubtype.locale.split("_").first()
ModelPaths.updateModelOption(applicationContext, language, outputFile)
} else {
// 1. Copy file
val contentResolver = applicationContext.contentResolver
contentResolver.openInputStream(uri!!)!!.use { inputStream ->
val outputFile =
File(applicationContext.getExternalFilesDir(null), outputFileName)
outputFile.outputStream().use { outputStream ->
inputStream.copyTo(outputStream, 1024)
}
}
// 2. Update reference
val key = fileKind.preferencesKeyFor(inputMethodSubtype.locale)
applicationContext.setSetting(key, outputFileName)
}
}
finish()
}
}
private fun updateContent() {
setContent {
themeOption.value?.let { themeOption ->
val themeIdx = useDataStore(key = THEME_KEY.key, default = themeOption.key)
val theme: ThemeOption = ThemeOptions[themeIdx.value] ?: themeOption
UixThemeWrapper(theme.obtainColors(LocalContext.current)) {
StatusBarColorSetter()
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
ImportScreen(
fileKind = fileKind.value,
file = fileBeingImported.value,
onApply = { fileKind, inputMethodSubtype -> applySetting(fileKind, inputMethodSubtype) },
onCancel = { finish() }
)
}
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.uri = intent?.data!!
val filePath = intent?.data?.path
fileBeingImported.value = filePath
fileKind.value = determineFileKind(applicationContext, uri!!)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
updateContent()
}
}
deferGetSetting(THEME_KEY) {
val themeOptionFromSettings = ThemeOptions[it]
val themeOption = when {
themeOptionFromSettings == null -> VoiceInputTheme
!themeOptionFromSettings.available(this) -> VoiceInputTheme
else -> themeOptionFromSettings
}
this.themeOption.value = themeOption
}
}
}

View File

@ -60,6 +60,7 @@ import org.futo.inputmethod.updates.DEFER_MANUAL_UPDATE_UNTIL
import org.futo.inputmethod.updates.MANUAL_UPDATE_PERIOD_MS
import org.futo.inputmethod.updates.openManualUpdateCheck
import org.futo.inputmethod.updates.retrieveSavedLastUpdateCheckResult
import java.util.Locale
private class LatinIMEActionInputTransaction(
@ -173,6 +174,10 @@ class UixActionKeyboardManager(val uixManager: UixManager, val latinIME: LatinIM
override fun performHapticAndAudioFeedback(code: Int, view: View) {
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(code, view)
}
override fun getActiveLocale(): Locale {
return latinIME.latinIMELegacy.locale
}
}
class UixManager(private val latinIME: LatinIME) {

View File

@ -23,23 +23,16 @@ import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.uix.Action
import org.futo.inputmethod.latin.uix.ActionInputTransaction
import org.futo.inputmethod.latin.uix.ActionWindow
import org.futo.inputmethod.latin.uix.DISALLOW_SYMBOLS
import org.futo.inputmethod.latin.uix.ENABLE_ENGLISH
import org.futo.inputmethod.latin.uix.ENABLE_MULTILINGUAL
import org.futo.inputmethod.latin.uix.ENABLE_SOUND
import org.futo.inputmethod.latin.uix.ENGLISH_MODEL_INDEX
import org.futo.inputmethod.latin.uix.KeyboardManagerForAction
import org.futo.inputmethod.latin.uix.LANGUAGE_TOGGLES
import org.futo.inputmethod.latin.uix.MULTILINGUAL_MODEL_INDEX
import org.futo.inputmethod.latin.uix.PersistentActionState
import org.futo.inputmethod.latin.uix.ResourceHelper
import org.futo.inputmethod.latin.uix.VERBOSE_PROGRESS
import org.futo.inputmethod.latin.uix.getSetting
import org.futo.inputmethod.latin.uix.voiceinput.downloader.DownloadActivity
import org.futo.inputmethod.latin.xlm.UserDictionaryObserver
import org.futo.voiceinput.shared.ENGLISH_MODELS
import org.futo.voiceinput.shared.MULTILINGUAL_MODELS
import org.futo.voiceinput.shared.ModelDoesNotExistException
import org.futo.voiceinput.shared.RecognizerView
import org.futo.voiceinput.shared.RecognizerViewListener
@ -51,6 +44,7 @@ import org.futo.voiceinput.shared.types.getLanguageFromWhisperString
import org.futo.voiceinput.shared.whisper.DecodingConfiguration
import org.futo.voiceinput.shared.whisper.ModelManager
import org.futo.voiceinput.shared.whisper.MultiModelRunConfiguration
import java.util.Locale
val SystemVoiceInputAction = Action(
icon = R.drawable.mic_fill,
@ -74,7 +68,8 @@ class VoiceInputPersistentState(val manager: KeyboardManagerForAction) : Persist
}
private class VoiceInputActionWindow(
val manager: KeyboardManagerForAction, val state: VoiceInputPersistentState
val manager: KeyboardManagerForAction, val state: VoiceInputPersistentState,
val model: ModelLoader, val locale: Locale
) : ActionWindow, RecognizerViewListener {
val context = manager.getContext()
@ -83,25 +78,12 @@ private class VoiceInputActionWindow(
val enableSound = async { context.getSetting(ENABLE_SOUND) }
val verboseFeedback = async { context.getSetting(VERBOSE_PROGRESS) }
val disallowSymbols = async { context.getSetting(DISALLOW_SYMBOLS) }
val enableEnglish = async { context.getSetting(ENABLE_ENGLISH) }
val englishModelIdx = async { context.getSetting(ENGLISH_MODEL_INDEX) }
val enableMultilingual = async { context.getSetting(ENABLE_MULTILINGUAL) }
val multilingualModelIdx = async { context.getSetting(MULTILINGUAL_MODEL_INDEX) }
val allowedLanguages = async {
context.getSetting(LANGUAGE_TOGGLES).mapNotNull { getLanguageFromWhisperString(it) }
.toSet()
}
val primaryModel = if (enableMultilingual.await()) {
MULTILINGUAL_MODELS[multilingualModelIdx.await()]
} else {
ENGLISH_MODELS[englishModelIdx.await()]
}
val primaryModel = model
val languageSpecificModels = mutableMapOf<Language, ModelLoader>()
if (enableEnglish.await()) {
languageSpecificModels[Language.English] = ENGLISH_MODELS[englishModelIdx.await()]
}
val allowedLanguages = setOf(
getLanguageFromWhisperString(locale.language)!!
)
shouldPlaySounds = enableSound.await()
@ -114,7 +96,7 @@ private class VoiceInputActionWindow(
),
decodingConfiguration = DecodingConfiguration(
glossary = state.userDictionaryObserver.getWords().map { it.word },
languages = allowedLanguages.await(),
languages = allowedLanguages,
suppressSymbols = disallowSymbols.await()
)
)
@ -234,14 +216,49 @@ private class VoiceInputActionWindow(
}
}
private class VoiceInputNoModelWindow(val locale: Locale) : ActionWindow {
@Composable
override fun windowName(): String {
return stringResource(R.string.voice_input_action_title)
}
@Composable
override fun WindowContents(keyboardShown: Boolean) {
Box(modifier = Modifier
.fillMaxSize()
.clickable(enabled = true,
onClickLabel = null,
onClick = { TODO() },
role = null,
indication = null,
interactionSource = remember { MutableInteractionSource() })) {
Text("No model available for ${locale.displayLanguage}, tap to check options?", modifier = Modifier.align(Alignment.Center))
}
}
override fun close() {
}
}
val VoiceInputAction = Action(icon = R.drawable.mic_fill,
name = R.string.voice_input_action_title,
simplePressImpl = null,
keepScreenAwake = true,
persistentState = { VoiceInputPersistentState(it) },
windowImpl = { manager, persistentState ->
VoiceInputActionWindow(
manager = manager, state = persistentState as VoiceInputPersistentState
)
val locale = manager.getActiveLocale()
val model = ResourceHelper.tryFindingVoiceInputModelForLocale(manager.getContext(), locale)
if(model == null) {
VoiceInputNoModelWindow(locale)
} else {
VoiceInputActionWindow(
manager = manager, state = persistentState as VoiceInputPersistentState,
locale = locale, model = model
)
}
}
)

View File

@ -4,11 +4,16 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.runBlocking
import org.futo.inputmethod.latin.BinaryDictionaryGetter
import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.RichInputMethodManager
import org.futo.inputmethod.latin.uix.FileKind
import org.futo.inputmethod.latin.uix.ResourceHelper
import org.futo.inputmethod.latin.uix.settings.NavigationItem
import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle
import org.futo.inputmethod.latin.uix.settings.ScreenTitle
@ -16,7 +21,9 @@ import org.futo.inputmethod.latin.uix.settings.ScrollableList
import org.futo.inputmethod.latin.uix.settings.Tip
import org.futo.inputmethod.latin.uix.settings.openLanguageSettings
import org.futo.inputmethod.latin.utils.SubtypeLocaleUtils
import org.futo.inputmethod.latin.xlm.ModelPaths
import org.futo.inputmethod.updates.openURI
import java.util.Locale
data class LanguageOptions(
val voiceInputModel: String?,
@ -43,39 +50,57 @@ fun LanguagesScreen(navController: NavHostController = rememberNavController())
RichInputMethodManager.getInstance().getMyEnabledInputMethodSubtypeList(true).forEach {
val name = SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(it)
val dummyOptions = LanguageOptions(
voiceInputModel = "Built-in English-39",
dictionary = "main.dict",
transformerModel = null
val locale = Locale.forLanguageTag(it.locale.replace("_", "-"))
val voiceInputModelName = ResourceHelper.tryFindingVoiceInputModelForLocale(context, locale)?.name?.let { stringResource(it) }
val dictionaryName = runBlocking { ResourceHelper.findFileForKind(context, locale, FileKind.Dictionary) }?.let {
"Imported Dictionary"
} ?: if(BinaryDictionaryGetter.getDictionaryFiles(locale, context, false, false).let {
println("DICTIONARIES FOR ${locale.displayLanguage}: ${it.toList().map { it.mFilename }.joinToString(",")}")
it
}.isNotEmpty()) {
"Built-in Dictionary"
} else {
null
}
val transformerName = runBlocking { ModelPaths.getModelOptions(context) }.get(locale.language)?.let {
it.loadDetails()?.name
}
val options = LanguageOptions(
voiceInputModel = voiceInputModelName,
dictionary = dictionaryName,
transformerModel = transformerName
)
ScreenTitle(name)
/*
NavigationItem(
title = dummyOptions.voiceInputModel ?: "None",
style = dummyOptions.voiceInputModel?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
title = options.voiceInputModel ?: "None",
style = options.voiceInputModel?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
navigate = {
context.openURI("https://keyboard.futo.org/voice-input-models", true)
},
icon = painterResource(id = R.drawable.mic_fill)
)
NavigationItem(
title = dummyOptions.dictionary ?: "None",
style = dummyOptions.dictionary?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
title = options.dictionary ?: "None",
style = options.dictionary?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
navigate = {
context.openURI("https://codeberg.org/Helium314/aosp-dictionaries#dictionaries", true)
},
icon = painterResource(id = R.drawable.book)
)
NavigationItem(
title = dummyOptions.transformerModel ?: "None",
style = dummyOptions.transformerModel?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
title = options.transformerModel ?: "None",
style = options.transformerModel?.let { NavigationItemStyle.HomeTertiary } ?: NavigationItemStyle.MiscNoArrow,
navigate = {
context.openURI("https://keyboard.futo.org/models", true)
},
icon = painterResource(id = R.drawable.cpu)
)
*/
}
}
}

View File

@ -4,6 +4,10 @@ import org.futo.voiceinput.shared.types.ModelBuiltInAsset
import org.futo.voiceinput.shared.types.ModelDownloadable
import org.futo.voiceinput.shared.types.ModelLoader
val BUILTIN_ENGLISH_MODEL: ModelLoader = ModelBuiltInAsset(
name = R.string.tiny_en_name,
ggmlFile = "tiny_en_acft_q8_0.bin.not.tflite"
)
val ENGLISH_MODELS: List<ModelLoader> = listOf(
ModelBuiltInAsset(

View File

@ -61,8 +61,8 @@ internal class ModelBuiltInAsset(
}
@Throws(IOException::class)
private fun Context.tryOpenDownloadedModel(pathStr: String): MappedByteBuffer {
val fis = File(this.filesDir, pathStr).inputStream()
private fun tryOpenDownloadedModel(file: File): MappedByteBuffer {
val fis = file.inputStream()
val channel = fis.channel
return channel.map(
@ -71,6 +71,11 @@ private fun Context.tryOpenDownloadedModel(pathStr: String): MappedByteBuffer {
).load()
}
@Throws(IOException::class)
private fun Context.tryOpenDownloadedModel(pathStr: String): MappedByteBuffer {
return tryOpenDownloadedModel(File(filesDir, pathStr))
}
internal class ModelDownloadable(
override val name: Int,
val ggmlFile: String,
@ -91,3 +96,21 @@ internal class ModelDownloadable(
return WhisperGGML(file)
}
}
public class ModelFileFile(
override val name: Int,
val file: File,
) : ModelLoader {
override fun exists(context: Context): Boolean {
return file.exists()
}
override fun getRequiredDownloadList(context: Context): List<String> {
return listOf()
}
override fun loadGGML(context: Context): WhisperGGML {
val file = tryOpenDownloadedModel(file)
return WhisperGGML(file)
}
}