SDK mobile headless per Android: code, canali e chat

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 è:

  1. Aggiungi un punto di accesso all'assistenza nella tua app (ad esempio, una scheda Guida o un pulsante Contattaci).

  2. Quando l'utente lo tocca, recuperi il menu della coda utilizzando l'SDK ed esegui il rendering della UI del menu.

  3. Quando l'utente sceglie una coda, esamini i canali disponibili.

  4. La tua app mostra questi canali all'utente (ad esempio, Chatta ora, Chiama ora, Pianifica una chiamata, Inviaci un'email, Visita il Centro assistenza).

  5. 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:

  1. Vai a Impostazioni > Coda > seleziona una coda dalla struttura del menu.

  2. Nella sezione Punto di accesso, fai clic su + Crea punto di accesso diretto.

  3. Inserisci la chiave nel modulo di testo.

  4. 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:

  • option indica cosa fare: PHONE, MESSAGE, URL, VOICEMAIL, QUEUE, SIP o CONTACT.
  • data contiene 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 da surveyEnabled e csatSetting.

  • 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 schermo

  • screenShareRequestedFromEndUser - l'utente finale ha richiesto la condivisione schermo

  • screenShareCodeGenerated - è stato generato il codice di condivisione schermo

  • screenShareFailed - condivisione schermo non riuscita

  • screenShareStarted - Sessione di condivisione schermo avviata

  • screenShareEnded - 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à redactionEnabled in ChatResponse.

  • 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
    }
}