Questo documento spiega come rendere disponibili i punti di contatto dell'assistenza (code e canali) nella tua app e come gestire l'intero ciclo di vita della chat: avvio, ripresa, invio di messaggi e allegati, indicazione della digitazione, oscuramento dei dati sensibili, escalation agli agenti umani e chiusura della sessione.
Prima di iniziare
Configura l'SDK. Per saperne di più, consulta SDK mobile headless per Android: Inizia.
Seleziona code e canali
Una coda di solito rappresenta un punto di accesso all'assistenza, ad esempio "Fatturazione", "Assistenza tecnica" o "Assistenza VIP".
Ogni coda può offrire uno o più dei seguenti canali:
Chat
Chiamata istantanea (voce)
Chiamata programmata
Email
Link di deviazione esterni (link alle tue pagine self-service o ad altri canali)
Le code e la disponibilità dei canali vengono configurate dagli amministratori nel portale di amministrazione di Contact Center AI Platform.
Come appare nella tua app
Nella tua app, un flusso tipico è:
Aggiungi un punto di accesso all'assistenza nella tua app (ad esempio, una scheda Guida o un pulsante Contattaci).
Quando l'utente lo tocca, recuperi il menu della coda utilizzando l'SDK ed esegui il rendering della UI del menu.
Quando l'utente sceglie una coda, esamini i canali disponibili.
La tua app mostra questi canali all'utente (ad esempio, Chatta ora, Chiama ora, Pianifica una chiamata, Inviaci un'email, Visita il Centro assistenza).
Quando l'utente seleziona un canale, chiami l'API SDK corrispondente.
Recupera i menu della coda dall'SDK
L'SDK fornisce queueMenuService per recuperare i menu della coda. Il metodo get restituisce un QueueMenuResult contenente l'elenco delle code (List<QueueMenu>) e un flag isDirectAccess. Puoi recuperare l'intero albero del menu o utilizzare un tasto di accesso diretto per passare direttamente a una coda specifica:
import com.ccaiplatform.ccaikit.CCAI
import com.ccaiplatform.ccaikit.models.response.queuemenu.QueueMenuResult
import com.ccaiplatform.ccaikit.models.response.queuemenu.QueueMenu
// Fetch the full queue menu tree
val menuResult: QueueMenuResult? = CCAI.queueMenuService?.get()
// Or fetch a specific queue using a Direct Access Key
val menuResult: QueueMenuResult? = CCAI.queueMenuService?.get(key = "billing_vip")
Il parametro facoltativo key ti consente di bypassare il menu della coda completa e indirizzare l'utente direttamente a una coda specifica. Nella piattaforma CCAI Platform, questo risultato viene ottenuto con un punto di accesso diretto (DAP).
Un DAP può essere configurato dagli amministratori nel portale della piattaforma CCAI nel seguente modo:
Vai a Impostazioni > Coda > seleziona una coda dalla struttura del menu.
Nella sezione Punto di accesso, fai clic su + Crea punto di accesso diretto.
Inserisci la chiave nel modulo di testo.
Fai clic su Salva.
Struttura dei dati QueueMenu
Ogni QueueMenu contiene:
| Proprietà | Tipo | Descrizione |
|---|---|---|
id |
Int | L'identificatore univoco di questa coda. |
name |
String? | Il nome visualizzato (ad esempio, "Assistenza per la fatturazione"). |
children |
List<QueueMenu> | Sottomenu (elenco vuoto se si tratta di un nodo foglia). |
channels |
Canali? | Canali disponibili per questa coda. |
hidden |
Booleano | Indica se questa coda deve essere nascosta ai consumatori (configurata dall'amministratore). |
position |
Int? | Posizione di visualizzazione definita dal server: mantiene l'ordine del portale amministratore. |
settings |
List<QueueMenuSetting>? | Impostazioni a livello di coda configurate nel portale dell'amministratore. |
redirectionExtra |
QueueMenuRedirection? | Metadati di reindirizzamento automatico, se configurati (vedi Reindirizzamento e deviazione). |
deflectionFrom |
List<QueueMenuDeflection>? | Le code che hanno deviato verso questa. La piattaforma restituisce questa coda al loro posto, quindi un elenco non vuoto spiega un reindirizzamento già avvenuto (vedi Deviazione). |
La classe di dati Channels ha le seguenti proprietà:
| Proprietà | Tipo | Descrizione |
|---|---|---|
chat |
ChatChannel? | Configurazione del canale di chat (null se la chat non è disponibile per questa coda). |
voiceCall |
VoiceCallChannel? | Configurazione del canale di chiamata vocale (null se la voce non è disponibile). Contiene flag come scheduleEnabled e preferenze di registrazione. |
email |
EmailChannel? | Configurazione del canale email (null se l'email non è disponibile). Accedi all'indirizzo utilizzando channels.email?.email. |
externalDeflectionLink |
ExternalDeflectionLink? | Configurazione del link di deviazione esterno (null se non disponibile). Accedi all'URL utilizzando le proprietà del tipo strutturato. |
Campi VoiceCallChannel (metadati di funzionalità e deviazione)
// VoiceCallChannel.kt (simplified)
data class VoiceCallChannel(
val instantEnabled: Boolean, // Live voice call allowed now
val preSessionSmartAction: Boolean, // Per-channel pre-session smart action flag
val scheduleEnabled: Boolean, // Scheduled calls supported
val recordingOption: RecordingOption?, // ALWAYS / NEVER / ASK_USER
val voicemailReason: String?,
val phoneNumber: String?,
val scheduleDeflectionType: String?,
val deflected: Boolean?,
val deflectedReason: String?
)
Esempio di implementazione
fun showAvailableChannels(queueMenu: QueueMenu) {
val channels = queueMenu.channels
val voice = channels?.voiceCall
// Show only what the platform enabled, and hide everything else so
// nothing carries over from a previously selected queue
chatButton.isVisible = channels?.chat?.enabled == true
callButton.isVisible = voice != null && voice.instantEnabled && !voice.deflected
scheduleButton.isVisible = voice?.scheduleEnabled == true
emailButton.isVisible = channels?.email?.enabled == true
// External deflection links: the enabled entries that have a URL
val links = channels?.externalDeflectionLink
?.takeIf { it.enabled }
?.links.orEmpty()
.filter { it.enabled && it.url != null }
helpLinksButton.isVisible = links.isNotEmpty()
}
Recupero dei tempi di attesa
Puoi anche recuperare i tempi di attesa stimati per una coda specifica:
// Fetch wait time for a specific queue
val waitTime = CCAI.queueMenuService?.getWaitTimes(menuId = queueMenu.id)
// waitTime contains:
// - chat: estimated wait time for chat (in seconds)
// - voiceCall: estimated wait time for voice call (in seconds)
Code a canale singolo
Se una coda offre un solo canale (ad esempio, solo email), puoi:
Salta il passaggio di selezione del canale e vai direttamente alla UI appropriata.
In alternativa, puoi comunque mostrare esplicitamente il canale disponibile se questo ha più senso nella tua UX.
La configurazione e i metadati dell'SDK indicano quali canali sono consentiti; l'app decide come mostrarli.
Reindirizzamento e deviazione
Gli amministratori possono configurare una coda per reindirizzare gli utenti anziché offrire canali. Quando QueueMenu.redirectionExtra è impostato, agisci non appena l'utente seleziona la coda:
optionindica cosa fare: PHONE, MESSAGE, URL, VOICEMAIL, QUEUE, SIP o CONTACT.datacontiene il valore, ad esempio il numero da comporre, il messaggio da mostrare o l'URL da aprire.
QueueMenu.deflectionFrom è diverso. Quando una coda viene deviata a un'altra coda, la piattaforma restituisce la coda di destinazione al posto di quella selezionata dall'utente ed elenca le code di origine in deflectionFrom. Spiega un reindirizzamento che è già avvenuto; non è un insieme di opzioni da offrire.
La deviazione fuori orario e per sovraccarico viene segnalata per canale. Per informazioni su come gestirlo, consulta SDK mobile headless per Android: azioni smart, allegati e deviazione.
Canali
Nell'SDK mobile headless, il portale dell'amministratore di CCAI Platform funge da fonte di attendibilità per le operazioni di assistenza. Gli amministratori configurano i canali di assistenza (ad esempio chat, voce, email o link utili) disponibili per le diverse code di assistenza (ad esempio "Fatturazione" o "Assistenza tecnica").
Il compito dell'SDK è recuperare in modo sicuro questa configurazione e consegnarla alla tua app. Il compito della tua app è leggere la configurazione e disegnare l'interfaccia utente appropriata.
Ispezione dei canali disponibili
Cosa fornisce l'SDK: un'istantanea in tempo reale dei canali di assistenza attivi e disponibili per la coda specifica selezionata dall'utente, nel rispetto dell'orario di apertura e dei limiti di capacità del portale.
Cosa crei: il menu dell'interfaccia utente (pulsanti, elenchi o schede) che mostra queste opzioni disponibili all'utente.
Esempio di implementazione: il seguente codice Kotlin prende la coda che l'utente ha appena toccato e ispeziona la classe di dati Channels per vedere quali canali sono attivi. Poi decide quali pulsanti mostrare sullo schermo.
// Assuming 'selectedMenu' is a QueueMenu returned by the SDK
fun setupChannelMenu(selectedMenu: QueueMenu) {
val channels = selectedMenu.channels
if (channels.chat != null) {
showChatNowButton()
}
if (channels.voiceCall != null) {
showCallNowButton()
}
if (channels.email != null) {
showEmailUsButton()
}
if (channels.externalDeflectionLink != null) {
showHelpCenterLinks()
}
}
Le sezioni seguenti descrivono la chat in dettaglio. Per i link di deviazione esterni, email, chiamate programmate, condivisione schermo e voce, consulta SDK per dispositivi mobili headless per Android: voce, condivisione schermo, chiamata programmata ed email
API del servizio Chat
chatService gestisce l'intero ciclo di vita della chat. I metodi principali che utilizzi più di frequente sono:
| Metodo | Descrizione |
|---|---|
start |
Avvia una nuova sessione di chat con un ChatRequest. |
sendMessage |
Invia un messaggio (testo, immagine, video o evento del modulo). |
typing |
Aggiorna l'indicatore di digitazione. |
endChat |
Termina la sessione di chat corrente. |
resumeChat |
Riprende una sessione di chat precedentemente stabilita. |
escalateToHumanAgent |
Trasferimenti da un agente virtuale a un agente umano. |
checkStatus |
Aggiorna lo stato della chat corrente dal server. |
Strutture di dati ChatRequest e ChatResponse
La classe di dati ChatRequest contiene la configurazione per avviare una nuova chat:
data class ChatRequest(
val chat: Chat? = null, // The session configuration (always pass one)
val isScreenShareable: Boolean? = null, // Optional: enable screen share for this session
val isVerifiable: Boolean? = null // Optional: allow verification smart actions
)
data class Chat(
val menuId: Int, // Required: queue menu ID
val lang: String? = "en", // Optional: language code override (defaults to "en")
val ticketId: String? = null, // Optional: associate with existing ticket
val greeting: String? = null, // Optional: custom greeting message
val customData: CustomData? = null, // Optional: structured custom data
val displayOrderInAdapter: List<String>? = null,
val directAccessKeyId: Int? = null, // Optional: from QueueMenuResult (Direct Access Point)
val dapRequestId: String? = null // Optional: from QueueMenuResult (Direct Access Point)
)
Dopo aver avviato o ripreso una chat, l'SDK invia il suo ChatResponse sul Flow chatReceived (start() e resumeChat() non restituiscono nulla). Le proprietà a cui farai riferimento più spesso in questa guida includono:
id: l'identificatore univoco della sessione di chat.status: lo stato attuale della chat (in coda, attiva, terminata e così via).agent/virtualAgent: l'agente umano o virtuale assegnato.postSessionRequired: indica se per la sessione è configurato un agente virtuale post-sessione. I sondaggi e il CSAT sono segnalati dasurveyEnabledecsatSetting.redactionEnabled: indica se l'oscuramento automatico delle PII è attivo per questa sessione.
Avvia una sessione di chat
Per avviare una sessione di chat, utilizza chatService con ChatRequest specificando l'ID del menu della coda.
Esempio molto semplice
import android.util.Log
import androidx.lifecycle.lifecycleScope
import com.ccaiplatform.ccaichat.chatService
import com.ccaiplatform.ccaichat.model.Chat
import com.ccaiplatform.ccaichat.model.ChatRequest
import com.ccaiplatform.ccaikit.CCAI
import com.ccaiplatform.ccaiscreenshare.screenShareService
import kotlinx.coroutines.launch
// If screen share is initialized, pass isScreenShareable for agent visibility
val chatRequest = ChatRequest(
chat = Chat(menuId = 123),
isScreenShareable = CCAI.screenShareService != null
)
startOrResumeChat(chatRequest)
// Resumes an earlier chat if there is one; otherwise starts a new one
fun startOrResumeChat(chatRequest: ChatRequest) {
lifecycleScope.launch {
val chatService = CCAI.chatService ?: return@launch
try {
// Pre-flight check: resume an earlier chat instead of starting a second one
val existing = chatService.getLastChatInProgress()
if (existing != null) {
chatService.resumeChat(existing)
return@launch
}
chatService.start(chatRequest)
// Chat started - navigate to your custom chat screen
} catch (e: Exception) {
Log.e("CCAI", "Failed to start chat: ${e.message}")
}
}
}
Se la tua app non include il modulo CCAIScreenShare, rimuovi l'importazione screenShareService e l'argomento isScreenShareable.
Con linguaggio esplicito
val chatRequest = ChatRequest(
chat = Chat(menuId = 123, lang = "en")
)
startOrResumeChat(chatRequest) // Defined in the Minimal example
Riprendi una sessione di chat
Riprendi una sessione di chat stabilita in precedenza utilizzando un oggetto ChatResponse:
lifecycleScope.launch {
try {
CCAI.chatService?.resumeChat(chat)
} catch (e: Exception) {
Log.e("CCAI", "Failed to resume chat: ${e.message}")
}
}
Gestione della coda della chat
Puoi rimettere la chat corrente in coda (lo stato diventa In coda) o controllare le chat in corso esistenti:
// Enqueue the current chat (automatically calls checkStatus() after enqueuing)
lifecycleScope.launch {
try {
CCAI.chatService?.enqueueCurrentChat()
Log.d("CCAI", "Chat enqueued successfully")
} catch (e: Exception) {
Log.e("CCAI", "Failed to enqueue chat: ${e.message}")
}
}
// Check for an existing in-progress chat
lifecycleScope.launch {
try {
val chat = CCAI.chatService?.getLastChatInProgress()
if (chat != null) {
Log.d("CCAI", "Found ongoing chat: ${chat.id}")
} else {
Log.d("CCAI", "No ongoing chat found")
}
} catch (e: Exception) {
Log.e("CCAI", "Failed to get last chat: ${e.message}")
}
}
// Retrieve previous chat history with pagination
lifecycleScope.launch {
try {
val previousChats = CCAI.chatService?.getPreviousChats(currentPage = 1)
if (previousChats != null) {
Log.d("CCAI", "Found ${previousChats.messages.size} previous messages")
}
} catch (e: Exception) {
Log.e("CCAI", "Failed to get previous chats: ${e.message}")
}
}
Invia messaggi
Il metodo sendMessage supporta più tipi di messaggi tramite la classe sealed OutgoingMessageContent:
sealed class OutgoingMessageContent {
data class Text(val content: String) : OutgoingMessageContent()
data class Photos(
val images: List<UploadableImage>,
val smartAction: SmartAction? = null
) : OutgoingMessageContent()
data class Videos(
val video: UploadableVideo,
val smartAction: SmartAction? = null
) : OutgoingMessageContent()
data class FormComplete(
val type: String? = null,
val signature: String? = null,
val data: FormCompleteEventData
) : OutgoingMessageContent()
data class ScreenShare(
val event: String,
val detail: ScreenShareResponse? = null
) : OutgoingMessageContent()
}
I tipi UploadableImage e UploadableVideo (in com.ccaiplatform.ccaikit.models.smartaction) eseguono il wrapping dei contenuti multimediali per il caricamento:
data class UploadableImage(
val data: ByteArray, // The binary data of the image
val mimeType: String, // For example, "image/jpeg" or "image/png"
val uri: Uri? = null // Optional URI for display and local file reference
)
data class UploadableVideo(
val uri: Uri, // The file URI of the video
val mimeType: String // For example, "video/mp4"
)
Photos accetta un elenco di immagini; Videos accetta un singolo video. Evita di conservare in memoria asset multimediali di grandi dimensioni mentre l'utente è in coda o mentre è in attesa di un caricamento.
Esempi di utilizzo
// Send text message
lifecycleScope.launch {
try {
CCAI.chatService?.sendMessage(OutgoingMessageContent.Text("Hello world"))
} catch (e: Exception) {
Log.e("CCAI", "Failed to send message: ${e.message}")
}
}
// Send image message (read the file off the main thread)
lifecycleScope.launch {
try {
val imageData = withContext(Dispatchers.IO) {
requireNotNull(contentResolver.openInputStream(selectedImageUri)).use { it.readBytes() }
}
val images = listOf(
UploadableImage(data = imageData, mimeType = "image/jpeg", uri = selectedImageUri)
)
CCAI.chatService?.sendMessage(
OutgoingMessageContent.Photos(
images = images,
smartAction = null
)
)
} catch (e: Exception) {
Log.e("CCAI", "Failed to send image: ${e.message}")
}
}
// Send video message (one video per message)
val video = UploadableVideo(uri = selectedVideoUri, mimeType = "video/mp4")
lifecycleScope.launch {
try {
CCAI.chatService?.sendMessage(
OutgoingMessageContent.Videos(
video = video,
smartAction = null
)
)
} catch (e: Exception) {
Log.e("CCAI", "Failed to send video: ${e.message}")
}
}
// Send form completion event
// Note: status is a nullable String: "success", "error", or "cancelled".
val formCompleteData = FormCompleteEventData(
smartActionId = 123,
status = "success",
timestamp = "2024-01-01T12:00:00Z",
details = FormCompleteEventDetails(message = "Form submitted successfully")
)
lifecycleScope.launch {
try {
CCAI.chatService?.sendMessage(
OutgoingMessageContent.FormComplete(data = formCompleteData)
)
Log.d("CCAI", "Form completion event sent")
} catch (e: Exception) {
Log.e("CCAI", "Failed to send form completion: ${e.message}")
}
}
Il tipo di messaggio ScreenShare viene utilizzato per inviare eventi e risposte di condivisione schermo al server. Gli eventi di condivisione schermo disponibili includono:
screenShareRequestedFromAgent- l'agente ha richiesto la condivisione schermoscreenShareRequestedFromEndUser- l'utente finale ha richiesto la condivisione schermoscreenShareCodeGenerated- è stato generato il codice di condivisione schermoscreenShareFailed- condivisione schermo non riuscitascreenShareStarted- Sessione di condivisione schermo avviatascreenShareEnded- Sessione di condivisione schermo terminata
Indicatori di digitazione e anteprime dei messaggi
Aggiorna lo stato di digitazione e invia le anteprime dei messaggi mentre l'utente digita:
// Update typing indicator
CCAI.chatService?.typing(true) // Start typing
CCAI.chatService?.typing(false) // Stop typing
// Send message preview
lifecycleScope.launch {
try {
CCAI.chatService?.sendMessagePreview("Typing...")
} catch (e: Exception) {
Log.e("CCAI", "Failed to send message preview: ${e.message}")
}
}
// Example: Send preview as user types
fun onTextChanged(text: String) {
lifecycleScope.launch {
try {
CCAI.chatService?.sendMessagePreview(text)
} catch (e: Exception) {
Log.e("CCAI", "Failed to send preview: ${e.message}")
}
}
}
Oscuramento dei messaggi
L'oscuramento automatico delle informazioni sensibili (PII, numeri di carte di credito e così via) è supportato per i messaggi chat in testo normale e le anteprime dei messaggi.
Configurazione dell'oscuramento:
Controllato dalla proprietà
redactionEnabledinChatResponse.Configurato nel portale in Impostazioni > Impostazioni chat > Oscuramento automatico.
Se questa opzione è attiva, i dati sensibili vengono sostituiti automaticamente con segnaposto prima di essere inviati agli agenti.
// Redaction is automatically handled, use sendMessage as usual
lifecycleScope.launch {
try {
CCAI.chatService?.sendMessage(
OutgoingMessageContent.Text("My phone number is 4155553423")
)
} catch (e: Exception) {
Log.e("CCAI", "Failed to send message: ${e.message}")
}
}
Termina e riassegna le chat
// End the current chat session
lifecycleScope.launch {
try {
CCAI.chatService?.endChat()
Log.d("CCAI", "Chat ended successfully")
} catch (e: Exception) {
Log.e("CCAI", "Failed to end chat: ${e.message}")
}
}
// Escalate to a human agent
lifecycleScope.launch {
try {
CCAI.chatService?.escalateToHumanAgent()
Log.d("CCAI", "Chat escalated to human agent")
} catch (e: Exception) {
Log.e("CCAI", "Failed to escalate chat: ${e.message}")
}
}
// Record the deflection channel the user chose for an existing escalation
// (the escalation must already exist; start one with escalateToHumanAgent())
val escalationOption = ChatEscalationOption(
escalationId = 123,
deflectionChannel = DeflectionChannel.Email
)
lifecycleScope.launch {
try {
CCAI.chatService?.updateEscalation(option = escalationOption)
Log.d("CCAI", "Escalation updated successfully")
} catch (e: Exception) {
Log.e("CCAI", "Failed to update escalation: ${e.message}")
}
}
// Check the current chat status
lifecycleScope.launch {
try {
CCAI.chatService?.checkStatus()
Log.d("CCAI", "Status check completed successfully")
} catch (e: Exception) {
Log.e("CCAI", "Failed to check status: ${e.message}")
}
}
Eventi di Chat
Inviare eventi di chat al server per monitorare le interazioni degli utenti.
ChatEvent.payload è un Map<String, String> non nullable: sia le chiavi che i valori devono essere stringhe.
val event = ChatEvent(
name = "form_received",
payload = mapOf("name" to "contact_form")
)
lifecycleScope.launch {
try {
CCAI.chatService?.sendEvent(event)
Log.d("CCAI", "Event sent successfully")
} catch (e: Exception) {
Log.e("CCAI", "Failed to send event: ${e.message}")
}
}
I casi d'uso comuni degli eventi includono le interazioni con i moduli ("form_received", "form_clicked"), gli eventi di azioni intelligenti ("smart_action_received", "smart_action_clicked") e le interazioni utente personalizzate per l'analisi.
Considerazioni sulla build specifiche per i moduli
Per una build specifica per un modulo, tieni presente quanto segue.
Livello SDK minimo
I moduli del livello multimediale CCAIChatRed e CCAICallRed richiedono min Sdk = 23
(Android 6.0). Se la tua app dichiara un minSdkVersion inferiore, Gradle non riuscirà a unire i manifest. Aumenta il tuo min Sdk ad almeno 23 in app/build.gradle.kts:
android {
defaultConfig {
minSdk = 23
}
}