Add linting and static code analysis
This commit is contained in:
parent
e4effcf40f
commit
1868bab690
215 changed files with 3158 additions and 2676 deletions
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# Security scanning: Consider adding Snyk (https://snyk.io) for vulnerability
|
||||
# detection. Snyk provides deeper analysis than Dependabot for transitive
|
||||
# dependencies and can be integrated via GitHub Actions or the Snyk GitHub App.
|
||||
|
|
@ -39,6 +39,8 @@ dependencies {
|
|||
implementation(libs.coil.compose)
|
||||
implementation(libs.timber)
|
||||
|
||||
debugImplementation(libs.leakcanary)
|
||||
|
||||
androidTestImplementation(libs.compose.ui.test)
|
||||
debugImplementation(libs.compose.ui.test.manifest)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +23,15 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
||||
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.core.common.network.ConnectivityObserver
|
||||
import com.librechat.android.core.data.datastore.ThemeDataStore
|
||||
import com.librechat.android.core.data.datastore.ThemeMode
|
||||
|
|
@ -40,7 +41,6 @@ import com.librechat.android.feature.chat.SharedContent
|
|||
import com.librechat.android.navigation.LibreChatNavHost
|
||||
import org.koin.android.ext.android.inject
|
||||
import timber.log.Timber
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
|
|
@ -153,6 +153,7 @@ class MainActivity : ComponentActivity() {
|
|||
|
||||
private fun handleShareIntent(intent: Intent) {
|
||||
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val sharedUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
|
|
|
|||
|
|
@ -48,16 +48,16 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.R
|
||||
import com.librechat.android.core.model.EModelEndpoint
|
||||
import com.librechat.android.core.ui.components.isMonochromeIcon
|
||||
import com.librechat.android.core.ui.components.toIconRes
|
||||
import com.librechat.android.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
/**
|
||||
* Lightweight snapshot of fields DrawerConversationItem actually renders.
|
||||
|
|
@ -119,10 +119,10 @@ fun DrawerContent(
|
|||
onSettingsClick: () -> Unit,
|
||||
onAgentsClick: () -> Unit,
|
||||
onFilesClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onToggleFavorite: (String) -> Unit = {},
|
||||
onRefresh: () -> Unit = {},
|
||||
onLoadMore: () -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
|
|
@ -367,8 +367,8 @@ fun DrawerContent(
|
|||
private fun DrawerConversationItem(
|
||||
data: DrawerConversationDisplayData,
|
||||
onClick: () -> Unit,
|
||||
onToggleFavorite: () -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
onToggleFavorite: () -> Unit = {},
|
||||
) {
|
||||
val endpointIconRes = remember(data.endpoint) {
|
||||
data.endpoint?.toIconRes()
|
||||
|
|
@ -517,4 +517,3 @@ private fun DrawerFooterItem(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package com.librechat.android.navigation
|
||||
|
||||
import android.net.Uri
|
||||
import com.librechat.android.MainActivity
|
||||
import timber.log.Timber
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
|
|
@ -15,7 +13,6 @@ import androidx.compose.animation.slideOut
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -31,12 +28,16 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.librechat.android.MainActivity
|
||||
import com.librechat.android.R
|
||||
import com.librechat.android.core.ui.components.BannerDisplay
|
||||
import com.librechat.android.feature.agents.navigation.AGENT_EDITOR_CREATE_ROUTE
|
||||
import com.librechat.android.feature.agents.navigation.agentsGraph
|
||||
import com.librechat.android.feature.auth.navigation.AUTH_GRAPH_ROUTE
|
||||
|
|
@ -50,7 +51,6 @@ import com.librechat.android.feature.conversations.navigation.conversationsGraph
|
|||
import com.librechat.android.feature.files.navigation.filesGraph
|
||||
import com.librechat.android.feature.settings.navigation.API_KEYS_ROUTE
|
||||
import com.librechat.android.feature.settings.navigation.PRESET_MANAGER_ROUTE
|
||||
import com.librechat.android.core.ui.components.BannerDisplay
|
||||
import com.librechat.android.feature.settings.navigation.SETTINGS_ACCOUNT_ROUTE
|
||||
import com.librechat.android.feature.settings.navigation.SETTINGS_CHAT_ROUTE
|
||||
import com.librechat.android.feature.settings.navigation.SETTINGS_DATA_ROUTE
|
||||
|
|
@ -59,8 +59,8 @@ import com.librechat.android.feature.settings.navigation.SETTINGS_TABBED_ROUTE
|
|||
import com.librechat.android.feature.settings.navigation.SHARED_LINKS_ROUTE
|
||||
import com.librechat.android.feature.settings.navigation.settingsGraph
|
||||
import kotlinx.coroutines.launch
|
||||
import com.librechat.android.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import timber.log.Timber
|
||||
|
||||
/** Maps a [SettingsCategory] to its corresponding navigation route. */
|
||||
fun SettingsCategory.toRoute(): String = when (this) {
|
||||
|
|
@ -311,118 +311,118 @@ private fun PhoneLayout(
|
|||
navController = navController,
|
||||
startDestination = startDestination,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
enterTransition = {
|
||||
slideIntoContainer(
|
||||
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||
animationSpec = tween(300),
|
||||
enterTransition = {
|
||||
slideIntoContainer(
|
||||
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||
animationSpec = tween(300),
|
||||
)
|
||||
},
|
||||
exitTransition = {
|
||||
fadeOut(animationSpec = tween(150))
|
||||
},
|
||||
popEnterTransition = {
|
||||
fadeIn(
|
||||
initialAlpha = 0.5f,
|
||||
animationSpec = tween(300, easing = LinearEasing),
|
||||
) + scaleIn(
|
||||
initialScale = 0.92f,
|
||||
animationSpec = tween(300, easing = LinearEasing),
|
||||
)
|
||||
},
|
||||
popExitTransition = {
|
||||
slideOut(
|
||||
targetOffset = { IntOffset((it.width * 0.15f).toInt(), 0) },
|
||||
animationSpec = tween(300, easing = FastOutSlowInEasing),
|
||||
) + scaleOut(
|
||||
targetScale = 0.92f,
|
||||
animationSpec = tween(300, easing = LinearEasing),
|
||||
)
|
||||
},
|
||||
) {
|
||||
authGraph(
|
||||
navController = navController,
|
||||
onAuthComplete = {
|
||||
navHostViewModel.onAuthComplete()
|
||||
navController.navigate(CHAT_GRAPH_ROUTE) {
|
||||
popUpTo(AUTH_GRAPH_ROUTE) { inclusive = true }
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
exitTransition = {
|
||||
fadeOut(animationSpec = tween(150))
|
||||
},
|
||||
popEnterTransition = {
|
||||
fadeIn(
|
||||
initialAlpha = 0.5f,
|
||||
animationSpec = tween(300, easing = LinearEasing),
|
||||
) + scaleIn(
|
||||
initialScale = 0.92f,
|
||||
animationSpec = tween(300, easing = LinearEasing),
|
||||
chatGraph(
|
||||
navController = navController,
|
||||
onOpenDrawer = {
|
||||
scope.launch { drawerState.open() }
|
||||
},
|
||||
)
|
||||
},
|
||||
popExitTransition = {
|
||||
slideOut(
|
||||
targetOffset = { IntOffset((it.width * 0.15f).toInt(), 0) },
|
||||
animationSpec = tween(300, easing = FastOutSlowInEasing),
|
||||
) + scaleOut(
|
||||
targetScale = 0.92f,
|
||||
animationSpec = tween(300, easing = LinearEasing),
|
||||
conversationsGraph(
|
||||
onConversationClick = { conversationId ->
|
||||
navController.navigateToChat(conversationId)
|
||||
},
|
||||
onNavigateToArchived = {
|
||||
navController.navigate("conversations/archived")
|
||||
},
|
||||
onNavigateBackFromArchived = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
)
|
||||
},
|
||||
) {
|
||||
authGraph(
|
||||
navController = navController,
|
||||
onAuthComplete = {
|
||||
navHostViewModel.onAuthComplete()
|
||||
navController.navigate(CHAT_GRAPH_ROUTE) {
|
||||
popUpTo(AUTH_GRAPH_ROUTE) { inclusive = true }
|
||||
}
|
||||
},
|
||||
)
|
||||
chatGraph(
|
||||
navController = navController,
|
||||
onOpenDrawer = {
|
||||
scope.launch { drawerState.open() }
|
||||
},
|
||||
)
|
||||
conversationsGraph(
|
||||
onConversationClick = { conversationId ->
|
||||
navController.navigateToChat(conversationId)
|
||||
},
|
||||
onNavigateToArchived = {
|
||||
navController.navigate("conversations/archived")
|
||||
},
|
||||
onNavigateBackFromArchived = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
)
|
||||
agentsGraph(
|
||||
onAgentClick = { agentId ->
|
||||
navController.navigate("agents/$agentId")
|
||||
},
|
||||
onBack = { navController.popBackStack() },
|
||||
onStartChat = { agentId ->
|
||||
navController.navigate(NEW_CHAT_ROUTE) {
|
||||
popUpTo(CHAT_GRAPH_ROUTE) { inclusive = false }
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
onCreateAgent = {
|
||||
navController.navigate(AGENT_EDITOR_CREATE_ROUTE)
|
||||
},
|
||||
onEditAgent = { agentId ->
|
||||
navController.navigate("agents/editor/$agentId")
|
||||
},
|
||||
)
|
||||
filesGraph()
|
||||
settingsGraph(
|
||||
onLogout = {
|
||||
navHostViewModel.logout()
|
||||
navController.navigate(AUTH_GRAPH_ROUTE) {
|
||||
popUpTo(0) { inclusive = true }
|
||||
}
|
||||
},
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToArchived = {
|
||||
navController.navigate("conversations/archived") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
onNavigateToSharedLinks = {
|
||||
navController.navigate(SHARED_LINKS_ROUTE) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
onNavigateBackFromSharedLinks = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
onNavigateToPresets = {
|
||||
navController.navigate(PRESET_MANAGER_ROUTE) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
onNavigateBackFromPresets = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
onNavigateToApiKeys = {
|
||||
navController.navigate(API_KEYS_ROUTE) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
onNavigateBackFromApiKeys = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
)
|
||||
}
|
||||
agentsGraph(
|
||||
onAgentClick = { agentId ->
|
||||
navController.navigate("agents/$agentId")
|
||||
},
|
||||
onBack = { navController.popBackStack() },
|
||||
onStartChat = { agentId ->
|
||||
navController.navigate(NEW_CHAT_ROUTE) {
|
||||
popUpTo(CHAT_GRAPH_ROUTE) { inclusive = false }
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
onCreateAgent = {
|
||||
navController.navigate(AGENT_EDITOR_CREATE_ROUTE)
|
||||
},
|
||||
onEditAgent = { agentId ->
|
||||
navController.navigate("agents/editor/$agentId")
|
||||
},
|
||||
)
|
||||
filesGraph()
|
||||
settingsGraph(
|
||||
onLogout = {
|
||||
navHostViewModel.logout()
|
||||
navController.navigate(AUTH_GRAPH_ROUTE) {
|
||||
popUpTo(0) { inclusive = true }
|
||||
}
|
||||
},
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToArchived = {
|
||||
navController.navigate("conversations/archived") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
onNavigateToSharedLinks = {
|
||||
navController.navigate(SHARED_LINKS_ROUTE) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
onNavigateBackFromSharedLinks = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
onNavigateToPresets = {
|
||||
navController.navigate(PRESET_MANAGER_ROUTE) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
onNavigateBackFromPresets = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
onNavigateToApiKeys = {
|
||||
navController.navigate(API_KEYS_ROUTE) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
onNavigateBackFromApiKeys = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,22 +3,16 @@ package com.librechat.android.navigation
|
|||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.librechat.android.core.common.extensions.toInstantOrNull
|
||||
import com.librechat.android.core.common.extensions.toRelativeDateGroup
|
||||
import com.librechat.android.core.common.result.Result
|
||||
import com.librechat.android.core.data.repository.AuthRepository
|
||||
import com.librechat.android.core.data.repository.BannerRepository
|
||||
import com.librechat.android.core.data.repository.ConfigRepository
|
||||
import com.librechat.android.core.data.repository.ConversationRepository
|
||||
import com.librechat.android.core.data.repository.VersionCheckResult
|
||||
import com.librechat.android.core.common.result.Result
|
||||
import com.librechat.android.core.common.extensions.toInstantOrNull
|
||||
import com.librechat.android.core.common.extensions.toRelativeDateGroup
|
||||
import com.librechat.android.core.model.Banner
|
||||
import com.librechat.android.core.model.Conversation
|
||||
import com.librechat.android.core.model.EModelEndpoint
|
||||
import com.librechat.android.core.network.client.TokenManager
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -32,6 +26,10 @@ import kotlinx.coroutines.flow.stateIn
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
/**
|
||||
* Combined UI state for the drawer sidebar. A single emission replaces
|
||||
|
|
@ -155,7 +153,13 @@ class NavHostViewModel(
|
|||
* not the entire PhoneLayout/TabletLayout.
|
||||
*/
|
||||
val drawerUiState: StateFlow<DrawerUiState> = combine(
|
||||
combine(_groupedConversations, _activeConversationId, _favorites, _favoriteConversations, _searchQuery) { grouped, activeId, favIds, favConvos, query ->
|
||||
combine(
|
||||
_groupedConversations,
|
||||
_activeConversationId,
|
||||
_favorites,
|
||||
_favoriteConversations,
|
||||
_searchQuery,
|
||||
) { grouped, activeId, favIds, favConvos, query ->
|
||||
DrawerDataSnapshot(grouped, activeId, favIds, favConvos, query)
|
||||
},
|
||||
combine(_isRefreshing, _isLoadingMore, _hasMore) { refreshing, loadingMore, hasMore ->
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
/**
|
||||
* Settings sidebar content shown when the sidebar mode is [SidebarMode.Settings].
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package com.librechat.android.navigation
|
||||
|
||||
import android.net.Uri
|
||||
import com.librechat.android.MainActivity
|
||||
import timber.log.Timber
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.core.Animatable
|
||||
|
|
@ -23,7 +21,6 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -35,12 +32,14 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.input.pointer.util.VelocityTracker
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import com.librechat.android.MainActivity
|
||||
import com.librechat.android.core.ui.components.BannerDisplay
|
||||
import com.librechat.android.feature.agents.navigation.AGENT_EDITOR_CREATE_ROUTE
|
||||
import com.librechat.android.feature.agents.navigation.agentsGraph
|
||||
|
|
@ -59,6 +58,7 @@ import com.librechat.android.feature.settings.navigation.SETTINGS_TABBED_ROUTE
|
|||
import com.librechat.android.feature.settings.navigation.SHARED_LINKS_ROUTE
|
||||
import com.librechat.android.feature.settings.navigation.settingsGraph
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
private val SidebarWidth = 320.dp
|
||||
|
||||
|
|
@ -73,8 +73,8 @@ fun TabletLayout(
|
|||
isInAuthFlow: Boolean,
|
||||
deepLinkUri: Uri?,
|
||||
onDeepLinkConsumed: () -> Unit,
|
||||
shareNavigationTrigger: Int = 0,
|
||||
modifier: Modifier = Modifier,
|
||||
shareNavigationTrigger: Int = 0,
|
||||
) {
|
||||
// Banner state only -- drawer state is collected inside DrawerContent itself
|
||||
val banners by navHostViewModel.banners.collectAsStateWithLifecycle()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ dependencies {
|
|||
compileOnly(libs.compose.gradlePlugin)
|
||||
compileOnly(libs.ksp.gradlePlugin)
|
||||
compileOnly(libs.room.gradlePlugin)
|
||||
compileOnly(libs.detekt.gradlePlugin)
|
||||
compileOnly(libs.kover.gradlePlugin)
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
|
|
@ -40,5 +42,9 @@ gradlePlugin {
|
|||
id = "librechat.kotlin.serialization"
|
||||
implementationClass = "KotlinSerializationConventionPlugin"
|
||||
}
|
||||
register("androidDetekt") {
|
||||
id = "librechat.android.detekt"
|
||||
implementationClass = "AndroidDetektConventionPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
|
|||
with(target) {
|
||||
pluginManager.apply("com.android.application")
|
||||
pluginManager.apply("org.jetbrains.kotlin.android")
|
||||
pluginManager.apply("librechat.android.detekt")
|
||||
pluginManager.apply("org.jetbrains.kotlinx.kover")
|
||||
|
||||
extensions.configure<ApplicationExtension> {
|
||||
compileSdk = 35
|
||||
|
|
@ -23,6 +25,9 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
|
|||
targetCompatibility = org.gradle.api.JavaVersion.VERSION_17
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
lint {
|
||||
disable += "NullSafeMutableLiveData"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ class AndroidComposeConventionPlugin : Plugin<Project> {
|
|||
add("debugImplementation", libs.findLibrary("compose-ui-tooling").get())
|
||||
add("androidTestImplementation", platform(bom))
|
||||
add("androidTestImplementation", libs.findLibrary("compose-ui-test").get())
|
||||
add("lintChecks", libs.findLibrary("compose-lint-checks").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
class AndroidDetektConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
with(target) {
|
||||
pluginManager.apply("io.gitlab.arturbosch.detekt")
|
||||
|
||||
extensions.configure<DetektExtension> {
|
||||
config.setFrom(rootProject.files("config/detekt/detekt.yml"))
|
||||
buildUponDefaultConfig = true
|
||||
allRules = false
|
||||
parallel = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
add("detektPlugins", libs.findLibrary("detekt-formatting").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val Project.libs
|
||||
get() = extensions.getByType(org.gradle.api.artifacts.VersionCatalogsExtension::class.java)
|
||||
.named("libs")
|
||||
|
|
@ -8,6 +8,8 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
|
|||
with(target) {
|
||||
pluginManager.apply("com.android.library")
|
||||
pluginManager.apply("org.jetbrains.kotlin.android")
|
||||
pluginManager.apply("librechat.android.detekt")
|
||||
pluginManager.apply("org.jetbrains.kotlinx.kover")
|
||||
|
||||
extensions.configure<LibraryExtension> {
|
||||
compileSdk = 35
|
||||
|
|
@ -20,6 +22,11 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
|
|||
targetCompatibility = org.gradle.api.JavaVersion.VERSION_17
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
lint {
|
||||
// Workaround: NonNullableMutableLiveDataDetector crashes with
|
||||
// IncompatibleClassChangeError on AGP 8.7.x + Kotlin 2.1.x
|
||||
disable += "NullSafeMutableLiveData"
|
||||
}
|
||||
testOptions {
|
||||
unitTests.isReturnDefaultValues = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,4 +6,14 @@ plugins {
|
|||
alias(libs.plugins.kotlin.serialization) apply false
|
||||
alias(libs.plugins.ksp) apply false
|
||||
alias(libs.plugins.room) apply false
|
||||
alias(libs.plugins.detekt) apply false
|
||||
alias(libs.plugins.kover)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
subprojects.forEach { subproject ->
|
||||
if (subproject.buildFile.exists()) {
|
||||
kover(subproject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
113
config/detekt/detekt.yml
Normal file
113
config/detekt/detekt.yml
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
build:
|
||||
maxIssues: 0
|
||||
|
||||
complexity:
|
||||
LongMethod:
|
||||
threshold: 300
|
||||
ignoreAnnotated:
|
||||
- "Composable"
|
||||
LongParameterList:
|
||||
functionThreshold: 10
|
||||
constructorThreshold: 20
|
||||
ignoreDefaultParameters: true
|
||||
ignoreAnnotated:
|
||||
- "Composable"
|
||||
TooManyFunctions:
|
||||
thresholdInFiles: 30
|
||||
thresholdInClasses: 100
|
||||
thresholdInInterfaces: 25
|
||||
thresholdInObjects: 20
|
||||
thresholdInEnums: 10
|
||||
NestedBlockDepth:
|
||||
threshold: 6
|
||||
CyclomaticComplexMethod:
|
||||
threshold: 60
|
||||
ignoreAnnotated:
|
||||
- "Composable"
|
||||
ComplexCondition:
|
||||
threshold: 10
|
||||
LargeClass:
|
||||
threshold: 2500
|
||||
|
||||
style:
|
||||
MagicNumber:
|
||||
active: false
|
||||
ReturnCount:
|
||||
max: 12
|
||||
excludeReturnFromLambda: true
|
||||
ignoreAnnotated:
|
||||
- "Composable"
|
||||
MaxLineLength:
|
||||
maxLineLength: 140
|
||||
excludeCommentStatements: true
|
||||
excludePackageStatements: true
|
||||
excludeImportStatements: true
|
||||
excludeRawStrings: true
|
||||
ForbiddenComment:
|
||||
active: false
|
||||
UnusedPrivateMember:
|
||||
active: true
|
||||
allowedNames: "(_|ignored|expected|serialVersionUID)"
|
||||
ignoreAnnotated:
|
||||
- "Preview"
|
||||
WildcardImport:
|
||||
active: true
|
||||
excludeImports:
|
||||
- "kotlinx.coroutines.flow.*"
|
||||
LoopWithTooManyJumpStatements:
|
||||
maxJumpCount: 3
|
||||
ThrowsCount:
|
||||
max: 4
|
||||
UseCheckOrError:
|
||||
active: false
|
||||
UseRequire:
|
||||
active: false
|
||||
|
||||
naming:
|
||||
FunctionNaming:
|
||||
ignoreAnnotated:
|
||||
- "Composable"
|
||||
TopLevelPropertyNaming:
|
||||
constantPattern: "[A-Z][A-Za-z0-9_]*"
|
||||
MatchingDeclarationName:
|
||||
active: false
|
||||
|
||||
exceptions:
|
||||
TooGenericExceptionCaught:
|
||||
active: true
|
||||
exceptionNames:
|
||||
- "RuntimeException"
|
||||
- "Throwable"
|
||||
allowedExceptionNameRegex: "_|(ignore|expected).*"
|
||||
SwallowedException:
|
||||
ignoredExceptionTypes:
|
||||
- "InterruptedException"
|
||||
- "MalformedURLException"
|
||||
- "NumberFormatException"
|
||||
- "ParseException"
|
||||
allowedExceptionNameRegex: "_|(ignore|expected).*"
|
||||
TooGenericExceptionThrown:
|
||||
active: true
|
||||
exceptionNames:
|
||||
- "Error"
|
||||
- "RuntimeException"
|
||||
- "Throwable"
|
||||
|
||||
coroutines:
|
||||
active: true
|
||||
|
||||
formatting:
|
||||
MaximumLineLength:
|
||||
active: false
|
||||
TrailingCommaOnCallSite:
|
||||
active: false
|
||||
TrailingCommaOnDeclarationSite:
|
||||
active: false
|
||||
Wrapping:
|
||||
active: false
|
||||
ArgumentListWrapping:
|
||||
active: false
|
||||
ParameterListWrapping:
|
||||
active: false
|
||||
FunctionSignature:
|
||||
active: false
|
||||
|
|
@ -2,7 +2,6 @@ package com.librechat.android.core.common.extensions
|
|||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.retryWhen
|
||||
import kotlin.math.pow
|
||||
|
|
|
|||
|
|
@ -12,5 +12,8 @@ fun String.ensureHttps(): String = when {
|
|||
}
|
||||
|
||||
fun String.truncate(maxLength: Int, ellipsis: String = "..."): String =
|
||||
if (length <= maxLength) this
|
||||
else take(maxLength - ellipsis.length) + ellipsis
|
||||
if (length <= maxLength) {
|
||||
this
|
||||
} else {
|
||||
take(maxLength - ellipsis.length) + ellipsis
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ class SafeApiCallTest {
|
|||
|
||||
@Test
|
||||
fun `safeApiCall returns Error on exception`() = runTest {
|
||||
val result = safeApiCall<String> { throw RuntimeException("boom") }
|
||||
val result = safeApiCall<String> { throw IllegalStateException("boom") }
|
||||
assertThat(result).isInstanceOf(Result.Error::class.java)
|
||||
val error = result as Result.Error
|
||||
assertThat(error.message).isEqualTo("boom")
|
||||
assertThat(error.exception).isInstanceOf(RuntimeException::class.java)
|
||||
assertThat(error.exception).isInstanceOf(IllegalStateException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ import io.ktor.client.request.header
|
|||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import java.io.IOException
|
||||
import java.security.KeyStoreException
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.security.KeyStoreException
|
||||
|
||||
class TokenDataStore(
|
||||
context: Context,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.librechat.android.core.data.mapper
|
||||
|
||||
import com.librechat.android.core.data.db.entity.MessageEntity
|
||||
import com.librechat.android.core.model.Attachment
|
||||
import com.librechat.android.core.model.Feedback
|
||||
import com.librechat.android.core.model.FileReference
|
||||
import com.librechat.android.core.model.Attachment
|
||||
import com.librechat.android.core.model.Message
|
||||
import com.librechat.android.core.model.MessageContentPart
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ interface AgentRepository {
|
|||
category: String? = null,
|
||||
): Result<PaginatedAgents>
|
||||
suspend fun getAgent(id: String): Result<Agent>
|
||||
|
||||
/**
|
||||
* Fetches complete agent data for editing via the /expanded endpoint.
|
||||
* Unlike [getAgent] which returns limited view-only fields, this method
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ import com.librechat.android.core.model.ToolAuthStatus
|
|||
import com.librechat.android.core.model.ToolCallRecord
|
||||
import com.librechat.android.core.model.ToolCallResult
|
||||
import com.librechat.android.core.model.request.CreateActionRequest
|
||||
import kotlinx.serialization.json.Json
|
||||
import com.librechat.android.core.model.request.CreateAgentRequest
|
||||
import com.librechat.android.core.model.request.RevertAgentRequest
|
||||
import com.librechat.android.core.model.request.ToolCallRequest
|
||||
import com.librechat.android.core.model.request.UpdateAgentRequest
|
||||
import com.librechat.android.core.network.api.AgentsApi
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class AgentRepositoryImpl(
|
||||
private val agentsApi: AgentsApi,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ import com.librechat.android.core.common.result.safeApiCall
|
|||
import com.librechat.android.core.model.FileReference
|
||||
import com.librechat.android.core.model.StreamEvent
|
||||
import com.librechat.android.core.model.request.AddedConversation
|
||||
import com.librechat.android.core.model.request.ChatAbortRequest
|
||||
import com.librechat.android.core.model.request.ChatRequest
|
||||
import com.librechat.android.core.model.request.EphemeralAgent
|
||||
import com.librechat.android.core.model.response.ChatStatusResponse
|
||||
import com.librechat.android.core.network.api.ChatApi
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package com.librechat.android.core.data.repository
|
||||
|
||||
import com.librechat.android.core.common.BackendVersion
|
||||
import com.librechat.android.core.common.result.ApiException
|
||||
import com.librechat.android.core.common.result.Result
|
||||
import com.librechat.android.core.common.result.safeApiCall
|
||||
import com.librechat.android.core.data.datastore.ConfigCacheDataStore
|
||||
import com.librechat.android.core.model.EndpointConfig
|
||||
import com.librechat.android.core.model.StartupConfig
|
||||
import com.librechat.android.core.network.api.ConfigApi
|
||||
import com.librechat.android.core.common.result.ApiException
|
||||
import io.ktor.client.plugins.HttpRequestTimeoutException
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.librechat.android.core.model
|
|||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -5,64 +5,138 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
enum class EModelEndpoint {
|
||||
@SerialName("azureOpenAI") AZURE_OPENAI,
|
||||
@SerialName("openAI") OPENAI,
|
||||
@SerialName("google") GOOGLE,
|
||||
@SerialName("anthropic") ANTHROPIC,
|
||||
@SerialName("assistants") ASSISTANTS,
|
||||
@SerialName("azureAssistants") AZURE_ASSISTANTS,
|
||||
@SerialName("agents") AGENTS,
|
||||
@SerialName("custom") CUSTOM,
|
||||
@SerialName("bedrock") BEDROCK,
|
||||
@SerialName("azureOpenAI")
|
||||
AZURE_OPENAI,
|
||||
|
||||
@SerialName("openAI")
|
||||
OPENAI,
|
||||
|
||||
@SerialName("google")
|
||||
GOOGLE,
|
||||
|
||||
@SerialName("anthropic")
|
||||
ANTHROPIC,
|
||||
|
||||
@SerialName("assistants")
|
||||
ASSISTANTS,
|
||||
|
||||
@SerialName("azureAssistants")
|
||||
AZURE_ASSISTANTS,
|
||||
|
||||
@SerialName("agents")
|
||||
AGENTS,
|
||||
|
||||
@SerialName("custom")
|
||||
CUSTOM,
|
||||
|
||||
@SerialName("bedrock")
|
||||
BEDROCK,
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class ContentType {
|
||||
@SerialName("text") TEXT,
|
||||
@SerialName("think") THINK,
|
||||
@SerialName("text_delta") TEXT_DELTA,
|
||||
@SerialName("tool_call") TOOL_CALL,
|
||||
@SerialName("image_file") IMAGE_FILE,
|
||||
@SerialName("image_url") IMAGE_URL,
|
||||
@SerialName("video_url") VIDEO_URL,
|
||||
@SerialName("input_audio") INPUT_AUDIO,
|
||||
@SerialName("agent_update") AGENT_UPDATE,
|
||||
@SerialName("error") ERROR,
|
||||
@SerialName("text")
|
||||
TEXT,
|
||||
|
||||
@SerialName("think")
|
||||
THINK,
|
||||
|
||||
@SerialName("text_delta")
|
||||
TEXT_DELTA,
|
||||
|
||||
@SerialName("tool_call")
|
||||
TOOL_CALL,
|
||||
|
||||
@SerialName("image_file")
|
||||
IMAGE_FILE,
|
||||
|
||||
@SerialName("image_url")
|
||||
IMAGE_URL,
|
||||
|
||||
@SerialName("video_url")
|
||||
VIDEO_URL,
|
||||
|
||||
@SerialName("input_audio")
|
||||
INPUT_AUDIO,
|
||||
|
||||
@SerialName("agent_update")
|
||||
AGENT_UPDATE,
|
||||
|
||||
@SerialName("error")
|
||||
ERROR,
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class StepType {
|
||||
@SerialName("tool_calls") TOOL_CALLS,
|
||||
@SerialName("message_creation") MESSAGE_CREATION,
|
||||
@SerialName("tool_calls")
|
||||
TOOL_CALLS,
|
||||
|
||||
@SerialName("message_creation")
|
||||
MESSAGE_CREATION,
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class ToolCallType {
|
||||
@SerialName("function") FUNCTION,
|
||||
@SerialName("retrieval") RETRIEVAL,
|
||||
@SerialName("file_search") FILE_SEARCH,
|
||||
@SerialName("code_interpreter") CODE_INTERPRETER,
|
||||
@SerialName("tool_call") TOOL_CALL,
|
||||
@SerialName("function")
|
||||
FUNCTION,
|
||||
|
||||
@SerialName("retrieval")
|
||||
RETRIEVAL,
|
||||
|
||||
@SerialName("file_search")
|
||||
FILE_SEARCH,
|
||||
|
||||
@SerialName("code_interpreter")
|
||||
CODE_INTERPRETER,
|
||||
|
||||
@SerialName("tool_call")
|
||||
TOOL_CALL,
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class FeedbackRating {
|
||||
@SerialName("thumbsUp") THUMBS_UP,
|
||||
@SerialName("thumbsDown") THUMBS_DOWN,
|
||||
@SerialName("thumbsUp")
|
||||
THUMBS_UP,
|
||||
|
||||
@SerialName("thumbsDown")
|
||||
THUMBS_DOWN,
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class Provider {
|
||||
@SerialName("openAI") OPENAI,
|
||||
@SerialName("anthropic") ANTHROPIC,
|
||||
@SerialName("azureOpenAI") AZURE,
|
||||
@SerialName("google") GOOGLE,
|
||||
@SerialName("vertexai") VERTEXAI,
|
||||
@SerialName("bedrock") BEDROCK,
|
||||
@SerialName("mistralai") MISTRALAI,
|
||||
@SerialName("mistral") MISTRAL,
|
||||
@SerialName("deepseek") DEEPSEEK,
|
||||
@SerialName("moonshot") MOONSHOT,
|
||||
@SerialName("openrouter") OPENROUTER,
|
||||
@SerialName("xai") XAI,
|
||||
@SerialName("openAI")
|
||||
OPENAI,
|
||||
|
||||
@SerialName("anthropic")
|
||||
ANTHROPIC,
|
||||
|
||||
@SerialName("azureOpenAI")
|
||||
AZURE,
|
||||
|
||||
@SerialName("google")
|
||||
GOOGLE,
|
||||
|
||||
@SerialName("vertexai")
|
||||
VERTEXAI,
|
||||
|
||||
@SerialName("bedrock")
|
||||
BEDROCK,
|
||||
|
||||
@SerialName("mistralai")
|
||||
MISTRALAI,
|
||||
|
||||
@SerialName("mistral")
|
||||
MISTRAL,
|
||||
|
||||
@SerialName("deepseek")
|
||||
DEEPSEEK,
|
||||
|
||||
@SerialName("moonshot")
|
||||
MOONSHOT,
|
||||
|
||||
@SerialName("openrouter")
|
||||
OPENROUTER,
|
||||
|
||||
@SerialName("xai")
|
||||
XAI,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,11 +22,24 @@ data class ParameterDefinition(
|
|||
|
||||
@Serializable
|
||||
enum class ParameterType {
|
||||
@SerialName("slider") SLIDER,
|
||||
@SerialName("dropdown") DROPDOWN,
|
||||
@SerialName("checkbox") CHECKBOX,
|
||||
@SerialName("text") TEXT,
|
||||
@SerialName("switch") SWITCH,
|
||||
@SerialName("textarea") TEXTAREA,
|
||||
@SerialName("tags") TAGS,
|
||||
@SerialName("slider")
|
||||
SLIDER,
|
||||
|
||||
@SerialName("dropdown")
|
||||
DROPDOWN,
|
||||
|
||||
@SerialName("checkbox")
|
||||
CHECKBOX,
|
||||
|
||||
@SerialName("text")
|
||||
TEXT,
|
||||
|
||||
@SerialName("switch")
|
||||
SWITCH,
|
||||
|
||||
@SerialName("textarea")
|
||||
TEXTAREA,
|
||||
|
||||
@SerialName("tags")
|
||||
TAGS,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,20 @@ data class McpServer(
|
|||
|
||||
@Serializable
|
||||
enum class McpServerType {
|
||||
@SerialName("sse") SSE,
|
||||
@SerialName("streamable-http") STREAMABLE_HTTP,
|
||||
@SerialName("http") HTTP,
|
||||
@SerialName("stdio") STDIO,
|
||||
@SerialName("websocket") WEBSOCKET,
|
||||
@SerialName("sse")
|
||||
SSE,
|
||||
|
||||
@SerialName("streamable-http")
|
||||
STREAMABLE_HTTP,
|
||||
|
||||
@SerialName("http")
|
||||
HTTP,
|
||||
|
||||
@SerialName("stdio")
|
||||
STDIO,
|
||||
|
||||
@SerialName("websocket")
|
||||
WEBSOCKET,
|
||||
}
|
||||
|
||||
/** Auth mode for the MCP server add/edit dialog. */
|
||||
|
|
@ -47,15 +56,23 @@ data class McpApiKeyConfig(
|
|||
|
||||
@Serializable
|
||||
enum class McpApiKeySource {
|
||||
@SerialName("admin") ADMIN,
|
||||
@SerialName("user") USER,
|
||||
@SerialName("admin")
|
||||
ADMIN,
|
||||
|
||||
@SerialName("user")
|
||||
USER,
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class McpAuthorizationType {
|
||||
@SerialName("bearer") BEARER,
|
||||
@SerialName("basic") BASIC,
|
||||
@SerialName("custom") CUSTOM,
|
||||
@SerialName("bearer")
|
||||
BEARER,
|
||||
|
||||
@SerialName("basic")
|
||||
BASIC,
|
||||
|
||||
@SerialName("custom")
|
||||
CUSTOM,
|
||||
}
|
||||
|
||||
/** Maps to backend oauth object. Only the most common fields are exposed in the UI. */
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import com.librechat.android.core.model.request.UpdateAgentRequest
|
|||
import com.librechat.android.core.model.response.AgentListResponse
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import io.ktor.client.request.delete
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.forms.submitFormWithBinaryData
|
||||
|
|
@ -28,6 +27,7 @@ import io.ktor.client.request.setBody
|
|||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.path
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import timber.log.Timber
|
||||
|
||||
class AgentsApi constructor(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package com.librechat.android.core.network.api
|
||||
|
||||
import com.librechat.android.core.model.request.LoginRequest
|
||||
import com.librechat.android.core.model.request.OtpVerificationRequest
|
||||
import com.librechat.android.core.model.request.PasswordResetRequest
|
||||
import com.librechat.android.core.model.request.RegisterRequest
|
||||
import com.librechat.android.core.model.request.ResetPasswordRequest
|
||||
import com.librechat.android.core.model.request.TwoFactorDisableRequest
|
||||
import com.librechat.android.core.model.request.OtpVerificationRequest
|
||||
import com.librechat.android.core.model.request.TwoFactorVerifyRequest
|
||||
import com.librechat.android.core.model.request.TwoFactorVerifyTempRequest
|
||||
import com.librechat.android.core.model.response.LoginResponse
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.librechat.android.core.network.api
|
||||
|
||||
import com.librechat.android.core.model.Conversation
|
||||
import com.librechat.android.core.model.request.ArchiveConversationRequest
|
||||
import com.librechat.android.core.model.request.ArchiveConversationArg
|
||||
import com.librechat.android.core.model.request.ConvoDeleteBody
|
||||
import com.librechat.android.core.model.request.ArchiveConversationRequest
|
||||
import com.librechat.android.core.model.request.ConvoDeleteArg
|
||||
import com.librechat.android.core.model.request.ConvoDeleteBody
|
||||
import com.librechat.android.core.model.request.ConvoUpdateArg
|
||||
import com.librechat.android.core.model.request.ConvoUpdateBody
|
||||
import com.librechat.android.core.model.request.DuplicateConversationRequest
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@ class FilesApi constructor(
|
|||
height: Int? = null,
|
||||
): FileObject {
|
||||
Timber.d(
|
||||
"uploadFile: filename=%s, type=%s, size=%d bytes, fileId=%s, endpoint=%s, model=%s, agentId=%s, messageFile=%s, width=%s, height=%s",
|
||||
"uploadFile: filename=%s, type=%s, size=%d bytes, fileId=%s, " +
|
||||
"endpoint=%s, model=%s, agentId=%s, messageFile=%s, width=%s, height=%s",
|
||||
filename, type, bytes.size, fileId, endpoint, model, agentId, messageFile, width, height,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ object LibreChatHttpClient {
|
|||
cacheDir: File? = null,
|
||||
debug: Boolean = false,
|
||||
): HttpClient = HttpClient(OkHttp) {
|
||||
|
||||
engine {
|
||||
if (cacheDir != null) {
|
||||
config {
|
||||
|
|
|
|||
|
|
@ -120,8 +120,7 @@ class SseEventMapper(private val json: Json) {
|
|||
val parseErrors = mutableListOf<String>()
|
||||
|
||||
val conversation = root["conversation"]?.let {
|
||||
try { json.decodeFromJsonElement(Conversation.serializer(), it) }
|
||||
catch (e: Exception) {
|
||||
try { json.decodeFromJsonElement(Conversation.serializer(), it) } catch (e: Exception) {
|
||||
val msg = "Failed to parse final conversation: ${e.message}"
|
||||
Timber.w(e, msg)
|
||||
parseErrors.add(msg)
|
||||
|
|
@ -129,8 +128,7 @@ class SseEventMapper(private val json: Json) {
|
|||
}
|
||||
}
|
||||
val requestMessage = root["requestMessage"]?.let {
|
||||
try { json.decodeFromJsonElement(Message.serializer(), it) }
|
||||
catch (e: Exception) {
|
||||
try { json.decodeFromJsonElement(Message.serializer(), it) } catch (e: Exception) {
|
||||
val msg = "Failed to parse final requestMessage: ${e.message}"
|
||||
Timber.w(e, msg)
|
||||
parseErrors.add(msg)
|
||||
|
|
@ -138,8 +136,7 @@ class SseEventMapper(private val json: Json) {
|
|||
}
|
||||
}
|
||||
val responseMessage = root["responseMessage"]?.let {
|
||||
try { json.decodeFromJsonElement(Message.serializer(), it) }
|
||||
catch (e: Exception) {
|
||||
try { json.decodeFromJsonElement(Message.serializer(), it) } catch (e: Exception) {
|
||||
val msg = "Failed to parse final responseMessage: ${e.message}"
|
||||
Timber.w(e, msg)
|
||||
parseErrors.add(msg)
|
||||
|
|
@ -148,8 +145,7 @@ class SseEventMapper(private val json: Json) {
|
|||
}
|
||||
// Legacy: some events use "message" instead of "responseMessage"
|
||||
val legacyMessage = root["message"]?.let {
|
||||
try { json.decodeFromJsonElement(Message.serializer(), it) }
|
||||
catch (e: Exception) {
|
||||
try { json.decodeFromJsonElement(Message.serializer(), it) } catch (e: Exception) {
|
||||
val msg = "Failed to parse final legacy message: ${e.message}"
|
||||
Timber.w(e, msg)
|
||||
parseErrors.add(msg)
|
||||
|
|
@ -158,8 +154,8 @@ class SseEventMapper(private val json: Json) {
|
|||
}
|
||||
|
||||
// If ALL critical fields failed to parse, emit an Error instead
|
||||
val allFieldsNull = conversation == null && requestMessage == null
|
||||
&& responseMessage == null && legacyMessage == null
|
||||
val allFieldsNull = conversation == null && requestMessage == null &&
|
||||
responseMessage == null && legacyMessage == null
|
||||
if (allFieldsNull && parseErrors.isNotEmpty()) {
|
||||
Timber.e("Final event: all fields failed to parse -- %s", parseErrors)
|
||||
return StreamEvent.Error(
|
||||
|
|
@ -256,7 +252,7 @@ class SseEventMapper(private val json: Json) {
|
|||
"on_message_delta" -> mapMessageDelta(data, agentId, groupId)
|
||||
"on_reasoning_delta" -> mapReasoningDelta(data, agentId, groupId)
|
||||
"on_run_step" -> mapRunStep(data, agentId, groupId)
|
||||
"on_run_step_delta" -> mapRunStepDelta(data)
|
||||
"on_run_step_delta" -> null // Tool call argument streaming - not currently tracked
|
||||
"on_run_step_completed" -> mapRunStepCompleted(data, agentId, groupId)
|
||||
"on_chat_model_end" -> null
|
||||
"on_agent_update" -> null
|
||||
|
|
@ -347,11 +343,6 @@ class SseEventMapper(private val json: Json) {
|
|||
)
|
||||
}
|
||||
|
||||
private fun mapRunStepDelta(data: JsonObject): StreamEvent? {
|
||||
// Tool call argument streaming - not currently tracked
|
||||
return null
|
||||
}
|
||||
|
||||
private fun mapRunStepCompleted(
|
||||
data: JsonObject,
|
||||
agentId: String?,
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ class SseLineParser(
|
|||
channel.readUTF8Line()
|
||||
}
|
||||
} catch (e: kotlinx.coroutines.TimeoutCancellationException) {
|
||||
Timber.w("SSE stream stalled: no data for ${lineReadTimeoutMs / 1000}s")
|
||||
throw IOException("Stream stalled: no data received for ${lineReadTimeoutMs / 1000}s")
|
||||
Timber.w(e, "SSE stream stalled: no data for ${lineReadTimeoutMs / 1000}s")
|
||||
throw IOException("Stream stalled: no data received for ${lineReadTimeoutMs / 1000}s", e)
|
||||
} ?: break
|
||||
|
||||
when {
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.LiveRegionMode
|
||||
import androidx.compose.ui.semantics.liveRegion
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.core.model.Banner
|
||||
import com.librechat.android.core.ui.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
/**
|
||||
* Renders a vertical stack of dismissible server banners.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -18,7 +19,6 @@ import androidx.compose.ui.unit.dp
|
|||
import com.librechat.android.core.model.ParameterDefinition
|
||||
import com.librechat.android.core.model.ParameterType
|
||||
import com.librechat.android.core.ui.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
/** Dispatches a list of ParameterDefinitions to typed controls (slider, dropdown, checkbox, input, textarea) by ParameterType. */
|
||||
@Composable
|
||||
|
|
@ -113,8 +113,11 @@ fun DynamicParameterPanel(
|
|||
ParameterType.TAGS -> {
|
||||
DynamicTagsInput(
|
||||
label = definition.label,
|
||||
tags = if (currentValue.isBlank()) emptyList()
|
||||
else currentValue.split("\n").filter { it.isNotBlank() },
|
||||
tags = if (currentValue.isBlank()) {
|
||||
emptyList()
|
||||
} else {
|
||||
currentValue.split("\n").filter { it.isNotBlank() }
|
||||
},
|
||||
onTagsChanged = { tags ->
|
||||
onValueChanged(definition.key, tags.joinToString("\n"))
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import kotlin.math.roundToInt
|
||||
import com.librechat.android.core.ui.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/** Labeled slider that snaps to step increments, with stepCount derived from (max - min) / step. */
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.core.ui.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
/** Tag input that renders chips and allows adding/removing text tags up to a max count. */
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ import androidx.compose.material3.TextButton
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.LiveRegionMode
|
||||
import androidx.compose.ui.semantics.liveRegion
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.core.ui.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@Composable
|
||||
fun ErrorBanner(
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.librechat.android.core.ui.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.core.ui.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import com.librechat.android.core.ui.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@Composable
|
||||
fun LoadingIndicator(
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.core.model.ParameterDefinition
|
||||
import com.librechat.android.core.model.ParameterType
|
||||
import com.librechat.android.core.ui.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@Stable
|
||||
data class ModelParameters(
|
||||
|
|
@ -174,7 +174,9 @@ fun ModelParameterContent(
|
|||
onValueChange = { newValue ->
|
||||
onParametersChanged(parameters.withUpdatedKey(definition.key, newValue))
|
||||
},
|
||||
placeholder = definition.default?.ifEmpty { stringResource(R.string.default_placeholder) } ?: stringResource(R.string.default_placeholder),
|
||||
placeholder = definition.default?.ifEmpty {
|
||||
stringResource(R.string.default_placeholder)
|
||||
} ?: stringResource(R.string.default_placeholder),
|
||||
description = definition.description,
|
||||
)
|
||||
}
|
||||
|
|
@ -243,8 +245,11 @@ fun ModelParameterContent(
|
|||
}
|
||||
|
||||
ParameterType.TAGS -> {
|
||||
val tags = if (currentValue.isBlank()) emptyList()
|
||||
else currentValue.split("\n").filter { it.isNotBlank() }
|
||||
val tags = if (currentValue.isBlank()) {
|
||||
emptyList()
|
||||
} else {
|
||||
currentValue.split("\n").filter { it.isNotBlank() }
|
||||
}
|
||||
|
||||
DynamicTagsInput(
|
||||
label = definition.label,
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
|
|
@ -57,10 +56,10 @@ import androidx.compose.ui.unit.dp
|
|||
@Composable
|
||||
fun OtpVerificationDialog(
|
||||
title: String,
|
||||
description: String? = null,
|
||||
isLoading: Boolean = false,
|
||||
onVerify: (token: String?, backupCode: String?) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
description: String? = null,
|
||||
isLoading: Boolean = false,
|
||||
verifyLabel: String = "Verify",
|
||||
cancelLabel: String = "Cancel",
|
||||
backupCodeLabel: String = "Backup Code",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ private const val FALLBACK_CORNER_RADIUS_DP = 24f
|
|||
@Composable
|
||||
fun ScreenTransitionWrapper(
|
||||
transition: Transition<EnterExitState>,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val screenCornerRadiusDp = rememberScreenCornerRadiusDp()
|
||||
|
|
@ -43,7 +44,7 @@ fun ScreenTransitionWrapper(
|
|||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.clip(RoundedCornerShape(cornerRadius.dp)),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -62,8 +62,11 @@ fun LibreChatTheme(
|
|||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
if (darkTheme) dynamicDarkColorScheme(LocalContext.current)
|
||||
else dynamicLightColorScheme(LocalContext.current)
|
||||
if (darkTheme) {
|
||||
dynamicDarkColorScheme(LocalContext.current)
|
||||
} else {
|
||||
dynamicLightColorScheme(LocalContext.current)
|
||||
}
|
||||
}
|
||||
darkTheme -> libreChatDarkColorScheme
|
||||
else -> libreChatLightColorScheme
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
|
|
@ -56,11 +57,10 @@ import com.librechat.android.core.model.ActionAuth
|
|||
import com.librechat.android.core.model.ActionMetadata
|
||||
import com.librechat.android.core.model.request.FunctionTool
|
||||
import com.librechat.android.feature.agents.AgentActionDisplayData
|
||||
import com.librechat.android.feature.agents.R
|
||||
import com.librechat.android.feature.agents.util.OpenApiSpecParser
|
||||
import com.librechat.android.feature.agents.util.ParsedFunctionInfo
|
||||
import kotlinx.coroutines.delay
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
private const val VALIDATION_DEBOUNCE_MS = 800L
|
||||
private const val HIDDEN_PLACEHOLDER = "<HIDDEN>"
|
||||
|
|
@ -313,17 +313,29 @@ private fun ActionEditorDialog(
|
|||
domain = parsedDomain,
|
||||
auth = auth,
|
||||
rawSpec = rawSpec,
|
||||
apiKey = if (authType == "service_http" && apiKey.isNotBlank() && apiKey != HIDDEN_PLACEHOLDER) {
|
||||
apiKey = if (
|
||||
authType == "service_http" &&
|
||||
apiKey.isNotBlank() &&
|
||||
apiKey != HIDDEN_PLACEHOLDER
|
||||
) {
|
||||
apiKey
|
||||
} else {
|
||||
null
|
||||
},
|
||||
oauthClientId = if (authType == "oauth" && oauthClientId.isNotBlank() && oauthClientId != HIDDEN_PLACEHOLDER) {
|
||||
oauthClientId = if (
|
||||
authType == "oauth" &&
|
||||
oauthClientId.isNotBlank() &&
|
||||
oauthClientId != HIDDEN_PLACEHOLDER
|
||||
) {
|
||||
oauthClientId
|
||||
} else {
|
||||
null
|
||||
},
|
||||
oauthClientSecret = if (authType == "oauth" && oauthClientSecret.isNotBlank() && oauthClientSecret != HIDDEN_PLACEHOLDER) {
|
||||
oauthClientSecret = if (
|
||||
authType == "oauth" &&
|
||||
oauthClientSecret.isNotBlank() &&
|
||||
oauthClientSecret != HIDDEN_PLACEHOLDER
|
||||
) {
|
||||
oauthClientSecret
|
||||
} else {
|
||||
null
|
||||
|
|
@ -373,7 +385,8 @@ private fun ActionEditorDialog(
|
|||
label = { Text(stringResource(R.string.label_openapi_spec)) },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "{\n \"openapi\": \"3.0.0\",\n \"info\": { ... },\n \"servers\": [{ \"url\": \"...\" }],\n \"paths\": { ... }\n}",
|
||||
text = "{\n \"openapi\": \"3.0.0\",\n" +
|
||||
" \"info\": { ... },\n \"paths\": { ... }\n}",
|
||||
fontFamily = FontFamily.Monospace,
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlin.math.roundToInt
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
data class AgentAdvancedSettings(
|
||||
val temperature: Float? = null,
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@Composable
|
||||
fun AgentAvatarPicker(
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
data class AgentCapabilities(
|
||||
val artifacts: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.core.model.AgentCategory
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
/**
|
||||
* Maps category values to human-readable English labels.
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@Composable
|
||||
fun AgentCodeInterpreterSection(
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@Composable
|
||||
fun AgentFileSearchSection(
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.feature.agents.AgentHandoffDisplayData
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@Composable
|
||||
fun AgentHandoffConfig(
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExpandLess
|
||||
|
|
@ -23,10 +21,10 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.core.model.mcp.McpTool
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
/** Hierarchical MCP tool picker grouped by server with checkbox selection. */
|
||||
@Composable
|
||||
|
|
@ -57,7 +55,7 @@ fun AgentMcpToolsSelector(
|
|||
)
|
||||
}
|
||||
|
||||
val unknownServerLabel = stringResource(R.string.unknown_server)
|
||||
val unknownServerLabel = stringResource(R.string.unknown_server)
|
||||
|
||||
AnimatedVisibility(visible = expanded) {
|
||||
Column(
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
data class ModelOption(
|
||||
val id: String,
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
enum class AgentVisibility(val label: String) {
|
||||
PRIVATE("Private"),
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import androidx.compose.material3.OutlinedTextField
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@androidx.compose.runtime.Immutable
|
||||
data class SupportContactState(
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ import androidx.compose.material3.rememberModalBottomSheetState
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
data class AgentVersion(
|
||||
val version: Int,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
|
|
@ -49,7 +50,6 @@ import androidx.compose.ui.window.DialogProperties
|
|||
import coil.compose.AsyncImage
|
||||
import com.librechat.android.feature.agents.AgentToolDisplayData
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
/**
|
||||
* Full-screen dialog for selecting agent tools, matching the web app's ToolSelectDialog.
|
||||
|
|
|
|||
|
|
@ -42,20 +42,19 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.core.ui.R as CoreUiR
|
||||
import com.librechat.android.core.ui.components.AvatarImage
|
||||
import com.librechat.android.core.ui.components.ErrorBanner
|
||||
import com.librechat.android.core.ui.components.LoadingIndicator
|
||||
import com.librechat.android.feature.agents.AgentDetailDisplayData
|
||||
import com.librechat.android.feature.agents.R
|
||||
import com.librechat.android.feature.agents.viewmodel.AgentDetailEvent
|
||||
import com.librechat.android.feature.agents.viewmodel.AgentDetailViewModel
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import com.librechat.android.core.ui.R as CoreUiR
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
|
|
@ -228,7 +227,11 @@ fun AgentDetailScreen(
|
|||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.by_author, agent.authorName ?: agent.author ?: stringResource(R.string.unknown_author)),
|
||||
text = stringResource(
|
||||
R.string.by_author,
|
||||
agent.authorName ?: agent.author
|
||||
?: stringResource(R.string.unknown_author),
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
|
@ -354,9 +357,11 @@ private fun AgentDetailSection(
|
|||
Text(
|
||||
text = content,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = if (dimContent) MaterialTheme.colorScheme.onSurfaceVariant
|
||||
else MaterialTheme.colorScheme.onSurface,
|
||||
color = if (dimContent) {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,11 +48,12 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.core.ui.components.ErrorBanner
|
||||
import com.librechat.android.core.ui.components.LoadingIndicator
|
||||
import com.librechat.android.feature.agents.R
|
||||
import com.librechat.android.feature.agents.components.AgentActionsPanel
|
||||
import com.librechat.android.feature.agents.components.AgentAdvancedPanel
|
||||
import com.librechat.android.feature.agents.components.AgentAvatarPicker
|
||||
|
|
@ -68,8 +69,7 @@ import com.librechat.android.feature.agents.components.AgentVersionHistory
|
|||
import com.librechat.android.feature.agents.components.ToolSelectDialog
|
||||
import com.librechat.android.feature.agents.viewmodel.AgentEditorEvent
|
||||
import com.librechat.android.feature.agents.viewmodel.AgentEditorViewModel
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -41,20 +41,20 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.core.ui.R as CoreUiR
|
||||
import com.librechat.android.core.ui.components.AvatarImage
|
||||
import com.librechat.android.core.ui.components.EmptyState
|
||||
import com.librechat.android.core.ui.components.ErrorBanner
|
||||
import com.librechat.android.core.ui.components.LibreChatTopBar
|
||||
import com.librechat.android.core.ui.components.LoadingIndicator
|
||||
import com.librechat.android.feature.agents.AgentCardDisplayData
|
||||
import com.librechat.android.feature.agents.viewmodel.AgentMarketplaceViewModel
|
||||
import com.librechat.android.feature.agents.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.agents.viewmodel.AgentMarketplaceViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import com.librechat.android.core.ui.R as CoreUiR
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -366,6 +366,7 @@ object OpenApiSpecParser {
|
|||
"Not valid JSON or YAML.\n" +
|
||||
"JSON error: ${jsonError.message}\n" +
|
||||
"YAML error: ${yamlError.message}",
|
||||
yamlError,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,8 +130,11 @@ class AgentDetailViewModel(
|
|||
|
||||
private fun Agent.toDetailDisplayData(): AgentDetailDisplayData {
|
||||
val resolvedUrl = avatarUrl?.let { url ->
|
||||
if (url.startsWith("http")) url
|
||||
else "${serverDataStore.getBaseUrl()}$url"
|
||||
if (url.startsWith("http")) {
|
||||
url
|
||||
} else {
|
||||
"${serverDataStore.getBaseUrl()}$url"
|
||||
}
|
||||
}
|
||||
return AgentDetailDisplayData(
|
||||
id = id,
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@ import com.librechat.android.core.common.result.Result
|
|||
import com.librechat.android.core.data.repository.AgentRepository
|
||||
import com.librechat.android.core.data.repository.ConfigRepository
|
||||
import com.librechat.android.core.data.repository.McpRepository
|
||||
import com.librechat.android.core.model.Agent
|
||||
import com.librechat.android.core.model.ActionAuth
|
||||
import com.librechat.android.core.model.ActionMetadata
|
||||
import com.librechat.android.core.model.Agent
|
||||
import com.librechat.android.core.model.AgentAction
|
||||
import com.librechat.android.core.model.AgentCategory
|
||||
import com.librechat.android.core.model.AgentTool
|
||||
|
|
@ -20,20 +19,20 @@ import com.librechat.android.core.model.SupportContact
|
|||
import com.librechat.android.core.model.mcp.McpTool
|
||||
import com.librechat.android.core.model.request.CreateActionRequest
|
||||
import com.librechat.android.core.model.request.CreateAgentRequest
|
||||
import com.librechat.android.core.model.request.FunctionTool
|
||||
import com.librechat.android.core.model.request.RevertAgentRequest
|
||||
import com.librechat.android.core.model.request.UpdateAgentRequest
|
||||
import com.librechat.android.core.model.request.FunctionTool
|
||||
import com.librechat.android.feature.agents.AgentActionDisplayData
|
||||
import com.librechat.android.feature.agents.AgentHandoffDisplayData
|
||||
import com.librechat.android.feature.agents.AgentToolDisplayData
|
||||
import com.librechat.android.feature.agents.components.AgentAdvancedSettings
|
||||
import com.librechat.android.feature.agents.util.OpenApiSpecParser
|
||||
import com.librechat.android.feature.agents.components.AgentCapabilities
|
||||
import com.librechat.android.feature.agents.components.AgentSharingState
|
||||
import com.librechat.android.feature.agents.components.AgentVersion
|
||||
import com.librechat.android.feature.agents.components.AgentVisibility
|
||||
import com.librechat.android.feature.agents.components.ModelOption
|
||||
import com.librechat.android.feature.agents.components.SupportContactState
|
||||
import com.librechat.android.feature.agents.util.OpenApiSpecParser
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
|
@ -887,13 +886,13 @@ class AgentEditorViewModel(
|
|||
val serverName = matchingTool.serverName
|
||||
if (serverName != null) {
|
||||
// Store as "toolName_mcp_serverName" format
|
||||
tools.add("${mcpToolName}${MCP_TOOL_SEPARATOR}${serverName}")
|
||||
tools.add("${mcpToolName}${MCP_TOOL_SEPARATOR}$serverName")
|
||||
} else {
|
||||
tools.add(mcpToolName)
|
||||
}
|
||||
} else {
|
||||
// May be a server name marker
|
||||
tools.add("${MCP_SERVER_MARKER}${MCP_TOOL_SEPARATOR}${mcpToolName}")
|
||||
tools.add("${MCP_SERVER_MARKER}${MCP_TOOL_SEPARATOR}$mcpToolName")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.librechat.android.feature.agents.viewmodel
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.librechat.android.core.common.result.Result
|
||||
import com.librechat.android.core.data.datastore.ServerDataStore
|
||||
import com.librechat.android.core.data.repository.AgentRepository
|
||||
|
|
@ -47,8 +47,11 @@ class AgentMarketplaceViewModel(
|
|||
|
||||
private fun Agent.toCardDisplayData(): AgentCardDisplayData {
|
||||
val resolvedUrl = avatarUrl?.let { url ->
|
||||
if (url.startsWith("http")) url
|
||||
else "${serverDataStore.getBaseUrl()}$url"
|
||||
if (url.startsWith("http")) {
|
||||
url
|
||||
} else {
|
||||
"${serverDataStore.getBaseUrl()}$url"
|
||||
}
|
||||
}
|
||||
return AgentCardDisplayData(
|
||||
id = id,
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.feature.auth.viewmodel.ForgotPasswordViewModel
|
||||
import com.librechat.android.feature.auth.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.auth.viewmodel.ForgotPasswordViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -21,26 +21,26 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.LifecycleEventEffect
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.feature.auth.viewmodel.LoginViewModel
|
||||
import com.librechat.android.feature.auth.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.auth.viewmodel.LoginViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun LoginScreen(
|
||||
onLoginSuccess: () -> Unit,
|
||||
onNavigateToRegister: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onNavigateToForgotPassword: () -> Unit = {},
|
||||
onNavigateToTwoFactor: (String) -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: LoginViewModel = koinViewModel(),
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
|
|
|||
|
|
@ -20,15 +20,15 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.feature.auth.viewmodel.RegisterViewModel
|
||||
import com.librechat.android.feature.auth.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.auth.viewmodel.RegisterViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun RegisterScreen(
|
||||
|
|
|
|||
|
|
@ -23,15 +23,15 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.feature.auth.viewmodel.ResetPasswordViewModel
|
||||
import com.librechat.android.feature.auth.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.auth.viewmodel.ResetPasswordViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.feature.auth.viewmodel.ServerUrlViewModel
|
||||
import com.librechat.android.feature.auth.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.auth.viewmodel.ServerUrlViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun ServerUrlScreen(
|
||||
|
|
|
|||
|
|
@ -26,15 +26,15 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.feature.auth.viewmodel.TermsViewModel
|
||||
import com.librechat.android.feature.auth.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.auth.viewmodel.TermsViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
/** Displays server terms of service loaded from the backend; gates navigation on user acceptance. */
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
|
|||
|
|
@ -31,16 +31,16 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.feature.auth.viewmodel.TwoFactorViewModel
|
||||
import com.librechat.android.feature.auth.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.auth.viewmodel.TwoFactorViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -81,7 +81,11 @@ fun TwoFactorScreen(
|
|||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = if (uiState.isBackupMode) stringResource(R.string.enter_backup_code) else stringResource(R.string.enter_verification_code),
|
||||
text = if (uiState.isBackupMode) {
|
||||
stringResource(R.string.enter_backup_code)
|
||||
} else {
|
||||
stringResource(R.string.enter_verification_code)
|
||||
},
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier.semantics { heading() },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,15 +24,15 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.librechat.android.feature.auth.viewmodel.VerifyEmailViewModel
|
||||
import com.librechat.android.feature.auth.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.auth.viewmodel.VerifyEmailViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.librechat.android.feature.auth.viewmodel
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.librechat.android.core.common.result.Result
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.librechat.android.core.data.repository.AuthRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.librechat.android.feature.auth.viewmodel
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.librechat.android.core.common.result.Result
|
||||
import com.librechat.android.core.data.repository.AuthRepository
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.librechat.android.core.data.repository.ConfigRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.librechat.android.feature.auth.viewmodel
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.librechat.android.core.common.result.Result
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.librechat.android.core.data.repository.AuthRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.librechat.android.feature.auth.viewmodel
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.librechat.android.core.common.result.Result
|
||||
import com.librechat.android.core.data.datastore.ServerDataStore
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.librechat.android.core.data.repository.ConfigRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.librechat.android.feature.auth.viewmodel
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.librechat.android.core.common.result.Result
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.librechat.android.core.data.repository.UserRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.librechat.android.feature.auth.viewmodel
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.librechat.android.core.common.result.Result
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.librechat.android.core.data.repository.AuthRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.librechat.android.feature.auth.viewmodel
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.librechat.android.core.common.result.Result
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.librechat.android.core.data.repository.UserRepository
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ class RegisterViewModelTest {
|
|||
viewModel.onNameChanged("Test")
|
||||
viewModel.onEmailChanged("test@test.com")
|
||||
viewModel.onUsernameChanged("testuser")
|
||||
viewModel.onPasswordChanged("short1234") // 9 chars, less than 12
|
||||
viewModel.onPasswordChanged("short1234") // 9 chars, less than 12
|
||||
viewModel.onConfirmPasswordChanged("short1234")
|
||||
viewModel.register()
|
||||
advanceUntilIdle()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.librechat.android.feature.chat.audio
|
|||
import android.content.Context
|
||||
import android.media.MediaRecorder
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
|
|
@ -13,10 +13,6 @@ import java.io.File
|
|||
*/
|
||||
class VoiceRecorder(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "VoiceRecorder"
|
||||
}
|
||||
|
||||
private var mediaRecorder: MediaRecorder? = null
|
||||
private var outputFile: File? = null
|
||||
private var isCurrentlyRecording = false
|
||||
|
|
@ -64,7 +60,7 @@ class VoiceRecorder(private val context: Context) {
|
|||
mediaRecorder = recorder
|
||||
isCurrentlyRecording = true
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to start voice recording", e)
|
||||
Timber.e(e, "Failed to start voice recording")
|
||||
cleanup()
|
||||
throw e
|
||||
}
|
||||
|
|
@ -87,7 +83,7 @@ class VoiceRecorder(private val context: Context) {
|
|||
outputFile = null
|
||||
bytes
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to stop voice recording", e)
|
||||
Timber.e(e, "Failed to stop voice recording")
|
||||
cleanup()
|
||||
null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
|
||||
/**
|
||||
|
|
@ -48,7 +48,8 @@ fun AgentHandoffCard(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val unknownAgent = stringResource(R.string.agent_unknown)
|
||||
val handoffCd = stringResource(R.string.cd_agent_handoff, handoff.fromAgent ?: unknownAgent, handoff.toAgent ?: unknownAgent)
|
||||
val handoffCd =
|
||||
stringResource(R.string.cd_agent_handoff, handoff.fromAgent ?: unknownAgent, handoff.toAgent ?: unknownAgent)
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
|
||||
import androidx.compose.material.icons.filled.AttachFile
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Code
|
||||
import androidx.compose.material.icons.filled.FindInPage
|
||||
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
|
||||
import androidx.compose.material.icons.filled.Extension
|
||||
import androidx.compose.material.icons.filled.FindInPage
|
||||
import androidx.compose.material.icons.filled.TravelExplore
|
||||
import androidx.compose.material3.AssistChip
|
||||
import androidx.compose.material3.AssistChipDefaults
|
||||
|
|
@ -43,6 +43,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.role
|
||||
|
|
@ -52,7 +53,6 @@ import androidx.compose.ui.unit.dp
|
|||
import coil.compose.AsyncImage
|
||||
import com.librechat.android.core.common.ToolConstants
|
||||
import com.librechat.android.feature.chat.McpServerDisplayData
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
|
||||
/**
|
||||
|
|
@ -65,11 +65,9 @@ fun AttachmentChipsRow(
|
|||
attachedFiles: List<AttachedFile>,
|
||||
enabledTools: Set<String>,
|
||||
onRemoveFile: (AttachedFile) -> Unit,
|
||||
onToggleTool: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
mcpServers: List<McpServerDisplayData> = emptyList(),
|
||||
selectedMcpServerNames: Set<String> = emptySet(),
|
||||
onToggleMcpServer: (String) -> Unit = {},
|
||||
) {
|
||||
val hasFiles = attachedFiles.isNotEmpty()
|
||||
val hasWebSearch = ToolConstants.WEB_SEARCH in enabledTools
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import androidx.compose.ui.viewinterop.AndroidView
|
|||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.datasource.ByteArrayDataSource
|
||||
import androidx.media3.datasource.DataSpec
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||
import androidx.media3.ui.PlayerView
|
||||
|
|
@ -50,7 +49,6 @@ fun AudioContent(
|
|||
}
|
||||
|
||||
val dataSource = ByteArrayDataSource(audioBytes)
|
||||
val dataSpec = DataSpec(android.net.Uri.parse("data:$mimeType;base64,"))
|
||||
|
||||
ExoPlayer.Builder(context).build().apply {
|
||||
val mediaSource = ProgressiveMediaSource.Factory { dataSource }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.librechat.android.feature.chat.components
|
||||
|
||||
import android.media.MediaPlayer
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -33,12 +32,13 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.feature.chat.R
|
||||
import kotlinx.coroutines.delay
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
|
||||
/** Plays audio via MediaPlayer with play/pause and seekbar. Polls progress every 250ms. Releases on dispose. */
|
||||
@Composable
|
||||
|
|
@ -66,7 +66,7 @@ fun AudioContentPlayer(
|
|||
durationMs = it.duration
|
||||
}
|
||||
setOnErrorListener { _, what, extra ->
|
||||
Log.e("AudioContentPlayer", "MediaPlayer error: what=$what, extra=$extra")
|
||||
Timber.e("MediaPlayer error: what=$what, extra=$extra")
|
||||
isPlaying = false
|
||||
true
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ fun AudioContentPlayer(
|
|||
mediaPlayer.setDataSource(audioUrl)
|
||||
mediaPlayer.prepareAsync()
|
||||
} catch (e: Exception) {
|
||||
Log.e("AudioContentPlayer", "Failed to set data source", e)
|
||||
Timber.e(e, "Failed to set data source")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import androidx.compose.foundation.shape.CircleShape
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Send
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
|
|
@ -59,29 +58,29 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import com.librechat.android.feature.chat.McpServerDisplayData
|
||||
import java.io.File
|
||||
import com.librechat.android.feature.chat.PromptMentionDisplayData
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
import java.io.File
|
||||
|
||||
data class AttachedFile(
|
||||
val uri: Uri,
|
||||
|
|
@ -212,7 +211,6 @@ fun ChatInput(
|
|||
}
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
var showToolsSheet by remember { mutableStateOf(false) }
|
||||
|
||||
// Use TextFieldValue internally so we can control cursor position.
|
||||
|
|
@ -256,10 +254,8 @@ fun ChatInput(
|
|||
attachedFiles = attachedFiles,
|
||||
enabledTools = enabledTools,
|
||||
onRemoveFile = onRemoveFile,
|
||||
onToggleTool = onToggleTool,
|
||||
mcpServers = mcpServers,
|
||||
selectedMcpServerNames = selectedMcpServerNames,
|
||||
onToggleMcpServer = onToggleMcpServer,
|
||||
)
|
||||
|
||||
Row(
|
||||
|
|
@ -327,7 +323,9 @@ fun ChatInput(
|
|||
val afterAt = inputText.substring(atIndex + 1)
|
||||
// Only show suggestions if there's no space after @ (still typing the mention)
|
||||
if (!afterAt.contains(' ')) afterAt else null
|
||||
} else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -339,7 +337,9 @@ fun ChatInput(
|
|||
group.name.contains(query, ignoreCase = true) ||
|
||||
group.command?.contains(query, ignoreCase = true) == true
|
||||
}.take(5)
|
||||
} else emptyList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -350,7 +350,9 @@ fun ChatInput(
|
|||
val afterSlash = inputText.substring(1)
|
||||
// Only show suggestions if there's no space (still typing the command)
|
||||
if (!afterSlash.contains(' ')) afterSlash else null
|
||||
} else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -362,7 +364,9 @@ fun ChatInput(
|
|||
val cmd = group.command
|
||||
cmd != null && cmd.contains(query, ignoreCase = true)
|
||||
}.take(5)
|
||||
} else emptyList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.core.common.ToolConstants
|
||||
import com.librechat.android.feature.chat.McpServerDisplayData
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
|
||||
data class ToolToggle(
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ import androidx.compose.foundation.layout.width
|
|||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.AttachFile
|
||||
import androidx.compose.material.icons.filled.Code
|
||||
import androidx.compose.material.icons.filled.Extension
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.FindInPage
|
||||
import androidx.compose.material.icons.filled.PhotoCamera
|
||||
import androidx.compose.material.icons.filled.PhotoLibrary
|
||||
|
|
@ -46,6 +46,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.role
|
||||
|
|
@ -54,7 +55,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.core.common.ToolConstants
|
||||
import com.librechat.android.feature.chat.McpServerDisplayData
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -72,6 +72,7 @@ fun ChatToolsBottomSheet(
|
|||
onOpenModelSelector: () -> Unit,
|
||||
selectedModelDisplay: String?,
|
||||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isCodeInterpreterAvailable: Boolean = true,
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
|
|
@ -79,6 +80,7 @@ fun ChatToolsBottomSheet(
|
|||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
modifier = modifier,
|
||||
sheetState = sheetState,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -284,7 +286,6 @@ fun ChatToolsBottomSheet(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.OpenInNew
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -24,6 +23,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
|
|
@ -35,7 +35,6 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
|
||||
/**
|
||||
|
|
@ -135,8 +134,8 @@ fun parseCitations(text: String): Pair<AnnotatedString, List<Citation>> {
|
|||
@Composable
|
||||
fun CitationText(
|
||||
text: String,
|
||||
fontSizeMultiplier: Float = 1.0f,
|
||||
modifier: Modifier = Modifier,
|
||||
fontSizeMultiplier: Float = 1.0f,
|
||||
) {
|
||||
val (annotatedString, citations) = remember(text) { parseCitations(text) }
|
||||
var activeCitation by remember { mutableStateOf<Citation?>(null) }
|
||||
|
|
@ -160,11 +159,14 @@ fun CitationText(
|
|||
}
|
||||
|
||||
val baseStyle = MaterialTheme.typography.bodyLarge.let { style ->
|
||||
if (fontSizeMultiplier == 1.0f) style
|
||||
else style.copy(
|
||||
fontSize = (style.fontSize.value * fontSizeMultiplier).sp,
|
||||
lineHeight = (style.lineHeight.value * fontSizeMultiplier).sp,
|
||||
)
|
||||
if (fontSizeMultiplier == 1.0f) {
|
||||
style
|
||||
} else {
|
||||
style.copy(
|
||||
fontSize = (style.fontSize.value * fontSizeMultiplier).sp,
|
||||
lineHeight = (style.lineHeight.value * fontSizeMultiplier).sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (citations.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -36,19 +36,19 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.delay
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
fun CodeBlock(
|
||||
|
|
@ -232,7 +232,6 @@ private val keywordStyle = SpanStyle(color = Color(0xFF569CD6))
|
|||
private val numberStyle = SpanStyle(color = Color(0xFFB5CEA8))
|
||||
private val typeStyle = SpanStyle(color = Color(0xFF4EC9B0))
|
||||
private val functionStyle = SpanStyle(color = Color(0xFFDCDCAA))
|
||||
private val punctuationStyle = SpanStyle(color = Color(0xFFD4D4D4))
|
||||
private val annotationStyle = SpanStyle(color = Color(0xFFD7BA7D))
|
||||
private val constantStyle = SpanStyle(color = Color(0xFF4FC1FF))
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Cancel
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Code
|
||||
import androidx.compose.material.icons.filled.ExpandLess
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
|
|
@ -35,13 +35,13 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
|
||||
/**
|
||||
|
|
@ -118,7 +118,14 @@ fun CodeExecutionCard(
|
|||
val isSuccess = exitCode == 0
|
||||
Icon(
|
||||
imageVector = if (isSuccess) Icons.Default.CheckCircle else Icons.Default.Cancel,
|
||||
contentDescription = stringResource(if (isSuccess) R.string.cd_exit_code_success else R.string.cd_exit_code_failure, exitCode),
|
||||
contentDescription = stringResource(
|
||||
if (isSuccess) {
|
||||
R.string.cd_exit_code_success
|
||||
} else {
|
||||
R.string.cd_exit_code_failure
|
||||
},
|
||||
exitCode,
|
||||
),
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = if (isSuccess) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -17,7 +16,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.feature.chat.R
|
||||
|
||||
|
|
@ -36,9 +34,9 @@ fun ComparisonDualPane(
|
|||
secondaryModelSelector: @Composable () -> Unit,
|
||||
primaryContent: @Composable () -> Unit,
|
||||
secondaryContent: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onContinueWithPrimary: (() -> Unit)? = null,
|
||||
onContinueWithSecondary: (() -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(modifier = modifier.fillMaxSize()) {
|
||||
// Primary pane
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ fun ComparisonTabBar(
|
|||
secondaryModelName: String,
|
||||
primaryContent: @Composable () -> Unit,
|
||||
secondaryContent: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onContinueWithPrimary: (() -> Unit)? = null,
|
||||
onContinueWithSecondary: (() -> Unit)? = null,
|
||||
onTabChanged: ((Int) -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val pagerState = rememberPagerState(pageCount = { 2 })
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
|
|
|||
|
|
@ -39,17 +39,19 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.LayoutCoordinates
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.layout.LayoutCoordinates
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import com.librechat.android.core.common.ToolConstants
|
||||
import com.librechat.android.core.model.AgentToolCall
|
||||
import com.librechat.android.core.model.ContentType
|
||||
import com.librechat.android.core.model.MessageContentPart
|
||||
import com.librechat.android.feature.chat.R
|
||||
import com.librechat.android.feature.chat.components.artifact.ArtifactButton
|
||||
import com.librechat.android.feature.chat.components.artifact.ArtifactPanel
|
||||
import com.librechat.android.feature.chat.components.artifact.ArtifactSegment
|
||||
|
|
@ -63,14 +65,13 @@ import kotlinx.serialization.json.intOrNull
|
|||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
|
||||
private val lenientJson = Json { ignoreUnknownKeys = true; isLenient = true }
|
||||
|
||||
@Composable
|
||||
fun ContentPartRenderer(
|
||||
part: MessageContentPart,
|
||||
modifier: Modifier = Modifier,
|
||||
baseUrl: String = "",
|
||||
fontSizeMultiplier: Float = 1.0f,
|
||||
useKatex: Boolean = false,
|
||||
|
|
@ -79,7 +80,6 @@ fun ContentPartRenderer(
|
|||
searchQuery: String? = null,
|
||||
searchFocusedOccurrence: Int = -1,
|
||||
onFocusedOccurrencePositioned: ((LayoutCoordinates) -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val constrainedModifier = modifier.fillMaxWidth()
|
||||
when (part.type) {
|
||||
|
|
@ -282,85 +282,84 @@ fun ContentPartRenderer(
|
|||
@Composable
|
||||
private fun TextContentPart(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
fontSizeMultiplier: Float = 1.0f,
|
||||
useKatex: Boolean = false,
|
||||
searchQuery: String? = null,
|
||||
searchFocusedOccurrence: Int = -1,
|
||||
onFocusedOccurrencePositioned: ((LayoutCoordinates) -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (text.isBlank()) return
|
||||
|
||||
val segments = remember(text) { detectArtifacts(text) }
|
||||
val hasArtifacts = remember(segments) { segments.any { it is ArtifactSegment.ArtifactReference } }
|
||||
|
||||
if (!hasArtifacts) {
|
||||
MarkdownContent(
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
useKatex = useKatex,
|
||||
searchQuery = searchQuery,
|
||||
searchFocusedOccurrence = searchFocusedOccurrence,
|
||||
onFocusedOccurrencePositioned = onFocusedOccurrencePositioned,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val versionMap = remember(segments) { groupArtifactVersions(segments) }
|
||||
var activeArtifact by remember {
|
||||
mutableStateOf<com.librechat.android.feature.chat.components.artifact.Artifact?>(null)
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
segments.forEach { segment ->
|
||||
when (segment) {
|
||||
is ArtifactSegment.Text -> {
|
||||
MarkdownContent(
|
||||
text = segment.text,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
useKatex = useKatex,
|
||||
searchQuery = searchQuery,
|
||||
searchFocusedOccurrence = searchFocusedOccurrence,
|
||||
onFocusedOccurrencePositioned = onFocusedOccurrencePositioned,
|
||||
)
|
||||
}
|
||||
is ArtifactSegment.ArtifactReference -> {
|
||||
val versions = versionMap[segment.artifact.identifier] ?: listOf(segment.artifact)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
ArtifactButton(
|
||||
artifact = segment.artifact,
|
||||
onClick = { activeArtifact = segment.artifact },
|
||||
versionCount = versions.size,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
if (!hasArtifacts) {
|
||||
MarkdownContent(
|
||||
text = text,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
useKatex = useKatex,
|
||||
searchQuery = searchQuery,
|
||||
searchFocusedOccurrence = searchFocusedOccurrence,
|
||||
onFocusedOccurrencePositioned = onFocusedOccurrencePositioned,
|
||||
)
|
||||
} else {
|
||||
val versionMap = remember(segments) { groupArtifactVersions(segments) }
|
||||
var activeArtifact by remember {
|
||||
mutableStateOf<com.librechat.android.feature.chat.components.artifact.Artifact?>(null)
|
||||
}
|
||||
|
||||
segments.forEach { segment ->
|
||||
when (segment) {
|
||||
is ArtifactSegment.Text -> {
|
||||
MarkdownContent(
|
||||
text = segment.text,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
useKatex = useKatex,
|
||||
searchQuery = searchQuery,
|
||||
searchFocusedOccurrence = searchFocusedOccurrence,
|
||||
onFocusedOccurrencePositioned = onFocusedOccurrencePositioned,
|
||||
)
|
||||
}
|
||||
is ArtifactSegment.ArtifactReference -> {
|
||||
val versions = versionMap[segment.artifact.identifier] ?: listOf(segment.artifact)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
ArtifactButton(
|
||||
artifact = segment.artifact,
|
||||
onClick = { activeArtifact = segment.artifact },
|
||||
versionCount = versions.size,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activeArtifact?.let { artifact ->
|
||||
val versions = versionMap[artifact.identifier] ?: listOf(artifact)
|
||||
ArtifactPanel(
|
||||
artifact = artifact,
|
||||
onDismiss = { activeArtifact = null },
|
||||
versions = versions,
|
||||
)
|
||||
activeArtifact?.let { artifact ->
|
||||
val versions = versionMap[artifact.identifier] ?: listOf(artifact)
|
||||
ArtifactPanel(
|
||||
artifact = artifact,
|
||||
onDismiss = { activeArtifact = null },
|
||||
versions = versions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ThinkingContentPart(
|
||||
thinkingText: String,
|
||||
modifier: Modifier = Modifier,
|
||||
fontSizeMultiplier: Float = 1.0f,
|
||||
useKatex: Boolean = false,
|
||||
searchQuery: String? = null,
|
||||
searchFocusedOccurrence: Int = -1,
|
||||
onFocusedOccurrencePositioned: ((LayoutCoordinates) -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var isExpanded by remember { mutableStateOf(false) }
|
||||
val thinkingToggleCd = stringResource(if (isExpanded) R.string.cd_collapse_thinking else R.string.cd_expand_thinking)
|
||||
val thinkingToggleCd =
|
||||
stringResource(if (isExpanded) R.string.cd_collapse_thinking else R.string.cd_expand_thinking)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
|
|
@ -438,7 +437,8 @@ private fun ToolCallContentPart(
|
|||
shape = RoundedCornerShape(8.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
val toolCallCd = stringResource(if (isExpanded) R.string.cd_collapse_tool_call else R.string.cd_expand_tool_call, toolName)
|
||||
val toolCallCd =
|
||||
stringResource(if (isExpanded) R.string.cd_collapse_tool_call else R.string.cd_expand_tool_call, toolName)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.librechat.android.core.model.request.ForkOption
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.librechat.android.feature.chat.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -38,12 +38,14 @@ import com.librechat.android.feature.chat.R
|
|||
fun ForkOptionsBottomSheet(
|
||||
onDismiss: () -> Unit,
|
||||
onFork: (option: String, splitAtTarget: Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
var splitAtTarget by remember { mutableStateOf(false) }
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
modifier = modifier,
|
||||
sheetState = sheetState,
|
||||
) {
|
||||
Column(
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue