Реалізація AI-автодповнення тексту при наборі в мобільному додатку
Вбудоване автодповнення клавіатури iOS та Android передбачає наступне слово за частотними n-грамами. AI-автодповнення розуміє контекст: знає, що користувач пише ділове листування, бачить перші два параграфи, пропонує цілі фрази, а не одне слово.
Розрив між концептом та робочою реалізацією — у деталях UX та продуктивності.
Коли пропонувати підказку
Найменш оцінена частина — триггер. Підказка не повинна з'являтися при кожному символі.
Робоча евристика: пропонуємо автодповнення, якщо користувач набрав від 3 слів у поточному рядку та зробив паузу > 600 мс, або натиснув пробіл у кінці незавершеної фрази.
// iOS - триггер автодповнення
private var autocompleteTask: Task<Void, Never>?
func textDidChange(_ textView: UITextView) {
autocompleteTask?.cancel()
let text = textView.text ?? ""
let cursorPosition = textView.selectedRange.location
let textBeforeCursor = String(text.prefix(cursorPosition))
// Не пропонуємо в середині слова
guard textBeforeCursor.last == " " || textBeforeCursor.last == "\n" else {
hideAutocomplete()
return
}
// Мінімум 15 символів контексту
guard textBeforeCursor.trimmingCharacters(in: .whitespaces).count > 15 else { return }
autocompleteTask = Task {
try? await Task.sleep(nanoseconds: 600_000_000) // 600ms debounce
guard !Task.isCancelled else { return }
await fetchAutocomplete(context: textBeforeCursor)
}
}
Запрос до моделі та парсинг відповіді
Для автодповнення використовуємо режим completion, не chat. gpt-4o-mini з max_tokens: 30 та temperature: 0.3 — швидко та передбачуваної.
struct AutocompleteRequest: Encodable {
let model = "gpt-4o-mini"
let messages: [ChatMessage]
let maxTokens = 30
let temperature = 0.3
let stop = ["\n", "."] // зупиняємось на кінці фрази
}
func buildPrompt(context: String) -> [ChatMessage] {
[
ChatMessage(role: "system", content: "Complete the text naturally. Continue from where it ends. Output only the continuation, no commentary."),
ChatMessage(role: "user", content: context)
]
}
Стоп-токени \n та . — важливі. Без них модель сгенерує кілька речень, а нам потрібно одне продовження.
Альтернатива для on-device — CreateML Text Classifier не підходить, потрібен generative model. На iOS 18+ є Foundation Models framework з on-device LLM (Apple Intelligence). На Android — Gemini Nano через Google AI Edge SDK:
// Android - Gemini Nano on-device (вимагає підтримки пристрою)
val generativeModel = GenerativeModel(
modelName = "gemini-nano",
generationConfig = generationConfig {
maxOutputTokens = 30
temperature = 0.3f
stopSequences = listOf(".", "\n")
}
)
val response = generativeModel.generateContent(
content { text("Complete naturally: $contextText") }
)
val completion = response.text?.trim() ?: ""
Gemini Nano доступний на Pixel 8+ та деяких Samsung — не універсальне рішення. Для широкої аудиторії потрібен серверний fallback.
Відображення підказки
Стандартний паттерн: сірий inline-текст після курсору. Користувач натискає Tab або свайп вправо — підказка приймається. Будь-який інший введення —ховається.
// Android Compose - inline suggestion
@Composable
fun TextFieldWithSuggestion(
value: String,
suggestion: String,
onValueChange: (String) -> Unit,
onAcceptSuggestion: () -> Unit
) {
val annotatedText = buildAnnotatedString {
append(value)
withStyle(SpanStyle(color = Color.Gray.copy(alpha = 0.6f))) {
append(suggestion)
}
}
BasicTextField(
value = TextFieldValue(
annotatedString = annotatedText,
selection = TextRange(value.length) // курсор — після реального тексту
),
onValueChange = { tfv ->
val newText = tfv.text.take(value.length + suggestion.length)
if (newText.startsWith(value + suggestion)) {
onAcceptSuggestion()
} else {
onValueChange(tfv.text.take(value.length))
}
},
keyboardActions = KeyboardActions(
onDone = { onAcceptSuggestion() }
)
)
}
На iOS inline suggestion через UITextInput + drawText(in:) або простіше через overlay label, позиціонований через caretRect(for:).
Частіші проблеми
Suggestion мигає. Відбувається якщо новий запрос повертається швидше 200 мс та одразу мінює попередній. Рішення — показувати тільки якщо новий suggestion відрізняється від попереднього більше ніж на 3 символи.
Модель продовжує видалений текст. Якщо користувач видалив частину тексту — у контексті для промпту повинна бути актуальна версія, не попередня. Стежте за синхронізацією textBeforeCursor з реальним станом TextStorage.
Tab перехоплюється системою. На Android Tab на мякій клавіатурі недоступний. Використовуємо кастомну inline-клавішу або жест свайп-вправо через GestureDetector.
Орієнтири за часом
Базове автодповнення з серверним API + inline UI — 5–8 днів. On-device через Apple Intelligence / Gemini Nano з серверним fallback — 2–3 тижні.







