Индустрия застряла в парадигме чат-ботов. Каждая CRM, таск-трекер и текстовый редактор обзавелись плавающей кнопкой со звездочками. Нажимаешь — открывается диалоговое окно. Пишешь промпт, ждешь генерацию, копируешь результат, закрываешь окно, вставляешь текст в основную форму. Продакт-менеджеры называют это революцией продуктивности. Инженеры видят в этом костыль.
Пользователь открывает интерфейс не для бесед с нейросетью. У него есть задача: заполнить карточку товара, написать email клиенту, составить баг-репорт. Чат-бот ломает этот процесс, требуя жесткого переключения контекста. Вместо прямого набора текста человек вынужден формулировать мета-инструкции о том, как этот текст должен быть написан. Когнитивная нагрузка возрастает.
Отказ от диалогового интерфейса в пользу предиктивного ввода — ghost text — возвращает фокус на задачу. Нейросеть перестает быть навязчивым собеседником и становится продолжением клавиатуры. Серый текст появляется впереди курсора, Tab принимает подсказку. Никаких лишних кликов. Никаких окон.
Но технически реализовать бесшовный ghost text на порядок сложнее, чем прикрутить очередной чат поверх интерфейса.
Анатомия миллисекунд
Чат прощает задержки. Пользователь отправил сообщение и психологически готов ждать две-три секунды. Автозаполнение живет в миллисекундах. Если подсказка появляется дольше 400 мс, она ломает ритм. Человек уже напечатал следующее слово, а интерфейс вдруг подсовывает ему устаревшее продолжение. Возникает рассинхронизация, которая бесит больше, чем отсутствие AI вообще.
Реализация на фронтенде требует жесткого контроля над сетевыми запросами. Мы не можем дергать API на каждое нажатие клавиши — это сожжет бюджет и положит сервер. Базовый инструмент здесь — Debounce. Мы слушаем события ввода, но отправляем запрос только когда пользователь делает микропаузу. Окно в 150-250 миллисекунд — оптимальный баланс между отзывчивостью и экономией ресурсов.
Однако Debounce не решает проблему гонки данных (race conditions). Пользователь сделал паузу, запрос ушел на сервер. Сервер начал генерацию. В этот момент пользователь передумал и напечатал еще один символ. Контекст изменился. Старый ответ, который все еще летит по сети, превратился в мусор. Если фронтенд его отрисует, курсор прыгнет, а текст превратится в кашу.
Здесь вступает в игру AbortController. Перед каждой новой отправкой запроса фронтенд обязан убить предыдущий.
class GhostTextProvider {
#controller = null;
#timeoutId = null;
async suggest(text, cursorPosition) {
clearTimeout(this.#timeoutId);
return new Promise((resolve, reject) => {
this.#timeoutId = setTimeout(async => {
if (this.#controller) {
this.#controller.abort;
}
this.#controller = new AbortController;
try {
const response = await fetch('/api/copilot/complete', {
method: 'POST',
signal: this.#controller.signal,
body: JSON.stringify({
prefix: text.slice(0, cursorPosition),
suffix: text.slice(cursorPosition)
})
});
resolve(await response.json);
} catch (err) {
if (err.name === 'AbortError') return;
reject(err);
}
}, 200);
});
}
}
Браузер мгновенно обрывает TCP-соединение. Сеть не ждет мертвых ответов, очередь запросов остается чистой.
Отрисовка иллюзии
Сам рендеринг серого текста — отдельная инженерная боль. Стандартные или не поддерживают разноцветный текст внутри себя. Разработчикам приходится строить оптические иллюзии.
Под прозрачным полем ввода (или поверх него, с pointer-events: none) размещается div-клон. Он в точности повторяет типографику, отступы, line-height и ширину оригинального поля. В этот клон копируется введенный пользователем текст (невидимым цветом), а следом добавляется сгенерированный ghost text (серым цветом). Любое расхождение в один пиксель из-за кастомного скроллбара или специфичного шрифта разрушает эффект. Текст начинает двоиться.
Бэкенд: Скорость важнее интеллекта
Идеальный фронтенд бессилен, если бэкенд тормозит. Для ghost text категорически не подходят тяжелые модели вроде GPT-4o или Claude 3.5 Sonnet. Их Time-to-First-Token (TTFT) слишком велик для предиктивного ввода.
Нам нужны легковесные, молниеносные модели. Llama 3 8B, Qwen 2.5 Coder, специализированные сборки Mistral. Задача этих моделей — не решать сложные логические головоломки, а угадывать следующие пять-десять слов на основе контекста.
Здесь архитектура упирается в маршрутизацию. Привязка к одному провайдеру API — это риск. Если условный OpenAI или Anthropic начинает испытывать сетевые задержки, чат-бот просто отвечает медленнее. Ghost text в такой ситуации полностью умирает, оставляя пользователя наедине с пустым экраном.
Интеграция через RouterAPI решает эту проблему на уровне инфраструктуры. RouterAPI позволяет абстрагироваться от конкретных эндпоинтов и настроить роутинг с приоритетом на минимальную задержку (latency-based routing).
Вместо жестко зашитого URL, бэкенд отправляет запрос в шлюз RouterAPI. Шлюз знает текущий пинг до Groq, Together AI, Fireworks и других провайдеров, хостящих нужную нам легковесную модель. Если один провайдер деградирует и отвечает дольше 500 мс, RouterAPI автоматически переключает трафик на запасной узел. Для конечного пользователя автозаполнение продолжает работать без рывков.
Fill-In-the-Middle (FIM)
Переход от чата к автозаполнению требует изменения формата запросов. Мы больше не используем эндпоинт /v1/chat/completions. Нам нужен сырой /v1/completions и поддержка Fill-In-the-Middle (FIM).
Когда пользователь редактирует текст в середине абзаца, нейросеть должна знать не только то, что написано до курсора, но и то, что идет после. Иначе она сгенерирует текст, который логически не состыкуется с хвостом предложения.
Модели, обученные для FIM, используют специальные токены. Запрос формируется на уровне бэкенда: Текст до курсораТекст после курсора
Модель начинает генерацию ровно с того места, где стоит .
Главная ошибка новичков — попытка засунуть в автозаполнение системные промпты. "Ты умный помощник, твоя задача продолжить текст в профессиональном стиле..". Системные инструкции сбивают легковесные модели с толку. Они начинают генерировать мета-текст: "Конечно, вот продолжение: ..". В контексте ghost text это фатально. Модель должна получать только сырой контекст формы. Если это поле "Описание задачи", префикс должен содержать название задачи и уже введенный текст. Никаких "пожалуйста" и "спасибо".
Температура генерации выкручивается почти в ноль (0.1 - 0.2). Нам нужна максимальная детерминированность. Стоп-токены настраиваются на перенос строки или конец предложения. Ghost text не должен писать поэмы — он должен закрывать текущую мысль.
Смещение фокуса
Интеграция AI в формы меняет природу поля ввода. Из пассивного приемника символов форма превращается в активного соавтора. Но этот соавтор лишен голоса и эго.
Разработка таких интерфейсов требует отказа от демонстрации интеллекта системы. Инженеры тратят дни на профилирование рендеринга, тюнинг Debounce-интервалов, настройку RouterAPI для балансировки задержек и обработку гонки сетевых запросов не ради того, чтобы поразить пользователя. Вся эта сложная инфраструктура строится с одной целью — чтобы пользователь вообще забыл о существовании нейросети.
Успех ghost text измеряется не длиной сгенерированных текстов, а количеством нажатий клавиши Tab. Когда система работает правильно, она растворяется в мышечной памяти. Человек просто замечает, что рутинные формы заполняются в два раза быстрее, а пальцы делают меньше лишних движений. Это и есть настоящий UX, где технологии обслуживают процесс, а не перетягивают внимание на себя.