From ab8e76c4869356d2bf213143b50a650551817341 Mon Sep 17 00:00:00 2001 From: ShiftHackZ Date: Sun, 11 Aug 2024 14:37:42 +0300 Subject: [PATCH 01/10] Developer mode, logger, Local Diffusion partial log coverage --- .../core/common/extensions/AppExtensions.kt | 5 + .../src/main/res/values-ru/strings.xml | 8 +- .../src/main/res/values-tr/strings.xml | 8 +- .../src/main/res/values-uk/strings.xml | 8 +- .../src/main/res/values-zh/strings.xml | 8 +- .../src/main/res/values/strings.xml | 8 +- .../data/preference/PreferenceManagerImpl.kt | 9 + .../aisdv1/domain/entity/Settings.kt | 1 + .../domain/preference/PreferenceManager.kt | 1 + .../diffusion/LocalDiffusionContract.kt | 4 + .../feature/diffusion/LocalDiffusionImpl.kt | 31 ++- .../ai/tokenizer/EnglishTextTokenizer.kt | 33 ++- .../aisdv1/feature/diffusion/ai/unet/UNet.kt | 38 +++- .../aisdv1/work/core/CoreGenerationWorker.kt | 1 - .../aisdv1/work/task/TextToImageTask.kt | 15 +- .../aisdv1/presentation/di/ViewModelModule.kt | 2 + .../navigation/graph/MainNavGraph.kt | 8 + .../navigation/router/main/MainRouter.kt | 2 + .../navigation/router/main/MainRouterImpl.kt | 8 +- .../screen/debug/DebugMenuAccessor.kt | 20 +- .../screen/debug/DebugMenuIntent.kt | 2 + .../screen/debug/DebugMenuScreen.kt | 25 ++- .../screen/debug/DebugMenuViewModel.kt | 10 + .../screen/logger/LoggerIntent.kt | 10 + .../screen/logger/LoggerScreen.kt | 198 ++++++++++++++++++ .../presentation/screen/logger/LoggerState.kt | 10 + .../screen/logger/LoggerViewModel.kt | 47 +++++ .../screen/settings/SettingsEffect.kt | 2 + .../screen/settings/SettingsIntent.kt | 2 + .../screen/settings/SettingsScreen.kt | 14 ++ .../screen/settings/SettingsState.kt | 1 + .../screen/settings/SettingsViewModel.kt | 9 +- .../aisdv1/presentation/utils/Constants.kt | 1 + .../router/main/MainRouterImplTest.kt | 64 +----- .../screen/debug/DebugMenuViewModelTest.kt | 3 + .../screen/logger/LoggerViewModelTest.kt | 56 +++++ .../screen/settings/SettingsViewModelTest.kt | 15 +- 37 files changed, 593 insertions(+), 94 deletions(-) create mode 100644 presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerIntent.kt create mode 100644 presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt create mode 100644 presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerState.kt create mode 100644 presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModel.kt create mode 100644 presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModelTest.kt diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt index f32dc5aa..79f1a41e 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt +++ b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt @@ -7,6 +7,7 @@ import android.content.Intent import android.net.Uri import android.provider.Settings import android.widget.Toast +import androidx.annotation.StringRes fun Context.isAppInForeground(): Boolean { @@ -18,6 +19,10 @@ fun Context.isAppInForeground(): Boolean { } } +fun Context.showToast(@StringRes resId: Int) { + resources.getString(resId).let(::showToast) +} + fun Context.showToast(text: String) { Toast.makeText(this, text, Toast.LENGTH_LONG).show() } diff --git a/core/localization/src/main/res/values-ru/strings.xml b/core/localization/src/main/res/values-ru/strings.xml index ecf330b6..2c6d581d 100644 --- a/core/localization/src/main/res/values-ru/strings.xml +++ b/core/localization/src/main/res/values-ru/strings.xml @@ -153,7 +153,8 @@ Конфигурация Выберите SD ML модель Очистить кэш приложения - Дебаг Меню + Режим разработчика + Логи ЛоРА Инверсия текста Инверсия @@ -298,8 +299,13 @@ Чтобы использовать локальную пользовательскую модель, поместите ее в локальную папку в памяти телефона: Download/SDAi/model Окончательная структура папок должна быть такой: + Отладка QA тест-кейсы + Режим разработчика разблокирован! + Посмотреть логи + Очистить все логи Внести битый Base64 в БД + Лог файл пуст. Здесь пусто… Добавьте что-нибуть на %1$s сервере в: \n\n%2$s diff --git a/core/localization/src/main/res/values-tr/strings.xml b/core/localization/src/main/res/values-tr/strings.xml index b65fb353..31f303fa 100644 --- a/core/localization/src/main/res/values-tr/strings.xml +++ b/core/localization/src/main/res/values-tr/strings.xml @@ -153,7 +153,8 @@ Sunucu Kurulumu SD Modeli seçin Uygulama önbelleğini temizle - Hata Ayıklama Menüsü + Geliştirici modu + Günlükler LoRA Metin İnversiyon İnversiyon @@ -298,8 +299,13 @@ Yerel özel modeli kullanmak için telefonunuzun depolama alanındaki yerel klasöre yerleştirin: Download/SDAi/model Son klasör yapısı şu şekilde olmalıdır:: + Hata ayıklama QA işlemleri + Geliştirici modu kilidi açıldı! + Günlükleri görüntüle + Tüm günlükleri temizle Kötü Base64\'ü DB\'ye yerleştirin + Herhangi bir günlük bulunamadı. Burada hiçbir şey… %1$s sunucusuna biraz içerik ekleyin: \n\n%2$s diff --git a/core/localization/src/main/res/values-uk/strings.xml b/core/localization/src/main/res/values-uk/strings.xml index 8435199d..6326d4ec 100644 --- a/core/localization/src/main/res/values-uk/strings.xml +++ b/core/localization/src/main/res/values-uk/strings.xml @@ -153,7 +153,8 @@ Конфігурація Оберіть SD ML модель Очистити кеш додатку - Дебаг Меню + Меню розробника + Логи ЛоРА Інверсія тексту Інверсія @@ -298,8 +299,13 @@ Щоб використовувати локальну спеціальну модель, помістіть її в локальну папку в пам’яті телефону: Download/SDAi/model Остаточна структура папок має бути такою: + Відладка QA тест-кейси + Режим розробника розблоковано! + Дивитися логи + Видалити всі логи Внести битий Base64 в БД + Лог файл пустий. Тут порожньо… Додайте щось на %1$s сервері до: \n\n%2$s diff --git a/core/localization/src/main/res/values-zh/strings.xml b/core/localization/src/main/res/values-zh/strings.xml index 078471ee..ac0271ae 100644 --- a/core/localization/src/main/res/values-zh/strings.xml +++ b/core/localization/src/main/res/values-zh/strings.xml @@ -191,7 +191,8 @@ 配置 选择SD ML模型 清除应用缓存 - 调试菜单 + 开发者模式 + 日志 LoRA 超网络 H-Net @@ -360,8 +361,13 @@ 最终的文件夹结构应该是: + 调试 QA操作 + 开发者模式已解锁! + 查看日志 + 清除所有日志 在数据库中插入错误的Base64 + 未找到日志。 这里什么都没有… diff --git a/core/localization/src/main/res/values/strings.xml b/core/localization/src/main/res/values/strings.xml index b9f9e542..15cc703a 100755 --- a/core/localization/src/main/res/values/strings.xml +++ b/core/localization/src/main/res/values/strings.xml @@ -171,7 +171,8 @@ Configuration Select SD ML Model Clear app cache - Debug Menu + Developer mode + Logs LoRA Hypernetworks H-Net @@ -320,8 +321,13 @@ To use local custom model, place it to local folder in your phone storage: Download/SDAi/model The final folder structure should be: + Debugging QA actions + Developer mode unlocked! + View logs + Clear all logs Insert bad Base64 in DB + No logs found. Nothing here… Add some content on %1$s server to: \n\n%2$s diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt index 224449af..a883b4cd 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt +++ b/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt @@ -51,6 +51,13 @@ class PreferenceManagerImpl( .apply() .also { onPreferencesChanged() } + override var developerMode: Boolean + get() = preferences.getBoolean(KEY_DEVELOPER_MODE, false) + set(value) = preferences.edit() + .putBoolean(KEY_DEVELOPER_MODE, value) + .apply() + .also { onPreferencesChanged() } + override var monitorConnectivity: Boolean get() = if (!source.featureTags.contains(FeatureTag.OwnServer)) false else preferences.getBoolean(KEY_MONITOR_CONNECTIVITY, true) @@ -232,6 +239,7 @@ class PreferenceManagerImpl( serverUrl = automatic1111ServerUrl, sdModel = sdModel, demoMode = demoMode, + developerMode = developerMode, monitorConnectivity = monitorConnectivity, backgroundGeneration = backgroundGeneration, autoSaveAiResults = autoSaveAiResults, @@ -257,6 +265,7 @@ class PreferenceManagerImpl( const val KEY_SWARM_SERVER_URL = "key_swarm_server_url" const val KEY_SWARM_MODEL = "key_swarm_model" const val KEY_DEMO_MODE = "key_demo_mode" + const val KEY_DEVELOPER_MODE = "key_developer_mode" const val KEY_MONITOR_CONNECTIVITY = "key_monitor_connectivity" const val KEY_AI_AUTO_SAVE = "key_ai_auto_save" const val KEY_SAVE_TO_MEDIA_STORE = "key_save_to_media_store" diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt index 723e5dac..57b66efd 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt @@ -4,6 +4,7 @@ data class Settings( val serverUrl: String = "", val sdModel: String = "", val demoMode: Boolean = false, + val developerMode: Boolean = false, val monitorConnectivity: Boolean = false, val backgroundGeneration: Boolean = false, val autoSaveAiResults: Boolean = false, diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt index bc52c431..3d2649aa 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt @@ -10,6 +10,7 @@ interface PreferenceManager { var swarmUiServerUrl: String var swarmUiModel: String var demoMode: Boolean + var developerMode: Boolean var monitorConnectivity: Boolean var autoSaveAiResults: Boolean var saveToMediaStore: Boolean diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionContract.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionContract.kt index c1dbf677..a59a7afc 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionContract.kt +++ b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionContract.kt @@ -3,6 +3,10 @@ package com.shifthackz.aisdv1.feature.diffusion internal object LocalDiffusionContract { + //region LOGGING + const val TAG = "LocalDiffusion" + //endregion + //region MODELS PATHS const val UNET_MODEL = "unet/model.ort" const val VAE_MODEL = "vae_decoder/model.ort" diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt index b2698c84..26fd9143 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt +++ b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt @@ -2,8 +2,11 @@ package com.shifthackz.aisdv1.feature.diffusion import ai.onnxruntime.OnnxTensor import android.graphics.Bitmap +import com.shifthackz.aisdv1.core.common.log.debugLog +import com.shifthackz.aisdv1.core.common.log.errorLog import com.shifthackz.aisdv1.domain.entity.TextToImagePayload import com.shifthackz.aisdv1.domain.feature.diffusion.LocalDiffusion +import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.TAG import com.shifthackz.aisdv1.feature.diffusion.ai.tokenizer.LocalDiffusionTextTokenizer import com.shifthackz.aisdv1.feature.diffusion.ai.unet.UNet import com.shifthackz.aisdv1.feature.diffusion.environment.OrtEnvironmentProvider @@ -21,15 +24,25 @@ internal class LocalDiffusionImpl( override fun process(payload: TextToImagePayload): Single = Single.create { emitter -> try { + emitter.setCancellable { + debugLog(TAG, "{$TAG} Received cancelable signal.") + interruptGeneration() + } uNet.setCallback(object : UNet.Callback { override fun onStep(maxStep: Int, step: Int) { + debugLog(TAG, "Received step update: ${maxStep}/${step}") statusSubject.onNext(LocalDiffusion.Status(step, maxStep)) } override fun onBuildImage(status: Int, bitmap: Bitmap?) { - if (!emitter.isDisposed) { - bitmap?.let(emitter::onSuccess) ?: emitter.onError(Throwable("Bitmap is null")) - } + bitmap + ?.let(emitter::onSuccess) + ?.also { debugLog("{$TAG} Bitmap built successfully!") } + ?: run { + val t = Throwable("Bitmap is null") + errorLog(t, "{$TAG} Bitmap is null.") + emitter.onError(t) + } } }) @@ -71,15 +84,21 @@ internal class LocalDiffusionImpl( height = payload.height, ) } catch (e: Exception) { - if (!emitter.isDisposed) emitter.onError(e) + errorLog(e, "{$TAG} Caught exception while Local Diffusion process.") + emitter.onError(e) } } // ToDo review method of LocalDiffusion cancellation, now next generation crashes using this approach override fun interrupt() = Completable.fromAction { - tokenizer.close() - uNet.close() + interruptGeneration() } override fun observeStatus() = statusSubject + + private fun interruptGeneration() { + debugLog("{$TAG} Trying to interrupt generation.") + tokenizer.close() + uNet.close() + } } diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt index 9cd3c615..93c01c62 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt +++ b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt @@ -8,11 +8,13 @@ import android.text.TextUtils import android.util.JsonReader import android.util.Pair import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor +import com.shifthackz.aisdv1.core.common.log.debugLog import com.shifthackz.aisdv1.core.common.log.errorLog import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_INPUT_IDS import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT_KEY_MODEL_FORMAT +import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.TAG import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.halfCorner import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.toArrays import com.shifthackz.aisdv1.feature.diffusion.environment.LocalModelIdProvider @@ -44,23 +46,32 @@ internal class EnglishTextTokenizer( override val maxLength = 77 override fun initialize() { - if (session != null) return + if (session != null) { + debugLog("{$TAG} {initialize} Session already initialized, skipping...") + return + } val options = OrtSession.SessionOptions() options.addConfigEntry(ORT_KEY_MODEL_FORMAT, ORT) session = ortEnvironmentProvider.get().createSession( "${modelPathPrefix(fileProviderDescriptor, localModelIdProvider)}/${LocalDiffusionContract.TOKENIZER_MODEL}", options ) + debugLog("{$TAG} {initialize} Session created successfully!") if (!isInitMap) { encoder.putAll(loadEncoder()) decoder.putAll(loadDecoder(encoder)) bpeRanks.putAll(loadBpeRanks()) } isInitMap = true + debugLog("{$TAG} {initialize} Tokenizer map initialized successfully!") } override fun decode(ids: IntArray?): String { - if (ids == null) return "" + debugLog("{$TAG} {decode} Trying to decode ${ids?.size ?: "null"} int array...") + if (ids == null) { + debugLog("{$TAG} {decode} Input ids array is null, skipping.") + return "" + } val stringBuilder = StringBuilder() for (value in ids) { if (decoder.containsKey(value)) stringBuilder.append(decoder[value]) @@ -74,10 +85,13 @@ internal class EnglishTextTokenizer( } val ints = IntArray(result.size) for (i in result.indices) ints[i] = result[i] - return String(ints, 0, ints.size) + val resultString = String(ints, 0, ints.size) + debugLog("{$TAG} {decode} Decode was successful!") + return resultString } override fun encode(text: String?): IntArray { + debugLog("{$TAG} {encode} Trying to encode ${text ?: "null"}...") var input = text input = input.toString().lowercase(Locale.getDefault()).halfCorner() val stringList: MutableList = ArrayList() @@ -113,11 +127,16 @@ internal class EnglishTextTokenizer( Arrays.fill(copy, 49407) System.arraycopy(ids, 0, copy, 0, if (ids.size < copy.size) ids.size else copy.size) copy[copy.size - 1] = 49407 + debugLog("{$TAG} {encode} Encode was successful!") return copy } override fun tensor(ids: IntArray?): OnnxTensor? { - if (ids == null) return null + debugLog("{$TAG} {tensor} Trying to tensor ${ids?.size ?: "null"} int array...") + if (ids == null) { + debugLog("{$TAG} {tensor} Input ids array is null, skipping.") + return null + } val inputIds = OnnxTensor.createTensor( ortEnvironmentProvider.get(), IntBuffer.wrap(ids), @@ -128,14 +147,18 @@ internal class EnglishTextTokenizer( val result = session!!.run(input) val lastHiddenState = result[0].value result.close() - return OnnxTensor.createTensor(ortEnvironmentProvider.get(), lastHiddenState) + val tensor = OnnxTensor.createTensor(ortEnvironmentProvider.get(), lastHiddenState) + debugLog("{$TAG} {tensor} Tensor formation was successful!") + return tensor } override fun createUnconditionalInput(text: String?): IntArray = encode(text) override fun close() { + debugLog("{$TAG} {close} Closing session...") session?.close() session = null + debugLog("{$TAG} Session closed successfully!") } private fun bpe(token: String): List { diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt index ba8f13b7..9a84d08e 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt +++ b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt @@ -9,6 +9,7 @@ import ai.onnxruntime.providers.NNAPIFlags import android.graphics.Bitmap import android.util.Pair import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor +import com.shifthackz.aisdv1.core.common.log.debugLog import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_ENCODER_HIDDEN_STATES import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_LATENT_SAMPLE @@ -16,6 +17,7 @@ import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_SAMPLE import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.KEY_TIME_STEP import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.ORT_KEY_MODEL_FORMAT +import com.shifthackz.aisdv1.feature.diffusion.LocalDiffusionContract.TAG import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.duplicate import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.getSizes import com.shifthackz.aisdv1.feature.diffusion.ai.extensions.multipleTensorsByFloat @@ -155,9 +157,17 @@ internal class UNet( width: Int, height: Int, ) { + debugLog("{$TAG} {inference} Trying to start inference:") + debugLog("{$TAG} {inference} - seed: $seedNum") + debugLog("{$TAG} {inference} - numInferenceSteps: $numInferenceSteps") + debugLog("{$TAG} {inference} - textEmbeddings: $textEmbeddings") + debugLog("{$TAG} {inference} - guidanceScale: $guidanceScale") + debugLog("{$TAG} {inference} - batchSize: $batchSize") + debugLog("{$TAG} {inference} - size: ${width}x${height}") this.width = width this.height = height val localDiffusionScheduler = EulerAncestralDiscreteLocalDiffusionScheduler() + debugLog("{$TAG} {inference} Initialized scheduler: $localDiffusionScheduler") val timeSteps: IntArray = localDiffusionScheduler.setTimeSteps(numInferenceSteps) val seed = if (seedNum <= 0) random.nextLong() else seedNum var latents: LocalDiffusionTensor<*> = generateLatentSample( @@ -167,13 +177,19 @@ internal class UNet( seed, localDiffusionScheduler.initNoiseSigma.toFloat() ) + debugLog("{$TAG} {inference} Got latents: ${latents.hashCode()}") val shape = longArrayOf(2, 4, (height / 8).toLong(), (width / 8).toLong()) + debugLog("{$TAG} {inference} Got shape: $shape") + debugLog("{$TAG} {inference} Starting steps processing! Total : ${timeSteps.size}") for (i in timeSteps.indices) { var latentModelInput: LocalDiffusionTensor<*> = duplicate( latents.tensor.floatBuffer.array(), shape, ) latentModelInput = localDiffusionScheduler.scaleModelInput(latentModelInput, i) + debugLog("{$TAG} {inference} {Step_$i} ------------------") + debugLog("{$TAG} {inference} {Step_$i} Latent model input: $latentModelInput") + debugLog("{$TAG} {inference} {Step_$i} Notifying callback about step.") callback?.onStep(timeSteps.size, i) val input = createUNetModelInput( textEmbeddings, @@ -184,8 +200,11 @@ internal class UNet( longArrayOf(1) ) ) + debugLog("{$TAG} {inference} {Step_$i} Got uNet model input: $input") val result = session!!.run(input) + debugLog("{$TAG} {inference} {Step_$i} Got result from uNet session: $result") val dataSet = result[0].value as Array3D + debugLog("{$TAG} {inference} {Step_$i} Trying to close ORT session in: $result") result.close() val splitTensors: Pair, Array3D> = splitTensor( @@ -194,7 +213,13 @@ internal class UNet( ) val noisePrediction = splitTensors.first val noisePredictionText = splitTensors.second + debugLog("{$TAG} {inference} {Step_$i} Got split tensors with prediction:") + debugLog("{$TAG} {inference} {Step_$i} - splitTensors: $splitTensors") + debugLog("{$TAG} {inference} {Step_$i} - noisePrediction: $noisePrediction") + debugLog("{$TAG} {inference} {Step_$i} - noisePredictionText: $noisePredictionText") + debugLog("{$TAG} {inference} {Step_$i} Trying to preform guidance...") performGuidance(noisePrediction, noisePredictionText, guidanceScale) + debugLog("{$TAG} {inference} {Step_$i} Guidance performed successfully!") latents = localDiffusionScheduler.step( LocalDiffusionTensor( OnnxTensor.createTensor( @@ -207,16 +232,22 @@ internal class UNet( i, latents, ) + debugLog("{$TAG} {inference} {Step_$i} Finalized latents: $latents") + debugLog("{$TAG} {inference} {Step_$i} ------------------") } callback?.also { clb -> + debugLog("{$TAG} {inference} Finalization / Flushing image...") callback?.onStep(timeSteps.size, timeSteps.size) val bitmap = decode(latents) + debugLog("{$TAG} {inference} Finalization / Decoded bitmap: ${bitmap.hashCode()}") clb.onBuildImage(0, bitmap) + debugLog("{$TAG} {inference} Finalization / Notifying callback and closing session.") close() } } fun decode(latents: LocalDiffusionTensor<*>): Bitmap { + debugLog("{$TAG} {decode} Trying to decode latents: ${latents.hashCode()}") val tensor: LocalDiffusionTensor<*> = multipleTensorsByFloat( latents.tensor.floatBuffer.array(), 1.0f / 0.18215f, @@ -225,21 +256,26 @@ internal class UNet( val decoderInput: MutableMap = HashMap() decoderInput[KEY_LATENT_SAMPLE] = tensor.tensor val value: Any = decoder!!.decode(decoderInput.toMap()) - return decoder!!.convertToImage( + val bitmap = decoder!!.convertToImage( value as Array3D, width, height, ) + debugLog("{$TAG} {decode} Bitmap generated successfully: ${bitmap.hashCode()}") + return bitmap } fun close() { + debugLog("{$TAG} Closing session...") session?.close() decoder?.close() session = null decoder = null + debugLog("{$TAG} Session closed successfully!") } fun setCallback(callback: Callback?) { + debugLog("{$TAG} Setting new result callback ${callback.hashCode()}") this.callback = callback } diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt index 1dd69c12..501f34f3 100644 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt +++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt @@ -118,7 +118,6 @@ internal abstract class CoreGenerationWorker( } protected fun handleError(t: Throwable) { - errorLog(t) backgroundWorkObserver.postFailedSignal(t) val title = applicationContext.getString(LocalizationR.string.notification_fail_title) val subTitle = applicationContext.getString(LocalizationR.string.notification_fail_sub_title) diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt index b392fd68..7e8d0808 100644 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt +++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt @@ -4,6 +4,8 @@ import android.content.Context import androidx.work.WorkerParameters import com.shifthackz.aisdv1.core.common.appbuild.ActivityIntentProvider import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor +import com.shifthackz.aisdv1.core.common.log.debugLog +import com.shifthackz.aisdv1.core.common.log.errorLog import com.shifthackz.aisdv1.core.notification.PushNotificationManager import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver import com.shifthackz.aisdv1.domain.preference.PreferenceManager @@ -54,6 +56,7 @@ internal class TextToImageTask( handleError(Throwable("Background process count > 0")) compositeDisposable.clear() preferenceManager.backgroundProcessCount = 0 + debugLog("Background process count > 0! Skipping task.") return Single.just(Result.failure()) } @@ -61,13 +64,16 @@ internal class TextToImageTask( handleStart() backgroundWorkObserver.refreshStatus() backgroundWorkObserver.dismissResult() + debugLog("Starting TextToImageTask!") return try { val file = File(fileProviderDescriptor.workCacheDirPath, Constants.FILE_TEXT_TO_IMAGE) if (!file.exists()) { preferenceManager.backgroundProcessCount-- - handleError(Throwable("File is null.")) + val t = Throwable("File is null.") + handleError(t) compositeDisposable.clear() + errorLog(t, "Payload file does not exist.") return Single.just(Result.failure()) } @@ -76,8 +82,10 @@ internal class TextToImageTask( if (payload == null) { preferenceManager.backgroundProcessCount-- - handleError(Throwable("Payload is null.")) + val t = Throwable("Payload is null.") + handleError(t) compositeDisposable.clear() + errorLog(t, "Payload was failed to read/parse.") return Single.just(Result.failure()) } @@ -89,11 +97,13 @@ internal class TextToImageTask( .map { result -> preferenceManager.backgroundProcessCount-- handleSuccess(result) + debugLog("Generation finished successfully!") Result.success() } .onErrorReturn { t -> preferenceManager.backgroundProcessCount-- handleError(t) + errorLog(t, "Caught exception from TextToImageUseCase!") Result.failure() } .doFinally { compositeDisposable.clear() } @@ -101,6 +111,7 @@ internal class TextToImageTask( preferenceManager.backgroundProcessCount-- handleError(e) compositeDisposable.clear() + errorLog(e, "Caught exception from TextToImageTask worker!") Single.just(Result.failure()) } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/ViewModelModule.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/ViewModelModule.kt index cbaa637f..a9a1aac4 100755 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/ViewModelModule.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/di/ViewModelModule.kt @@ -14,6 +14,7 @@ import com.shifthackz.aisdv1.presentation.screen.home.HomeNavigationViewModel import com.shifthackz.aisdv1.presentation.screen.img2img.ImageToImageViewModel import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintViewModel import com.shifthackz.aisdv1.presentation.screen.loader.ConfigurationLoaderViewModel +import com.shifthackz.aisdv1.presentation.screen.logger.LoggerViewModel import com.shifthackz.aisdv1.presentation.screen.settings.SettingsViewModel import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupLaunchSource import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupViewModel @@ -50,6 +51,7 @@ val viewModelModule = module { viewModelOf(::WebUiViewModel) viewModelOf(::DonateViewModel) viewModelOf(::BackgroundWorkViewModel) + viewModelOf(::LoggerViewModel) viewModel { parameters -> val launchSource = ServerSetupLaunchSource.fromKey(parameters.get()) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/MainNavGraph.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/MainNavGraph.kt index a654de47..ef8c49a4 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/MainNavGraph.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/MainNavGraph.kt @@ -10,6 +10,7 @@ import com.shifthackz.aisdv1.presentation.screen.donate.DonateScreen import com.shifthackz.aisdv1.presentation.screen.gallery.detail.GalleryDetailScreen import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintScreen import com.shifthackz.aisdv1.presentation.screen.loader.ConfigurationLoaderScreen +import com.shifthackz.aisdv1.presentation.screen.logger.LoggerScreen import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupLaunchSource import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupScreen import com.shifthackz.aisdv1.presentation.screen.splash.SplashScreen @@ -65,6 +66,13 @@ fun NavGraphBuilder.mainNavGraph() { route = Constants.ROUTE_DEBUG } ) + addDestination( + ComposeNavigator.Destination(provider[ComposeNavigator::class]) { + LoggerScreen() + }.apply { + route = Constants.ROUTE_LOGGER + } + ) addDestination( ComposeNavigator.Destination(provider[ComposeNavigator::class]) { InPaintScreen() diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouter.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouter.kt index f0b70dd9..7f64a566 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouter.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouter.kt @@ -21,4 +21,6 @@ interface MainRouter : Router { fun navigateToDonate() fun navigateToDebugMenu() + + fun navigateToLogger() } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImpl.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImpl.kt index 873874a2..24a8c52c 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImpl.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImpl.kt @@ -56,8 +56,10 @@ internal class MainRouterImpl( } override fun navigateToDebugMenu() { - if (debugMenuAccessor()) { - effectSubject.onNext(NavigationEffect.Navigate.Route(Constants.ROUTE_DEBUG)) - } + effectSubject.onNext(NavigationEffect.Navigate.Route(Constants.ROUTE_DEBUG)) + } + + override fun navigateToLogger() { + effectSubject.onNext(NavigationEffect.Navigate.Route(Constants.ROUTE_LOGGER)) } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuAccessor.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuAccessor.kt index 5681de85..b080c44b 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuAccessor.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuAccessor.kt @@ -1,19 +1,23 @@ package com.shifthackz.aisdv1.presentation.screen.debug -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider +import com.shifthackz.aisdv1.domain.preference.PreferenceManager import com.shifthackz.aisdv1.presentation.utils.Constants -class DebugMenuAccessor(private val buildInfoProvider: BuildInfoProvider) { +class DebugMenuAccessor( + private val preferenceManager: PreferenceManager, +) { private var tapCount = 0; operator fun invoke(): Boolean { - if (buildInfoProvider.isDebug) { - tapCount++ - if (tapCount >= Constants.DEBUG_MENU_ACCESS_TAPS) { - tapCount = 0; - return true - } + if (preferenceManager.developerMode) { + return true + } + tapCount++ + if (tapCount >= Constants.DEBUG_MENU_ACCESS_TAPS) { + tapCount = 0 + preferenceManager.developerMode = true + return true } return false } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt index 22c7e1f3..4502ddbb 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt @@ -4,5 +4,7 @@ import com.shifthackz.android.core.mvi.MviIntent enum class DebugMenuIntent : MviIntent { NavigateBack, + ViewLogs, + ClearLogs, InsertBadBase64; } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt index 06cded3f..1f052412 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt @@ -9,7 +9,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.TextSnippet import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.filled.CleaningServices import androidx.compose.material.icons.filled.SettingsEthernet import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api @@ -25,6 +27,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.shifthackz.aisdv1.core.model.asUiText import com.shifthackz.aisdv1.core.ui.MviComponent +import com.shifthackz.aisdv1.presentation.widget.item.SettingsHeader import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem import org.koin.androidx.compose.koinViewModel import com.shifthackz.aisdv1.core.localization.R as LocalizationR @@ -81,10 +84,26 @@ private fun ScreenContent( .fillMaxWidth() .padding(bottom = 8.dp) - Text( + SettingsHeader( modifier = headerModifier, - text = stringResource(id = LocalizationR.string.debug_section_qa), - style = MaterialTheme.typography.headlineSmall, + text = LocalizationR.string.debug_section_main.asUiText(), + ) + SettingsItem( + modifier = itemModifier, + startIcon = Icons.AutoMirrored.Filled.TextSnippet, + text = LocalizationR.string.debug_action_logger.asUiText(), + onClick = { processIntent(DebugMenuIntent.ViewLogs) }, + ) + SettingsItem( + modifier = itemModifier, + startIcon = Icons.Default.CleaningServices, + text = LocalizationR.string.debug_action_logger_clear.asUiText(), + onClick = { processIntent(DebugMenuIntent.ClearLogs) }, + ) + + SettingsHeader( + modifier = headerModifier, + text = LocalizationR.string.debug_section_qa.asUiText(), ) SettingsItem( modifier = itemModifier, diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt index fbcbacd3..f7b54670 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt @@ -1,5 +1,7 @@ package com.shifthackz.aisdv1.presentation.screen.debug +import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor +import com.shifthackz.aisdv1.core.common.log.FileLoggingTree import com.shifthackz.aisdv1.core.common.log.errorLog import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread @@ -11,6 +13,7 @@ import com.shifthackz.android.core.mvi.EmptyState import io.reactivex.rxjava3.kotlin.subscribeBy class DebugMenuViewModel( + private val fileProviderDescriptor: FileProviderDescriptor, private val debugInsertBadBase64UseCase: DebugInsertBadBase64UseCase, private val schedulersProvider: SchedulersProvider, private val mainRouter: MainRouter, @@ -21,9 +24,16 @@ class DebugMenuViewModel( override fun processIntent(intent: DebugMenuIntent) { when (intent) { DebugMenuIntent.NavigateBack -> mainRouter.navigateBack() + DebugMenuIntent.InsertBadBase64 -> !debugInsertBadBase64UseCase() .subscribeOnMainThread(schedulersProvider) .subscribeBy(::errorLog) + + DebugMenuIntent.ClearLogs -> { + FileLoggingTree.clearLog(fileProviderDescriptor) + } + + DebugMenuIntent.ViewLogs -> mainRouter.navigateToLogger() } } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerIntent.kt new file mode 100644 index 00000000..a1fc441c --- /dev/null +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerIntent.kt @@ -0,0 +1,10 @@ +package com.shifthackz.aisdv1.presentation.screen.logger + +import com.shifthackz.android.core.mvi.MviIntent + +sealed interface LoggerIntent : MviIntent { + + data object ReadLogs : LoggerIntent + + data object NavigateBack : LoggerIntent +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt new file mode 100644 index 00000000..e0dc2692 --- /dev/null +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt @@ -0,0 +1,198 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package com.shifthackz.aisdv1.presentation.screen.logger + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.filled.ArrowDownward +import androidx.compose.material.icons.filled.ArrowUpward +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.shifthackz.aisdv1.core.ui.MviComponent +import kotlinx.coroutines.launch +import org.koin.androidx.compose.koinViewModel +import com.shifthackz.aisdv1.core.localization.R as LocalizationR + +@Composable +fun LoggerScreen() { + MviComponent( + viewModel = koinViewModel(), + ) { state, processIntent -> + LoggerScreenContent( + state = state, + processIntent = processIntent, + ) + } +} + +@Composable +@Preview +private fun LoggerScreenContent( + state: LoggerState = LoggerState(), + processIntent: (LoggerIntent) -> Unit = {}, +) { + val scrollState = rememberScrollState() + val scope = rememberCoroutineScope() + Scaffold( + topBar = { + CenterAlignedTopAppBar( + navigationIcon = { + IconButton( + onClick = { processIntent(LoggerIntent.NavigateBack) }, + content = { + Icon( + Icons.AutoMirrored.Outlined.ArrowBack, + contentDescription = "Back button", + ) + }, + ) + }, + title = { + Text( + text = stringResource(id = LocalizationR.string.title_debug_logger), + style = MaterialTheme.typography.headlineMedium, + ) + }, + actions = { + AnimatedVisibility( + visible = !state.loading, + enter = fadeIn(), + exit = fadeOut(), + ) { + IconButton( + onClick = { + processIntent(LoggerIntent.ReadLogs) + }, + content = { + Icon( + Icons.Default.Refresh, + contentDescription = "Refresh", + ) + }, + ) + } + } + ) + }, + bottomBar = { + AnimatedVisibility( + visible = !state.loading && state.text.isNotBlank(), + enter = fadeIn(), + exit = fadeOut(), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End), + ) { + IconButton( + onClick = { + scope.launch { + scrollState.animateScrollTo(0) + } + }, + content = { + Icon( + Icons.Default.ArrowUpward, + contentDescription = "Down", + ) + }, + ) + IconButton( + onClick = { + scope.launch { + scrollState.animateScrollTo(scrollState.maxValue) + } + }, + content = { + Icon( + Icons.Default.ArrowDownward, + contentDescription = "Down", + ) + }, + ) + } + } + + } + ) { paddingValues -> + val scrollState2 = rememberScrollState() + if (!state.loading && state.text.isBlank()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + contentAlignment = Alignment.Center, + ) { + Text( + text = stringResource(id = LocalizationR.string.debug_logger_empty), + textAlign = TextAlign.Center, + ) + } + } + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .verticalScroll(scrollState), + ) { + AnimatedVisibility( + visible = state.loading, + enter = fadeIn(), + exit = fadeOut(), + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator( + modifier = Modifier + .size(60.dp) + .aspectRatio(1f), + ) + } + } + + Text( + modifier = Modifier + .horizontalScroll(scrollState2) + , + text = if (!state.loading) state.text else "", + fontFamily = FontFamily.Monospace, + fontSize = 11.sp, + lineHeight = 12.sp, + ) + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerState.kt new file mode 100644 index 00000000..0571411a --- /dev/null +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerState.kt @@ -0,0 +1,10 @@ +package com.shifthackz.aisdv1.presentation.screen.logger + +import androidx.compose.runtime.Immutable +import com.shifthackz.android.core.mvi.MviState + +@Immutable +data class LoggerState( + val loading: Boolean = true, + val text: String = "", +) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModel.kt new file mode 100644 index 00000000..21e503f3 --- /dev/null +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModel.kt @@ -0,0 +1,47 @@ +package com.shifthackz.aisdv1.presentation.screen.logger + +import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor +import com.shifthackz.aisdv1.core.common.log.FileLoggingTree +import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel +import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter +import com.shifthackz.android.core.mvi.EmptyEffect +import java.io.File + +class LoggerViewModel( + private val fileProviderDescriptor: FileProviderDescriptor, + private val mainRouter: MainRouter, +) : MviRxViewModel() { + + override val initialState = LoggerState() + + init { + readLogs() + } + + override fun processIntent(intent: LoggerIntent) { + when (intent) { + LoggerIntent.ReadLogs -> readLogs() + LoggerIntent.NavigateBack -> mainRouter.navigateBack() + } + } + + private fun readLogs() { + updateState { it.copy(loading = true, text = "") } + try { + val logFile = File( + fileProviderDescriptor.logsCacheDirPath + + "/" + + FileLoggingTree.LOGGER_FILENAME + ) + val content = logFile.readText() + updateState { + it.copy( + loading = false, + text = content, + ) + } + } catch (e: Exception) { + updateState { it.copy(loading = false) } + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsEffect.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsEffect.kt index 664c0cf5..f43aeca4 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsEffect.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsEffect.kt @@ -11,5 +11,7 @@ sealed interface SettingsEffect : MviEffect { data object ShareLogFile : SettingsEffect + data object DeveloperModeUnlocked : SettingsEffect + data class OpenUrl(val url: String) : SettingsEffect } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsIntent.kt index ff8b0fbc..be85a4fb 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsIntent.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsIntent.kt @@ -13,6 +13,8 @@ sealed interface SettingsIntent : MviIntent { data object NavigateConfiguration : SettingsIntent + data object NavigateDeveloperMode : SettingsIntent + sealed interface SdModel : SettingsIntent { data object OpenChooser : SdModel diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsScreen.kt index de5bac26..f4757741 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsScreen.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.material.icons.filled.Code import androidx.compose.material.icons.filled.ColorLens import androidx.compose.material.icons.filled.DarkMode import androidx.compose.material.icons.filled.DeleteForever +import androidx.compose.material.icons.filled.DeveloperMode import androidx.compose.material.icons.filled.DynamicForm import androidx.compose.material.icons.filled.Folder import androidx.compose.material.icons.filled.FormatColorFill @@ -60,6 +61,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.shifthackz.aisdv1.core.common.extensions.openUrl +import com.shifthackz.aisdv1.core.common.extensions.showToast import com.shifthackz.aisdv1.core.common.math.roundTo import com.shifthackz.aisdv1.core.model.UiText import com.shifthackz.aisdv1.core.model.asUiText @@ -115,6 +117,9 @@ fun SettingsScreen() { } SettingsEffect.ShareLogFile -> ReportProblemEmailComposer().invoke(context) is SettingsEffect.OpenUrl -> context.openUrl(effect.url) + SettingsEffect.DeveloperModeUnlocked -> context.showToast( + LocalizationR.string.debug_action_unlock, + ) } }, applySystemUiColors = false, @@ -297,6 +302,15 @@ private fun ContentSettingsState( style = MaterialTheme.typography.labelMedium, ) } + AnimatedVisibility(visible = !state.loading && state.developerMode) { + SettingsItem( + modifier = itemModifier, + loading = state.loading, + startIcon = Icons.Default.DeveloperMode, + text = LocalizationR.string.title_debug_menu.asUiText(), + onClick = { processIntent(SettingsIntent.NavigateDeveloperMode) }, + ) + } //endregion //region APP SETTINGS diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsState.kt index e8bb7490..432ec831 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsState.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsState.kt @@ -29,6 +29,7 @@ data class SettingsState( val colorToken: ColorToken = ColorToken.MAUVE, val darkThemeToken: DarkThemeToken = DarkThemeToken.FRAPPE, val galleryGrid: Grid = Grid.Fixed2, + val developerMode: Boolean = false, val appVersion: String = "", ) : MviState { diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModel.kt index d6bd101b..ff9c9cd7 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModel.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModel.kt @@ -19,6 +19,7 @@ import com.shifthackz.aisdv1.domain.usecase.stabilityai.ObserveStabilityAiCredit import com.shifthackz.aisdv1.presentation.model.Modal import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter +import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuAccessor import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupLaunchSource import io.reactivex.rxjava3.core.Flowable @@ -32,6 +33,7 @@ class SettingsViewModel( private val clearAppCacheUseCase: ClearAppCacheUseCase, private val schedulersProvider: SchedulersProvider, private val preferenceManager: PreferenceManager, + private val debugMenuAccessor: DebugMenuAccessor, private val buildInfoProvider: BuildInfoProvider, private val mainRouter: MainRouter, private val drawerRouter: DrawerRouter, @@ -80,6 +82,7 @@ class SettingsViewModel( colorToken = ColorToken.parse(settings.designColorToken), darkThemeToken = DarkThemeToken.parse(settings.designDarkThemeToken), galleryGrid = settings.galleryGrid, + developerMode = settings.developerMode, appVersion = version, ) } @@ -89,7 +92,9 @@ class SettingsViewModel( override fun processIntent(intent: SettingsIntent) { when (intent) { - SettingsIntent.Action.AppVersion -> mainRouter.navigateToDebugMenu() + SettingsIntent.Action.AppVersion -> if (debugMenuAccessor()) { + emitEffect(SettingsEffect.DeveloperModeUnlocked) + } SettingsIntent.Action.ClearAppCache.Request -> updateState { it.copy(screenModal = Modal.ClearAppCache) @@ -107,6 +112,8 @@ class SettingsViewModel( ServerSetupLaunchSource.SETTINGS ) + SettingsIntent.NavigateDeveloperMode -> mainRouter.navigateToDebugMenu() + SettingsIntent.SdModel.OpenChooser -> updateState { it.copy( screenModal = Modal.SelectSdModel( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/Constants.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/Constants.kt index 43f101f0..ad1e0095 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/Constants.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/Constants.kt @@ -20,6 +20,7 @@ object Constants { const val ROUTE_GALLERY_DETAIL_FULL = "$ROUTE_GALLERY_DETAIL/{$PARAM_ITEM_ID}" const val ROUTE_SETTINGS = "settings" const val ROUTE_DEBUG = "debug" + const val ROUTE_LOGGER = "logger" const val ROUTE_IN_PAINT = "in_paint" const val ROUTE_DONATE = "donate" diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImplTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImplTest.kt index 64527033..ecec9c5c 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImplTest.kt +++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/navigation/router/main/MainRouterImplTest.kt @@ -1,21 +1,26 @@ package com.shifthackz.aisdv1.presentation.navigation.router.main -import com.shifthackz.aisdv1.core.common.appbuild.BuildInfoProvider +import com.shifthackz.aisdv1.domain.preference.PreferenceManager import com.shifthackz.aisdv1.presentation.navigation.NavigationEffect import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuAccessor import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupLaunchSource import com.shifthackz.aisdv1.presentation.utils.Constants -import io.mockk.every import io.mockk.mockk +import org.junit.Before import org.junit.Test class MainRouterImplTest { - private val stubBuildInfoProvider = mockk() - private val stubDebugMenuAccessor = DebugMenuAccessor(stubBuildInfoProvider) + private val stubPreferenceManager = mockk() + private val stubDebugMenuAccessor = DebugMenuAccessor(stubPreferenceManager) private val router = MainRouterImpl(stubDebugMenuAccessor) + @Before + fun initialize() { + + } + @Test fun `given user navigates back, expected router emits Back event`() { router @@ -105,63 +110,14 @@ class MainRouterImplTest { ) } - @Test - fun `given user tapped hidden menu 6 times, build is debuggable, expected router emits no events`() { - every { - stubBuildInfoProvider.isDebug - } returns true - - val stubObserver = router.observe().test() - - repeat(6) { router.navigateToDebugMenu() } - - stubObserver - .assertNoErrors() - .assertNoValues() - } - @Test fun `given user tapped hidden menu 7 times, build is debuggable, expected router emits Route event with ROUTE_DEBUG route`() { - every { - stubBuildInfoProvider.isDebug - } returns true - val stubObserver = router.observe().test() - repeat(7) { router.navigateToDebugMenu() } + router.navigateToDebugMenu() stubObserver .assertNoErrors() .assertValueAt(0, NavigationEffect.Navigate.Route(Constants.ROUTE_DEBUG)) } - - @Test - fun `given user tapped hidden menu 6 times, build is NOT debuggable, expected router emits no events`() { - every { - stubBuildInfoProvider.isDebug - } returns false - - val stubObserver = router.observe().test() - - repeat(6) { router.navigateToDebugMenu() } - - stubObserver - .assertNoErrors() - .assertNoValues() - } - - @Test - fun `given user tapped hidden menu 7 times, build is NOT debuggable, expected router emits no events`() { - every { - stubBuildInfoProvider.isDebug - } returns false - - val stubObserver = router.observe().test() - - repeat(7) { router.navigateToDebugMenu() } - - stubObserver - .assertNoErrors() - .assertNoValues() - } } diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt index e501b0f3..73d56fd8 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt +++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt @@ -1,5 +1,6 @@ package com.shifthackz.aisdv1.presentation.screen.debug +import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCase import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter @@ -12,10 +13,12 @@ import org.junit.Test class DebugMenuViewModelTest : CoreViewModelTest() { + private val stubFileProviderDescriptor = mockk() private val stubDebugInsertBadBase64UseCase = mockk() private val stubMainRouter = mockk() override fun initializeViewModel() = DebugMenuViewModel( + fileProviderDescriptor = stubFileProviderDescriptor, debugInsertBadBase64UseCase = stubDebugInsertBadBase64UseCase, schedulersProvider = stubSchedulersProvider, mainRouter = stubMainRouter, diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModelTest.kt new file mode 100644 index 00000000..2840ba85 --- /dev/null +++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerViewModelTest.kt @@ -0,0 +1,56 @@ +package com.shifthackz.aisdv1.presentation.screen.logger + +import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor +import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest +import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class LoggerViewModelTest : CoreViewModelTest() { + + private val stubFileProviderDescriptor = mockk() + private val stubMainRouter = mockk() + + override fun initializeViewModel() = LoggerViewModel( + fileProviderDescriptor = stubFileProviderDescriptor, + mainRouter = stubMainRouter, + ) + + @Before + override fun initialize() { + super.initialize() + every { + stubFileProviderDescriptor.logsCacheDirPath + } returns "/tmp/local" + } + + @Test + fun `initialize, read logs, expected loaded state`() { + runTest { + val expected = LoggerState( + loading = false, + text = "" + ) + val actual = viewModel.state.value + Assert.assertEquals(expected, actual) + } + } + + @Test + fun `given received NavigateBack intent, expected router navigateBack() called`() { + every { + stubMainRouter.navigateBack() + } returns Unit + + viewModel.processIntent(LoggerIntent.NavigateBack) + + verify { + stubMainRouter.navigateBack() + } + } +} diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModelTest.kt index 2b93515e..5aa56c54 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModelTest.kt +++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/settings/SettingsViewModelTest.kt @@ -16,6 +16,7 @@ import com.shifthackz.aisdv1.presentation.mocks.mockStableDiffusionModels import com.shifthackz.aisdv1.presentation.model.Modal import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter +import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuAccessor import com.shifthackz.aisdv1.presentation.screen.setup.ServerSetupLaunchSource import com.shifthackz.aisdv1.presentation.stub.stubSchedulersProvider import io.mockk.every @@ -46,6 +47,7 @@ class SettingsViewModelTest : CoreViewModelTest() { private val stubBuildInfoProvider = mockk() private val stubMainRouter = mockk() private val stubDrawerRouter = mockk() + private val stubDebugMenuAccessor = mockk() override fun initializeViewModel() = SettingsViewModel( getStableDiffusionModelsUseCase = stubGetStableDiffusionModelsUseCase, @@ -57,6 +59,7 @@ class SettingsViewModelTest : CoreViewModelTest() { buildInfoProvider = stubBuildInfoProvider, mainRouter = stubMainRouter, drawerRouter = stubDrawerRouter, + debugMenuAccessor = stubDebugMenuAccessor, ) @Before @@ -90,15 +93,17 @@ class SettingsViewModelTest : CoreViewModelTest() { } @Test - fun `given received Action AppVersion intent, expected router navigateToDebugMenu() method called`() { + fun `given received Action AppVersion intent, expected DeveloperModeUnlocked effect delivered to effect collector`() { every { - stubMainRouter.navigateToDebugMenu() - } returns Unit + stubDebugMenuAccessor.invoke() + } returns true viewModel.processIntent(SettingsIntent.Action.AppVersion) - verify { - stubMainRouter.navigateToDebugMenu() + runTest { + viewModel.effect.test { + Assert.assertEquals(SettingsEffect.DeveloperModeUnlocked, awaitItem()) + } } } From 81a6cddcf12bbc5dc5c85f9613fb232e4e821bd8 Mon Sep 17 00:00:00 2001 From: ShiftHackZ Date: Sun, 11 Aug 2024 15:07:42 +0300 Subject: [PATCH 02/10] Implement cancel LD test flag --- .../src/main/res/values/strings.xml | 2 ++ .../data/preference/PreferenceManagerImpl.kt | 9 +++++++ .../aisdv1/domain/entity/Settings.kt | 1 + .../domain/preference/PreferenceManager.kt | 1 + .../feature/diffusion/LocalDiffusionImpl.kt | 2 ++ .../aisdv1/work/core/CoreGenerationWorker.kt | 11 +++++++-- .../aisdv1/work/di/SdaiWorkerFactory.kt | 4 ++++ .../aisdv1/work/task/ImageToImageTask.kt | 3 +++ .../aisdv1/work/task/TextToImageTask.kt | 3 +++ .../presentation/modal/ModalRenderer.kt | 7 ++++++ .../aisdv1/presentation/model/Modal.kt | 5 +++- .../screen/debug/DebugMenuIntent.kt | 1 + .../screen/debug/DebugMenuScreen.kt | 24 ++++++++++++++++++- .../screen/debug/DebugMenuState.kt | 7 ++++++ .../screen/debug/DebugMenuViewModel.kt | 22 ++++++++++++++--- .../screen/txt2img/TextToImageViewModel.kt | 14 +++++------ .../screen/debug/DebugMenuViewModelTest.kt | 15 ++++++++++++ 17 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt diff --git a/core/localization/src/main/res/values/strings.xml b/core/localization/src/main/res/values/strings.xml index 15cc703a..e0e7ba48 100755 --- a/core/localization/src/main/res/values/strings.xml +++ b/core/localization/src/main/res/values/strings.xml @@ -322,10 +322,12 @@ The final folder structure should be: Debugging + Local Diffusion QA actions Developer mode unlocked! View logs Clear all logs + Allow to interrupt generation Insert bad Base64 in DB No logs found. diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt index a883b4cd..e6a2c85f 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt +++ b/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt @@ -58,6 +58,13 @@ class PreferenceManagerImpl( .apply() .also { onPreferencesChanged() } + override var allowLocalDiffusionCancel: Boolean + get() = preferences.getBoolean(KEY_ALLOW_LOCAL_DIFFUSION_CANCEL, false) + set(value) = preferences.edit() + .putBoolean(KEY_ALLOW_LOCAL_DIFFUSION_CANCEL, value) + .apply() + .also { onPreferencesChanged() } + override var monitorConnectivity: Boolean get() = if (!source.featureTags.contains(FeatureTag.OwnServer)) false else preferences.getBoolean(KEY_MONITOR_CONNECTIVITY, true) @@ -240,6 +247,7 @@ class PreferenceManagerImpl( sdModel = sdModel, demoMode = demoMode, developerMode = developerMode, + allowLocalDiffusionCancel = allowLocalDiffusionCancel, monitorConnectivity = monitorConnectivity, backgroundGeneration = backgroundGeneration, autoSaveAiResults = autoSaveAiResults, @@ -266,6 +274,7 @@ class PreferenceManagerImpl( const val KEY_SWARM_MODEL = "key_swarm_model" const val KEY_DEMO_MODE = "key_demo_mode" const val KEY_DEVELOPER_MODE = "key_developer_mode" + const val KEY_ALLOW_LOCAL_DIFFUSION_CANCEL = "key_allow_local_diffusion_cancel" const val KEY_MONITOR_CONNECTIVITY = "key_monitor_connectivity" const val KEY_AI_AUTO_SAVE = "key_ai_auto_save" const val KEY_SAVE_TO_MEDIA_STORE = "key_save_to_media_store" diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt index 57b66efd..7bfae59a 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt @@ -5,6 +5,7 @@ data class Settings( val sdModel: String = "", val demoMode: Boolean = false, val developerMode: Boolean = false, + val allowLocalDiffusionCancel: Boolean = false, val monitorConnectivity: Boolean = false, val backgroundGeneration: Boolean = false, val autoSaveAiResults: Boolean = false, diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt index 3d2649aa..bbc1b230 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt @@ -11,6 +11,7 @@ interface PreferenceManager { var swarmUiModel: String var demoMode: Boolean var developerMode: Boolean + var allowLocalDiffusionCancel: Boolean var monitorConnectivity: Boolean var autoSaveAiResults: Boolean var saveToMediaStore: Boolean diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt index 26fd9143..56b3f39d 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt +++ b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/LocalDiffusionImpl.kt @@ -85,6 +85,7 @@ internal class LocalDiffusionImpl( ) } catch (e: Exception) { errorLog(e, "{$TAG} Caught exception while Local Diffusion process.") + interruptGeneration() emitter.onError(e) } } @@ -100,5 +101,6 @@ internal class LocalDiffusionImpl( debugLog("{$TAG} Trying to interrupt generation.") tokenizer.close() uNet.close() + debugLog("{$TAG} Generation interrupt successful!") } } diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt index 501f34f3..f7dc1a9c 100644 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt +++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt @@ -13,6 +13,7 @@ import com.shifthackz.aisdv1.domain.entity.AiGenerationResult import com.shifthackz.aisdv1.domain.entity.ServerSource import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -24,10 +25,11 @@ internal abstract class CoreGenerationWorker( workerParameters: WorkerParameters, pushNotificationManager: PushNotificationManager, activityIntentProvider: ActivityIntentProvider, - preferenceManager: PreferenceManager, + private val preferenceManager: PreferenceManager, private val backgroundWorkObserver: BackgroundWorkObserver, private val observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, private val observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, + private val interruptGenerationUseCase: InterruptGenerationUseCase, ) : NotificationWorker( context = context, workerParameters = workerParameters, @@ -41,6 +43,11 @@ internal abstract class CoreGenerationWorker( override fun onStopped() { super.onStopped() + runCatching { + interruptGenerationUseCase() + .onErrorComplete() + .blockingAwait() + } compositeDisposable.clear() backgroundWorkObserver.postCancelSignal() } @@ -86,7 +93,7 @@ internal abstract class CoreGenerationWorker( body = subTitle, silent = true, progress = status.current to status.total, - canCancel = false, + canCancel = preferenceManager.allowLocalDiffusionCancel, ) } } diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/SdaiWorkerFactory.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/SdaiWorkerFactory.kt index 8d5dc6ce..68d419d1 100644 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/SdaiWorkerFactory.kt +++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/di/SdaiWorkerFactory.kt @@ -10,6 +10,7 @@ import com.shifthackz.aisdv1.core.notification.PushNotificationManager import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver import com.shifthackz.aisdv1.domain.preference.PreferenceManager import com.shifthackz.aisdv1.domain.usecase.generation.ImageToImageUseCase +import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase import com.shifthackz.aisdv1.domain.usecase.generation.TextToImageUseCase @@ -24,6 +25,7 @@ class SdaiWorkerFactory( private val imageToImageUseCase: ImageToImageUseCase, private val observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, private val observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, + private val interruptGenerationUseCase: InterruptGenerationUseCase, private val fileProviderDescriptor: FileProviderDescriptor, private val activityIntentProvider: ActivityIntentProvider, ) : WorkerFactory() { @@ -44,6 +46,7 @@ class SdaiWorkerFactory( textToImageUseCase = textToImageUseCase, observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase = interruptGenerationUseCase, fileProviderDescriptor = fileProviderDescriptor, ) @@ -57,6 +60,7 @@ class SdaiWorkerFactory( imageToImageUseCase = imageToImageUseCase, observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase = interruptGenerationUseCase, fileProviderDescriptor = fileProviderDescriptor, ) diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/ImageToImageTask.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/ImageToImageTask.kt index 7dedd982..c6b69bf8 100644 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/ImageToImageTask.kt +++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/ImageToImageTask.kt @@ -8,6 +8,7 @@ import com.shifthackz.aisdv1.core.notification.PushNotificationManager import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver import com.shifthackz.aisdv1.domain.preference.PreferenceManager import com.shifthackz.aisdv1.domain.usecase.generation.ImageToImageUseCase +import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase import com.shifthackz.aisdv1.work.Constants @@ -26,6 +27,7 @@ internal class ImageToImageTask( preferenceManager: PreferenceManager, observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase: InterruptGenerationUseCase, private val backgroundWorkObserver: BackgroundWorkObserver, private val imageToImageUseCase: ImageToImageUseCase, private val fileProviderDescriptor: FileProviderDescriptor, @@ -38,6 +40,7 @@ internal class ImageToImageTask( backgroundWorkObserver = backgroundWorkObserver, observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase = interruptGenerationUseCase, ) { override val notificationId = NOTIFICATION_IMAGE_TO_IMAGE_FOREGROUND diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt index 7e8d0808..f9e68631 100644 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt +++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/task/TextToImageTask.kt @@ -9,6 +9,7 @@ import com.shifthackz.aisdv1.core.common.log.errorLog import com.shifthackz.aisdv1.core.notification.PushNotificationManager import com.shifthackz.aisdv1.domain.feature.work.BackgroundWorkObserver import com.shifthackz.aisdv1.domain.preference.PreferenceManager +import com.shifthackz.aisdv1.domain.usecase.generation.InterruptGenerationUseCase import com.shifthackz.aisdv1.domain.usecase.generation.ObserveHordeProcessStatusUseCase import com.shifthackz.aisdv1.domain.usecase.generation.ObserveLocalDiffusionProcessStatusUseCase import com.shifthackz.aisdv1.domain.usecase.generation.TextToImageUseCase @@ -27,6 +28,7 @@ internal class TextToImageTask( activityIntentProvider: ActivityIntentProvider, observeHordeProcessStatusUseCase: ObserveHordeProcessStatusUseCase, observeLocalDiffusionProcessStatusUseCase: ObserveLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase: InterruptGenerationUseCase, private val preferenceManager: PreferenceManager, private val backgroundWorkObserver: BackgroundWorkObserver, private val textToImageUseCase: TextToImageUseCase, @@ -40,6 +42,7 @@ internal class TextToImageTask( backgroundWorkObserver = backgroundWorkObserver, observeHordeProcessStatusUseCase = observeHordeProcessStatusUseCase, observeLocalDiffusionProcessStatusUseCase = observeLocalDiffusionProcessStatusUseCase, + interruptGenerationUseCase = interruptGenerationUseCase, ) { override val notificationId: Int = NOTIFICATION_TEXT_TO_IMAGE_FOREGROUND diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt index 5cfdcd6b..89dcfb80 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt @@ -93,6 +93,13 @@ fun ModalRenderer( titleResId = LocalizationR.string.communicating_local_title, canDismiss = false, step = screenModal.pair, + content = screenModal.canCancel.takeIf { it }?.let { + { + ProgressDialogCancelButton { + processIntent(GenerationMviIntent.Cancel.Generation) + } + } + }, ) is Modal.Image.Single -> GenerationImageResultDialog( diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt index 3951ed0c..f452c2c4 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt @@ -37,7 +37,10 @@ sealed interface Modal { data class SelectSdModel(val models: List, val selected: String) : Modal @Immutable - data class Generating(val status: LocalDiffusion.Status? = null) : Modal { + data class Generating( + val canCancel: Boolean = false, + val status: LocalDiffusion.Status? = null, + ) : Modal { val pair: Pair? get() = status?.let { (current, total) -> current to total } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt index 4502ddbb..1bd5ea6e 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt @@ -6,5 +6,6 @@ enum class DebugMenuIntent : MviIntent { NavigateBack, ViewLogs, ClearLogs, + AllowLocalDiffusionCancel, InsertBadBase64; } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt index 1f052412..23a06812 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.TextSnippet import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.filled.CancelScheduleSend import androidx.compose.material.icons.filled.CleaningServices import androidx.compose.material.icons.filled.SettingsEthernet import androidx.compose.material3.CenterAlignedTopAppBar @@ -19,6 +20,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -36,9 +38,10 @@ import com.shifthackz.aisdv1.core.localization.R as LocalizationR fun DebugMenuScreen() { MviComponent( viewModel = koinViewModel(), - ) { _, intentHandler -> + ) { state, intentHandler -> ScreenContent( modifier = Modifier.fillMaxSize(), + state = state, processIntent = intentHandler, ) } @@ -47,6 +50,7 @@ fun DebugMenuScreen() { @Composable private fun ScreenContent( modifier: Modifier = Modifier, + state: DebugMenuState = DebugMenuState(), processIntent: (DebugMenuIntent) -> Unit = {}, ) { Scaffold( @@ -101,6 +105,24 @@ private fun ScreenContent( onClick = { processIntent(DebugMenuIntent.ClearLogs) }, ) + SettingsHeader( + modifier = headerModifier, + text = LocalizationR.string.debug_section_ld.asUiText(), + ) + SettingsItem( + modifier = itemModifier, + startIcon = Icons.Default.CancelScheduleSend, + text = LocalizationR.string.debug_action_ld_allow_cancel.asUiText(), + onClick = { processIntent(DebugMenuIntent.AllowLocalDiffusionCancel) }, + endValueContent = { + Switch( + modifier = Modifier.padding(horizontal = 8.dp), + checked = state.allowLocalDiffusionCancel, + onCheckedChange = { processIntent(DebugMenuIntent.AllowLocalDiffusionCancel) }, + ) + } + ) + SettingsHeader( modifier = headerModifier, text = LocalizationR.string.debug_section_qa.asUiText(), diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt new file mode 100644 index 00000000..1b3bf278 --- /dev/null +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt @@ -0,0 +1,7 @@ +package com.shifthackz.aisdv1.presentation.screen.debug + +import com.shifthackz.android.core.mvi.MviState + +data class DebugMenuState( + val allowLocalDiffusionCancel: Boolean = false, +) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt index f7b54670..e1e9b53b 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt @@ -6,20 +6,32 @@ import com.shifthackz.aisdv1.core.common.log.errorLog import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel +import com.shifthackz.aisdv1.domain.preference.PreferenceManager import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCase import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter import com.shifthackz.android.core.mvi.EmptyEffect -import com.shifthackz.android.core.mvi.EmptyState import io.reactivex.rxjava3.kotlin.subscribeBy class DebugMenuViewModel( + private val preferenceManager: PreferenceManager, private val fileProviderDescriptor: FileProviderDescriptor, private val debugInsertBadBase64UseCase: DebugInsertBadBase64UseCase, private val schedulersProvider: SchedulersProvider, private val mainRouter: MainRouter, -) : MviRxViewModel() { +) : MviRxViewModel() { - override val initialState = EmptyState + override val initialState = DebugMenuState() + + init { + !preferenceManager + .observe() + .subscribeOnMainThread(schedulersProvider) + .subscribeBy(::errorLog) { settings -> + updateState { + it.copy(allowLocalDiffusionCancel = settings.allowLocalDiffusionCancel) + } + } + } override fun processIntent(intent: DebugMenuIntent) { when (intent) { @@ -34,6 +46,10 @@ class DebugMenuViewModel( } DebugMenuIntent.ViewLogs -> mainRouter.navigateToLogger() + + DebugMenuIntent.AllowLocalDiffusionCancel -> { + preferenceManager.allowLocalDiffusionCancel = !currentState.allowLocalDiffusionCancel + } } } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt index dbf31dad..afd53bcb 100755 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt @@ -66,7 +66,7 @@ class TextToImageViewModel( private val progressModal: Modal get() { if (currentState.mode == ServerSource.LOCAL) { - return Modal.Generating() + return Modal.Generating(canCancel = preferenceManager.allowLocalDiffusionCancel) } return Modal.Communicating() } @@ -126,14 +126,14 @@ class TextToImageViewModel( } override fun onReceivedHordeStatus(status: HordeProcessStatus) { - if (currentState.screenModal is Modal.Communicating) { - setActiveModal(Modal.Communicating(hordeProcessStatus = status)) - } + (currentState.screenModal as? Modal.Communicating) + ?.copy(hordeProcessStatus = status) + ?.let(::setActiveModal) } override fun onReceivedLocalDiffusionStatus(status: LocalDiffusion.Status) { - if (currentState.screenModal is Modal.Generating) { - setActiveModal(Modal.Generating(status)) - } + (currentState.screenModal as? Modal.Generating) + ?.copy(status = status) + ?.let(::setActiveModal) } } diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt index 73d56fd8..a6a07e3f 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt +++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt @@ -1,6 +1,8 @@ package com.shifthackz.aisdv1.presentation.screen.debug import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor +import com.shifthackz.aisdv1.domain.entity.Settings +import com.shifthackz.aisdv1.domain.preference.PreferenceManager import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCase import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter @@ -9,6 +11,8 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import org.junit.Before import org.junit.Test class DebugMenuViewModelTest : CoreViewModelTest() { @@ -16,14 +20,25 @@ class DebugMenuViewModelTest : CoreViewModelTest() { private val stubFileProviderDescriptor = mockk() private val stubDebugInsertBadBase64UseCase = mockk() private val stubMainRouter = mockk() + private val stubPreferenceManager = mockk() override fun initializeViewModel() = DebugMenuViewModel( + preferenceManager = stubPreferenceManager, fileProviderDescriptor = stubFileProviderDescriptor, debugInsertBadBase64UseCase = stubDebugInsertBadBase64UseCase, schedulersProvider = stubSchedulersProvider, mainRouter = stubMainRouter, ) + @Before + override fun initialize() { + super.initialize() + + every { + stubPreferenceManager.observe() + } returns Flowable.just(Settings()) + } + @Test fun `given received NavigateBack intent, expected router navigateBack() method called`() { every { From 3b34b3ffb04066b2ab0e96c8f6407c3584aaabd7 Mon Sep 17 00:00:00 2001 From: ShiftHackZ Date: Sun, 11 Aug 2024 15:07:53 +0300 Subject: [PATCH 03/10] Update translations --- core/localization/src/main/res/values-ru/strings.xml | 1 + core/localization/src/main/res/values-tr/strings.xml | 1 + core/localization/src/main/res/values-uk/strings.xml | 1 + core/localization/src/main/res/values-zh/strings.xml | 1 + 4 files changed, 4 insertions(+) diff --git a/core/localization/src/main/res/values-ru/strings.xml b/core/localization/src/main/res/values-ru/strings.xml index 2c6d581d..401f5175 100644 --- a/core/localization/src/main/res/values-ru/strings.xml +++ b/core/localization/src/main/res/values-ru/strings.xml @@ -304,6 +304,7 @@ Режим разработчика разблокирован! Посмотреть логи Очистить все логи + Разрешить прерывание генерации Внести битый Base64 в БД Лог файл пуст. diff --git a/core/localization/src/main/res/values-tr/strings.xml b/core/localization/src/main/res/values-tr/strings.xml index 31f303fa..3ca3018d 100644 --- a/core/localization/src/main/res/values-tr/strings.xml +++ b/core/localization/src/main/res/values-tr/strings.xml @@ -304,6 +304,7 @@ Geliştirici modu kilidi açıldı! Günlükleri görüntüle Tüm günlükleri temizle + Oluşturmayı kesmeye izin ver Kötü Base64\'ü DB\'ye yerleştirin Herhangi bir günlük bulunamadı. diff --git a/core/localization/src/main/res/values-uk/strings.xml b/core/localization/src/main/res/values-uk/strings.xml index 6326d4ec..d257a2ce 100644 --- a/core/localization/src/main/res/values-uk/strings.xml +++ b/core/localization/src/main/res/values-uk/strings.xml @@ -304,6 +304,7 @@ Режим розробника розблоковано! Дивитися логи Видалити всі логи + Дозволити переривання генерації Внести битий Base64 в БД Лог файл пустий. diff --git a/core/localization/src/main/res/values-zh/strings.xml b/core/localization/src/main/res/values-zh/strings.xml index ac0271ae..e652c144 100644 --- a/core/localization/src/main/res/values-zh/strings.xml +++ b/core/localization/src/main/res/values-zh/strings.xml @@ -366,6 +366,7 @@ 开发者模式已解锁! 查看日志 清除所有日志 + 允许中断生成 在数据库中插入错误的Base64 未找到日志。 From b078f22c22145262453e63cfaae03509ca5e88b3 Mon Sep 17 00:00:00 2001 From: ShiftHackZ Date: Sun, 11 Aug 2024 15:15:32 +0300 Subject: [PATCH 04/10] Extend logs --- .../ai/tokenizer/EnglishTextTokenizer.kt | 26 ++++---- .../aisdv1/feature/diffusion/ai/unet/UNet.kt | 66 +++++++++---------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt index 93c01c62..e539317b 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt +++ b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/tokenizer/EnglishTextTokenizer.kt @@ -47,7 +47,7 @@ internal class EnglishTextTokenizer( override fun initialize() { if (session != null) { - debugLog("{$TAG} {initialize} Session already initialized, skipping...") + debugLog("{$TAG} {TOKENIZER} {initialize} Session already initialized, skipping...") return } val options = OrtSession.SessionOptions() @@ -56,20 +56,20 @@ internal class EnglishTextTokenizer( "${modelPathPrefix(fileProviderDescriptor, localModelIdProvider)}/${LocalDiffusionContract.TOKENIZER_MODEL}", options ) - debugLog("{$TAG} {initialize} Session created successfully!") + debugLog("{$TAG} {TOKENIZER} {initialize} Session created successfully!") if (!isInitMap) { encoder.putAll(loadEncoder()) decoder.putAll(loadDecoder(encoder)) bpeRanks.putAll(loadBpeRanks()) } isInitMap = true - debugLog("{$TAG} {initialize} Tokenizer map initialized successfully!") + debugLog("{$TAG} {TOKENIZER} {initialize} Tokenizer map initialized successfully!") } override fun decode(ids: IntArray?): String { - debugLog("{$TAG} {decode} Trying to decode ${ids?.size ?: "null"} int array...") + debugLog("{$TAG} {TOKENIZER} {decode} Trying to decode ${ids?.size ?: "null"} int array...") if (ids == null) { - debugLog("{$TAG} {decode} Input ids array is null, skipping.") + debugLog("{$TAG} {TOKENIZER} {decode} Input ids array is null, skipping.") return "" } val stringBuilder = StringBuilder() @@ -86,12 +86,12 @@ internal class EnglishTextTokenizer( val ints = IntArray(result.size) for (i in result.indices) ints[i] = result[i] val resultString = String(ints, 0, ints.size) - debugLog("{$TAG} {decode} Decode was successful!") + debugLog("{$TAG} {TOKENIZER} {decode} Decode was successful!") return resultString } override fun encode(text: String?): IntArray { - debugLog("{$TAG} {encode} Trying to encode ${text ?: "null"}...") + debugLog("{$TAG} {TOKENIZER} {encode} Trying to encode ${text ?: "null"}...") var input = text input = input.toString().lowercase(Locale.getDefault()).halfCorner() val stringList: MutableList = ArrayList() @@ -127,14 +127,14 @@ internal class EnglishTextTokenizer( Arrays.fill(copy, 49407) System.arraycopy(ids, 0, copy, 0, if (ids.size < copy.size) ids.size else copy.size) copy[copy.size - 1] = 49407 - debugLog("{$TAG} {encode} Encode was successful!") + debugLog("{$TAG} {TOKENIZER} {encode} Encode was successful!") return copy } override fun tensor(ids: IntArray?): OnnxTensor? { - debugLog("{$TAG} {tensor} Trying to tensor ${ids?.size ?: "null"} int array...") + debugLog("{$TAG} {TOKENIZER} {tensor} Trying to tensor ${ids?.size ?: "null"} int array...") if (ids == null) { - debugLog("{$TAG} {tensor} Input ids array is null, skipping.") + debugLog("{$TAG} {TOKENIZER} {tensor} Input ids array is null, skipping.") return null } val inputIds = OnnxTensor.createTensor( @@ -148,17 +148,17 @@ internal class EnglishTextTokenizer( val lastHiddenState = result[0].value result.close() val tensor = OnnxTensor.createTensor(ortEnvironmentProvider.get(), lastHiddenState) - debugLog("{$TAG} {tensor} Tensor formation was successful!") + debugLog("{$TAG} {TOKENIZER} {tensor} Tensor formation was successful!") return tensor } override fun createUnconditionalInput(text: String?): IntArray = encode(text) override fun close() { - debugLog("{$TAG} {close} Closing session...") + debugLog("{$TAG} {TOKENIZER} {close} Closing session...") session?.close() session = null - debugLog("{$TAG} Session closed successfully!") + debugLog("{$TAG} {TOKENIZER} {close} Session closed successfully!") } private fun bpe(token: String): List { diff --git a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt index 9a84d08e..96d427f2 100644 --- a/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt +++ b/feature/diffusion/src/main/java/com/shifthackz/aisdv1/feature/diffusion/ai/unet/UNet.kt @@ -157,17 +157,17 @@ internal class UNet( width: Int, height: Int, ) { - debugLog("{$TAG} {inference} Trying to start inference:") - debugLog("{$TAG} {inference} - seed: $seedNum") - debugLog("{$TAG} {inference} - numInferenceSteps: $numInferenceSteps") - debugLog("{$TAG} {inference} - textEmbeddings: $textEmbeddings") - debugLog("{$TAG} {inference} - guidanceScale: $guidanceScale") - debugLog("{$TAG} {inference} - batchSize: $batchSize") - debugLog("{$TAG} {inference} - size: ${width}x${height}") + debugLog("{$TAG} {uNet} {inference} Trying to start inference:") + debugLog("{$TAG} {uNet} {inference} - seed: $seedNum") + debugLog("{$TAG} {uNet} {inference} - numInferenceSteps: $numInferenceSteps") + debugLog("{$TAG} {uNet} {inference} - textEmbeddings: $textEmbeddings") + debugLog("{$TAG} {uNet} {inference} - guidanceScale: $guidanceScale") + debugLog("{$TAG} {uNet} {inference} - batchSize: $batchSize") + debugLog("{$TAG} {uNet} {inference} - size: ${width}x${height}") this.width = width this.height = height val localDiffusionScheduler = EulerAncestralDiscreteLocalDiffusionScheduler() - debugLog("{$TAG} {inference} Initialized scheduler: $localDiffusionScheduler") + debugLog("{$TAG} {uNet} {inference} Initialized scheduler: $localDiffusionScheduler") val timeSteps: IntArray = localDiffusionScheduler.setTimeSteps(numInferenceSteps) val seed = if (seedNum <= 0) random.nextLong() else seedNum var latents: LocalDiffusionTensor<*> = generateLatentSample( @@ -177,19 +177,19 @@ internal class UNet( seed, localDiffusionScheduler.initNoiseSigma.toFloat() ) - debugLog("{$TAG} {inference} Got latents: ${latents.hashCode()}") + debugLog("{$TAG} {uNet} {inference} Got latents: ${latents.hashCode()}") val shape = longArrayOf(2, 4, (height / 8).toLong(), (width / 8).toLong()) - debugLog("{$TAG} {inference} Got shape: $shape") - debugLog("{$TAG} {inference} Starting steps processing! Total : ${timeSteps.size}") + debugLog("{$TAG} {uNet} {inference} Got shape: $shape") + debugLog("{$TAG} {uNet} {inference} Starting steps processing! Total : ${timeSteps.size}") for (i in timeSteps.indices) { var latentModelInput: LocalDiffusionTensor<*> = duplicate( latents.tensor.floatBuffer.array(), shape, ) latentModelInput = localDiffusionScheduler.scaleModelInput(latentModelInput, i) - debugLog("{$TAG} {inference} {Step_$i} ------------------") - debugLog("{$TAG} {inference} {Step_$i} Latent model input: $latentModelInput") - debugLog("{$TAG} {inference} {Step_$i} Notifying callback about step.") + debugLog("{$TAG} {uNet} {inference} {Step_$i} ------------------") + debugLog("{$TAG} {uNet} {inference} {Step_$i} Latent model input: $latentModelInput") + debugLog("{$TAG} {uNet} {inference} {Step_$i} Notifying callback about step.") callback?.onStep(timeSteps.size, i) val input = createUNetModelInput( textEmbeddings, @@ -200,11 +200,11 @@ internal class UNet( longArrayOf(1) ) ) - debugLog("{$TAG} {inference} {Step_$i} Got uNet model input: $input") + debugLog("{$TAG} {uNet} {inference} {Step_$i} Got uNet model input: $input") val result = session!!.run(input) - debugLog("{$TAG} {inference} {Step_$i} Got result from uNet session: $result") + debugLog("{$TAG} {uNet} {inference} {Step_$i} Got result from uNet session: $result") val dataSet = result[0].value as Array3D - debugLog("{$TAG} {inference} {Step_$i} Trying to close ORT session in: $result") + debugLog("{$TAG} {uNet} {inference} {Step_$i} Trying to close ORT session in: $result") result.close() val splitTensors: Pair, Array3D> = splitTensor( @@ -213,13 +213,13 @@ internal class UNet( ) val noisePrediction = splitTensors.first val noisePredictionText = splitTensors.second - debugLog("{$TAG} {inference} {Step_$i} Got split tensors with prediction:") - debugLog("{$TAG} {inference} {Step_$i} - splitTensors: $splitTensors") - debugLog("{$TAG} {inference} {Step_$i} - noisePrediction: $noisePrediction") - debugLog("{$TAG} {inference} {Step_$i} - noisePredictionText: $noisePredictionText") - debugLog("{$TAG} {inference} {Step_$i} Trying to preform guidance...") + debugLog("{$TAG} {uNet} {inference} {Step_$i} Got split tensors with prediction:") + debugLog("{$TAG} {uNet} {inference} {Step_$i} - splitTensors: $splitTensors") + debugLog("{$TAG} {uNet} {inference} {Step_$i} - noisePrediction: $noisePrediction") + debugLog("{$TAG} {uNet} {inference} {Step_$i} - noisePredictionText: $noisePredictionText") + debugLog("{$TAG} {uNet} {inference} {Step_$i} Trying to preform guidance...") performGuidance(noisePrediction, noisePredictionText, guidanceScale) - debugLog("{$TAG} {inference} {Step_$i} Guidance performed successfully!") + debugLog("{$TAG} {uNet} {inference} {Step_$i} Guidance performed successfully!") latents = localDiffusionScheduler.step( LocalDiffusionTensor( OnnxTensor.createTensor( @@ -232,22 +232,22 @@ internal class UNet( i, latents, ) - debugLog("{$TAG} {inference} {Step_$i} Finalized latents: $latents") - debugLog("{$TAG} {inference} {Step_$i} ------------------") + debugLog("{$TAG} {uNet} {inference} {Step_$i} Finalized latents: $latents") + debugLog("{$TAG} {uNet} {inference} {Step_$i} ------------------") } callback?.also { clb -> - debugLog("{$TAG} {inference} Finalization / Flushing image...") + debugLog("{$TAG} {uNet} {inference} Finalization / Flushing image...") callback?.onStep(timeSteps.size, timeSteps.size) val bitmap = decode(latents) - debugLog("{$TAG} {inference} Finalization / Decoded bitmap: ${bitmap.hashCode()}") + debugLog("{$TAG} {uNet} {inference} Finalization / Decoded bitmap: ${bitmap.hashCode()}") clb.onBuildImage(0, bitmap) - debugLog("{$TAG} {inference} Finalization / Notifying callback and closing session.") + debugLog("{$TAG} {uNet} {inference} Finalization / Notifying callback and closing session.") close() } } fun decode(latents: LocalDiffusionTensor<*>): Bitmap { - debugLog("{$TAG} {decode} Trying to decode latents: ${latents.hashCode()}") + debugLog("{$TAG} {uNet} {decode} Trying to decode latents: ${latents.hashCode()}") val tensor: LocalDiffusionTensor<*> = multipleTensorsByFloat( latents.tensor.floatBuffer.array(), 1.0f / 0.18215f, @@ -261,21 +261,21 @@ internal class UNet( width, height, ) - debugLog("{$TAG} {decode} Bitmap generated successfully: ${bitmap.hashCode()}") + debugLog("{$TAG} {uNet} {decode} Bitmap generated successfully: ${bitmap.hashCode()}") return bitmap } fun close() { - debugLog("{$TAG} Closing session...") + debugLog("{$TAG} {uNet} {close} Closing session...") session?.close() decoder?.close() session = null decoder = null - debugLog("{$TAG} Session closed successfully!") + debugLog("{$TAG} {uNet} {close} Session closed successfully!") } fun setCallback(callback: Callback?) { - debugLog("{$TAG} Setting new result callback ${callback.hashCode()}") + debugLog("{$TAG} {uNet} Setting new result callback ${callback.hashCode()}") this.callback = callback } From 4e00ec62ffdd967732de6295fbce846e7880dc6d Mon Sep 17 00:00:00 2001 From: ShiftHackZ Date: Sun, 11 Aug 2024 16:19:30 +0300 Subject: [PATCH 05/10] Implement dev mode in Drawer navigation graph --- .../presentation/navigation/graph/DrawerNavGraph.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/DrawerNavGraph.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/DrawerNavGraph.kt index c0f92edc..b5f0bfbe 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/DrawerNavGraph.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/navigation/graph/DrawerNavGraph.kt @@ -1,6 +1,7 @@ package com.shifthackz.aisdv1.presentation.navigation.graph import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.DeveloperMode import androidx.compose.material.icons.filled.SettingsEthernet import androidx.compose.material.icons.filled.Web import com.shifthackz.aisdv1.core.model.UiText @@ -23,6 +24,9 @@ fun mainDrawerNavItems(settings: Settings? = null): List = buildList { } add(settingsTab()) add(configuration()) + settings?.developerMode?.takeIf { it }?.let { + add(developerMode()) + } } private fun webUi(source: ServerSource) = NavItem( @@ -45,3 +49,11 @@ private fun configuration() = NavItem( vector = Icons.Default.SettingsEthernet, ), ) + +private fun developerMode() = NavItem( + name = LocalizationR.string.title_debug_menu.asUiText(), + route = Constants.ROUTE_DEBUG, + icon = NavItem.Icon.Vector( + vector = Icons.Default.DeveloperMode, + ) +) From 13d0d4ef0649e32461dee983958e8b1488f01d43 Mon Sep 17 00:00:00 2001 From: ShiftHackZ Date: Sun, 11 Aug 2024 16:29:56 +0300 Subject: [PATCH 06/10] Logs auto scroll after load --- .../presentation/screen/logger/LoggerScreen.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt index e0dc2692..def1e935 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/logger/LoggerScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -147,7 +148,8 @@ private fun LoggerScreenContent( } ) { paddingValues -> - val scrollState2 = rememberScrollState() + val text = if (!state.loading) state.text else "" + val scrollStateHorizontal = rememberScrollState() if (!state.loading && state.text.isBlank()) { Box( modifier = Modifier @@ -183,16 +185,18 @@ private fun LoggerScreenContent( ) } } - Text( - modifier = Modifier - .horizontalScroll(scrollState2) - , - text = if (!state.loading) state.text else "", + modifier = Modifier.horizontalScroll(scrollStateHorizontal), + text = text, fontFamily = FontFamily.Monospace, fontSize = 11.sp, lineHeight = 12.sp, ) } + LaunchedEffect(state.text) { + if (!state.loading) { + scrollState.scrollTo(scrollState.maxValue) + } + } } } From 1c33bf067deed10941591d74e448b916de4cfac5 Mon Sep 17 00:00:00 2001 From: ShiftHackZ Date: Mon, 12 Aug 2024 09:37:30 +0300 Subject: [PATCH 07/10] LD Scheduler thread setting --- .../common/schedulers/SchedulersProvider.kt | 8 ++++ .../core/common/schedulers/SchedulersToken.kt | 8 ++++ .../src/main/res/values/strings.xml | 8 ++++ .../data/preference/PreferenceManagerImpl.kt | 16 ++++++- .../LocalDiffusionGenerationRepositoryImpl.kt | 4 +- ...alDiffusionGenerationRepositoryImplTest.kt | 5 +++ .../aisdv1/domain/entity/Settings.kt | 5 ++- .../domain/preference/PreferenceManager.kt | 4 +- .../aisdv1/work/core/CoreGenerationWorker.kt | 2 +- .../presentation/modal/ModalRenderer.kt | 16 +++++++ .../ldscheduler/LDSchedulerBottomSheer.kt | 44 ++++++++++++++++++ .../aisdv1/presentation/model/Modal.kt | 3 ++ .../screen/debug/DebugMenuEffect.kt | 8 ++++ .../screen/debug/DebugMenuIntent.kt | 27 ++++++++--- .../screen/debug/DebugMenuMappers.kt | 13 ++++++ .../screen/debug/DebugMenuScreen.kt | 14 +++++- .../screen/debug/DebugMenuState.kt | 6 ++- .../screen/debug/DebugMenuViewModel.kt | 45 ++++++++++++++++--- .../screen/txt2img/TextToImageViewModel.kt | 2 +- 19 files changed, 215 insertions(+), 23 deletions(-) create mode 100644 core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersToken.kt create mode 100644 presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt create mode 100644 presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuEffect.kt create mode 100644 presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuMappers.kt diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersProvider.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersProvider.kt index f904069f..2e7bea78 100755 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersProvider.kt +++ b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersProvider.kt @@ -1,6 +1,7 @@ package com.shifthackz.aisdv1.core.common.schedulers import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.schedulers.Schedulers import java.util.concurrent.Executor interface SchedulersProvider { @@ -8,4 +9,11 @@ interface SchedulersProvider { val ui: Scheduler val computation: Scheduler val singleThread: Executor + + fun byToken(token: SchedulersToken): Scheduler = when (token) { + SchedulersToken.MAIN_THREAD -> ui + SchedulersToken.IO_THREAD -> io + SchedulersToken.COMPUTATION -> computation + SchedulersToken.SINGLE_THREAD -> Schedulers.from(singleThread) + } } diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersToken.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersToken.kt new file mode 100644 index 00000000..c674d7c5 --- /dev/null +++ b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/schedulers/SchedulersToken.kt @@ -0,0 +1,8 @@ +package com.shifthackz.aisdv1.core.common.schedulers + +enum class SchedulersToken(val type: String) { + MAIN_THREAD("Main thread"), + IO_THREAD("IO thread"), + COMPUTATION("Computation"), + SINGLE_THREAD("Single thread"), +} diff --git a/core/localization/src/main/res/values/strings.xml b/core/localization/src/main/res/values/strings.xml index e0e7ba48..efc48d34 100755 --- a/core/localization/src/main/res/values/strings.xml +++ b/core/localization/src/main/res/values/strings.xml @@ -20,6 +20,8 @@ Apply Close Next + ✅ Success + ❌ Failure Two Three @@ -328,6 +330,7 @@ View logs Clear all logs Allow to interrupt generation + Process scheduler Insert bad Base64 in DB No logs found. @@ -361,4 +364,9 @@ Missing permissions. Please allow %1$s permission in application settings. + + Main Thread + I/O Thread + Computation + Single Thread diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt index e6a2c85f..1a41e3a5 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt +++ b/data/src/main/java/com/shifthackz/aisdv1/data/preference/PreferenceManagerImpl.kt @@ -3,6 +3,7 @@ package com.shifthackz.aisdv1.data.preference import android.content.SharedPreferences import com.shifthackz.aisdv1.core.common.extensions.fixUrlSlashes import com.shifthackz.aisdv1.core.common.extensions.shouldUseNewMediaStore +import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken import com.shifthackz.aisdv1.domain.entity.ColorToken import com.shifthackz.aisdv1.domain.entity.DarkThemeToken import com.shifthackz.aisdv1.domain.entity.FeatureTag @@ -58,13 +59,22 @@ class PreferenceManagerImpl( .apply() .also { onPreferencesChanged() } - override var allowLocalDiffusionCancel: Boolean + override var localDiffusionAllowCancel: Boolean get() = preferences.getBoolean(KEY_ALLOW_LOCAL_DIFFUSION_CANCEL, false) set(value) = preferences.edit() .putBoolean(KEY_ALLOW_LOCAL_DIFFUSION_CANCEL, value) .apply() .also { onPreferencesChanged() } + override var localDiffusionSchedulerThread: SchedulersToken + get() = preferences + .getInt(KEY_LOCAL_DIFFUSION_SCHEDULER_THREAD, SchedulersToken.COMPUTATION.ordinal) + .let { SchedulersToken.entries[it] } + set(value) = preferences.edit() + .putInt(KEY_LOCAL_DIFFUSION_SCHEDULER_THREAD, value.ordinal) + .apply() + .also { onPreferencesChanged() } + override var monitorConnectivity: Boolean get() = if (!source.featureTags.contains(FeatureTag.OwnServer)) false else preferences.getBoolean(KEY_MONITOR_CONNECTIVITY, true) @@ -247,7 +257,8 @@ class PreferenceManagerImpl( sdModel = sdModel, demoMode = demoMode, developerMode = developerMode, - allowLocalDiffusionCancel = allowLocalDiffusionCancel, + localDiffusionAllowCancel = localDiffusionAllowCancel, + localDiffusionSchedulerThread = localDiffusionSchedulerThread, monitorConnectivity = monitorConnectivity, backgroundGeneration = backgroundGeneration, autoSaveAiResults = autoSaveAiResults, @@ -275,6 +286,7 @@ class PreferenceManagerImpl( const val KEY_DEMO_MODE = "key_demo_mode" const val KEY_DEVELOPER_MODE = "key_developer_mode" const val KEY_ALLOW_LOCAL_DIFFUSION_CANCEL = "key_allow_local_diffusion_cancel" + const val KEY_LOCAL_DIFFUSION_SCHEDULER_THREAD = "key_local_diffusion_scheduler_thread" const val KEY_MONITOR_CONNECTIVITY = "key_monitor_connectivity" const val KEY_AI_AUTO_SAVE = "key_ai_auto_save" const val KEY_SAVE_TO_MEDIA_STORE = "key_save_to_media_store" diff --git a/data/src/main/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt b/data/src/main/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt index 36856cbe..0254b3d8 100644 --- a/data/src/main/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt +++ b/data/src/main/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImpl.kt @@ -19,8 +19,8 @@ internal class LocalDiffusionGenerationRepositoryImpl( mediaStoreGateway: MediaStoreGateway, base64ToBitmapConverter: Base64ToBitmapConverter, localDataSource: GenerationResultDataSource.Local, - preferenceManager: PreferenceManager, backgroundWorkObserver: BackgroundWorkObserver, + private val preferenceManager: PreferenceManager, private val localDiffusion: LocalDiffusion, private val downloadableLocalDataSource: DownloadableModelDataSource.Local, private val bitmapToBase64Converter: BitmapToBase64Converter, @@ -46,7 +46,7 @@ internal class LocalDiffusionGenerationRepositoryImpl( private fun generate(payload: TextToImagePayload) = localDiffusion .process(payload) - .subscribeOn(schedulersProvider.computation) + .subscribeOn(schedulersProvider.byToken(preferenceManager.localDiffusionSchedulerThread)) .map(BitmapToBase64Converter::Input) .flatMap(bitmapToBase64Converter::invoke) .map(BitmapToBase64Converter.Output::base64ImageString) diff --git a/data/src/test/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt b/data/src/test/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt index c92ecaaf..75df885f 100644 --- a/data/src/test/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt +++ b/data/src/test/java/com/shifthackz/aisdv1/data/repository/LocalDiffusionGenerationRepositoryImplTest.kt @@ -2,6 +2,7 @@ package com.shifthackz.aisdv1.data.repository import android.graphics.Bitmap import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider +import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken import com.shifthackz.aisdv1.core.imageprocessing.Base64ToBitmapConverter import com.shifthackz.aisdv1.core.imageprocessing.BitmapToBase64Converter import com.shifthackz.aisdv1.data.mocks.mockLocalAiModel @@ -61,6 +62,10 @@ class LocalDiffusionGenerationRepositoryImplTest { @Before fun initialize() { + every { + stubPreferenceManager::localDiffusionSchedulerThread.get() + } returns SchedulersToken.COMPUTATION + every { stubBackgroundWorkObserver.hasActiveTasks() } returns false diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt index 7bfae59a..c4da9e46 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/entity/Settings.kt @@ -1,11 +1,14 @@ package com.shifthackz.aisdv1.domain.entity +import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken + data class Settings( val serverUrl: String = "", val sdModel: String = "", val demoMode: Boolean = false, val developerMode: Boolean = false, - val allowLocalDiffusionCancel: Boolean = false, + val localDiffusionAllowCancel: Boolean = false, + val localDiffusionSchedulerThread: SchedulersToken = SchedulersToken.COMPUTATION, val monitorConnectivity: Boolean = false, val backgroundGeneration: Boolean = false, val autoSaveAiResults: Boolean = false, diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt index bbc1b230..2ea33eef 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/preference/PreferenceManager.kt @@ -1,5 +1,6 @@ package com.shifthackz.aisdv1.domain.preference +import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken import com.shifthackz.aisdv1.domain.entity.Grid import com.shifthackz.aisdv1.domain.entity.ServerSource import com.shifthackz.aisdv1.domain.entity.Settings @@ -11,7 +12,8 @@ interface PreferenceManager { var swarmUiModel: String var demoMode: Boolean var developerMode: Boolean - var allowLocalDiffusionCancel: Boolean + var localDiffusionAllowCancel: Boolean + var localDiffusionSchedulerThread: SchedulersToken var monitorConnectivity: Boolean var autoSaveAiResults: Boolean var saveToMediaStore: Boolean diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt index f7dc1a9c..d3076dee 100644 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt +++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/core/CoreGenerationWorker.kt @@ -93,7 +93,7 @@ internal abstract class CoreGenerationWorker( body = subTitle, silent = true, progress = status.current to status.total, - canCancel = preferenceManager.allowLocalDiffusionCancel, + canCancel = preferenceManager.localDiffusionAllowCancel, ) } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt index 89dcfb80..860d3fd0 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ModalRenderer.kt @@ -27,8 +27,10 @@ import com.shifthackz.aisdv1.presentation.modal.extras.ExtrasScreen import com.shifthackz.aisdv1.presentation.modal.grid.GridBottomSheet import com.shifthackz.aisdv1.presentation.modal.history.InputHistoryScreen import com.shifthackz.aisdv1.presentation.modal.language.LanguageBottomSheet +import com.shifthackz.aisdv1.presentation.modal.ldscheduler.LDSchedulerBottomSheet import com.shifthackz.aisdv1.presentation.modal.tag.EditTagDialog import com.shifthackz.aisdv1.presentation.model.Modal +import com.shifthackz.aisdv1.presentation.screen.debug.DebugMenuIntent import com.shifthackz.aisdv1.presentation.screen.gallery.detail.GalleryDetailIntent import com.shifthackz.aisdv1.presentation.screen.gallery.list.GalleryIntent import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintIntent @@ -57,6 +59,7 @@ fun ModalRenderer( processIntent(GalleryIntent.DismissDialog) processIntent(GalleryDetailIntent.DismissDialog) processIntent(InPaintIntent.ScreenModal.Dismiss) + processIntent(DebugMenuIntent.DismissModal) } val context = LocalContext.current when (screenModal) { @@ -339,5 +342,18 @@ fun ModalRenderer( } ) } + + is Modal.LDScheduler -> ModalBottomSheet( + onDismissRequest = dismiss, + shape = RectangleShape, + ) { + LDSchedulerBottomSheet( + currentScheduler = screenModal.scheduler, + onSelected = { + processIntent(DebugMenuIntent.LocalDiffusionScheduler.Confirm(it)) + dismiss() + } + ) + } } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt new file mode 100644 index 00000000..8d5b49eb --- /dev/null +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/modal/ldscheduler/LDSchedulerBottomSheer.kt @@ -0,0 +1,44 @@ +package com.shifthackz.aisdv1.presentation.modal.ldscheduler + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Construction +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken +import com.shifthackz.aisdv1.presentation.screen.debug.mapToUi +import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem + +@Composable +@Preview +fun LDSchedulerBottomSheet( + modifier: Modifier = Modifier, + currentScheduler: SchedulersToken = SchedulersToken.COMPUTATION, + onSelected: (SchedulersToken) -> Unit = {}, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .navigationBarsPadding() + .padding(bottom = 16.dp), + ) { + SchedulersToken.entries.forEach { scheduler -> + SettingsItem( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + selected = scheduler == currentScheduler, + text = scheduler.mapToUi(), + showChevron = false, + onClick = { onSelected(scheduler) }, + startIcon = Icons.Default.Construction, + ) + } + } +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt index f452c2c4..0e53eb82 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/model/Modal.kt @@ -2,6 +2,7 @@ package com.shifthackz.aisdv1.presentation.model import android.graphics.Bitmap import androidx.compose.runtime.Immutable +import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken import com.shifthackz.aisdv1.core.model.UiText import com.shifthackz.aisdv1.domain.entity.AiGenerationResult import com.shifthackz.aisdv1.domain.entity.Grid @@ -107,5 +108,7 @@ sealed interface Modal { data object Language : Modal + data class LDScheduler(val scheduler: SchedulersToken) : Modal + data class GalleryGrid(val grid: Grid) : Modal } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuEffect.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuEffect.kt new file mode 100644 index 00000000..132c6522 --- /dev/null +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuEffect.kt @@ -0,0 +1,8 @@ +package com.shifthackz.aisdv1.presentation.screen.debug + +import com.shifthackz.aisdv1.core.model.UiText +import com.shifthackz.android.core.mvi.MviEffect + +sealed interface DebugMenuEffect : MviEffect { + data class Message(val message: UiText) : DebugMenuEffect +} diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt index 1bd5ea6e..649accf0 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt @@ -1,11 +1,26 @@ package com.shifthackz.aisdv1.presentation.screen.debug +import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken import com.shifthackz.android.core.mvi.MviIntent -enum class DebugMenuIntent : MviIntent { - NavigateBack, - ViewLogs, - ClearLogs, - AllowLocalDiffusionCancel, - InsertBadBase64; +sealed interface DebugMenuIntent : MviIntent { + + data object NavigateBack : DebugMenuIntent + + data object ViewLogs : DebugMenuIntent + + data object ClearLogs : DebugMenuIntent + + data object AllowLocalDiffusionCancel : DebugMenuIntent + + data object InsertBadBase64 : DebugMenuIntent + + sealed interface LocalDiffusionScheduler : DebugMenuIntent { + + data class Confirm(val token: SchedulersToken) : DebugMenuIntent + + data object Request : DebugMenuIntent + } + + data object DismissModal : DebugMenuIntent } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuMappers.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuMappers.kt new file mode 100644 index 00000000..3d2c705f --- /dev/null +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuMappers.kt @@ -0,0 +1,13 @@ +package com.shifthackz.aisdv1.presentation.screen.debug + +import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken +import com.shifthackz.aisdv1.core.model.UiText +import com.shifthackz.aisdv1.core.model.asUiText +import com.shifthackz.aisdv1.core.localization.R as LocalizationR + +fun SchedulersToken.mapToUi(): UiText = when (this) { + SchedulersToken.MAIN_THREAD -> LocalizationR.string.scheduler_main + SchedulersToken.IO_THREAD -> LocalizationR.string.scheduler_io + SchedulersToken.COMPUTATION -> LocalizationR.string.scheduler_computation + SchedulersToken.SINGLE_THREAD -> LocalizationR.string.scheduler_single_thread +}.asUiText() diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt index 23a06812..b8920b68 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.material.icons.automirrored.filled.TextSnippet import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.filled.CancelScheduleSend import androidx.compose.material.icons.filled.CleaningServices +import androidx.compose.material.icons.filled.Construction import androidx.compose.material.icons.filled.SettingsEthernet import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api @@ -29,6 +30,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.shifthackz.aisdv1.core.model.asUiText import com.shifthackz.aisdv1.core.ui.MviComponent +import com.shifthackz.aisdv1.presentation.modal.ModalRenderer import com.shifthackz.aisdv1.presentation.widget.item.SettingsHeader import com.shifthackz.aisdv1.presentation.widget.item.SettingsItem import org.koin.androidx.compose.koinViewModel @@ -117,11 +119,18 @@ private fun ScreenContent( endValueContent = { Switch( modifier = Modifier.padding(horizontal = 8.dp), - checked = state.allowLocalDiffusionCancel, + checked = state.localDiffusionAllowCancel, onCheckedChange = { processIntent(DebugMenuIntent.AllowLocalDiffusionCancel) }, ) } ) + SettingsItem( + modifier = itemModifier, + startIcon = Icons.Default.Construction, + text = LocalizationR.string.debug_action_ld_scheduler.asUiText(), + onClick = { processIntent(DebugMenuIntent.LocalDiffusionScheduler.Request) }, + endValueText = state.localDiffusionSchedulerThread.mapToUi(), + ) SettingsHeader( modifier = headerModifier, @@ -134,6 +143,9 @@ private fun ScreenContent( onClick = { processIntent(DebugMenuIntent.InsertBadBase64) }, ) } + ModalRenderer(screenModal = state.screenModal) { + (it as? DebugMenuIntent)?.let(processIntent::invoke) + } } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt index 1b3bf278..20fa36e4 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuState.kt @@ -1,7 +1,11 @@ package com.shifthackz.aisdv1.presentation.screen.debug +import com.shifthackz.aisdv1.core.common.schedulers.SchedulersToken +import com.shifthackz.aisdv1.presentation.model.Modal import com.shifthackz.android.core.mvi.MviState data class DebugMenuState( - val allowLocalDiffusionCancel: Boolean = false, + val screenModal: Modal = Modal.None, + val localDiffusionAllowCancel: Boolean = false, + val localDiffusionSchedulerThread: SchedulersToken = SchedulersToken.COMPUTATION, ) : MviState diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt index e1e9b53b..15bbbc9d 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt @@ -5,12 +5,14 @@ import com.shifthackz.aisdv1.core.common.log.FileLoggingTree import com.shifthackz.aisdv1.core.common.log.errorLog import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread +import com.shifthackz.aisdv1.core.model.asUiText import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel import com.shifthackz.aisdv1.domain.preference.PreferenceManager import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCase +import com.shifthackz.aisdv1.presentation.model.Modal import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter -import com.shifthackz.android.core.mvi.EmptyEffect import io.reactivex.rxjava3.kotlin.subscribeBy +import com.shifthackz.aisdv1.core.localization.R as LocalizationR class DebugMenuViewModel( private val preferenceManager: PreferenceManager, @@ -18,7 +20,7 @@ class DebugMenuViewModel( private val debugInsertBadBase64UseCase: DebugInsertBadBase64UseCase, private val schedulersProvider: SchedulersProvider, private val mainRouter: MainRouter, -) : MviRxViewModel() { +) : MviRxViewModel() { override val initialState = DebugMenuState() @@ -27,8 +29,11 @@ class DebugMenuViewModel( .observe() .subscribeOnMainThread(schedulersProvider) .subscribeBy(::errorLog) { settings -> - updateState { - it.copy(allowLocalDiffusionCancel = settings.allowLocalDiffusionCancel) + updateState { state -> + state.copy( + localDiffusionAllowCancel = settings.localDiffusionAllowCancel, + localDiffusionSchedulerThread = settings.localDiffusionSchedulerThread, + ) } } } @@ -39,17 +44,43 @@ class DebugMenuViewModel( DebugMenuIntent.InsertBadBase64 -> !debugInsertBadBase64UseCase() .subscribeOnMainThread(schedulersProvider) - .subscribeBy(::errorLog) + .subscribeBy(::onError, ::onSuccess) DebugMenuIntent.ClearLogs -> { - FileLoggingTree.clearLog(fileProviderDescriptor) + try { + FileLoggingTree.clearLog(fileProviderDescriptor) + onSuccess() + } catch (e: Exception) { + onError(e) + } } DebugMenuIntent.ViewLogs -> mainRouter.navigateToLogger() DebugMenuIntent.AllowLocalDiffusionCancel -> { - preferenceManager.allowLocalDiffusionCancel = !currentState.allowLocalDiffusionCancel + preferenceManager.localDiffusionAllowCancel = !currentState.localDiffusionAllowCancel + } + + DebugMenuIntent.LocalDiffusionScheduler.Request -> updateState { + it.copy(screenModal = Modal.LDScheduler(it.localDiffusionSchedulerThread)) + } + + is DebugMenuIntent.LocalDiffusionScheduler.Confirm -> { + preferenceManager.localDiffusionSchedulerThread = intent.token + } + + DebugMenuIntent.DismissModal -> updateState { + it.copy(screenModal = Modal.None) } } } + + private fun onSuccess() { + emitEffect(DebugMenuEffect.Message(LocalizationR.string.success.asUiText())) + } + + private fun onError(t: Throwable) { + errorLog(t) + emitEffect(DebugMenuEffect.Message(LocalizationR.string.failure.asUiText())) + } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt index afd53bcb..6f98575f 100755 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/txt2img/TextToImageViewModel.kt @@ -66,7 +66,7 @@ class TextToImageViewModel( private val progressModal: Modal get() { if (currentState.mode == ServerSource.LOCAL) { - return Modal.Generating(canCancel = preferenceManager.allowLocalDiffusionCancel) + return Modal.Generating(canCancel = preferenceManager.localDiffusionAllowCancel) } return Modal.Communicating() } From d25d8d73aaafe380d965978a2c9c2eec02afd1a8 Mon Sep 17 00:00:00 2001 From: ShiftHackZ Date: Mon, 12 Aug 2024 10:05:59 +0300 Subject: [PATCH 08/10] Update localization --- .../core/common/extensions/AppExtensions.kt | 1 - .../src/main/res/values-ru/strings.xml | 12 ++++++ .../src/main/res/values-tr/strings.xml | 12 ++++++ .../src/main/res/values-uk/strings.xml | 12 ++++++ .../src/main/res/values-zh/strings.xml | 12 ++++++ .../src/main/res/values/strings.xml | 4 ++ .../feature/work/BackgroundTaskManager.kt | 3 ++ .../aisdv1/work/BackgroundTaskManagerImpl.kt | 39 ++++++++++++++++++- .../screen/debug/DebugMenuIntent.kt | 4 ++ .../screen/debug/DebugMenuScreen.kt | 35 +++++++++++++++++ .../screen/debug/DebugMenuViewModel.kt | 19 +++++++++ .../screen/debug/DebugMenuViewModelTest.kt | 3 ++ 12 files changed, 154 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt index 79f1a41e..db9aaad0 100644 --- a/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt +++ b/core/common/src/main/java/com/shifthackz/aisdv1/core/common/extensions/AppExtensions.kt @@ -9,7 +9,6 @@ import android.provider.Settings import android.widget.Toast import androidx.annotation.StringRes - fun Context.isAppInForeground(): Boolean { val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val processes = activityManager.runningAppProcesses ?: return false diff --git a/core/localization/src/main/res/values-ru/strings.xml b/core/localization/src/main/res/values-ru/strings.xml index 401f5175..726aaa23 100644 --- a/core/localization/src/main/res/values-ru/strings.xml +++ b/core/localization/src/main/res/values-ru/strings.xml @@ -16,6 +16,8 @@ Применить Закрыть Далее + ✅ Успешно + ❌ Ошибка Два Три @@ -301,10 +303,15 @@ Отладка QA тест-кейсы + Work Manager API Режим разработчика разблокирован! Посмотреть логи Очистить все логи + Перезапустить последнюю txt2img задачу + Перезапустить последнюю img2img задачу + Отменить все задачи Разрешить прерывание генерации + Поток Внести битый Base64 в БД Лог файл пуст. @@ -337,4 +344,9 @@ Отсутствует разрешение. Разрешите права на %1$s в настройках. + + UI поток + I/O Поток + Вычислительный + Однопоточность diff --git a/core/localization/src/main/res/values-tr/strings.xml b/core/localization/src/main/res/values-tr/strings.xml index 3ca3018d..1c4a97ae 100644 --- a/core/localization/src/main/res/values-tr/strings.xml +++ b/core/localization/src/main/res/values-tr/strings.xml @@ -16,6 +16,8 @@ Uygula Kapalı Sonraki + ✅ Başarılı + ❌ Başarısızlık İki Üç @@ -300,11 +302,16 @@ Son klasör yapısı şu şekilde olmalıdır:: Hata ayıklama + Work Manager API QA işlemleri Geliştirici modu kilidi açıldı! Günlükleri görüntüle Tüm günlükleri temizle + Son txt2img görevini yeniden başlat + Son img2img görevini yeniden başlat + Tüm çalışanları iptal et Oluşturmayı kesmeye izin ver + İşlem zamanlayıcısı Kötü Base64\'ü DB\'ye yerleştirin Herhangi bir günlük bulunamadı. @@ -337,4 +344,9 @@ Eksik izinler. Lütfen uygulama ayarlarında %1$s iznini verin. + + Ana İş Parçacığı + G/Ç İş Parçacığı + Hesaplama + Tek İş Parçacığı diff --git a/core/localization/src/main/res/values-uk/strings.xml b/core/localization/src/main/res/values-uk/strings.xml index d257a2ce..8b901cd4 100644 --- a/core/localization/src/main/res/values-uk/strings.xml +++ b/core/localization/src/main/res/values-uk/strings.xml @@ -16,6 +16,8 @@ Застосувати Закрити Далі + ✅ Успіх + ❌ Помилка Два Три @@ -300,11 +302,16 @@ Остаточна структура папок має бути такою: Відладка + Work Manager API QA тест-кейси Режим розробника розблоковано! Дивитися логи Видалити всі логи + Перезапуск останнього txt2img завдання + Перезапуск останнього img2img завдання + Скасувати всі завдання Дозволити переривання генерації + Потік Внести битий Base64 в БД Лог файл пустий. @@ -337,4 +344,9 @@ Додаток не має дозволів. Довольте права на %1$s в налаштуваннях. + + UI Потік + I/O Потік + Обчислення + Один потік diff --git a/core/localization/src/main/res/values-zh/strings.xml b/core/localization/src/main/res/values-zh/strings.xml index e652c144..0849958f 100644 --- a/core/localization/src/main/res/values-zh/strings.xml +++ b/core/localization/src/main/res/values-zh/strings.xml @@ -21,6 +21,8 @@ 应用 关闭 下一步 + ✅ 成功 + ❌ 失败 @@ -362,11 +364,16 @@ 调试 + 工作管理器 API QA操作 开发者模式已解锁! 查看日志 清除所有日志 + 重新启动最后一个 txt2img 任务 + 重新启动最后一个 img2img 任务 + 取消所有工作程序 允许中断生成 + 进程调度程序 在数据库中插入错误的Base64 未找到日志。 @@ -403,4 +410,9 @@ 缺少权限。 请在应用程序设置中允许 %1$s 权限。 + + 主线程 + I/O 线程 + 计算 + 单线程 diff --git a/core/localization/src/main/res/values/strings.xml b/core/localization/src/main/res/values/strings.xml index efc48d34..bd1b8e31 100755 --- a/core/localization/src/main/res/values/strings.xml +++ b/core/localization/src/main/res/values/strings.xml @@ -325,10 +325,14 @@ Debugging Local Diffusion + Work Manager API QA actions Developer mode unlocked! View logs Clear all logs + Restart last txt2img task + Restart last img2img task + Cancel all workers Allow to interrupt generation Process scheduler Insert bad Base64 in DB diff --git a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundTaskManager.kt b/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundTaskManager.kt index ecf2f0cc..78199398 100644 --- a/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundTaskManager.kt +++ b/domain/src/main/java/com/shifthackz/aisdv1/domain/feature/work/BackgroundTaskManager.kt @@ -6,4 +6,7 @@ import com.shifthackz.aisdv1.domain.entity.TextToImagePayload interface BackgroundTaskManager { fun scheduleTextToImageTask(payload: TextToImagePayload) fun scheduleImageToImageTask(payload: ImageToImagePayload) + fun retryLastTextToImageTask(): Result + fun retryLastImageToImageTask(): Result + fun cancelAll(): Result } diff --git a/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundTaskManagerImpl.kt b/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundTaskManagerImpl.kt index 175a62d3..c1444ec9 100644 --- a/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundTaskManagerImpl.kt +++ b/feature/work/src/main/java/com/shifthackz/aisdv1/work/BackgroundTaskManagerImpl.kt @@ -25,6 +25,33 @@ internal class BackgroundTaskManagerImpl : BackgroundTaskManager { runWork(payload.toByteArray(), Constants.FILE_IMAGE_TO_IMAGE) } + override fun retryLastTextToImageTask(): Result { + try { + val bytes = readPayload(Constants.FILE_TEXT_TO_IMAGE) + ?: return Result.failure(Throwable("Payload is null.")) + runWork(bytes, Constants.FILE_TEXT_TO_IMAGE) + return Result.success(Unit) + } catch (e: Exception) { + return Result.failure(e) + } + } + + override fun retryLastImageToImageTask(): Result { + try { + val bytes = readPayload(Constants.FILE_IMAGE_TO_IMAGE) + ?: return Result.failure(Throwable("Payload is null.")) + runWork(bytes, Constants.FILE_IMAGE_TO_IMAGE) + return Result.success(Unit) + } catch (e: Exception) { + return Result.failure(e) + } + } + + override fun cancelAll(): Result = runCatching { + val workManager: WorkManagerProvider by inject(WorkManagerProvider::class.java) + workManager().cancelAllWork() + } + private inline fun runWork(bytes: ByteArray, fileName: String) { val workManager: WorkManagerProvider by inject(WorkManagerProvider::class.java) val workRequest = OneTimeWorkRequestBuilder() @@ -33,7 +60,7 @@ internal class BackgroundTaskManagerImpl : BackgroundTaskManager { .build() writePayload(bytes, fileName) - workManager().cancelAllWork() + workManager().cancelUniqueWork(Constants.TAG_GENERATION) workManager().enqueueUniqueWork( Constants.TAG_GENERATION, ExistingWorkPolicy.REPLACE, @@ -41,6 +68,16 @@ internal class BackgroundTaskManagerImpl : BackgroundTaskManager { ) } + private fun readPayload(fileName: String): ByteArray? { + val fileProviderDescriptor: FileProviderDescriptor by inject(FileProviderDescriptor::class.java) + val cacheDirectory = File(fileProviderDescriptor.workCacheDirPath) + if (!cacheDirectory.exists()) { + return null + } + val outFile = File(cacheDirectory, fileName) + return outFile.readBytes() + } + private fun writePayload(bytes: ByteArray, fileName: String) { val fileProviderDescriptor: FileProviderDescriptor by inject(FileProviderDescriptor::class.java) val cacheDirectory = File(fileProviderDescriptor.workCacheDirPath) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt index 649accf0..dae80520 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuIntent.kt @@ -22,5 +22,9 @@ sealed interface DebugMenuIntent : MviIntent { data object Request : DebugMenuIntent } + enum class WorkManager : DebugMenuIntent { + CancelAll, RestartTxt2Img, RestartImg2Img; + } + data object DismissModal : DebugMenuIntent } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt index b8920b68..24776091 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuScreen.kt @@ -11,9 +11,11 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.TextSnippet import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.filled.Cancel import androidx.compose.material.icons.filled.CancelScheduleSend import androidx.compose.material.icons.filled.CleaningServices import androidx.compose.material.icons.filled.Construction +import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.SettingsEthernet import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api @@ -25,9 +27,11 @@ import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.shifthackz.aisdv1.core.common.extensions.showToast import com.shifthackz.aisdv1.core.model.asUiText import com.shifthackz.aisdv1.core.ui.MviComponent import com.shifthackz.aisdv1.presentation.modal.ModalRenderer @@ -38,8 +42,16 @@ import com.shifthackz.aisdv1.core.localization.R as LocalizationR @Composable fun DebugMenuScreen() { + val context = LocalContext.current MviComponent( viewModel = koinViewModel(), + processEffect = { effect -> + when (effect) { + is DebugMenuEffect.Message -> context.showToast( + effect.message.asString(context) + ) + } + } ) { state, intentHandler -> ScreenContent( modifier = Modifier.fillMaxSize(), @@ -107,6 +119,29 @@ private fun ScreenContent( onClick = { processIntent(DebugMenuIntent.ClearLogs) }, ) + SettingsHeader( + modifier = headerModifier, + text = LocalizationR.string.debug_section_work_manager.asUiText(), + ) + SettingsItem( + modifier = itemModifier, + startIcon = Icons.Default.Refresh, + text = LocalizationR.string.debug_action_work_restart_txt2img.asUiText(), + onClick = { processIntent(DebugMenuIntent.WorkManager.RestartTxt2Img) }, + ) + SettingsItem( + modifier = itemModifier, + startIcon = Icons.Default.Refresh, + text = LocalizationR.string.debug_action_work_restart_img2img.asUiText(), + onClick = { processIntent(DebugMenuIntent.WorkManager.RestartImg2Img) }, + ) + SettingsItem( + modifier = itemModifier, + startIcon = Icons.Default.Cancel, + text = LocalizationR.string.debug_action_work_cancel_all.asUiText(), + onClick = { processIntent(DebugMenuIntent.WorkManager.CancelAll) }, + ) + SettingsHeader( modifier = headerModifier, text = LocalizationR.string.debug_section_ld.asUiText(), diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt index 15bbbc9d..9336acaf 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModel.kt @@ -7,6 +7,7 @@ import com.shifthackz.aisdv1.core.common.schedulers.SchedulersProvider import com.shifthackz.aisdv1.core.common.schedulers.subscribeOnMainThread import com.shifthackz.aisdv1.core.model.asUiText import com.shifthackz.aisdv1.core.viewmodel.MviRxViewModel +import com.shifthackz.aisdv1.domain.feature.work.BackgroundTaskManager import com.shifthackz.aisdv1.domain.preference.PreferenceManager import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCase import com.shifthackz.aisdv1.presentation.model.Modal @@ -20,6 +21,7 @@ class DebugMenuViewModel( private val debugInsertBadBase64UseCase: DebugInsertBadBase64UseCase, private val schedulersProvider: SchedulersProvider, private val mainRouter: MainRouter, + private val backgroundTaskManager: BackgroundTaskManager, ) : MviRxViewModel() { override val initialState = DebugMenuState() @@ -72,9 +74,26 @@ class DebugMenuViewModel( DebugMenuIntent.DismissModal -> updateState { it.copy(screenModal = Modal.None) } + + DebugMenuIntent.WorkManager.CancelAll -> backgroundTaskManager + .cancelAll() + .handleState() + + DebugMenuIntent.WorkManager.RestartTxt2Img -> backgroundTaskManager + .retryLastTextToImageTask() + .handleState() + + DebugMenuIntent.WorkManager.RestartImg2Img -> backgroundTaskManager + .retryLastImageToImageTask() + .handleState() } } + private fun Result.handleState() = this.fold( + onSuccess = { onSuccess() }, + onFailure = ::onError, + ) + private fun onSuccess() { emitEffect(DebugMenuEffect.Message(LocalizationR.string.success.asUiText())) } diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt index a6a07e3f..4175f057 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt +++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/debug/DebugMenuViewModelTest.kt @@ -2,6 +2,7 @@ package com.shifthackz.aisdv1.presentation.screen.debug import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor import com.shifthackz.aisdv1.domain.entity.Settings +import com.shifthackz.aisdv1.domain.feature.work.BackgroundTaskManager import com.shifthackz.aisdv1.domain.preference.PreferenceManager import com.shifthackz.aisdv1.domain.usecase.debug.DebugInsertBadBase64UseCase import com.shifthackz.aisdv1.presentation.core.CoreViewModelTest @@ -21,6 +22,7 @@ class DebugMenuViewModelTest : CoreViewModelTest() { private val stubDebugInsertBadBase64UseCase = mockk() private val stubMainRouter = mockk() private val stubPreferenceManager = mockk() + private val stubBackgroundTaskManager = mockk() override fun initializeViewModel() = DebugMenuViewModel( preferenceManager = stubPreferenceManager, @@ -28,6 +30,7 @@ class DebugMenuViewModelTest : CoreViewModelTest() { debugInsertBadBase64UseCase = stubDebugInsertBadBase64UseCase, schedulersProvider = stubSchedulersProvider, mainRouter = stubMainRouter, + backgroundTaskManager = stubBackgroundTaskManager, ) @Before From 5a566a25fe2571055e48c9f8eb4af050e0bb30a6 Mon Sep 17 00:00:00 2001 From: ShiftHackZ Date: Mon, 12 Aug 2024 16:00:00 +0300 Subject: [PATCH 09/10] Update test --- .../screen/donate/DonateViewModelTest.kt | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt index 7859cc9a..6acc2b47 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt +++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt @@ -32,14 +32,12 @@ class DonateViewModelTest : CoreViewModelTest() { stubFetchAndGetSupportersUseCase() } returns Single.just(mockSupporters) - runTest { - val expected = DonateState( - loading = false, - supporters = mockSupporters, - ) - val actual = viewModel.state.value - Assert.assertEquals(expected, actual) - } + val expected = DonateState( + loading = false, + supporters = mockSupporters, + ) + val actual = viewModel.state.value + Assert.assertEquals(expected, actual) } @Test @@ -48,14 +46,13 @@ class DonateViewModelTest : CoreViewModelTest() { stubFetchAndGetSupportersUseCase() } returns Single.error(stubException) - runTest { - val expected = DonateState( - loading = false, - supporters = emptyList(), - ) - val actual = viewModel.state.value - Assert.assertEquals(expected, actual) - } + val expected = DonateState( + loading = false, + supporters = emptyList(), + ) + val actual = viewModel.state.value + Assert.assertEquals(expected, actual) + } @Test From 176883deeaa8b0762e1025eed704113ad7dc0cb6 Mon Sep 17 00:00:00 2001 From: ShiftHackZ Date: Mon, 12 Aug 2024 16:07:16 +0300 Subject: [PATCH 10/10] Update test --- .../screen/donate/DonateViewModelTest.kt | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt index 6acc2b47..218cd875 100644 --- a/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt +++ b/presentation/src/test/java/com/shifthackz/aisdv1/presentation/screen/donate/DonateViewModelTest.kt @@ -9,8 +9,6 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import io.reactivex.rxjava3.core.Single -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.test.runTest import org.junit.Assert import org.junit.Test @@ -71,24 +69,4 @@ class DonateViewModelTest : CoreViewModelTest() { stubMainRouter.navigateBack() } } - - @Test - fun `given received LaunchUrl intent, expected OpenUrl effect delivered to effect collector`() { - every { - stubFetchAndGetSupportersUseCase() - } returns Single.just(mockSupporters) - - val intent = mockk() - every { - intent::url.get() - } returns "https://5598.is.my.favourite.com" - - viewModel.processIntent(intent) - - runTest { - val expected = DonateEffect.OpenUrl("https://5598.is.my.favourite.com") - val actual = viewModel.effect.firstOrNull() - Assert.assertEquals(expected, actual) - } - } }