Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -760,21 +760,24 @@ class ChatSessionServiceIT {
fun `forkSession should not copy outdated messages`() {
val baseTime = Instant.now()
val session = sessionRepository.createSession(ChatSession(id = "", title = "Outdated Test"))
service.addMessage(ChatMessage(id = "", sessionId = session.id, role = MessageRole.USER, content = "Q1", createdAt = baseTime))
val outdatedAi = service.addMessage(ChatMessage(id = "", sessionId = session.id, role = MessageRole.ASSISTANT, content = "Old answer", createdAt = baseTime.plusSeconds(1)))
// Mark the outdated branch
service.markMessagesAsOutdatedAfter(session.id, outdatedAi.id)
messageRepository.markMessageAsOutdated(outdatedAi.id)
// New active branch
// Simulate a user edit: Q1 is sent, AI replies "Old answer", user edits Q1 which
// marks Q1 and everything after it (including "Old answer") as outdated via greaterEq.
val q1 = service.addMessage(ChatMessage(id = "", sessionId = session.id, role = MessageRole.USER, content = "Q1", createdAt = baseTime))
service.addMessage(ChatMessage(id = "", sessionId = session.id, role = MessageRole.ASSISTANT, content = "Old answer", createdAt = baseTime.plusSeconds(1)))
// Mark the outdated branch: calling with q1.id marks Q1 (createdAt = baseTime) AND
// "Old answer" (createdAt > baseTime) as outdated — matching the real edit flow.
service.markMessagesAsOutdatedAfter(session.id, q1.id)
// New active branch after the edit
service.addMessage(ChatMessage(id = "", sessionId = session.id, role = MessageRole.USER, content = "Q2", createdAt = baseTime.plusSeconds(2)))
val activeAi = service.addMessage(ChatMessage(id = "", sessionId = session.id, role = MessageRole.ASSISTANT, content = "New answer", createdAt = baseTime.plusSeconds(3)))

val forked = service.forkSession(session.id, activeAi.id)
val forkedMessages = service.getMessages(forked.id)

// Only the two active messages should be copied
// Only the two active messages should be copied (Q1 and "Old answer" are both outdated)
assertEquals(2, forkedMessages.size)
assertFalse(forkedMessages.any { it.content == "Old answer" })
assertFalse(forkedMessages.any { it.content == "Q1" })
assertTrue(forkedMessages.any { it.content == "Q2" })
assertTrue(forkedMessages.any { it.content == "New answer" })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ private fun statCardsSection(
statCard(
label = stringResource("discover.stat.chats"),
value = totalChats?.let { LocalizationManager.formatNumber(it) } ?: "—",
icon = { Icon(Icons.Default.ChatBubbleOutline, contentDescription = null, modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer) },
icon = { Icon(Icons.Default.ChatBubbleOutline, contentDescription = null, modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onSecondaryContainer) },
onClick = onNavigateToSessions,
modifier = Modifier.weight(1f),
)
Expand All @@ -286,7 +286,7 @@ private fun statCardsSection(
statCard(
label = stringResource("discover.stat.projects"),
value = totalProjects?.let { LocalizationManager.formatNumber(it) } ?: "—",
icon = { Icon(Icons.Default.FolderOpen, contentDescription = null, modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer) },
icon = { Icon(Icons.Default.FolderOpen, contentDescription = null, modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onSecondaryContainer) },
onClick = onNavigateToProjects,
modifier = Modifier.weight(1f),
)
Expand All @@ -296,7 +296,7 @@ private fun statCardsSection(
statCard(
label = stringResource("discover.stat.mcp"),
value = LocalizationManager.formatNumber(totalMcpServers),
icon = { Icon(Icons.Default.Settings, contentDescription = null, modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer) },
icon = { Icon(Icons.Default.Settings, contentDescription = null, modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onSecondaryContainer) },
onClick = onNavigateToMcpSettings,
modifier = Modifier.weight(1f),
)
Expand All @@ -306,7 +306,7 @@ private fun statCardsSection(
statCard(
label = stringResource("discover.stat.plans"),
value = totalPlans?.let { LocalizationManager.formatNumber(it) } ?: "—",
icon = { Icon(Icons.Default.PlayCircle, contentDescription = null, modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer) },
icon = { Icon(Icons.Default.PlayCircle, contentDescription = null, modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onSecondaryContainer) },
onClick = onNavigateToPlans,
modifier = Modifier.weight(1f),
)
Expand All @@ -316,7 +316,7 @@ private fun statCardsSection(
statCard(
label = stringResource("discover.stat.skills"),
value = totalSkills?.let { LocalizationManager.formatNumber(it) } ?: "—",
icon = { Icon(Icons.Default.Extension, contentDescription = null, modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer) },
icon = { Icon(Icons.Default.Extension, contentDescription = null, modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onSecondaryContainer) },
onClick = onNavigateToSkills,
modifier = Modifier.weight(1f),
)
Expand All @@ -332,7 +332,7 @@ private fun statCard(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
) {
clickableCard(onClick = onClick, modifier = modifier, colors = AppComponents.primaryCardColors()) {
clickableCard(onClick = onClick, modifier = modifier, colors = AppComponents.secondaryCardColors()) {
Column(
modifier = Modifier.fillMaxWidth().padding(Spacing.large),
verticalArrangement = Arrangement.spacedBy(Spacing.medium),
Expand All @@ -342,12 +342,12 @@ private fun statCard(
text = value,
style = MaterialTheme.typography.headlineLarge,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onPrimaryContainer,
color = MaterialTheme.colorScheme.onSecondaryContainer,
)
Text(
text = label,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.82f),
color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.82f),
)
}
}
Expand Down Expand Up @@ -573,7 +573,7 @@ private fun exploreFeaturesSection() {
) {
enabledCards.forEach { card ->
exploreCard(
icon = { Icon(card.icon, contentDescription = null, modifier = Modifier.size(22.dp), tint = MaterialTheme.colorScheme.onSecondaryContainer) },
icon = { Icon(card.icon, contentDescription = null, modifier = Modifier.size(22.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant) },
title = stringResource(card.titleKey),
description = stringResource(card.descKey),
url = card.url,
Expand All @@ -595,7 +595,7 @@ private fun exploreCard(
clickableCard(
onClick = { runCatching { Desktop.getDesktop().browse(URI(url)) } },
modifier = modifier,
colors = AppComponents.secondaryCardColors(),
colors = AppComponents.surfaceVariantCardColors(),
) {
Column(
modifier = Modifier.fillMaxWidth().fillMaxHeight().padding(Spacing.large),
Expand All @@ -607,10 +607,10 @@ private fun exploreCard(
verticalAlignment = Alignment.Top,
) {
icon()
Icon(Icons.AutoMirrored.Filled.ArrowForward, contentDescription = null, modifier = Modifier.size(14.dp), tint = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.5f))
Icon(Icons.AutoMirrored.Filled.ArrowForward, contentDescription = null, modifier = Modifier.size(14.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f))
}
Text(text = title, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSecondaryContainer)
Text(text = description, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.82f))
Text(text = title, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurfaceVariant)
Text(text = description, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.82f))
}
}
}
Expand Down
40 changes: 11 additions & 29 deletions desktop-shared/src/main/kotlin/io/askimo/ui/shell/NativeMenuBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,13 @@ object NativeMenuBar {
isProjectsVisible: Boolean = true,
onShowSystemDiagnostics: () -> Unit = {},
onNavigateToBookmarks: () -> Unit,
onSupportAskimo: () -> Unit = {},
onShareFeedback: () -> Unit = {},
) {
val window = frameWindowScope.window

// Setup AWT menu bar for all platforms (includes Documentation)
setupAWTMenuBar(window, onShowAbout, onNewChat, onNewProject, onSearchInSessions, onShowSettings, onShowEventLog, onCheckForUpdates, onEnterFullScreen, onNavigateToSessions, onNavigateToProjects, onNavigateToDiscover, onToggleSidebar, onInvalidateCaches, onExportBackup, onImportBackup, onShowGettingStarted, onOpenTerminal, onClearPreferences, onClearAccountPreferences, onTogglePlans, onToggleSkills, onToggleProjects, isPlansVisible, isSkillsVisible, isProjectsVisible, onShowSystemDiagnostics, onNavigateToBookmarks)
setupAWTMenuBar(window, onShowAbout, onNewChat, onNewProject, onSearchInSessions, onShowSettings, onShowEventLog, onCheckForUpdates, onEnterFullScreen, onNavigateToSessions, onNavigateToProjects, onNavigateToDiscover, onToggleSidebar, onInvalidateCaches, onExportBackup, onImportBackup, onShowGettingStarted, onOpenTerminal, onClearPreferences, onClearAccountPreferences, onTogglePlans, onToggleSkills, onToggleProjects, isPlansVisible, isSkillsVisible, isProjectsVisible, onShowSystemDiagnostics, onNavigateToBookmarks, onSupportAskimo, onShareFeedback)

// On macOS, also register the About handler for the app menu
if (Platform.isMac) {
Expand Down Expand Up @@ -147,6 +149,8 @@ object NativeMenuBar {
isProjectsVisible: Boolean,
onShowSystemDiagnostics: () -> Unit,
onNavigateToBookmarks: () -> Unit,
onSupportAskimo: () -> Unit,
onShareFeedback: () -> Unit,
) {
if (window is Frame) {
val menuBar = MenuBar()
Expand Down Expand Up @@ -395,14 +399,10 @@ object NativeMenuBar {
}
helpMenu.add(gettingStartedItem)

// Share Feedback — moved here from the footer status bar for better discoverability
// Share Feedback — opens the inline feedback dialog
val shareFeedbackItem = MenuItem(menuLabel("system.share.feedback"))
shareFeedbackItem.addActionListener {
runCatching {
if (Desktop.isDesktopSupported()) {
Desktop.getDesktop().browse(URI("https://$DOMAIN/contact/"))
}
}
onShareFeedback()
}
helpMenu.add(shareFeedbackItem)

Expand All @@ -420,18 +420,6 @@ object NativeMenuBar {
}
helpMenu.add(releaseNotesItem)

// Star on GitHub
val starGitHubItem = MenuItem(menuLabel("menu.help.star.github"))
starGitHubItem.addActionListener {
runCatching {
if (Desktop.isDesktopSupported()) {
Desktop.getDesktop()
.browse(URI("https://github.com/askimo-ai/askimo"))
}
}
}
helpMenu.add(starGitHubItem)

// Join Discord Community
val discordItem = MenuItem(menuLabel("menu.help.discord"))
discordItem.addActionListener {
Expand All @@ -441,16 +429,10 @@ object NativeMenuBar {
}
helpMenu.add(discordItem)

// Share Askimo submenu
val shareMenu = Menu(menuLabel("menu.help.share"))

ShareTarget.entries.forEach { target ->
val item = MenuItem(ShareUtils.labelFor(target))
item.addActionListener { ShareUtils.share(target) }
shareMenu.add(item)
}

helpMenu.add(shareMenu)
// Support Askimo — opens the star/share prompt (star + share in one place)
val supportAskimoItem = MenuItem(menuLabel("menu.help.support.askimo"))
supportAskimoItem.addActionListener { onSupportAskimo() }
helpMenu.add(supportAskimoItem)

helpMenu.addSeparator()

Expand Down
Loading