feat(intellij): Add dont-show-again for warnings. Add more online help links. (#823)

release-fix-intellij-update-support-version-range
Zhiming Ma 2023-11-18 16:03:24 +08:00 committed by GitHub
parent 2cc751766f
commit 5e7ca4f569
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 191 additions and 21 deletions

View File

@ -1,21 +1,23 @@
package com.tabbyml.intellijtabby.actions package com.tabbyml.intellijtabby.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.invokeLater import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.tabbyml.intellijtabby.agent.Agent import com.tabbyml.intellijtabby.agent.Agent
import com.tabbyml.intellijtabby.agent.AgentService import com.tabbyml.intellijtabby.agent.AgentService
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.net.URL import java.net.URL
class CheckIssueDetail : AnAction() { class CheckIssueDetail : AnAction() {
private val logger = Logger.getInstance(CheckIssueDetail::class.java) private val logger = Logger.getInstance(CheckIssueDetail::class.java)
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
val settings = service<ApplicationSettingsState>()
val agentService = service<AgentService>() val agentService = service<AgentService>()
agentService.issueNotification?.expire() agentService.issueNotification?.expire()
@ -24,7 +26,23 @@ class CheckIssueDetail : AnAction() {
if (detail["name"] == "connectionFailed") { if (detail["name"] == "connectionFailed") {
invokeLater { invokeLater {
val messages = "<html>" + (detail["message"] as String?)?.replace("\n", "<br/>") + "</html>" val messages = "<html>" + (detail["message"] as String?)?.replace("\n", "<br/>") + "</html>"
Messages.showErrorDialog(messages, "Cannot Connect to Tabby Server") val selected = Messages.showDialog(
messages,
"Cannot Connect to Tabby Server",
arrayOf("OK", "Online Help"),
0,
Messages.getErrorIcon(),
)
when (selected) {
0 -> {
// OK
}
1 -> {
// Online Help
showOnlineHelp(e)
}
}
} }
return@launch return@launch
} else { } else {
@ -38,8 +56,47 @@ class CheckIssueDetail : AnAction() {
} }
val message = buildDetailMessage(detail, serverHealthState, agentConfig) val message = buildDetailMessage(detail, serverHealthState, agentConfig)
invokeLater { invokeLater {
Messages.showInfoMessage(message, title) val selected = Messages.showDialog(
message,
title,
arrayOf("OK", "Online Help", "Don't Show Again"),
0,
Messages.getWarningIcon(),
)
when (selected) {
0 -> {
// OK
} }
1 -> {
// Online Help
showOnlineHelp(e)
}
2 -> {
// Don't Show Again
settings.notificationsMuted += listOf("completionResponseTimeIssues")
}
}
}
}
}
}
private fun showOnlineHelp(e: AnActionEvent) {
e.project?.let {
invokeLater {
val actionManager = ActionManager.getInstance()
val actionGroup = actionManager.getAction("Tabby.OpenOnlineHelp") as? ActionGroup ?: return@invokeLater
val popup = JBPopupFactory.getInstance().createActionGroupPopup(
"Online Help",
actionGroup,
e.dataContext,
false,
null,
10,
)
popup.showCenteredInCurrentWindow(it)
} }
} }
} }
@ -76,7 +133,7 @@ class CheckIssueDetail : AnAction() {
""" """
Your Tabby server is running model <i>$model</i> on CPU. Your Tabby server is running model <i>$model</i> on CPU.
This model may be performing poorly due to its large parameter size, please consider trying smaller models or switch to GPU. This model may be performing poorly due to its large parameter size, please consider trying smaller models or switch to GPU.
You can find a list of supported models in the <a href='https://tabby.tabbyml.com/docs/models/'>model directory</a>. You can find a list of recommend models in the <a href='https://tabby.tabbyml.com/docs/models/'>model registry</a>.
""".trimIndent() """.trimIndent()
} else { } else {
"" ""
@ -85,7 +142,7 @@ class CheckIssueDetail : AnAction() {
val host = URL(agentConfig.server?.endpoint).host val host = URL(agentConfig.server?.endpoint).host
if (helpMessageForRunningLargeModelOnCPU.isEmpty()) { if (helpMessageForRunningLargeModelOnCPU.isEmpty()) {
commonHelpMessage += "<li>The running model <i>$model</i> may be performing poorly due to its large parameter size.<br/>" commonHelpMessage += "<li>The running model <i>$model</i> may be performing poorly due to its large parameter size.<br/>"
commonHelpMessage += "Please consider trying smaller models. You can find a list of supported models in the <a href='https://tabby.tabbyml.com/docs/models/'>model directory</a>.</li>" commonHelpMessage += "Please consider trying smaller models. You can find a list of recommend models in the <a href='https://tabby.tabbyml.com/docs/models/'>model registry</a>.</li>"
} }
if (!(host.startsWith("localhost") || host.startsWith("127.0.0.1"))) { if (!(host.startsWith("localhost") || host.startsWith("127.0.0.1"))) {
commonHelpMessage += "<li>A poor network connection. Please check your network and proxy settings.</li>" commonHelpMessage += "<li>A poor network connection. Please check your network and proxy settings.</li>"
@ -106,8 +163,13 @@ class CheckIssueDetail : AnAction() {
} }
override fun update(e: AnActionEvent) { override fun update(e: AnActionEvent) {
val settings = service<ApplicationSettingsState>()
val agentService = service<AgentService>() val agentService = service<AgentService>()
e.presentation.isVisible = agentService.currentIssue.value != null val muted = mutableListOf<String>()
if (settings.notificationsMuted.contains("completionResponseTimeIssues")) {
muted += listOf("slowCompletionResponseTime", "highCompletionTimeoutRate")
}
e.presentation.isVisible = agentService.currentIssue.value != null && agentService.currentIssue.value !in muted
} }
override fun getActionUpdateThread(): ActionUpdateThread { override fun getActionUpdateThread(): ActionUpdateThread {

View File

@ -0,0 +1,11 @@
package com.tabbyml.intellijtabby.actions
import com.intellij.ide.BrowserUtil
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
class JoinTabbySlackCommunity: AnAction() {
override fun actionPerformed(e: AnActionEvent) {
BrowserUtil.browse("https://join.slack.com/t/tabbycommunity/shared_invite/zt-1xeiddizp-bciR2RtFTaJ37RBxr8VxpA")
}
}

View File

@ -4,8 +4,8 @@ import com.intellij.ide.BrowserUtil
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
class OpenOnlineDocs: AnAction() { class OpenModelRegistry: AnAction() {
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
BrowserUtil.browse("https://tabby.tabbyml.com/docs/extensions/") BrowserUtil.browse("https://tabby.tabbyml.com/docs/models/")
} }
} }

View File

@ -0,0 +1,11 @@
package com.tabbyml.intellijtabby.actions
import com.intellij.ide.BrowserUtil
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
class OpenOnlineDocumentation: AnAction() {
override fun actionPerformed(e: AnActionEvent) {
BrowserUtil.browse("https://tabby.tabbyml.com/")
}
}

View File

@ -0,0 +1,11 @@
package com.tabbyml.intellijtabby.actions
import com.intellij.ide.BrowserUtil
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
class OpenTabbyGithubRepo : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
BrowserUtil.browse("https://github.com/tabbyml/tabby")
}
}

View File

@ -8,6 +8,8 @@ import com.intellij.notification.NotificationType
import com.intellij.notification.Notifications import com.intellij.notification.Notifications
import com.intellij.openapi.Disposable import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.application.invokeLater import com.intellij.openapi.application.invokeLater
@ -76,7 +78,13 @@ class AgentService : Disposable {
"${e.message}", "${e.message}",
NotificationType.ERROR, NotificationType.ERROR,
) )
notification.addAction(ActionManager.getInstance().getAction("Tabby.OpenOnlineDocs")) notification.addAction(
object : AnAction("Open Online Documentation") {
override fun actionPerformed(e: AnActionEvent) {
BrowserUtil.browse("https://tabby.tabbyml.com/docs/extensions/troubleshooting/#tabby-initialization-failed")
}
}
)
invokeLater { invokeLater {
initFailedNotification?.expire() initFailedNotification?.expire()
initFailedNotification = notification initFailedNotification = notification
@ -131,16 +139,18 @@ class AgentService : Disposable {
scope.launch { scope.launch {
agent.currentIssue.collect { issueName -> agent.currentIssue.collect { issueName ->
val showCompletionResponseWarnings = !completionResponseWarningShown &&
!settings.notificationsMuted.contains("completionResponseTimeIssues")
val message = when (issueName) { val message = when (issueName) {
"connectionFailed" -> "Cannot connect to Tabby server" "connectionFailed" -> "Cannot connect to Tabby server"
"slowCompletionResponseTime" -> if (!completionResponseWarningShown) { "slowCompletionResponseTime" -> if (showCompletionResponseWarnings) {
completionResponseWarningShown = true completionResponseWarningShown = true
"Completion requests appear to take too much time" "Completion requests appear to take too much time"
} else { } else {
return@collect return@collect
} }
"highCompletionTimeoutRate" -> if (!completionResponseWarningShown) { "highCompletionTimeoutRate" -> if (showCompletionResponseWarnings) {
completionResponseWarningShown = true completionResponseWarningShown = true
"Most completion requests timed out" "Most completion requests timed out"
} else { } else {
@ -160,6 +170,16 @@ class AgentService : Disposable {
NotificationType.WARNING, NotificationType.WARNING,
) )
notification.addAction(ActionManager.getInstance().getAction("Tabby.CheckIssueDetail")) notification.addAction(ActionManager.getInstance().getAction("Tabby.CheckIssueDetail"))
if (issueName in listOf("slowCompletionResponseTime", "highCompletionTimeoutRate")) {
notification.addAction(
object : AnAction("Don't Show Again") {
override fun actionPerformed(e: AnActionEvent) {
issueNotification?.expire()
settings.notificationsMuted += listOf("completionResponseTimeIssues")
}
}
)
}
invokeLater { invokeLater {
issueNotification?.expire() issueNotification?.expire()
issueNotification = notification issueNotification = notification

View File

@ -168,6 +168,19 @@ class ApplicationSettingsPanel {
) )
.panel .panel
private val resetMutedNotificationsButton = JButton("Reset \"Don't Show Again\" Notifications").apply {
addActionListener {
val settings = service<ApplicationSettingsState>()
settings.notificationsMuted = listOf()
invokeLater(ModalityState.stateForComponent(this@ApplicationSettingsPanel.mainPanel)) {
Messages.showInfoMessage("Reset \"Don't Show Again\" notifications successfully.", "Reset Notifications")
}
}
}
private val resetMutedNotificationsPanel: JPanel = FormBuilder.createFormBuilder()
.addComponent(resetMutedNotificationsButton)
.panel
val mainPanel: JPanel = FormBuilder.createFormBuilder() val mainPanel: JPanel = FormBuilder.createFormBuilder()
.addLabeledComponent("Server endpoint", serverEndpointPanel, 5, false) .addLabeledComponent("Server endpoint", serverEndpointPanel, 5, false)
.addSeparator(5) .addSeparator(5)
@ -176,6 +189,12 @@ class ApplicationSettingsPanel {
.addLabeledComponent("<html>Node binary<br/>(Requires restart IDE)</html>", nodeBinaryPanel, 5, false) .addLabeledComponent("<html>Node binary<br/>(Requires restart IDE)</html>", nodeBinaryPanel, 5, false)
.addSeparator(5) .addSeparator(5)
.addLabeledComponent("Anonymous usage tracking", isAnonymousUsageTrackingPanel, 5, false) .addLabeledComponent("Anonymous usage tracking", isAnonymousUsageTrackingPanel, 5, false)
.apply {
if (service<ApplicationSettingsState>().notificationsMuted.isNotEmpty()) {
addSeparator(5)
addLabeledComponent("Notifications", resetMutedNotificationsPanel, 5, false)
}
}
.addComponentFillVertically(JPanel(), 0) .addComponentFillVertically(JPanel(), 0)
.panel .panel

View File

@ -56,11 +56,20 @@ class ApplicationSettingsState : PersistentStateComponent<ApplicationSettingsSta
isAnonymousUsageTrackingDisabledFlow.value = value isAnonymousUsageTrackingDisabledFlow.value = value
} }
private val notificationsMutedFlow = MutableStateFlow(listOf<String>())
val notificationsMutedState = notificationsMutedFlow.asStateFlow()
var notificationsMuted: List<String> = listOf()
set(value) {
field = value
notificationsMutedFlow.value = value
}
data class State( data class State(
val completionTriggerMode: TriggerMode, val completionTriggerMode: TriggerMode,
val serverEndpoint: String, val serverEndpoint: String,
val nodeBinary: String, val nodeBinary: String,
val isAnonymousUsageTrackingDisabled: Boolean, val isAnonymousUsageTrackingDisabled: Boolean,
val notificationsMuted: List<String>,
) )
val data: State val data: State
@ -69,6 +78,7 @@ class ApplicationSettingsState : PersistentStateComponent<ApplicationSettingsSta
serverEndpoint = serverEndpoint, serverEndpoint = serverEndpoint,
nodeBinary = nodeBinary, nodeBinary = nodeBinary,
isAnonymousUsageTrackingDisabled = isAnonymousUsageTrackingDisabled, isAnonymousUsageTrackingDisabled = isAnonymousUsageTrackingDisabled,
notificationsMuted = notificationsMuted,
) )
val state = combine( val state = combine(
@ -76,12 +86,14 @@ class ApplicationSettingsState : PersistentStateComponent<ApplicationSettingsSta
serverEndpointState, serverEndpointState,
nodeBinaryState, nodeBinaryState,
isAnonymousUsageTrackingDisabledState, isAnonymousUsageTrackingDisabledState,
) { completionTriggerMode, serverEndpoint, nodeBinary, isAnonymousUsageTrackingDisabled -> notificationsMutedState,
) { completionTriggerMode, serverEndpoint, nodeBinary, isAnonymousUsageTrackingDisabled, notificationsMuted ->
State( State(
completionTriggerMode = completionTriggerMode, completionTriggerMode = completionTriggerMode,
serverEndpoint = serverEndpoint, serverEndpoint = serverEndpoint,
nodeBinary = nodeBinary, nodeBinary = nodeBinary,
isAnonymousUsageTrackingDisabled = isAnonymousUsageTrackingDisabled, isAnonymousUsageTrackingDisabled = isAnonymousUsageTrackingDisabled,
notificationsMuted = notificationsMuted,
) )
}.stateIn(CoroutineScope(Dispatchers.IO), SharingStarted.Eagerly, this.data) }.stateIn(CoroutineScope(Dispatchers.IO), SharingStarted.Eagerly, this.data)

View File

@ -87,7 +87,7 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
actionManager.getAction("Tabby.CheckIssueDetail"), actionManager.getAction("Tabby.CheckIssueDetail"),
actionManager.getAction("Tabby.ToggleInlineCompletionTriggerMode"), actionManager.getAction("Tabby.ToggleInlineCompletionTriggerMode"),
actionManager.getAction("Tabby.OpenSettings"), actionManager.getAction("Tabby.OpenSettings"),
actionManager.getAction("Tabby.OpenOnlineDocs"), actionManager.getAction("Tabby.OpenOnlineHelp"),
) )
} }
}, },
@ -109,7 +109,11 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
tooltip = "Tabby: Initialization failed" tooltip = "Tabby: Initialization failed"
} }
Agent.Status.READY -> { Agent.Status.READY -> {
if (state.currentIssue != null) { val muted = mutableListOf<String>()
if (state.settings.notificationsMuted.contains("completionResponseTimeIssues")) {
muted += listOf("slowCompletionResponseTime", "highCompletionTimeoutRate")
}
if (state.currentIssue != null && state.currentIssue !in muted) {
icon = AllIcons.General.Warning icon = AllIcons.General.Warning
tooltip = when(state.currentIssue) { tooltip = when(state.currentIssue) {
"slowCompletionResponseTime" -> "Tabby: Completion requests appear to take too much time" "slowCompletionResponseTime" -> "Tabby: Completion requests appear to take too much time"

View File

@ -93,11 +93,31 @@
text="Open Settings..." text="Open Settings..."
description="Show settings for Tabby."> description="Show settings for Tabby.">
</action> </action>
<action id="Tabby.OpenOnlineDocs" <group id="Tabby.OpenOnlineHelp"
class="com.tabbyml.intellijtabby.actions.OpenOnlineDocs" popup="true"
text="Open Online Help..." text="Open Online Help"
description="Open the online docs in your web browser."> description="View useful links for online help.">
<action id="Tabby.OpenOnlineHelp.OpenOnlineDocumentation"
class="com.tabbyml.intellijtabby.actions.OpenOnlineDocumentation"
text="Online Documentation..."
description="Open the documentation page in your web browser.">
</action> </action>
<action id="Tabby.OpenOnlineHelp.OpenModelRegistry"
class="com.tabbyml.intellijtabby.actions.OpenModelRegistry"
text="Model Registry..."
description="Explore more recommend models from Tabby's model registry.">
</action>
<action id="Tabby.OpenOnlineHelp.JoinTabbySlackCommunity"
class="com.tabbyml.intellijtabby.actions.JoinTabbySlackCommunity"
text="Tabby Slack Community..."
description="Join Tabby's Slack community to get help or feed back.">
</action>
<action id="Tabby.OpenOnlineHelp.OpenTabbyGithubRepo"
class="com.tabbyml.intellijtabby.actions.OpenTabbyGithubRepo"
text="Tabby GitHub Repository..."
description="View the source code for Tabby, and open issues.">
</action>
</group>
</group> </group>
</actions> </actions>
</idea-plugin> </idea-plugin>