From 63b07db7f0398afe14a1a6d150e57c338cfb06b0 Mon Sep 17 00:00:00 2001 From: Hai Phuc Nguyen <3423575+haiphucnguyen@users.noreply.github.com> Date: Sat, 25 Jul 2026 09:06:54 -0700 Subject: [PATCH] Minor change dialog layouts --- .../io/askimo/ui/chat/MessageComponents.kt | 66 ++- .../kotlin/io/askimo/ui/mcp/McpToolsDialog.kt | 508 ++++++++---------- .../askimo/ui/session/DeleteSessionDialog.kt | 94 ++-- .../askimo/ui/session/ExportSessionDialog.kt | 213 +++----- .../askimo/ui/session/RenameSessionDialog.kt | 8 +- .../io/askimo/core/util/SystemPrompts.kt | 1 - 6 files changed, 388 insertions(+), 502 deletions(-) diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/chat/MessageComponents.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/chat/MessageComponents.kt index e38460a78..53f8b2be8 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/chat/MessageComponents.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/chat/MessageComponents.kt @@ -1107,42 +1107,38 @@ private fun aiMessageBubble( // Run code dialog — outside SelectionContainer val runRequest = pendingRunRequest if (runRequest != null) { - Dialog(onDismissRequest = { pendingRunRequest = null }) { - Surface( - shape = MaterialTheme.shapes.medium, - tonalElevation = 8.dp, - color = MaterialTheme.colorScheme.surface, - ) { - Column( - modifier = Modifier.padding(24.dp).width(360.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - Text(text = stringResource("code.run.dialog.title"), style = MaterialTheme.typography.titleMedium) - Text( - text = stringResource("code.run.dialog.message"), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End), - ) { - secondaryButton(onClick = { - pendingRunRequest = null - EventBus.post(RunCodeEvent(code = runRequest.first, language = runRequest.second, couldExecute = false)) - }) { - Text(stringResource("code.run.dialog.paste")) - } - primaryButton(onClick = { - pendingRunRequest = null - EventBus.post(RunCodeEvent(code = runRequest.first, language = runRequest.second, couldExecute = true)) - }) { - Text(stringResource("code.run.dialog.execute")) - } - } + AppComponents.alertDialog( + onDismissRequest = { pendingRunRequest = null }, + title = { + Text( + text = stringResource("code.run.dialog.title"), + style = MaterialTheme.typography.titleMedium, + ) + }, + text = { + Text( + text = stringResource("code.run.dialog.message"), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + }, + dismissButton = { + secondaryButton(onClick = { + pendingRunRequest = null + EventBus.post(RunCodeEvent(code = runRequest.first, language = runRequest.second, couldExecute = false)) + }) { + Text(stringResource("code.run.dialog.paste")) } - } - } + }, + confirmButton = { + primaryButton(onClick = { + pendingRunRequest = null + EventBus.post(RunCodeEvent(code = runRequest.first, language = runRequest.second, couldExecute = true)) + }) { + Text(stringResource("code.run.dialog.execute")) + } + }, + ) } } diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/mcp/McpToolsDialog.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/mcp/McpToolsDialog.kt index 70fac10a0..90e38a47b 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/mcp/McpToolsDialog.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/mcp/McpToolsDialog.kt @@ -4,22 +4,18 @@ */ package io.askimo.ui.mcp -import androidx.compose.foundation.VerticalScrollbar import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.rememberScrollbarAdapter +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff @@ -80,7 +76,7 @@ fun mcpToolsDialog( var tools by remember { mutableStateOf?>(null) } var isLoading by remember { mutableStateOf(true) } val dialogState = rememberDialogState() - var exportMessage by remember { mutableStateOf?>(null) } // true=success, false=error + var exportMessage by remember { mutableStateOf?>(null) } var searchQuery by remember { mutableStateOf("") } val filteredTools = remember(tools, searchQuery) { @@ -99,9 +95,7 @@ fun mcpToolsDialog( isLoading = true dialogState.clearError() try { - val result = withContext(Dispatchers.IO) { - mcpClientFactory.listTools(instance) - } + val result = withContext(Dispatchers.IO) { mcpClientFactory.listTools(instance) } result.fold( onSuccess = { tools = it }, onFailure = { e -> @@ -124,8 +118,14 @@ fun mcpToolsDialog( } } - AppComponents.alertDialog( + val exportSuccessMsg = stringResource("mcp.tools.dialog.export.success") + val exportFailedMsg = stringResource("mcp.tools.dialog.export.failed") + val exportDialogTitle = stringResource("mcp.tools.dialog.export") + + AppComponents.scaffoldDialog( onDismissRequest = onDismiss, + onCloseRequest = onDismiss, + width = 800.dp, title = { Text( text = stringResource("mcp.tools.dialog.title", instance.name), @@ -134,306 +134,264 @@ fun mcpToolsDialog( color = MaterialTheme.colorScheme.onSurface, ) }, - text = { - Box( + stickyHeader = { + if (!tools.isNullOrEmpty()) { + OutlinedTextField( + value = searchQuery, + onValueChange = { searchQuery = it }, + placeholder = { Text(stringResource("mcp.tools.dialog.search.placeholder")) }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + colors = AppComponents.outlinedTextFieldColors(), + ) + } + }, + actions = { + if (!tools.isNullOrEmpty()) { + secondaryButton( + onClick = { + scope.launch { + exportToolsToJson( + tools = tools!!, + instanceName = instance.name, + dialogTitle = exportDialogTitle, + ).fold( + onSuccess = { path -> + exportMessage = true to exportSuccessMsg.replace("{0}", path) + }, + onFailure = { e -> + exportMessage = false to exportFailedMsg.replace("{0}", e.message ?: "Unknown error") + }, + ) + } + }, + ) { + Text(stringResource("mcp.tools.dialog.export")) + } + Spacer(modifier = Modifier.width(Spacing.small)) + } + primaryButton(onClick = onDismiss) { + Text(stringResource("dialog.close")) + } + }, + ) { + // ── Instance info card ───────────────────────────────────────────── + Card( + modifier = Modifier.fillMaxWidth(), + colors = AppComponents.secondaryCardColors(), + ) { + Column( modifier = Modifier .fillMaxWidth() - .height(400.dp), + .padding(Spacing.medium), + verticalArrangement = Arrangement.spacedBy(Spacing.small), ) { - val scrollState = rememberScrollState() - - Column( - modifier = Modifier - .fillMaxWidth() - .verticalScroll(scrollState) - .padding(end = Spacing.medium), - verticalArrangement = Arrangement.spacedBy(Spacing.medium), + Text( + text = stringResource("mcp.tools.dialog.instance.info"), + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSecondaryContainer, + ) + HorizontalDivider( + color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.2f), + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, ) { - // ── Instance info card ───────────────────────────────── - Card( - modifier = Modifier.fillMaxWidth(), - colors = AppComponents.secondaryCardColors(), - ) { - Column( + Text( + text = stringResource("mcp.instance.field.serverId"), + style = MaterialTheme.typography.bodySmall, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.7f), + ) + SelectionContainer { + Text( + text = instance.serverId, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSecondaryContainer, + ) + } + } + if (instance.parameterValues.isNotEmpty()) { + Text( + text = stringResource("mcp.tools.dialog.parameters"), + style = MaterialTheme.typography.bodySmall, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.7f), + modifier = Modifier.padding(top = Spacing.extraSmall), + ) + instance.parameterValues.forEach { (key, value) -> + val isSecret = SecretDetector.isSecret(key, serverDefinition) + var showSecret by remember(key) { mutableStateOf(false) } + Row( modifier = Modifier .fillMaxWidth() - .padding(Spacing.medium), - verticalArrangement = Arrangement.spacedBy(Spacing.small), + .padding(start = Spacing.small), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, ) { - Text( - text = stringResource("mcp.tools.dialog.instance.info"), - style = MaterialTheme.typography.titleSmall, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.onSecondaryContainer, - ) - HorizontalDivider( - color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.2f), - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - ) { + SelectionContainer { Text( - text = stringResource("mcp.instance.field.serverId"), + text = key, style = MaterialTheme.typography.bodySmall, - fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.7f), + color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.6f), ) - SelectionContainer { + } + Row( + modifier = Modifier.weight(1f, fill = false), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End, + ) { + if (isSecret && showSecret) { + SelectionContainer(modifier = Modifier.weight(1f, fill = false)) { + Text( + text = value, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier.padding(start = Spacing.small), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } else { Text( - text = instance.serverId, + text = if (isSecret) "••••••••" else value, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier + .weight(1f, fill = false) + .padding(start = Spacing.small), + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) } - } - if (instance.parameterValues.isNotEmpty()) { - Text( - text = stringResource("mcp.tools.dialog.parameters"), - style = MaterialTheme.typography.bodySmall, - fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.7f), - modifier = Modifier.padding(top = Spacing.extraSmall), - ) - instance.parameterValues.forEach { (key, value) -> - val isSecret = SecretDetector.isSecret(key, serverDefinition) - var showSecret by remember(key) { mutableStateOf(false) } - Row( + if (isSecret) { + IconButton( + onClick = { showSecret = !showSecret }, modifier = Modifier - .fillMaxWidth() - .padding(start = Spacing.small), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, + .size(28.dp) + .pointerHoverIcon(PointerIcon.Hand), ) { - // Key — fixed, never shrinks - SelectionContainer { - Text( - text = key, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.6f), - ) - } - Row( - modifier = Modifier.weight(1f, fill = false), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End, - ) { - // Value — takes remaining space, truncates if too long - if (isSecret && showSecret) { - SelectionContainer(modifier = Modifier.weight(1f, fill = false)) { - Text( - text = value, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSecondaryContainer, - modifier = Modifier.padding(start = Spacing.small), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - } else { - Text( - text = if (isSecret) "••••••••" else value, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSecondaryContainer, - modifier = Modifier - .weight(1f, fill = false) - .padding(start = Spacing.small), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - // Icon — always reserved space, never squeezed out - if (isSecret) { - IconButton( - onClick = { showSecret = !showSecret }, - modifier = Modifier - .size(28.dp) - .pointerHoverIcon(PointerIcon.Hand), - ) { - Icon( - imageVector = if (showSecret) { - Icons.Default.VisibilityOff - } else { - Icons.Default.Visibility - }, - contentDescription = stringResource( - if (showSecret) { - "mcp.instance.password.hide" - } else { - "mcp.instance.password.show" - }, - ), - modifier = Modifier.size(14.dp), - tint = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.6f), - ) - } - } - } + Icon( + imageVector = if (showSecret) Icons.Default.VisibilityOff else Icons.Default.Visibility, + contentDescription = stringResource( + if (showSecret) "mcp.instance.password.hide" else "mcp.instance.password.show", + ), + modifier = Modifier.size(14.dp), + tint = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.6f), + ) } } } } } + } + } + } - // ── Search field — shown once tools are loaded ───────── - if (!tools.isNullOrEmpty()) { - OutlinedTextField( - value = searchQuery, - onValueChange = { searchQuery = it }, - placeholder = { Text(stringResource("mcp.tools.dialog.search.placeholder")) }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - colors = AppComponents.outlinedTextFieldColors(), - ) - } - - // ── Main content ─────────────────────────────────────── - when { - isLoading -> { - Row( - horizontalArrangement = Arrangement.spacedBy(Spacing.medium), - verticalAlignment = Alignment.CenterVertically, - ) { - CircularProgressIndicator( - modifier = Modifier.size(24.dp), - color = MaterialTheme.colorScheme.onSurface, - ) - Text( - text = stringResource("mcp.tools.dialog.loading"), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } + // ── Main content ─────────────────────────────────────────────────── + when { + isLoading -> { + Row( + horizontalArrangement = Arrangement.spacedBy(Spacing.medium), + verticalAlignment = Alignment.CenterVertically, + ) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + color = MaterialTheme.colorScheme.onSurface, + ) + Text( + text = stringResource("mcp.tools.dialog.loading"), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } - dialogState.errorMessage != null -> { - inlineErrorMessage(errorMessage = dialogState.errorMessage) - } + dialogState.errorMessage != null -> { + inlineErrorMessage(errorMessage = dialogState.errorMessage) + } - tools.isNullOrEmpty() -> { - Text( - text = stringResource("mcp.tools.dialog.empty"), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } + tools.isNullOrEmpty() -> { + Text( + text = stringResource("mcp.tools.dialog.empty"), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } - else -> { - Text( - text = if (searchQuery.isBlank()) { - stringResource("mcp.tools.dialog.count", tools!!.size) - } else { - stringResource("mcp.tools.dialog.search.count", filteredTools!!.size, tools!!.size) - }, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) + else -> { + Text( + text = if (searchQuery.isBlank()) { + stringResource("mcp.tools.dialog.count", tools!!.size) + } else { + stringResource("mcp.tools.dialog.search.count", filteredTools!!.size, tools!!.size) + }, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) - if (filteredTools.isNullOrEmpty()) { - Text( - text = stringResource("mcp.tools.dialog.search.empty"), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } else { - filteredTools.forEach { tool -> - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant, - ), - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(Spacing.medium), - verticalArrangement = Arrangement.spacedBy(Spacing.small), - ) { - SelectionContainer { - Text( - text = tool.specification.name(), - style = MaterialTheme.typography.bodyLarge, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.onSurface, - ) - } - tool.specification.description()?.let { desc -> - SelectionContainer { - Text( - text = desc, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } - Row( - horizontalArrangement = Arrangement.spacedBy(Spacing.small), - verticalAlignment = Alignment.CenterVertically, - ) { - toolCategoryChip(tool.category) - toolStrategyChip(tool.strategy) - } - } + if (filteredTools.isNullOrEmpty()) { + Text( + text = stringResource("mcp.tools.dialog.search.empty"), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } else { + filteredTools.forEach { tool -> + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + ), + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(Spacing.medium), + verticalArrangement = Arrangement.spacedBy(Spacing.small), + ) { + SelectionContainer { + Text( + text = tool.specification.name(), + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, + ) + } + tool.specification.description()?.let { desc -> + SelectionContainer { + Text( + text = desc, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) } } + Row( + horizontalArrangement = Arrangement.spacedBy(Spacing.small), + verticalAlignment = Alignment.CenterVertically, + ) { + toolCategoryChip(tool.category) + toolStrategyChip(tool.strategy) + } } } } - - // Export feedback — shown below tools, never replaces them - exportMessage?.let { (isSuccess, msg) -> - if (isSuccess) { - inlineSuccessMessage(message = msg) - } else { - inlineErrorMessage(errorMessage = msg) - } - } } - - VerticalScrollbar( - adapter = rememberScrollbarAdapter(scrollState), - modifier = Modifier - .align(Alignment.CenterEnd) - .fillMaxHeight(), - style = AppComponents.scrollbarStyle(), - ) } - }, - confirmButton = { - val exportSuccessMsg = stringResource("mcp.tools.dialog.export.success") - val exportFailedMsg = stringResource("mcp.tools.dialog.export.failed") - val exportDialogTitle = stringResource("mcp.tools.dialog.export") + } - Row(horizontalArrangement = Arrangement.spacedBy(Spacing.small)) { - // Export button — only enabled when tools are loaded - if (!tools.isNullOrEmpty()) { - secondaryButton( - onClick = { - scope.launch { - exportToolsToJson( - tools = tools!!, - instanceName = instance.name, - dialogTitle = exportDialogTitle, - ).fold( - onSuccess = { path -> - exportMessage = true to exportSuccessMsg.replace("{0}", path) - }, - onFailure = { e -> - exportMessage = false to exportFailedMsg.replace("{0}", e.message ?: "Unknown error") - }, - ) - } - }, - ) { - Text(stringResource("mcp.tools.dialog.export")) - } - } - primaryButton(onClick = onDismiss) { - Text(stringResource("dialog.close")) - } + // Export feedback + exportMessage?.let { (isSuccess, msg) -> + if (isSuccess) { + inlineSuccessMessage(message = msg) + } else { + inlineErrorMessage(errorMessage = msg) } - }, - ) + } + } } /** diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/session/DeleteSessionDialog.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/session/DeleteSessionDialog.kt index 2e5deda08..ec60e70fd 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/session/DeleteSessionDialog.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/session/DeleteSessionDialog.kt @@ -5,27 +5,21 @@ package io.askimo.ui.session import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog import io.askimo.ui.common.components.dangerButton import io.askimo.ui.common.components.secondaryButton import io.askimo.ui.common.i18n.stringResource +import io.askimo.ui.common.theme.AppComponents import io.askimo.ui.common.theme.Spacing /** @@ -40,62 +34,40 @@ fun deleteSessionDialog( onConfirm: () -> Unit, onDismiss: () -> Unit, ) { - Dialog(onDismissRequest = onDismiss) { - Surface( - modifier = Modifier.width(450.dp), - shape = MaterialTheme.shapes.large, - tonalElevation = 8.dp, - ) { - Column( - modifier = Modifier.padding(Spacing.extraLarge), - verticalArrangement = Arrangement.spacedBy(Spacing.large), + AppComponents.alertDialog( + onDismissRequest = onDismiss, + title = { + Row( + horizontalArrangement = Arrangement.spacedBy(Spacing.medium), + verticalAlignment = Alignment.CenterVertically, ) { - // Title with warning icon - Row( - horizontalArrangement = Arrangement.spacedBy(Spacing.medium), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = null, - tint = MaterialTheme.colorScheme.error, - modifier = Modifier.size(32.dp), - ) - Text( - text = stringResource("session.delete.confirm.title"), - style = MaterialTheme.typography.headlineSmall, - color = MaterialTheme.colorScheme.onSurface, - ) - } - - // Confirmation message + Icon( + imageVector = Icons.Default.Delete, + contentDescription = null, + tint = MaterialTheme.colorScheme.error, + modifier = Modifier.size(28.dp), + ) Text( - text = stringResource("session.delete.confirm.message", sessionTitle), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, + text = stringResource("session.delete.confirm.title"), + style = MaterialTheme.typography.headlineSmall, ) - - // Action buttons - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ) { - secondaryButton( - onClick = onDismiss, - ) { - Text(stringResource("session.delete.confirm.cancel")) - } - - Spacer(modifier = Modifier.width(Spacing.small)) - - dangerButton( - onClick = onConfirm, - ) { - Text(stringResource("session.delete.confirm.button")) - } - } } - } - } + }, + text = { + Text( + text = stringResource("session.delete.confirm.message", sessionTitle), + style = MaterialTheme.typography.bodyMedium, + ) + }, + dismissButton = { + secondaryButton(onClick = onDismiss) { + Text(stringResource("session.delete.confirm.cancel")) + } + }, + confirmButton = { + dangerButton(onClick = onConfirm) { + Text(stringResource("session.delete.confirm.button")) + } + }, + ) } diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/session/ExportSessionDialog.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/session/ExportSessionDialog.kt index 389347cd8..e656b681e 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/session/ExportSessionDialog.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/session/ExportSessionDialog.kt @@ -12,13 +12,10 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Code @@ -31,7 +28,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.PlainTooltip -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox @@ -50,8 +46,6 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties import io.askimo.ui.common.components.primaryButton import io.askimo.ui.common.components.secondaryButton import io.askimo.ui.common.export.ExportFormat @@ -93,7 +87,6 @@ fun exportSessionDialog( val homeDir = System.getProperty("user.home") filePath = "$homeDir/$defaultFilename" } else { - // Update extension only val directory = filePath.substringBeforeLast('/') val baseFilename = filePath.substringAfterLast('/').substringBeforeLast('.') filePath = "$directory/$baseFilename.${selectedFormat.extension}" @@ -115,137 +108,101 @@ fun exportSessionDialog( showFileBrowser = false } } - Dialog( - onDismissRequest = onDismiss, - properties = DialogProperties(usePlatformDefaultWidth = false), - ) { - Surface( - modifier = Modifier - .width(600.dp) - .padding(Spacing.large), - shape = MaterialTheme.shapes.large, - tonalElevation = 8.dp, - ) { - Column( - modifier = Modifier - .padding(Spacing.extraLarge) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(Spacing.large), - ) { - Column( - verticalArrangement = Arrangement.spacedBy(Spacing.extraSmall), - ) { - Text( - text = stringResource("session.export.title"), - style = MaterialTheme.typography.headlineSmall, - color = MaterialTheme.colorScheme.onSurface, - ) - Text( - text = sessionTitle, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.fillMaxWidth(), - ) - } - - // Description - Text( - text = stringResource("session.export.description"), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - // Format selection title + AppComponents.scaffoldDialog( + onDismissRequest = onDismiss, + width = 700.dp, + title = { + Column(verticalArrangement = Arrangement.spacedBy(Spacing.extraSmall)) { Text( - text = stringResource("session.export.select.format"), - style = MaterialTheme.typography.titleMedium, + text = stringResource("session.export.title"), + style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onSurface, ) - - // Markdown format card - formatCard( - format = ExportFormat.MARKDOWN, - isSelected = selectedFormat == ExportFormat.MARKDOWN, - icon = Icons.Default.Description, - onClick = { onFormatChange(ExportFormat.MARKDOWN) }, - ) - - // JSON format card - formatCard( - format = ExportFormat.JSON, - isSelected = selectedFormat == ExportFormat.JSON, - icon = Icons.Default.Code, - onClick = { onFormatChange(ExportFormat.JSON) }, - ) - - // HTML format card - formatCard( - format = ExportFormat.HTML, - isSelected = selectedFormat == ExportFormat.HTML, - icon = Icons.Default.Language, - onClick = { onFormatChange(ExportFormat.HTML) }, - ) - - Spacer(modifier = Modifier.height(Spacing.extraSmall)) - - // File path selection Text( - text = stringResource("session.export.location"), - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSurface, - ) - - Row( + text = sessionTitle, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(Spacing.small), - verticalAlignment = Alignment.CenterVertically, - ) { - OutlinedTextField( - value = filePath, - onValueChange = { filePath = it }, - label = { Text(stringResource("session.export.file.path")) }, - modifier = Modifier.weight(1f), - singleLine = true, - colors = AppComponents.outlinedTextFieldColors(), - ) + ) + } + }, + actions = { + secondaryButton(onClick = onDismiss) { + Text(stringResource("action.cancel")) + } + Spacer(modifier = Modifier.width(Spacing.small)) + primaryButton( + onClick = { if (filePath.isNotBlank()) onExport(filePath) }, + enabled = filePath.isNotBlank(), + ) { + Text(stringResource("session.export.button.export")) + } + }, + ) { + // Description + Text( + text = stringResource("session.export.description"), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) - IconButton( - onClick = { showFileBrowser = true }, - modifier = Modifier.pointerHoverIcon(PointerIcon.Hand), - ) { - Icon( - imageVector = Icons.Default.FolderOpen, - contentDescription = stringResource("session.export.browse"), - tint = MaterialTheme.colorScheme.onSurface, - ) - } - } + // Format selection title + Text( + text = stringResource("session.export.select.format"), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + ) - Spacer(modifier = Modifier.height(Spacing.small)) + // Format cards + formatCard( + format = ExportFormat.MARKDOWN, + isSelected = selectedFormat == ExportFormat.MARKDOWN, + icon = Icons.Default.Description, + onClick = { onFormatChange(ExportFormat.MARKDOWN) }, + ) + formatCard( + format = ExportFormat.JSON, + isSelected = selectedFormat == ExportFormat.JSON, + icon = Icons.Default.Code, + onClick = { onFormatChange(ExportFormat.JSON) }, + ) + formatCard( + format = ExportFormat.HTML, + isSelected = selectedFormat == ExportFormat.HTML, + icon = Icons.Default.Language, + onClick = { onFormatChange(ExportFormat.HTML) }, + ) - // Action buttons - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(Spacing.small, Alignment.End), - verticalAlignment = Alignment.CenterVertically, - ) { - secondaryButton( - onClick = onDismiss, - ) { - Text(stringResource("action.cancel")) - } + // File path section + Text( + text = stringResource("session.export.location"), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + ) - primaryButton( - onClick = { - if (filePath.isNotBlank()) { - onExport(filePath) - } - }, - enabled = filePath.isNotBlank(), - ) { - Text(stringResource("session.export.button.export")) - } - } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(Spacing.small), + verticalAlignment = Alignment.CenterVertically, + ) { + OutlinedTextField( + value = filePath, + onValueChange = { filePath = it }, + label = { Text(stringResource("session.export.file.path")) }, + modifier = Modifier.weight(1f), + singleLine = true, + colors = AppComponents.outlinedTextFieldColors(), + ) + IconButton( + onClick = { showFileBrowser = true }, + modifier = Modifier.pointerHoverIcon(PointerIcon.Hand), + ) { + Icon( + imageVector = Icons.Default.FolderOpen, + contentDescription = stringResource("session.export.browse"), + tint = MaterialTheme.colorScheme.onSurface, + ) } } } diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/session/RenameSessionDialog.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/session/RenameSessionDialog.kt index 6e8a6f2b1..6d0053d2a 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/session/RenameSessionDialog.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/session/RenameSessionDialog.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import io.askimo.ui.common.components.primaryButton import io.askimo.ui.common.components.secondaryButton import io.askimo.ui.common.i18n.stringResource @@ -70,10 +71,13 @@ fun renameSessionDialog( focusRequester.requestFocus() } - Dialog(onDismissRequest = onDismiss) { + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false), + ) { Surface( modifier = Modifier - .width(500.dp) + .width(700.dp) .padding(Spacing.large), shape = MaterialTheme.shapes.large, tonalElevation = 8.dp, diff --git a/shared/src/main/kotlin/io/askimo/core/util/SystemPrompts.kt b/shared/src/main/kotlin/io/askimo/core/util/SystemPrompts.kt index ffc4082fa..f6b2ff498 100644 --- a/shared/src/main/kotlin/io/askimo/core/util/SystemPrompts.kt +++ b/shared/src/main/kotlin/io/askimo/core/util/SystemPrompts.kt @@ -8,7 +8,6 @@ object SystemPrompts { val markdownDefaults = listOf( "By default, always respond in GitHub-Flavored Markdown.", - "If you are uncertain or lack reliable information about something, acknowledge it openly and qualify your answer — do not present guesses as facts.", ) fun systemMessage(vararg extras: String) = (markdownDefaults + extras).joinToString("\n\n")