From 54a252394e88808ae4509373950af6fe37430090 Mon Sep 17 00:00:00 2001 From: Hai Nguyen <3423575+haiphucnguyen@users.noreply.github.com> Date: Tue, 26 May 2026 11:39:59 -0700 Subject: [PATCH] refactor(core): replace hardcoded domain URLs with AppConstants - add AppConstants.kt exposing DOMAIN constant used through app - update all UI, config, analytics, and tools to reference DOMAIN instead of literal host - adjust analytics event definitions and error sanitization logic for improved consistency - rewrite request URLs in UpdateChecker, WebSearchTools, and other network modules to use $DOMAIN - ensure all imports of AppConstants added across changed files - verify build succeeds against updated API surface --- .../io/askimo/ui/chat/ChatInputField.kt | 28 +++++---- .../main/kotlin/io/askimo/ui/chat/ChatView.kt | 59 ++++++++++--------- .../io/askimo/ui/chat/MessageComponents.kt | 26 ++++---- .../io/askimo/ui/discover/DiscoverView.kt | 9 +-- .../ui/onboarding/OnboardingWizardDialog.kt | 9 +-- .../io/askimo/ui/plan/PlanDetailView.kt | 22 +++---- .../io/askimo/ui/plan/PlansGalleryView.kt | 3 +- .../ui/settings/SkillsSettingsSection.kt | 3 +- .../io/askimo/ui/shell/NativeMenuBar.kt | 5 +- .../kotlin/io/askimo/ui/shell/ShareUtils.kt | 3 +- .../io/askimo/ui/shell/UpdateViewModel.kt | 3 +- .../io/askimo/ui/skills/SkillExecutorView.kt | 3 +- .../io/askimo/ui/skills/SkillsGalleryView.kt | 3 +- .../src/main/kotlin/io/askimo/desktop/Main.kt | 3 +- .../io/askimo/desktop/plan/PlanEditorView.kt | 3 +- .../io/askimo/desktop/project/ProjectView.kt | 3 +- .../settings/AIProviderSettingsSection.kt | 3 +- .../io/askimo/desktop/settings/AboutDialog.kt | 5 +- .../desktop/settings/AboutSettingsSection.kt | 5 +- .../settings/McpServerManagementSection.kt | 3 +- .../settings/NetworkSettingsSection.kt | 3 +- .../settings/ProviderSelectionDialog.kt | 5 +- .../io/askimo/desktop/shell/FooterBar.kt | 3 +- .../kotlin/io/askimo/core/AppConstants.kt | 9 +++ .../askimo/core/analytics/AnalyticsEvent.kt | 20 +------ .../core/chat/domain/KnowledgeSourceConfig.kt | 2 +- .../core/chat/util/UrlContentExtractor.kt | 3 +- .../kotlin/io/askimo/core/config/AppConfig.kt | 3 +- .../io/askimo/core/service/UpdateChecker.kt | 5 +- .../telemetry/TelemetryChatModelListener.kt | 42 ++++++++++--- .../io/askimo/tools/web/WebSearchTools.kt | 3 +- 31 files changed, 174 insertions(+), 125 deletions(-) create mode 100644 shared/src/main/kotlin/io/askimo/core/AppConstants.kt diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatInputField.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatInputField.kt index b9becc40..be6f20be 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatInputField.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatInputField.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState 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.automirrored.filled.Send @@ -70,6 +71,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow @@ -101,6 +103,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.java.KoinJavaComponent import java.awt.Cursor +import java.io.File import java.time.Instant import java.util.UUID import kotlin.collections.minus @@ -240,7 +243,7 @@ fun chatInputField( if (paths.isNotEmpty()) { try { val maxFileSizeBytes = AppConfig.indexing.maxFileBytes - val files = paths.map { java.io.File(it) } + val files = paths.map { File(it) } val invalidFiles = files.filter { it.length() > maxFileSizeBytes } if (invalidFiles.isNotEmpty()) { @@ -1105,7 +1108,7 @@ private fun fileAttachmentItem( isLoadingPreview = true previewContent = withContext(Dispatchers.IO) { try { - val file = attachment.filePath?.let { java.io.File(it) } + val file = attachment.filePath?.let { File(it) } if (file != null && file.exists() && file.length() < 512 * 1024) { // Read up to 200 lines for preview file.bufferedReader().use { reader -> @@ -1233,15 +1236,18 @@ private fun fileAttachmentItem( previewContent != null -> { val scrollState = rememberScrollState() - androidx.compose.foundation.text.selection.SelectionContainer { - Text( - text = previewContent!!, - style = MaterialTheme.typography.labelSmall.copy( - fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace, - ), - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.verticalScroll(scrollState), - ) + Box( + modifier = Modifier.verticalScroll(scrollState), + ) { + SelectionContainer { + Text( + text = previewContent!!, + style = MaterialTheme.typography.labelSmall.copy( + fontFamily = FontFamily.Monospace, + ), + color = MaterialTheme.colorScheme.onSurface, + ) + } } } } diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatView.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatView.kt index 3de5ddc5..425f965a 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatView.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/chat/ChatView.kt @@ -72,6 +72,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.chat.domain.ChatDirective import io.askimo.core.chat.domain.Project import io.askimo.core.chat.dto.ChatMessageDTO @@ -755,7 +756,7 @@ fun chatView( } }, onClick = { - uriHandler.openUri("https://askimo.chat/docs/desktop/directives/") + uriHandler.openUri("https://$DOMAIN/docs/desktop/directives/") directiveDropdownExpanded = false }, modifier = Modifier.pointerHoverIcon(PointerIcon.Hand), @@ -1147,33 +1148,35 @@ fun chatView( } // end centered Box } // End of main chat Column - // Project Side Panel (right side) - projectSidePanelSlot( - project, - ragIndexingStatus, - ragIndexingPercentage, - sidePanelExpanded, - { sidePanelExpanded = it }, - { filePaths -> - val newAttachments = filePaths.map { path -> - val file = File(path) - FileAttachmentDTO( - id = randomUUID().toString(), - messageId = "", - sessionId = sessionId ?: "", - fileName = file.name, - mimeType = file.extension, - size = file.length(), - createdAt = Instant.now(), - content = null, - filePath = file.absolutePath, - ) - } - val existingPaths = attachments.mapNotNull { it.filePath }.toSet() - val toAdd = newAttachments.filter { it.filePath !in existingPaths } - if (toAdd.isNotEmpty()) attachments = attachments + toAdd - }, - ) + // Project Side Panel (right side) — only shown when session belongs to a project + if (project != null) { + projectSidePanelSlot( + project, + ragIndexingStatus, + ragIndexingPercentage, + sidePanelExpanded, + { sidePanelExpanded = it }, + { filePaths -> + val newAttachments = filePaths.map { path -> + val file = File(path) + FileAttachmentDTO( + id = randomUUID().toString(), + messageId = "", + sessionId = sessionId ?: "", + fileName = file.name, + mimeType = file.extension, + size = file.length(), + createdAt = Instant.now(), + content = null, + filePath = file.absolutePath, + ) + } + val existingPaths = attachments.mapNotNull { it.filePath }.toSet() + val toAdd = newAttachments.filter { it.filePath !in existingPaths } + if (toAdd.isNotEmpty()) attachments = attachments + toAdd + }, + ) + } } // End of Row // AI Message Edit Dialog 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 ddbbd9f5..4b075232 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 @@ -591,29 +591,29 @@ private fun aiMessageBubble( } } - SelectionContainer { - if (searchQuery.isNotBlank()) { + if (searchQuery.isNotBlank()) { + SelectionContainer { Text( text = highlightSearchText( text = markdownToPlainText(message.content), query = searchQuery, - highlightColor = Color(0xFFFFD54F), // amber-300 — visible on any bg + highlightColor = Color(0xFFFFD54F), isActiveResult = isActiveSearchResult, - activeHighlightColor = Color(0xFFFF8F00), // amber-800 — bold active match + activeHighlightColor = Color(0xFFFF8F00), ), modifier = Modifier.padding(start = 12.dp, end = 48.dp, top = 12.dp, bottom = 12.dp), style = MaterialTheme.typography.bodyMedium, ) - } else { - markdownText( - markdown = message.content, - modifier = Modifier.padding(start = 12.dp, end = 48.dp, top = 12.dp, bottom = 12.dp), - viewportTopY = viewportTopY, - isStreaming = isStreaming, - onRunRequest = { cmd, lang -> pendingRunRequest = Pair(cmd, lang) }, - messageId = message.id, - ) } + } else { + markdownText( + markdown = message.content, + modifier = Modifier.padding(start = 12.dp, end = 48.dp, top = 12.dp, bottom = 12.dp), + viewportTopY = viewportTopY, + isStreaming = isStreaming, + onRunRequest = { cmd, lang -> pendingRunRequest = Pair(cmd, lang) }, + messageId = message.id, + ) } if (isOutdatedMessage) { diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/discover/DiscoverView.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/discover/DiscoverView.kt index 407685a8..d7f5f41a 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/discover/DiscoverView.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/discover/DiscoverView.kt @@ -54,6 +54,7 @@ import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.chat.domain.ChatSession import io.askimo.core.chat.repository.ChatSessionRepository import io.askimo.core.chat.repository.ProjectRepository @@ -319,28 +320,28 @@ private fun exploreFeaturesSection() { icon = { Icon(Icons.Default.Extension, contentDescription = null, modifier = Modifier.size(22.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant) }, title = stringResource("discover.explore.mcp.title"), description = stringResource("discover.explore.mcp.desc"), - url = "https://askimo.chat/docs/desktop/mcp-integration/", + url = "https://$DOMAIN/docs/desktop/mcp-integration/", modifier = Modifier.weight(1f), ) exploreCard( icon = { Icon(Icons.AutoMirrored.Filled.LibraryBooks, contentDescription = null, modifier = Modifier.size(22.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant) }, title = stringResource("discover.explore.rag.title"), description = stringResource("discover.explore.rag.desc"), - url = "https://askimo.chat/docs/desktop/rag/", + url = "https://$DOMAIN/docs/desktop/rag/", modifier = Modifier.weight(1f), ) exploreCard( icon = { Icon(Icons.Default.PlayCircle, contentDescription = null, modifier = Modifier.size(22.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant) }, title = stringResource("discover.explore.plans.title"), description = stringResource("discover.explore.plans.desc"), - url = "https://askimo.chat/docs/desktop/plans/", + url = "https://$DOMAIN/docs/desktop/plans/", modifier = Modifier.weight(1f), ) exploreCard( icon = { Icon(Icons.Default.Extension, contentDescription = null, modifier = Modifier.size(22.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant) }, title = stringResource("discover.explore.skills.title"), description = stringResource("discover.explore.skills.desc"), - url = "https://askimo.chat/docs/desktop/skills/", + url = "https://$DOMAIN/docs/desktop/skills/", modifier = Modifier.weight(1f), ) } diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/onboarding/OnboardingWizardDialog.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/onboarding/OnboardingWizardDialog.kt index 7a9381f0..b0de513b 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/onboarding/OnboardingWizardDialog.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/onboarding/OnboardingWizardDialog.kt @@ -59,6 +59,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.i18n.LocalizationManager import io.askimo.ui.common.components.primaryButton import io.askimo.ui.common.components.secondaryButton @@ -490,7 +491,7 @@ private fun onboardingStepAnalytics( } TextButton( - onClick = { uriHandler.openUri("https://askimo.chat/security/") }, + onClick = { uriHandler.openUri("https://$DOMAIN/security/") }, modifier = Modifier.pointerHoverIcon(PointerIcon.Hand), ) { Text( @@ -650,7 +651,7 @@ private fun onboardingStepDirectives() { onboardingDocLink( label = stringResource("onboarding.step.directives.link"), - url = "https://askimo.chat/docs/desktop/directives/", + url = "https://$DOMAIN/docs/desktop/directives/", ) } } @@ -836,12 +837,12 @@ private fun onboardingStepReady() { ) { onboardingLinkItem( title = stringResource("onboarding.step.ready.link.docs"), - onClick = { uriHandler.openUri("https://askimo.chat/docs/") }, + onClick = { uriHandler.openUri("https://$DOMAIN/docs/") }, ) HorizontalDivider() onboardingLinkItem( title = stringResource("onboarding.step.ready.link.providers"), - onClick = { uriHandler.openUri("https://askimo.chat/docs/desktop/ai-providers/") }, + onClick = { uriHandler.openUri("https://$DOMAIN/docs/desktop/ai-providers/") }, ) HorizontalDivider() onboardingLinkItem( diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/plan/PlanDetailView.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/plan/PlanDetailView.kt index f5cdd41a..e0b31f41 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/plan/PlanDetailView.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/plan/PlanDetailView.kt @@ -795,12 +795,10 @@ private fun agenticStepRow( when (event) { is PlanStepEvent.Completed -> { if (!suppressOutput && !copyableOutput.isNullOrBlank() && outputExpanded) { - SelectionContainer { - markdownText( - markdown = copyableOutput, - modifier = Modifier.padding(top = 2.dp).fillMaxWidth(), - ) - } + markdownText( + markdown = copyableOutput, + modifier = Modifier.padding(top = 2.dp).fillMaxWidth(), + ) } } @@ -1171,13 +1169,11 @@ private fun resultPanel( } } HorizontalDivider(modifier = Modifier.padding(bottom = Spacing.medium)) - SelectionContainer { - markdownText( - markdown = if (isStreaming) "$displayedOutput▍" else displayedOutput, - modifier = Modifier.fillMaxWidth(), - messageId = executionId, - ) - } + markdownText( + markdown = if (isStreaming) "$displayedOutput▍" else displayedOutput, + modifier = Modifier.fillMaxWidth(), + messageId = executionId, + ) } } } diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/plan/PlansGalleryView.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/plan/PlansGalleryView.kt index ed8a44c0..95918988 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/plan/PlansGalleryView.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/plan/PlansGalleryView.kt @@ -55,6 +55,7 @@ import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.plan.domain.PlanDef import io.askimo.ui.common.i18n.stringResource import io.askimo.ui.common.theme.AppComponents @@ -112,7 +113,7 @@ fun plansGalleryView( IconButton( onClick = { runCatching { - Desktop.getDesktop().browse(URI("https://askimo.chat/docs/desktop/plans/")) + Desktop.getDesktop().browse(URI("https://$DOMAIN/docs/desktop/plans/")) } }, modifier = Modifier.pointerHoverIcon(PointerIcon.Hand), diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/settings/SkillsSettingsSection.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/settings/SkillsSettingsSection.kt index 8229e73b..5213a45c 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/settings/SkillsSettingsSection.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/settings/SkillsSettingsSection.kt @@ -84,6 +84,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.skills.SkillImporter import io.askimo.core.skills.SkillRepository import io.askimo.core.skills.agent.ExternalAgentLoader @@ -442,7 +443,7 @@ private fun skillsMainContent( } themedTooltip(text = stringResource("skills.view.docs.tooltip")) { IconButton( - onClick = { runCatching { Desktop.getDesktop().browse(URI("https://askimo.chat/docs/desktop/skills/")) } }, + onClick = { runCatching { Desktop.getDesktop().browse(URI("https://$DOMAIN/docs/desktop/skills/")) } }, modifier = Modifier.pointerHoverIcon(PointerIcon.Hand), ) { Icon( diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/shell/NativeMenuBar.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/shell/NativeMenuBar.kt index 0eada9fe..709fc88f 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/shell/NativeMenuBar.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/shell/NativeMenuBar.kt @@ -5,6 +5,7 @@ package io.askimo.ui.shell import androidx.compose.ui.window.FrameWindowScope +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.config.AppConfig import io.askimo.core.i18n.LocalizationManager import io.askimo.ui.common.theme.ThemeMode @@ -448,7 +449,7 @@ object NativeMenuBar { val docsItem = MenuItem(LocalizationManager.getString("menu.documentation")) docsItem.addActionListener( ActionListener { - runCatching { if (Desktop.isDesktopSupported()) Desktop.getDesktop().browse(URI("https://askimo.chat/docs/")) } + runCatching { if (Desktop.isDesktopSupported()) Desktop.getDesktop().browse(URI("https://$DOMAIN/docs/")) } }, ) helpMenu.add(docsItem) @@ -468,7 +469,7 @@ object NativeMenuBar { val releaseNotesItem = MenuItem(LocalizationManager.getString("menu.help.release.notes")) releaseNotesItem.addActionListener( ActionListener { - runCatching { if (Desktop.isDesktopSupported()) Desktop.getDesktop().browse(URI("https://askimo.chat/docs/changelogs/")) } + runCatching { if (Desktop.isDesktopSupported()) Desktop.getDesktop().browse(URI("https://$DOMAIN/docs/changelogs/")) } }, ) helpMenu.add(releaseNotesItem) diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/shell/ShareUtils.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/shell/ShareUtils.kt index f638f679..ac24d03e 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/shell/ShareUtils.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/shell/ShareUtils.kt @@ -4,6 +4,7 @@ */ package io.askimo.ui.shell +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.i18n.LocalizationManager import java.awt.Desktop import java.net.URI @@ -32,7 +33,7 @@ object ShareUtils { if (!Desktop.isDesktopSupported()) return val desktop = Desktop.getDesktop() val encoded = URLEncoder.encode(shareText(), "UTF-8") - val url = URLEncoder.encode("https://askimo.chat", "UTF-8") + val url = URLEncoder.encode("https://$DOMAIN", "UTF-8") val uri = when (target) { ShareTarget.X -> "https://x.com/intent/tweet?text=$encoded" ShareTarget.LINKEDIN -> "https://www.linkedin.com/sharing/share-offsite/?url=$url" diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/shell/UpdateViewModel.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/shell/UpdateViewModel.kt index f10408ca..2bb78ae8 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/shell/UpdateViewModel.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/shell/UpdateViewModel.kt @@ -7,6 +7,7 @@ package io.askimo.ui.shell import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.logging.logger import io.askimo.core.service.UpdateInfo import io.askimo.ui.common.preferences.AccountPreferences @@ -107,7 +108,7 @@ class UpdateViewModel( } fun openHowToUpdatePage() { - runCatching { Desktop.getDesktop().browse(URI("https://askimo.chat/docs/desktop/updating/")) } + runCatching { Desktop.getDesktop().browse(URI("https://$DOMAIN/docs/desktop/updating/")) } .onFailure { log.error("Failed to open how-to-update page", it) } } diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/skills/SkillExecutorView.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/skills/SkillExecutorView.kt index f1b201c2..234ce6d2 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/skills/SkillExecutorView.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/skills/SkillExecutorView.kt @@ -67,6 +67,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.db.DatabaseManager import io.askimo.core.skills.agent.AgentCommand import io.askimo.core.skills.agent.ExternalAgent @@ -514,7 +515,7 @@ internal fun skillExecutionArea( Spacer(modifier = Modifier.weight(1f)) TextButton( - onClick = { uriHandler.openUri("https://askimo.chat/docs/desktop/skills/#supported-runtimes-today") }, + onClick = { uriHandler.openUri("https://$DOMAIN/docs/desktop/skills/#supported-runtimes-today") }, modifier = Modifier.pointerHoverIcon(PointerIcon.Hand), ) { Text( diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/skills/SkillsGalleryView.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/skills/SkillsGalleryView.kt index c3e391ba..62527f75 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/skills/SkillsGalleryView.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/skills/SkillsGalleryView.kt @@ -55,6 +55,7 @@ import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.skills.agent.ExternalAgentLoader import io.askimo.core.skills.domain.SkillDefinition import io.askimo.ui.common.i18n.stringResource @@ -142,7 +143,7 @@ internal fun skillsListContent( ) { themedTooltip(text = stringResource("skills.view.docs.tooltip")) { IconButton( - onClick = { runCatching { Desktop.getDesktop().browse(URI("https://askimo.chat/docs/desktop/skills/")) } }, + onClick = { runCatching { Desktop.getDesktop().browse(URI("https://$DOMAIN/docs/desktop/skills/")) } }, modifier = Modifier.pointerHoverIcon(PointerIcon.Hand), ) { Icon( diff --git a/desktop/src/main/kotlin/io/askimo/desktop/Main.kt b/desktop/src/main/kotlin/io/askimo/desktop/Main.kt index 6e7048b8..f754393c 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/Main.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/Main.kt @@ -54,6 +54,7 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPlacement import androidx.compose.ui.window.WindowState import androidx.compose.ui.window.application +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.analytics.Analytics import io.askimo.core.analytics.AnalyticsEvent import io.askimo.core.backup.BackupManager @@ -1623,7 +1624,7 @@ fun app(frameWindowScope: FrameWindowScope? = null, windowState: WindowState? = runCatching { if (Desktop.isDesktopSupported()) { Desktop.getDesktop().browse( - URI("https://askimo.chat/feedback/?sentiment=$feedbackSentiment"), + URI("https://$DOMAIN/feedback/?sentiment=$feedbackSentiment"), ) } }.onFailure { log.error("Cannot open browser for feedback", it) } diff --git a/desktop/src/main/kotlin/io/askimo/desktop/plan/PlanEditorView.kt b/desktop/src/main/kotlin/io/askimo/desktop/plan/PlanEditorView.kt index 67952b46..00cb1d34 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/plan/PlanEditorView.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/plan/PlanEditorView.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.ui.common.components.linkButton import io.askimo.ui.common.components.primaryButton import io.askimo.ui.common.components.secondaryButton @@ -282,7 +283,7 @@ fun planEditorView( linkButton( onClick = { runCatching { - Desktop.getDesktop().browse(URI("https://askimo.chat/docs/desktop/plans/")) + Desktop.getDesktop().browse(URI("https://$DOMAIN/docs/desktop/plans/")) } }, ) { diff --git a/desktop/src/main/kotlin/io/askimo/desktop/project/ProjectView.kt b/desktop/src/main/kotlin/io/askimo/desktop/project/ProjectView.kt index c3d314d1..da871950 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/project/ProjectView.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/project/ProjectView.kt @@ -64,6 +64,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.chat.domain.ChatSession import io.askimo.core.chat.domain.KnowledgeSourceConfig import io.askimo.core.chat.domain.LocalFilesKnowledgeSourceConfig @@ -690,7 +691,7 @@ private fun knowledgeSourcesPanel( onClick = { try { if (Desktop.isDesktopSupported()) { - Desktop.getDesktop().browse(URI("https://askimo.chat/docs/desktop/rag/")) + Desktop.getDesktop().browse(URI("https://$DOMAIN/docs/desktop/rag/")) } } catch (_: Exception) {} }, diff --git a/desktop/src/main/kotlin/io/askimo/desktop/settings/AIProviderSettingsSection.kt b/desktop/src/main/kotlin/io/askimo/desktop/settings/AIProviderSettingsSection.kt index 410af969..22da1b6c 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/settings/AIProviderSettingsSection.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/settings/AIProviderSettingsSection.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.config.AppConfig import io.askimo.core.context.AppContext import io.askimo.core.providers.ChatModelFactory @@ -261,7 +262,7 @@ private fun providerModelConfigCard(provider: ModelProvider) { onClick = { try { if (Desktop.isDesktopSupported()) { - Desktop.getDesktop().browse(URI("https://askimo.chat/docs/desktop/ai-model-configuration/")) + Desktop.getDesktop().browse(URI("https://$DOMAIN/docs/desktop/ai-model-configuration/")) } } catch (_: Exception) {} }, diff --git a/desktop/src/main/kotlin/io/askimo/desktop/settings/AboutDialog.kt b/desktop/src/main/kotlin/io/askimo/desktop/settings/AboutDialog.kt index ded599b3..2ec0a99c 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/settings/AboutDialog.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/settings/AboutDialog.kt @@ -17,6 +17,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.VersionInfo import io.askimo.ui.common.components.linkButton import io.askimo.ui.common.components.secondaryButton @@ -73,14 +74,14 @@ fun aboutDialog( linkButton( onClick = { try { - Desktop.getDesktop().browse(URI("https://askimo.chat")) + Desktop.getDesktop().browse(URI("https://$DOMAIN")) } catch (_: Exception) { // Silently fail if browser cannot be opened } }, ) { Text( - text = "https://askimo.chat", + text = "https://$DOMAIN", style = MaterialTheme.typography.bodyMedium, ) } diff --git a/desktop/src/main/kotlin/io/askimo/desktop/settings/AboutSettingsSection.kt b/desktop/src/main/kotlin/io/askimo/desktop/settings/AboutSettingsSection.kt index a4a26844..290aebea 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/settings/AboutSettingsSection.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/settings/AboutSettingsSection.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.VersionInfo import io.askimo.ui.common.components.linkButton import io.askimo.ui.common.i18n.stringResource @@ -126,7 +127,7 @@ fun aboutSettingsSection() { // Website Link linkButton( onClick = { - openUrl("https://askimo.chat") + openUrl("https://$DOMAIN") }, ) { Icon( @@ -136,7 +137,7 @@ fun aboutSettingsSection() { ) Spacer(modifier = Modifier.size(8.dp)) Text( - text = "askimo.chat", + text = DOMAIN, style = MaterialTheme.typography.bodyMedium, ) } diff --git a/desktop/src/main/kotlin/io/askimo/desktop/settings/McpServerManagementSection.kt b/desktop/src/main/kotlin/io/askimo/desktop/settings/McpServerManagementSection.kt index 236b8610..db3df5c6 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/settings/McpServerManagementSection.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/settings/McpServerManagementSection.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.mcp.McpInstance import io.askimo.core.mcp.McpInstanceService import io.askimo.core.mcp.McpServerDefinition @@ -117,7 +118,7 @@ fun mcpServerTemplatesSection() { onClick = { try { if (Desktop.isDesktopSupported()) { - Desktop.getDesktop().browse(URI("https://askimo.chat/docs/desktop/mcp-integration/")) + Desktop.getDesktop().browse(URI("https://$DOMAIN/docs/desktop/mcp-integration/")) } } catch (_: Exception) {} }, diff --git a/desktop/src/main/kotlin/io/askimo/desktop/settings/NetworkSettingsSection.kt b/desktop/src/main/kotlin/io/askimo/desktop/settings/NetworkSettingsSection.kt index 4cd76b1c..771ff97a 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/settings/NetworkSettingsSection.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/settings/NetworkSettingsSection.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.config.AppConfig import io.askimo.core.config.ProxyType import io.askimo.ui.common.components.linkButton @@ -137,7 +138,7 @@ private fun proxyConfigurationCard() { onClick = { try { if (Desktop.isDesktopSupported()) { - Desktop.getDesktop().browse(URI("https://askimo.chat/docs/desktop/proxy-configuration/")) + Desktop.getDesktop().browse(URI("https://$DOMAIN/docs/desktop/proxy-configuration/")) } } catch (_: Exception) {} }, diff --git a/desktop/src/main/kotlin/io/askimo/desktop/settings/ProviderSelectionDialog.kt b/desktop/src/main/kotlin/io/askimo/desktop/settings/ProviderSelectionDialog.kt index d0ff56d8..964f3bdb 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/settings/ProviderSelectionDialog.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/settings/ProviderSelectionDialog.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.providers.ModelProvider import io.askimo.core.providers.ProviderConfigField import io.askimo.ui.common.components.linkButton @@ -315,7 +316,7 @@ fun providerSelectionDialog( try { val providerName = viewModel.selectedProvider?.name?.lowercase() ?: return@linkButton Desktop.getDesktop().browse( - URI("https://askimo.chat/docs/desktop/providers/$providerName/"), + URI("https://$DOMAIN/docs/desktop/providers/$providerName/"), ) } catch (_: Exception) { // Silently fail if browser cannot be opened @@ -448,7 +449,7 @@ fun providerSelectionDialog( onClick = { try { Desktop.getDesktop().browse( - URI("https://askimo.chat/security/"), + URI("https://$DOMAIN/security/"), ) } catch (_: Exception) { // Silently fail if browser cannot be opened diff --git a/desktop/src/main/kotlin/io/askimo/desktop/shell/FooterBar.kt b/desktop/src/main/kotlin/io/askimo/desktop/shell/FooterBar.kt index 09f46f3a..0a4bb2d0 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/shell/FooterBar.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/shell/FooterBar.kt @@ -54,6 +54,7 @@ import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.context.AppContext import io.askimo.core.context.getConfigInfo import io.askimo.core.event.EventBus @@ -508,7 +509,7 @@ fun footerBar( TextButton( onClick = { runCatching { - Desktop.getDesktop().browse(URI("https://askimo.chat/contact/")) + Desktop.getDesktop().browse(URI("https://$DOMAIN/contact/")) } }, modifier = Modifier.pointerHoverIcon(PointerIcon.Hand), diff --git a/shared/src/main/kotlin/io/askimo/core/AppConstants.kt b/shared/src/main/kotlin/io/askimo/core/AppConstants.kt new file mode 100644 index 00000000..c7bda457 --- /dev/null +++ b/shared/src/main/kotlin/io/askimo/core/AppConstants.kt @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: AGPLv3 + * + * Copyright (c) 2026 Askimo + */ +package io.askimo.core + +object AppConstants { + const val DOMAIN = "askimo.chat" +} diff --git a/shared/src/main/kotlin/io/askimo/core/analytics/AnalyticsEvent.kt b/shared/src/main/kotlin/io/askimo/core/analytics/AnalyticsEvent.kt index 12ae4af4..831a18a9 100644 --- a/shared/src/main/kotlin/io/askimo/core/analytics/AnalyticsEvent.kt +++ b/shared/src/main/kotlin/io/askimo/core/analytics/AnalyticsEvent.kt @@ -238,12 +238,6 @@ enum class AnalyticsEvent( "Directive applied to a message.", ), - /** An image was generated via the image model. */ - IMAGE_GENERATED( - "image_generated", - "Image generated via image model.", - ), - // ── Skills ─────────────────────────────────────────────────────────────── /** @@ -260,12 +254,12 @@ enum class AnalyticsEvent( /** * A categorised error occurred. - * Properties: `error_type=provider_timeout|rate_limit|auth_error|context_length|…`, - * `provider` (when relevant), `error_message` (sanitised, no PII). + * Properties: `error_type=provider_timeout|rate_limit|auth_error|context_length|connection_refused|model_not_found|server_error|network_error|provider_error`, + * `provider` (when relevant), `error_message` (sanitised, no PII, up to 4000 chars). */ ERROR_OCCURRED( "error_occurred", - "Categorised error. Properties: error_type, provider (optional), error_message (sanitised).", + "Categorised error. Properties: error_type, provider (optional), error_message (sanitised, up to 4000 chars).", ), // ── Retention ──────────────────────────────────────────────────────────── @@ -335,12 +329,4 @@ enum class AnalyticsEvent( ; override fun toString(): String = eventName - - companion object { - /** All wire names — use this to keep the ingest worker allow-list in sync. */ - val allEventNames: Set = entries.map { it.eventName }.toSet() - - /** Look up by wire name, or null if unknown. */ - fun fromEventName(name: String): AnalyticsEvent? = entries.firstOrNull { it.eventName == name } - } } diff --git a/shared/src/main/kotlin/io/askimo/core/chat/domain/KnowledgeSourceConfig.kt b/shared/src/main/kotlin/io/askimo/core/chat/domain/KnowledgeSourceConfig.kt index 7dddbee4..15e43351 100644 --- a/shared/src/main/kotlin/io/askimo/core/chat/domain/KnowledgeSourceConfig.kt +++ b/shared/src/main/kotlin/io/askimo/core/chat/domain/KnowledgeSourceConfig.kt @@ -78,5 +78,5 @@ data class UrlKnowledgeSourceConfig( @Serializable data class IndexedPathsData( val version: Int = 1, - val sources: List, + val sources: List = emptyList(), ) diff --git a/shared/src/main/kotlin/io/askimo/core/chat/util/UrlContentExtractor.kt b/shared/src/main/kotlin/io/askimo/core/chat/util/UrlContentExtractor.kt index d09257c2..9d572f52 100644 --- a/shared/src/main/kotlin/io/askimo/core/chat/util/UrlContentExtractor.kt +++ b/shared/src/main/kotlin/io/askimo/core/chat/util/UrlContentExtractor.kt @@ -4,6 +4,7 @@ */ package io.askimo.core.chat.util +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.logging.currentFileLogger import io.askimo.core.util.ProxyUtil import org.apache.tika.metadata.Metadata @@ -69,7 +70,7 @@ object UrlContentExtractor { val request = HttpRequest.newBuilder() .uri(URI(normalizedUrl)) - .header("User-Agent", "Mozilla/5.0 (compatible; Askimo/1.0; +https://askimo.chat)") + .header("User-Agent", "Mozilla/5.0 (compatible; Askimo/1.0; +https://$DOMAIN)") .timeout(Duration.ofSeconds(30)) .GET() .build() diff --git a/shared/src/main/kotlin/io/askimo/core/config/AppConfig.kt b/shared/src/main/kotlin/io/askimo/core/config/AppConfig.kt index bfb19b12..098d0023 100644 --- a/shared/src/main/kotlin/io/askimo/core/config/AppConfig.kt +++ b/shared/src/main/kotlin/io/askimo/core/config/AppConfig.kt @@ -20,6 +20,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator import com.fasterxml.jackson.module.kotlin.KotlinFeature import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.readValue +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.context.AppContextParams import io.askimo.core.event.EventBus import io.askimo.core.event.internal.LanguageDirectiveChangedEvent @@ -349,7 +350,7 @@ data class ModelsConfig( data class AnalyticsConfig( /** True only when the user has explicitly opted in via the consent dialog. Default false. */ val optedIn: Boolean = false, - val endpoint: String = "https://analytics.askimo.chat/ingest", + val endpoint: String = "https://analytics.$DOMAIN/ingest", ) data class AppConfigData( diff --git a/shared/src/main/kotlin/io/askimo/core/service/UpdateChecker.kt b/shared/src/main/kotlin/io/askimo/core/service/UpdateChecker.kt index 812f09f3..83f723fd 100644 --- a/shared/src/main/kotlin/io/askimo/core/service/UpdateChecker.kt +++ b/shared/src/main/kotlin/io/askimo/core/service/UpdateChecker.kt @@ -4,6 +4,7 @@ */ package io.askimo.core.service +import io.askimo.core.AppConstants.DOMAIN import io.askimo.core.VersionInfo import io.askimo.core.logging.logger import io.askimo.core.util.JsonUtils.json @@ -49,7 +50,7 @@ class UpdateChecker( ) { private val log = logger() - private val releasesUrl = "https://api.askimo.chat/releases?per_page=1" + private val releasesUrl = "https://api.$DOMAIN/releases?per_page=1" private val httpClient = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) @@ -99,7 +100,7 @@ class UpdateChecker( latestVersion = latestVersion, releaseName = latest.name, releaseDate = formatReleaseDate(latest.published_at), - downloadUrl = "https://askimo.chat/download/", + downloadUrl = "https://$DOMAIN/download/", releaseNotes = htmlToMarkdown(latest.body_html.orEmpty()), isNewVersion = isNewVersion, versionsBehind = if (isNewVersion) 1 else 0, diff --git a/shared/src/main/kotlin/io/askimo/core/telemetry/TelemetryChatModelListener.kt b/shared/src/main/kotlin/io/askimo/core/telemetry/TelemetryChatModelListener.kt index 1e504343..99e5e1dd 100644 --- a/shared/src/main/kotlin/io/askimo/core/telemetry/TelemetryChatModelListener.kt +++ b/shared/src/main/kotlin/io/askimo/core/telemetry/TelemetryChatModelListener.kt @@ -144,21 +144,47 @@ class TelemetryChatModelListener( telemetry.recordLLMError(provider, model, error) // Categorise the error type without including the message (may contain PII/keys) + val msg = error.message ?: error.cause?.message ?: "" val errorType = when { - error.message?.contains("timeout", ignoreCase = true) == true -> "provider_timeout" + msg.contains("timeout", ignoreCase = true) || + msg.contains("timed out", ignoreCase = true) -> "provider_timeout" - error.message?.contains("rate limit", ignoreCase = true) == true || - error.message?.contains("429", ignoreCase = true) == true -> "rate_limit" + msg.contains("rate limit", ignoreCase = true) || + msg.contains("429") -> "rate_limit" - error.message?.contains("401", ignoreCase = true) == true || - error.message?.contains("403", ignoreCase = true) == true -> "auth_error" + msg.contains("401") || + msg.contains("403") || + msg.contains("unauthorized", ignoreCase = true) || + msg.contains("forbidden", ignoreCase = true) -> "auth_error" - error.message?.contains("context length", ignoreCase = true) == true || - error.message?.contains("too long", ignoreCase = true) == true -> "context_length" + msg.contains("context length", ignoreCase = true) || + msg.contains("context window", ignoreCase = true) || + msg.contains("too long", ignoreCase = true) || + msg.contains("maximum context", ignoreCase = true) -> "context_length" + + msg.contains("connection refused", ignoreCase = true) || + error is java.net.ConnectException || + error.cause is java.net.ConnectException -> "connection_refused" + + msg.contains("unknown host", ignoreCase = true) || + error is java.net.UnknownHostException || + error.cause is java.net.UnknownHostException -> "connection_refused" + + (msg.contains("model", ignoreCase = true) && msg.contains("not found", ignoreCase = true)) || + msg.contains("no such file", ignoreCase = true) -> "model_not_found" + + msg.contains("500") || + msg.contains("internal server error", ignoreCase = true) -> "server_error" + + error is java.io.IOException || + error.cause is java.io.IOException -> "network_error" else -> "provider_error" } - val sanitisedMessage = error.message?.take(200)?.replace(Regex("[\\r\\n]+"), " ") ?: "unknown" + val sanitisedMessage = (error.message ?: error.cause?.message) + ?.take(4000) + ?.replace(Regex("[\\r\\n]+"), " ") + ?: "unknown" Analytics.track( AnalyticsEvent.ERROR_OCCURRED, mapOf("error_type" to errorType, "provider" to provider, "error_message" to sanitisedMessage), diff --git a/shared/src/main/kotlin/io/askimo/tools/web/WebSearchTools.kt b/shared/src/main/kotlin/io/askimo/tools/web/WebSearchTools.kt index cc853d2c..514d6cd0 100644 --- a/shared/src/main/kotlin/io/askimo/tools/web/WebSearchTools.kt +++ b/shared/src/main/kotlin/io/askimo/tools/web/WebSearchTools.kt @@ -6,6 +6,7 @@ package io.askimo.tools.web import dev.langchain4j.agent.tool.P import dev.langchain4j.agent.tool.Tool +import io.askimo.core.AppConstants.DOMAIN import io.askimo.tools.ToolResponseBuilder import org.jsoup.HttpStatusException import org.jsoup.Jsoup @@ -22,7 +23,7 @@ object WebSearchTools { private const val CLASS_NAME = "io.askimo.tools.web.WebSearchTools" private const val PAGE_TIMEOUT_MS = 15_000 private const val USER_AGENT = - "Mozilla/5.0 (compatible; Askimo/1.0; +https://askimo.chat)" + "Mozilla/5.0 (compatible; Askimo/1.0; +https://$DOMAIN)" /** * Fetch and extract the readable text content of a web page.