Improve AI correction: loading indicator, field safety, memory, dismiss
- Show "…" placeholder immediately after debounce so users know the AI is working (KIND_AI_LOADING_FLAG = 0x20000) - Skip AI correction in password, URL, and e-mail fields (mIsGeneralTextInput + mIsPasswordField guards in InputLogic) - Protect suggestion strip from being cleared while AI suggestion is visible (mAiSuggestionVisible flag + clearAiSuggestionAndSetNeutral) - Release Gemma 4 engine on TRIM_MEMORY_RUNNING_CRITICAL - Long-press on AI suggestion dismisses it (dismissAiSuggestion) - Warm-up model at IME startup for instant first correction Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,9 @@ import com.google.ai.edge.litertlm.ConversationConfig
|
||||
import com.google.ai.edge.litertlm.Engine
|
||||
import com.google.ai.edge.litertlm.EngineConfig
|
||||
import com.google.ai.edge.litertlm.SamplerConfig
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
@@ -114,6 +116,21 @@ class AiCorrectionEngine(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-loads the model in the background so the first correction request
|
||||
* doesn't have to wait. Call once after IME startup.
|
||||
*/
|
||||
fun warmUp(scope: CoroutineScope) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
getOrCreate()
|
||||
Log.i(TAG, "Warm-up complete")
|
||||
} catch (ex: Exception) {
|
||||
Log.w(TAG, "Warm-up failed (model may not be present yet): ${ex.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun close() {
|
||||
engine?.close()
|
||||
engine = null
|
||||
|
||||
@@ -45,6 +45,10 @@ class AiSuggestionManager {
|
||||
const val KIND_AI_FLAG = 0x10000
|
||||
const val KIND_AI_CORRECTION = SuggestedWordInfo.KIND_CORRECTION or KIND_AI_FLAG
|
||||
|
||||
/** Marks a transient "loading" placeholder — not tappable. */
|
||||
const val KIND_AI_LOADING_FLAG = 0x20000
|
||||
const val KIND_AI_LOADING = SuggestedWordInfo.KIND_CORRECTION or KIND_AI_LOADING_FLAG
|
||||
|
||||
/**
|
||||
* Factory method accessible from Java (LatinIME.java patch).
|
||||
* Returns a [CoroutineScope] tied to a SupervisorJob so individual
|
||||
@@ -126,6 +130,20 @@ class AiSuggestionManager {
|
||||
/** Returns the original (pre-correction) sentence, or null if none is pending. */
|
||||
fun getOriginalSentence(): String? = originalSentence
|
||||
|
||||
/** Shows a non-interactive "…" placeholder while inference is running. */
|
||||
fun showLoadingPlaceholder() {
|
||||
val wordInfo = SuggestedWordInfo(
|
||||
"…", "", Int.MAX_VALUE, KIND_AI_LOADING,
|
||||
null, SuggestedWordInfo.NOT_AN_INDEX, SuggestedWordInfo.NOT_A_CONFIDENCE
|
||||
)
|
||||
val words = SuggestedWords(
|
||||
arrayListOf(wordInfo), null, wordInfo,
|
||||
false, false, false,
|
||||
SuggestedWords.INPUT_STYLE_PREDICTION, SuggestedWords.NOT_A_SEQUENCE_NUMBER
|
||||
)
|
||||
accessor?.showAiSuggestion(words)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the AI suggestion from the strip.
|
||||
* Called when the correction matches the original (no change needed)
|
||||
@@ -134,6 +152,6 @@ class AiSuggestionManager {
|
||||
fun clearSuggestion() {
|
||||
_currentSuggestion.value = null
|
||||
originalSentence = null
|
||||
accessor?.setNeutralSuggestionStrip()
|
||||
accessor?.clearAiSuggestionAndSetNeutral()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ class AiTriggerHook(
|
||||
debounceJob?.cancel()
|
||||
debounceJob = scope.launch {
|
||||
delay(DEBOUNCE_MS)
|
||||
manager.showLoadingPlaceholder()
|
||||
Log.d(TAG, "Triggering AI correction for: \"$sentence\"")
|
||||
val corrected = engine.correctSentence(sentence)
|
||||
if (corrected != sentence) {
|
||||
|
||||
Reference in New Issue
Block a user