AI Assistant Compose
AI chat for intelligent document analysis and interaction using our Jetpack Compose API. You must also run the AI Assistant demo server on your Machine.
/* * Copyright © 2025-2026 PSPDFKit GmbH. All rights reserved. * * The PSPDFKit Sample applications are licensed with a modified BSD license. * Please see License for details. This notice may not be removed from this file. */
package com.pspdfkit.catalog.examples.kotlin
import android.content.Contextimport android.content.Intentimport android.graphics.RectFimport android.os.Bundleimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.appcompat.app.AppCompatActivityimport androidx.compose.animation.AnimatedVisibilityimport androidx.compose.animation.expandVerticallyimport androidx.compose.animation.fadeInimport androidx.compose.animation.fadeOutimport androidx.compose.animation.shrinkVerticallyimport androidx.compose.animation.slideInVerticallyimport androidx.compose.animation.slideOutVerticallyimport androidx.compose.foundation.layout.Boximport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.navigationBarsPaddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Iconimport androidx.compose.material3.IconButtonimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.material3.TopAppBarDefaultsimport androidx.compose.runtime.Composableimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Alignmentimport androidx.compose.ui.Modifierimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.platform.LocalDensityimport androidx.compose.ui.res.painterResourceimport androidx.compose.ui.unit.dpimport com.pspdfkit.ai.createAiAssistantimport com.pspdfkit.catalog.Rimport com.pspdfkit.catalog.SdkExampleimport com.pspdfkit.catalog.SdkExample.Companion.WELCOME_DOCimport com.pspdfkit.catalog.ui.theming.CatalogThemeimport com.pspdfkit.catalog.utils.JwtGeneratorimport com.pspdfkit.configuration.activity.PdfActivityConfigurationimport com.pspdfkit.document.providers.AssetDataProviderimport com.pspdfkit.jetpack.compose.interactors.DefaultListenersimport com.pspdfkit.jetpack.compose.interactors.DocumentStateimport com.pspdfkit.jetpack.compose.interactors.getDefaultDocumentManagerimport com.pspdfkit.jetpack.compose.interactors.rememberDocumentStateimport com.pspdfkit.jetpack.compose.views.DocumentViewimport com.pspdfkit.ui.DocumentDescriptorimport com.pspdfkit.ui.PdfActivityimport io.nutrient.domain.ai.AiAssistantimport io.nutrient.domain.ai.AiAssistantProvider
/** * Shows how to implement AI Assistant for the DocumentView in a Compose way. */class AiAssistantComposeExample(context: Context) : SdkExample( context, R.string.jetpackAiAssistantExampleTitle, R.string.jetpackAiAssistantExampleDescription) { override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) { val intent = Intent(context, AiAssistantComposeActivity::class.java) context.startActivity(intent) }}
class AiAssistantComposeActivity : AppCompatActivity(), AiAssistantProvider { private lateinit var documentState: DocumentState private val sessionId = AiAssistantComposeActivity::class.java.simpleName private val assetProvider = AssetDataProvider(WELCOME_DOC) private var ipAddressValue: String? = null
override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState)
val preferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE) ipAddressValue = preferences.getString(PREF_AI_IP_ADDRESS, "") ?: ""
setContent { var enabled by remember { mutableStateOf(false) } var toolbarVisibility by remember { mutableStateOf(true) }
CatalogTheme { val activityConfiguration = PdfActivityConfiguration.Builder(LocalContext.current) .setAiAssistantEnabled(true) .defaultToolbarEnabled(false) .theme(R.style.PSPDFCatalog_AIAssistantDialog) .themeDark(R.style.PSPDFCatalog_AIAssistantDialog_Dark) .build()
documentState = rememberDocumentState(assetProvider, activityConfiguration)
Box( modifier = Modifier .fillMaxSize() .navigationBarsPadding() ) { DocumentView( documentState = documentState, documentManager = getDefaultDocumentManager( documentListener = DefaultListeners.documentListeners( onDocumentLoaded = { enabled = true } ), uiListener = DefaultListeners.uiListeners( onImmersiveModeEnabled = { toolbarVisibility = !it } ) ) ) } CustomToolbar(documentState, enabled, toolbarVisibility) } } } var assistant: AiAssistant? = null
override fun getAiAssistant(): AiAssistant { val documentDescriptor = DocumentDescriptor.fromDataProviders(listOf(assetProvider), listOf(), listOf())
return assistant ?: run { createAiAssistant( context = this@AiAssistantComposeActivity, documentsDescriptors = listOf(documentDescriptor), serverUrl = "http://$ipAddressValue:4000", sessionId = sessionId, jwtToken = { documentIds -> JwtGenerator.generateJwtToken( this@AiAssistantComposeActivity, claims = mapOf( "document_ids" to documentIds, "session_ids" to listOf(sessionId), "request_limit" to mapOf( "requests" to 160, "time_period_s" to 1000 * 60 * 10 ) ) ) } ).also { // Enable/disable text selection in AI Assistant chat messages // false = disable, true = enable (default) // it.enableTextSelection(false) assistant = it } } }
override fun navigateTo( documentRect: List<RectF>, pageIndex: Int, documentIndex: Int ) { documentState.documentConnection.highlight(pageIndex, documentRect) }
companion object { const val EXTRA_URI = "JetpackComposeImageActivity.DocumentUri" const val PREFERENCES_NAME = "Nutrient.AiAssistant" const val PREF_AI_IP_ADDRESS = "ai_ip_address" }}
/** * A custom toolbar component for document viewer with animated visibility. * * @param documentState The current state of the document being viewed * @param enabled Whether the toolbar actions are enabled * @param toolbarVisibility Whether the toolbar should be visible */@OptIn(ExperimentalMaterial3Api::class)@Composablefun CustomToolbar( documentState: DocumentState, enabled: Boolean, toolbarVisibility: Boolean) { val localDensity = LocalDensity.current
// Animate toolbar appearance/disappearance with slide, expand and fade effects AnimatedVisibility( visible = toolbarVisibility, enter = slideInVertically { with(localDensity) { -40.dp.roundToPx() } } + expandVertically(expandFrom = Alignment.Top) + fadeIn(initialAlpha = 0.3f), exit = slideOutVertically() + shrinkVertically() + fadeOut() ) { TopAppBar( title = { // Display document title or empty string if null Text( text = documentState.getTitle().orEmpty(), color = Color.White ) }, actions = { // AI Assistant action button IconButton( onClick = { documentState.toggleView(PdfActivity.MENU_OPTION_AI_ASSISTANT) }, enabled = enabled ) { Icon( painter = painterResource(id = R.drawable.ic_ai_assistant), contentDescription = "AI Assistant", tint = Color.White ) }
// Settings action button IconButton( onClick = { documentState.toggleView(PdfActivity.MENU_OPTION_SETTINGS) }, enabled = enabled ) { Icon( painter = painterResource(id = R.drawable.ic_topbar_settings), contentDescription = "Settings", tint = Color.White ) } }, colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primary, titleContentColor = MaterialTheme.colorScheme.onPrimary ) ) }}This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.