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