Provider-Wechsel nur für Admin (403 für Nutzer); App blendet Provider-Auswahl für Nicht-Admins aus; v0.3.1
This commit is contained in:
parent
161bc02bb9
commit
70d316851a
12 changed files with 102 additions and 17 deletions
|
|
@ -31,8 +31,8 @@ android {
|
|||
applicationId = "dev.byteclaw.client"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 3
|
||||
versionName = "0.3.0"
|
||||
versionCode = 4
|
||||
versionName = "0.3.1"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ data class UiState(
|
|||
/** Login screen state. */
|
||||
val authBusy: Boolean = false,
|
||||
val authError: String? = null,
|
||||
/** From /api/auth/me: only the admin may change providers. */
|
||||
val isAdmin: Boolean = false,
|
||||
) {
|
||||
/** What the current chat effectively uses: session override, else the global active provider. */
|
||||
val effectiveProvider: ActiveProvider? get() = sessionProvider ?: provider.active
|
||||
|
|
@ -266,6 +268,7 @@ class ChatViewModel(app: Application) : AndroidViewModel(app) {
|
|||
}
|
||||
|
||||
fun loadProviders() = viewModelScope.launch {
|
||||
runCatching { api().me() }.onSuccess { u -> _state.update { it.copy(isAdmin = u.admin) } }
|
||||
runCatching { api().providers() }
|
||||
.onSuccess { r -> _state.update { it.copy(provider = it.provider.copy(providers = r.providers, active = r.active, status = null)) } }
|
||||
.onFailure { e -> if (e is UnauthorizedException) fail(e) else _state.update { it.copy(provider = it.provider.copy(status = "Provider laden fehlgeschlagen: ${e.message}")) } }
|
||||
|
|
|
|||
|
|
@ -296,7 +296,14 @@ fun SettingsScreen(
|
|||
}
|
||||
status?.let { item { Text(it) } }
|
||||
if (state.settings.isConfigured) {
|
||||
item { ProviderSection(state.provider, onLoadProviders, onLoadModels, onSelectProvider) }
|
||||
if (state.isAdmin) {
|
||||
item { ProviderSection(state.provider, onLoadProviders, onLoadModels, onSelectProvider) }
|
||||
} else {
|
||||
item {
|
||||
Text("LLM-Provider: " + (state.provider.active?.let { ProviderPresets.label(it.provider) + " · " + it.model } ?: "–") +
|
||||
"\nNur der Server-Admin kann den Provider ändern.", style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -370,7 +377,7 @@ private fun SessionProviderChip(state: UiState, onPick: (String) -> Unit, initia
|
|||
Box {
|
||||
AssistChip(
|
||||
onClick = { open = true }, label = { Text(label, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.labelSmall) },
|
||||
enabled = state.settings.isConfigured && !state.busy,
|
||||
enabled = state.settings.isConfigured && !state.busy && state.isAdmin,
|
||||
modifier = Modifier.height(28.dp).widthIn(max = 230.dp),
|
||||
)
|
||||
DropdownMenu(expanded = open, onDismissRequest = { open = false }) {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class PreviewScreenshots(private val dark: Boolean) {
|
|||
|
||||
@Test fun chat() = snap("chat") {
|
||||
ChatScreen(
|
||||
state = UiState(
|
||||
state = UiState(isAdmin = true,
|
||||
settings = settings, sessionId = "demo", busy = true,
|
||||
lines = listOf(
|
||||
ChatLine("user", "[DEMO] Wie spät ist es in Berlin, und was steht auf golang.org?"),
|
||||
|
|
@ -68,7 +68,7 @@ class PreviewScreenshots(private val dark: Boolean) {
|
|||
|
||||
@Test fun chatProvider() = snap("chat_provider") {
|
||||
ChatScreen(
|
||||
state = UiState(
|
||||
state = UiState(isAdmin = true,
|
||||
settings = settings, sessionId = "demo", provider = demoProviders,
|
||||
sessionProvider = ActiveProvider("gonkarouter", "MiniMaxAI/MiniMax-M2.7"),
|
||||
lines = listOf(
|
||||
|
|
@ -83,7 +83,7 @@ class PreviewScreenshots(private val dark: Boolean) {
|
|||
|
||||
@Test fun chatMarkdown() = snap("chat_markdown") {
|
||||
ChatScreen(
|
||||
state = UiState(
|
||||
state = UiState(isAdmin = true,
|
||||
settings = settings, sessionId = "demo", online = true,
|
||||
lines = listOf(
|
||||
ChatLine("user", "[DEMO] Wie lese ich in Go eine Datei zeilenweise?"),
|
||||
|
|
@ -96,7 +96,7 @@ class PreviewScreenshots(private val dark: Boolean) {
|
|||
|
||||
@Test fun sessionsSearch() = snap("sessions_search") {
|
||||
SessionsScreen(
|
||||
state = UiState(
|
||||
state = UiState(isAdmin = true,
|
||||
settings = settings,
|
||||
sessions = listOf(
|
||||
Session("1", "[DEMO] Go-Datei zeilenweise lesen", "2026-10-25T03:32:00Z"),
|
||||
|
|
@ -110,7 +110,7 @@ class PreviewScreenshots(private val dark: Boolean) {
|
|||
|
||||
@Test fun sessions() = snap("sessions") {
|
||||
SessionsScreen(
|
||||
state = UiState(
|
||||
state = UiState(isAdmin = true,
|
||||
settings = settings,
|
||||
sessions = listOf(
|
||||
Session("1", "[DEMO] Wie spät ist es in Berlin?", "2026-10-25T03:32:00Z"),
|
||||
|
|
@ -126,14 +126,14 @@ class PreviewScreenshots(private val dark: Boolean) {
|
|||
|
||||
@Test fun settings() = snap("settings") {
|
||||
SettingsScreen(
|
||||
state = UiState(settings = settings), onSave = {}, onTest = { _, _ -> }, onBack = {},
|
||||
state = UiState(isAdmin = true, settings = settings), onSave = {}, onTest = { _, _ -> }, onBack = {},
|
||||
initialStatus = "Verbindung OK · Provider: Dahl (dahl.global) · Modell: MiniMaxAI/MiniMax-M2.7 [DEMO]",
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun providers() = snap("providers") {
|
||||
SettingsScreen(
|
||||
state = UiState(
|
||||
state = UiState(isAdmin = true,
|
||||
settings = settings,
|
||||
provider = ProviderUi(
|
||||
active = ActiveProvider("dahl", "MiniMaxAI/MiniMax-M2.7"),
|
||||
|
|
@ -152,12 +152,12 @@ class PreviewScreenshots(private val dark: Boolean) {
|
|||
}
|
||||
|
||||
@Test fun login() = snap("login") {
|
||||
LoginScreen(state = UiState(settings = ServerSettings("https://demo.byteclaw.example", "", "crypto")), onAuthenticate = { _, _, _, _ -> })
|
||||
LoginScreen(state = UiState(isAdmin = true, settings = ServerSettings("https://demo.byteclaw.example", "", "crypto")), onAuthenticate = { _, _, _, _ -> })
|
||||
}
|
||||
|
||||
@Test fun loginRegister() = snap("login_register") {
|
||||
LoginScreen(
|
||||
state = UiState(settings = ServerSettings("https://demo.byteclaw.example"), authError = "Benutzername ist bereits vergeben"),
|
||||
state = UiState(isAdmin = true, settings = ServerSettings("https://demo.byteclaw.example"), authError = "Benutzername ist bereits vergeben"),
|
||||
onAuthenticate = { _, _, _, _ -> }, initialRegister = true, initialUser = "crypto",
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue