Flutter
Пакет kavchat_flutter добавляет в приложение Flutter чат поддержки по сценарию: кнопки, распознавание свободного текста, форму для создания обращения, копию бота для работы без сети и отображение справа налево для иврита и арабского.
Установка
dependencies:
kavchat_flutter: ^0.0.11Вместе с пакетом устанавливаются http, shared_preferences (кеш версии и сохранённого диалога) и url_launcher (ссылки в сценариях). Чтобы открывать ссылки tel: на Android 11 и выше, добавьте в AndroidManifest.xml запись <queries> для схемы tel и ещё одну для https, чтобы открывать ссылки на сайты (см. README пакета url_launcher). На iOS параметр LSApplicationQueriesSchemes нужен только в том случае, если вы сами вызываете canLaunchUrl.
Использование
final kavchat = KavchatController(
publicKey: 'pk_live_...',
// apiBase: 'https://api.kavchat.com', // default
// locale: 'he', // default: device language if translated, else the bot language
onScreen: (screen, targetId) => navigatorKey.currentState?.pushNamed('/$screen', arguments: targetId),
);
Scaffold(
floatingActionButton: KavchatLauncher(controller: kavchat), // hidden while the bot is off
);
// or open it yourself
showKavchat(context, kavchat);
// or embed the screen in your own route
KavchatChat(controller: kavchat, onClose: () => Navigator.pop(context));Создайте контроллер один раз (например, в State или провайдере) и вызывайте dispose() при освобождении объекта, которому он принадлежит.
KavchatLauncher представляет собой FloatingActionButton. Его расположение задаётся через Scaffold.floatingActionButtonLocation. Настройка kavchat.settings?.position (start или end) указывает, какая сторона выбрана в панели управления.
Если в панели управления настроено сообщение рядом с кнопкой чата (облачко, текст по дуге над кнопкой, сама кнопка, которая разворачивается в капсулу и «говорит», или линия, которая поднимается от кнопки и показывает первые варианты бота), KavchatLauncher показывает его после заданной задержки, не чаще одного раза в день. Нажатие на вариант на линии открывает чат с этим вариантом, как если бы его выбрали внутри чата. Если закрыть сообщение или открыть чат, сообщение скрывается до завтра. Пока чат открыт, сообщение не показывается. Если на устройстве включено уменьшение движения, сообщение отображается без анимации. При включённой настройке «Не показывать на телефонах» сообщение отображается только на планшетах (с короткой стороной экрана от 600 точек). На телефоне линия показывает до двух вариантов. KavchatLauncher(controller: kavchat, attention: false) отключает показ сообщения.
Если в теме оформления для кнопки выбрана иконка с лицом, оно время от времени моргает, пока кнопка не используется. Лицо не моргает, когда чат открыт, приложение работает в фоне или включено уменьшение движения.
Бот не отображается (плавающая кнопка не появляется, а KavchatChat показывает «Недоступно»), если у версии задано enabled: false: бот выключен, доступен только тестовым пользователям, а текущий пользователь к ним не относится, или исчерпана квота (reason: 'quota').
Токен идентификации
Для пользователей, вошедших в аккаунт, подпишите короткий JWT на вашем сервере с помощью секретного ключа (никогда не делайте это в приложении, см. Идентификация пользователей) и передайте его:
KavchatController(publicKey: 'pk_live_...', userToken: jwt, userName: 'Dana');
await kavchat.setUser(token: newJwt, name: 'Dana'); // after login
await kavchat.setUser(token: null); // after logoutТокен отправляется в заголовке Authorization: Bearer <token> при каждом запросе. С токеном форма обращения не требует email или телефон, а пользователь распознаётся в группе тестовых пользователей. setUser загружает версию заново, а для другого пользователя начинает новый диалог. Поэтому сохранённый на устройстве диалог никогда не переходит в другой аккаунт.
Ссылки, экраны и форма обращения
- Кнопки типа
linkоткрывают ссылки черезurl_launcher, только для адресовhttps:иtel:. Ссылки в сообщениях бота: толькоhttps:. - Кнопки типа
screenвызываютonScreen(screen, targetId). Без этой функции они ничего не делают. - Кнопка «Обратиться в поддержку» открывает встроенную форму (имя, email, телефон, тема и сообщение, с приложенным диалогом), а после отправки показывает номер обращения.
Список экранов в редакторе
Один раз объявите экраны, которые приложение может открывать, в KavchatController(screens: ...). SDK сам отправит список в Kavchat, и редактор сценариев предложит эти экраны по названиям в выпадающем списке вместо поля для свободного ввода. Редактор также запрашивает идентификатор элемента, если он нужен экрану, и предупреждает о кнопках, ведущих на необъявленный экран. Вводить список экранов вручную в панели управления не нужно.
final kavchat = KavchatController(
publicKey: 'pk_live_...',
screens: const [
KavchatScreen(id: 'orders', label: 'My orders'),
KavchatScreen(id: 'product', label: 'Product page', needsTarget: true), // the button carries a targetId
],
onScreen: (screen, targetId) => navigatorKey.currentState?.pushNamed('/$screen', arguments: targetId),
);
// later, to replace the list: await kavchat.registerScreens([...]);id— значение, которое получаетonScreen: латинские буквы, цифры,_,.и-, до 40 символов.label— название, которое видит администратор, до 60 символов. Допускается до 100 экранов. Некорректные записи и повторяющиеся идентификаторы пропускаются.needsTarget: кнопка также передаёт идентификатор элемента (например, товара), который поступает вonScreenкакtargetId.- Список отправляется в фоне после загрузки версии, только если он изменился или раз в неделю. При ошибке уведомление не показывается, а отправка повторяется при следующем запуске. Пользователь ничего не видит.
- Зарегистрированные экраны отображаются в панели управления в разделе «Бот», «Настройки», «Экраны приложения» вместе с платформами, которые сообщили о каждом экране. Экран, удалённый из приложения, можно удалить и там.
RTL
Чат задаёт собственное значение Directionality по языку интерфейса: для he и ar справа налево, для остальных слева направо. Направление в вашем приложении не меняется. Каждое облачко сообщения также выбирает направление по первой букве с явно заданным направлением письма, чтобы смешанный текст читался правильно. Язык интерфейса и язык сценария независимы: бот на английском может показывать кнопки на арабском, например «Назад», при locale: 'ar'.
Цвета и брендинг
Цвета берутся из настройки бота primaryColor. Текст на этом фоне будет чёрным или белым в зависимости от контрастности по WCAG. Слишком светлый цвет заменяется тёмным там, где он используется для текста. Вот как изменить цвета:
KavchatLauncher(controller: kavchat, theme: KavchatThemeData.fromColor(const Color(0xFF0F766E)));
KavchatChat(controller: kavchat, theme: KavchatThemeData.fromHex('#0f766e').copyWith(background: Colors.white));Надпись «Работает на Kavchat by maxsave» со ссылкой на kavchat.com отображается на всех тарифах. На тарифе Premium её можно скрыть в настройках бота.
Чат с оператором
Для начала работы ничего настраивать не нужно. Когда вы включаете чат с операторами в панели управления, эта возможность появляется в чате приложения так же, как в виджете для сайта.
- Кнопка: «Чат с оператором» появляется перед кнопкой «Обратиться в поддержку» и только после того, как сервер подтвердит наличие свободного оператора. При нажатии доступность проверяется повторно. В нерабочее время или когда все операторы заняты показывается только форма обращения с кратким объяснением и временем возвращения команды, если оно известно. Когда месячная квота исчерпана, форма показывается без объяснения.
- Запрос на чат: гости оставляют email или телефон на случай, если ни один оператор не освободится вовремя. Можно также добавить имя и первое сообщение. Если открыто несколько отделов, нужно выбрать один. Идентифицированным пользователям (
userToken) не нужно вводить контактные данные. Если ни один оператор не может принять чат, открывается форма обращения с уже введёнными данными. - Ожидание: отображается «Ожидаем оператора» с обратным отсчётом заданного вами времени ожидания, а рядом кнопки «Оставить сообщение» и «Завершить чат». Писать можно уже во время ожидания. Когда время истекает, чат автоматически превращается в обращение. Показываются номер обращения и объяснение, что ни один оператор не освободился вовремя.
- Диалог: имя оператора, сообщения, индикатор набора текста, перевод на другого оператора и завершение чата. Всё, что приходит с сервера, отображается как обычный текст без ссылок.
- В реальном времени: соединение Server-Sent Events через потоковый ответ
http. Если соединение обрывается, чат подключается заново с увеличивающимися интервалами и загружает всё пропущенное. Поэтому сообщения не теряются и не появляются дважды. В сетях, блокирующих это соединение, и во Flutter Web чат использует повторные запросы (long-poll). У каждого сообщения есть идентификатор, поэтому повторная попытка не создаёт дубликат. Неотправленное сообщение помечается, и его можно отправить повторно нажатием. - Фоновый режим и перезапуск: когда приложение переходит в фон (
paused,hidden), соединение закрывается. При возвращении (resumed) чат загружает всё пропущенное. Токен доступа к чату действует до 12 часов или до завершения чата и сохраняется в выбранном вами хранилище. Поэтому после перезапуска приложения незавершённый чат снова появляется на экране. Токен не сохраняется в URL и не записывается в журнал. - В этой версии нет push-уведомлений: сообщения от оператора приходят только тогда, когда приложение открыто.
Рекомендуем хранить токен доступа в защищённом хранилище (Keychain на iOS, Keystore на Android), например с помощью flutter_secure_storage. Если не задать secureStorage, токен сохраняется в том же хранилище, что и диалог. Ваша собственная кнопка чата с оператором открывает форму запроса на чат, если есть свободный оператор. Если свободных операторов нет, открывается форма обращения с объяснением:
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureKavchatStorage implements KavchatStorage {
final _s = const FlutterSecureStorage();
@override
Future<String?> read(String key) => _s.read(key: key);
@override
Future<void> write(String key, String value) => _s.write(key: key, value: value);
@override
Future<void> remove(String key) => _s.delete(key: key);
}
final kavchat = KavchatController(
publicKey: 'pk_live_...',
secureStorage: SecureKavchatStorage(),
livePage: 'checkout',
onLivePhase: (phase) => debugPrint('live chat: $phase'),
);
// your own button
FilledButton(onPressed: kavchat.openLive, child: const Text('Chat with us'));Та же логика без Flutter находится в kavchat_engine (KavchatClient, LiveSession и общая машина состояний liveApplyEvent, liveLines и liveStatus, которая проходит те же наборы тестовых данных, что и виджет для сайта).
Вопросы, условия и лиды
Настройки в приложении не нужны. Сценарий может запрашивать данные (имя, телефон, email, число или текст), выбирать дальнейший ход по условиям (Premium) и отправлять лид. Всё выполняется на устройстве.
- Подходящая клавиатура: когда бот ждёт ответ, для текстового поля открывается клавиатура для ввода телефона, email или чисел с автозаполнением. Если ответ некорректен, бот объясняет, чего не хватает, а поле выделяется. Для вопроса, который можно пропустить, появляется кнопка «Пропустить».
- Лид: если задан текст согласия, появляются кнопки «Отправить» и «Нет, спасибо». Если нет подключения к сети, лид сохраняется на устройстве и отправляется после восстановления связи, без дубликатов.
- Предзаполнение: ответы, которые пользователь уже дал, заполняют форму обращения и форму запроса на чат с оператором, а также прикладываются к обращению и чату с оператором. Имя, email и телефон из токена идентификации считаются известными ответами, и бот не спрашивает их повторно.
- Текущий экран:
livePageтакже указывает экран, который проверяется в условиях и отправляется вместе с лидами. Когда пользователь переходит на другой экран, вызывайтеsetPage('checkout').
Ответы ИИ (Premium)
В приложении ничего настраивать не нужно. Когда ответы ИИ включены в панели управления (с вашим собственным ключом провайдера ИИ), вопрос, на который нет ответа ни в одном сценарии, отправляется в Kavchat. Во время ожидания, до 20 секунд, чат показывает, что бот набирает текст. Ответ отображается как обычный текст с небольшой пометкой «Ответ ИИ» на языке интерфейса. Под ним появляются кнопка связанной темы, если она есть, и кнопка «Обратиться в поддержку».
Если ответа нет, запрос завершился ошибкой, занял слишком много времени или превысил лимит (10 ответов на диалог за каждые 15 минут), чат показывает стандартное сообщение «Не удалось найти ответ». Вопрос по-прежнему попадает в отчёт о вопросах без ответа, а ответы ИИ сохраняются в диалоге, как и любые другие сообщения.
Работа без сети и кеширование
Версия сохраняется в кеше вместе со своим ETag (If-None-Match, 304). Если нет сети или сервер вернул ошибку, используется сохранённая копия и показывается соответствующее сообщение. Диалог сохраняется отдельно для каждого бота и восстанавливается в течение 24 часов, пока версия не изменилась. Ход диалога отправляется на сервер через 2 секунды после последнего действия, а также при переходе приложения в фон.