Впровадження AI-автодоповнення: від концепції до готового UX
Уявіть: користувач набирає відповідь у месенджері, і додаток пропонує закінчити речення фразою «Дякую за лист, я розгляну вашу пропозицію». Якщо підказка з'являється із затримкою 2 секунди або мигає при кожному символі — UX зруйновано. Ми вирішували цю проблему для фінтех-додатка з аудиторією 500k+ користувачів. Результат: 30% користувачів використовують автодоповнення щодня, час набору повідомлень скоротився на 40%. Розрив між концептом і робочою реалізацією — в деталях UX і продуктивності.
Коли пропонувати підказку
Найнедооціненіша частина — тригер. Підказка не повинна з'являтися при кожному символі. Робоча евристика: пропонуємо автодоповнення, якщо користувач набрав від 3 слів у поточному рядку і зробив паузу > 600 мс, або натиснув пробіл у кінці незавершеного речення. Нижче — порівняння поширених стратегій тригера.
| Евристика | Затримка | Точність | Приклад сценарію |
|---|---|---|---|
| Кожен символ | 0 мс | Низька (багато хибних спрацьовувань) | Набір кожного символу |
| Пауза >600 мс + мінімум 3 слова | ~600 мс | Висока | Користувач задумався |
| Пробіл після кінця речення | 0 мс | Середня (тільки контекстно) | «Я вважаю, що. » (пробіл) |
Докладніше про тригери
На практиці комбінуємо дві евристики: пауза понад 600 мс після введення не менше 15 символів і натискання пробілу після крапки, знаку питання або оклику. Це покриває 95% сценаріїв, де користувач очікує підказку.
// 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 моделі не завжди підходять?
Альтернатива для on-device — CreateML Text Classifier не підходить, потрібен generative model. На iOS 18+ є Foundation Models framework з on-device LLM (Apple Intelligence). На Android — Gemini Nano через Google AI Edge SDK. Однак Gemini Nano доступний на Pixel 8+ і деяких Samsung — не універсальне рішення. Згідно з Apple Foundation Models, on-device LLM вимагає A17 Pro або M1+. Для широкої аудиторії потрібен серверний fallback.
// 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() ?: ""
У таблиці нижче порівняємо підходи:
| Параметр | Серверне API (gpt-4o-mini) | On-device (Apple Intelligence/Gemini Nano) |
|---|---|---|
| Затримка | ~300-800 мс (залежить від мережі) | <100 мс (без мережі) |
| Якість | Висока (потужніша модель) | Середня (обмежені ресурси) |
| Доступність | Будь-який пристрій з інтернетом | Тільки флагманські пристрої |
| Приватність | Дані йдуть на сервер | Повна приватність на пристрої |
| Вартість | Оплата за токени | Безкоштовно для розробника |
| Підтримка офлайн | Ні | Так |
Гібридний підхід — on-device з серверним fallback — дає краще з обох світів.
Як уникнути мерехтіння підказки?
Suggestion мигає. Виникає, якщо новий запит повертається швидше ніж за 200 мс і одразу змінює попередній. Рішення — показувати новий suggestion лише якщо він відрізняється від попереднього більше ніж на 3 символи.
Модель продовжує віддалений текст. Якщо користувач видалив частину тексту — в контексті для промпта має бути актуальна версія, не попередня. Слідкуйте за синхронізацією textBeforeCursor з реальним станом TextStorage.
Tab перехоплюється системою. На Android Tab на м'якій клавіатурі недоступний. Використовуйте кастомну inline-клавішу або жест свайп-вправо через GestureDetector.
Відображення підказки
Стандартний патерн: сірий 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:).
Що входить у роботу
При замовленні реалізації AI-автодоповнення ви отримуєте:
- Архітектурну документацію: вибір підходу (сервер / on-device / гібрид), схему інтеграції.
- Реалізацію тригера та debounce з урахуванням UX.
- Інтеграцію з обраним API або on-device SDK.
- UI-компоненти inline-підказки під iOS та Android.
- Тестування на реальних пристроях: достовірність підказок, відсутність мерехтіння, коректна поведінка при видаленні.
- Інструкцію з експлуатації та рекомендації з донавчання моделі.
Спираючись на досвід реалізації понад 10 проектів у сфері NLP, ми гарантуємо стабільну роботу підказок. Зв'яжіться з нами, щоб оцінити ваш проект — ми підберемо оптимальне рішення за 1-2 дні.
Орієнтири за термінами
Базове автодоповнення з серверним API + inline UI — 5–8 днів. On-device через Apple Intelligence / Gemini Nano з серверним fallback — 2–3 тижні. Точні терміни залежать від кількості платформ, вимог до дизайну та необхідності підтримки офлайн-режиму. Отримайте консультацію — ми розрахуємо термін під ваш проект.
Часті проблеми та їх вирішення
Поширені помилки при реалізації
- Неврахування видалення тексту: контекст застаріває → модель дописує видалені символи. Рішення: синхронізувати
textBeforeCursorпісля кожної зміни. - Відсутність debounce: кожен символ → запит до API → 500+ запитів на хвилину → перевантаження та рахунок за токени. Рішення: поріг 600 мс та відміна попередньої задачі.
- Ігнорування стоп-токенів: модель генерує кілька речень → підказка займає пів екрану. Рішення:
stop: ["\n", "."]таmax_tokens: 30.







