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 או ב-provider) וקוראים ל-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> בכל קריאה. עם טוקן, טופס הפנייה לא מחייב מייל או טלפון, וקהל הבודקים מזהה את המשתמש. setUser טוען מחדש את הגרסה, ומשתמש אחר מתחיל שיחה חדשה, כך ששיחה ששמורה במכשיר לא עוברת אף פעם לחשבון אחר.
קישורים, מסכים וטופס הפנייה
- כפתורים מסוג
linkנפתחים עםurl_launcher, רק לכתובותhttps:ו-tel:. קישורים בתוך הודעות הבוט: רקhttps:. - כפתורים מסוג
screenקוראים ל-onScreen(screen, targetId). בלי הפונקציה הזו הם לא עושים כלום. - הכפתור "פנייה לצוות" פותח את הטופס המובנה (שם, מייל, טלפון, נושא והודעה, עם השיחה המצורפת), ואחרי השליחה מציג את מספר הפנייה.
רשימת המסכים בעורך
מצהירים פעם אחת על המסכים שהאפליקציה יודעת לפתוח ב-KavchatController(screens: ...). ערכת הפיתוח שולחת את הרשימה ל-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. בפרימיום אפשר להסתיר אותו בהגדרות הבוט.
צ'אט עם נציג
אין צורך בהגדרה כדי להתחיל. כשמפעילים צ'אט עם נציגים במסך הניהול, האפשרות מופיעה בצ'אט באפליקציה בדיוק כמו בווידג'ט לאתר.
- האפשרות: "שיחה עם נציג" מופיעה לפני "פנייה לצוות", ורק אחרי שהשרת אישר שיש נציג פנוי. הזמינות נבדקת שוב כשמקישים על הכפתור. מחוץ לשעות הפעילות, או כשכל הנציגים עסוקים, מוצג רק טופס פנייה עם הסבר קצר ומועד החזרה של הצוות, אם הוא ידוע. כשהמכסה החודשית נגמרת, הטופס מוצג בלי הסבר.
- הבקשה: אורחים משאירים אימייל או טלפון למקרה שאף נציג לא יתפנה בזמן. אפשר להוסיף גם שם והודעה ראשונה. כשיש יותר ממחלקה אחת פתוחה, בוחרים מחלקה. משתמשים מזוהים (
userToken) לא צריכים למלא פרטי קשר. אם אף נציג לא יכול לקבל את השיחה, נפתח טופס פנייה עם הפרטים שכבר הוזנו. - ההמתנה: מוצג "ממתינים לנציג" עם ספירה לאחור עד סוף זמן ההמתנה שהגדרתם, ולצידו הכפתורים "להשאיר הודעה במקום" ו"סיום השיחה". אפשר לכתוב כבר בזמן ההמתנה. כשהזמן נגמר, השיחה הופכת אוטומטית לפנייה. מוצגים מספר הפנייה והסבר שאף נציג לא התפנה בזמן.
- השיחה: שם הנציג, ההודעות, חיווי הקלדה, העברה לנציג אחר וסיום השיחה. כל מה שמגיע מהשרת מוצג כטקסט רגיל, בלי קישורים.
- בזמן אמת: חיבור Server-Sent Events דרך תשובת
httpבזרימה. אם החיבור מתנתק, הצ'אט מתחבר מחדש במרווחים הולכים וגדלים ומשלים את כל מה שהוחמץ. כך שום הודעה לא הולכת לאיבוד ולא מופיעה פעמיים. ברשתות שחוסמות את החיבור הזה, וב-Flutter Web, הצ'אט עובד בבדיקות חוזרות (long-poll). לכל הודעה יש מזהה, כך שניסיון חוזר לא יוצר כפילות. הודעה שלא נשלחה מסומנת, ואפשר לשלוח אותה שוב בהקשה. - מעבר לרקע והפעלה מחדש: כשהאפליקציה עוברת לרקע (
paused,hidden), החיבור נסגר. כשהיא חוזרת (resumed), הצ'אט משלים את מה שהוחמץ. אישור הגישה לשיחה תקף עד 12 שעות או עד שהשיחה מסתיימת, ונשמר באחסון שתבחרו. כך, אחרי הפעלה מחדש של האפליקציה, שיחה פתוחה חוזרת למסך. האישור לא נשמר בכתובת ולא נכתב ללוג. - אין התראות 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, שעוברת את אותם וקטורי בדיקה כמו הווידג'ט לאתר).
שאלות, תנאים ולידים
אין צורך בהגדרות באפליקציה. תסריט יכול לבקש פרטים (שם, טלפון, אימייל, מספר או טקסט), לבחור איך להמשיך לפי תנאים (פרימיום) ולשלוח ליד. הכל רץ במכשיר.
- מקלדת מתאימה: כשהבוט מחכה לתשובה, בשדה הטקסט מוצגת מקלדת טלפון, אימייל או מספרים, עם השלמה אוטומטית. אם התשובה לא תקינה, הבוט מסביר מה חסר והשדה מסומן. בשאלה שאפשר לדלג עליה מופיע כפתור "דילוג".
- ליד: כשיש שורת הסכמה, מופיעים הכפתורים "שליחה" ו"לא, תודה". אם אין חיבור לרשת, הליד נשמר במכשיר ונשלח כשהחיבור חוזר, בלי כפילויות.
- מילוי מראש: התשובות שהמשתמש כבר נתן ממלאות את טופס הפנייה ואת טופס הבקשה לנציג, ומצורפות לפנייה ולשיחה עם הנציג. שם, אימייל וטלפון מטוקן הזהות נחשבים לתשובות ידועות, והבוט לא שואל עליהם שוב.
- המסך הנוכחי:
livePageמציין גם את המסך שהתנאים בודקים ושנשלח עם לידים. כשהמשתמש עובר למסך אחר, קוראים ל-setPage('checkout').
תשובות AI (פרימיום)
אין מה להגדיר באפליקציה. כשמפעילים תשובות AI במסך הניהול (עם מפתח של ספק AI משלכם), שאלה שאף תסריט לא עונה עליה נשלחת ל-Kavchat, ובזמן ההמתנה, עד 20 שניות, הצ'אט מראה שהבוט מקליד. התשובה מוצגת כטקסט רגיל עם תווית קטנה "תשובת AI" בשפת הממשק, ומתחתיה כפתור לנושא קשור, אם יש כזה, וכפתור "פנייה לצוות".
אם אין תשובה, או שהבקשה נכשלה, לקחה יותר מדי זמן או עברה את המגבלה (10 תשובות לשיחה בכל 15 דקות), הצ'אט מציג את ההודעה הרגילה של "לא מצאתי תשובה". השאלה עדיין מופיעה בדוח השאלות שלא נמצאה להן תשובה, ותשובות AI נשמרות בשיחה השמורה כמו כל הודעה אחרת.
עבודה בלי רשת ומטמון
הגרסה נשמרת במטמון עם ה-ETag שלה (If-None-Match, 304). כשאין רשת או שיש שגיאת שרת, נעשה שימוש בעותק השמור ומוצגת הודעה על כך. השיחה נשמרת לכל בוט ומשוחזרת במשך 24 שעות, כל עוד הגרסה לא השתנתה. מהלך השיחה מדווח לשרת 2 שניות אחרי הפעולה האחרונה, וגם כשהאפליקציה עוברת לרקע.