feat(intellij): update tabby-agent interface and add issue notifications. (#401)
parent
3ab365f2c9
commit
abfa7975e8
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,94 @@
|
|||
package com.tabbyml.intellijtabby.actions
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.invokeLater
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.tabbyml.intellijtabby.agent.Agent
|
||||
import com.tabbyml.intellijtabby.agent.AgentService
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class CheckIssueDetail : AnAction() {
|
||||
private val logger = Logger.getInstance(CheckIssueDetail::class.java)
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val agentService = service<AgentService>()
|
||||
agentService.issueNotification?.expire()
|
||||
|
||||
agentService.scope.launch {
|
||||
val detail = agentService.getCurrentIssueDetail() ?: return@launch
|
||||
val serverHealthState = agentService.getServerHealthState()
|
||||
logger.info("Show issue detail: $detail, $serverHealthState")
|
||||
val title = when (detail["name"]) {
|
||||
"slowCompletionResponseTime" -> "Completion Requests Appear to Take Too Much Time"
|
||||
"highCompletionTimeoutRate" -> "Most Completion Requests Timed Out"
|
||||
else -> return@launch
|
||||
}
|
||||
val message = buildDetailMessage(detail, serverHealthState)
|
||||
invokeLater {
|
||||
Messages.showInfoMessage(message, title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDetailMessage(detail: Map<String, Any>, serverHealthState: Map<String, Any>?): String {
|
||||
val stats = detail["completionResponseStats"] as Map<*, *>?
|
||||
val statsMessages = when (detail["name"]) {
|
||||
"slowCompletionResponseTime" -> if (stats != null && stats["responses"] is Number && stats["averageResponseTime"] is Number) {
|
||||
val response = (stats["responses"] as Number).toInt()
|
||||
val averageResponseTime = (stats["averageResponseTime"] as Number).toInt()
|
||||
"The average response time of recent $response completion requests is $averageResponseTime ms.\n\n"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
"highCompletionTimeoutRate" -> if (stats != null && stats["total"] is Number && stats["timeouts"] is Number) {
|
||||
val timeout = (stats["timeouts"] as Number).toInt()
|
||||
val total = (stats["total"] as Number).toInt()
|
||||
"$timeout of $total completion requests timed out.\n\n"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
else -> ""
|
||||
}
|
||||
|
||||
val device = serverHealthState?.get("device") as String? ?: ""
|
||||
val model = serverHealthState?.get("model") as String? ?: ""
|
||||
val helpMessageForRunningLargeModelOnCPU = if (device == "cpu" && model.endsWith("B")) {
|
||||
"""
|
||||
Your Tabby server is running model $model on CPU.
|
||||
This model is too large to run on CPU, please try a smaller model or switch to GPU.
|
||||
You can find supported model list by search TabbyML on HuggingFace.
|
||||
"""
|
||||
} else {
|
||||
""
|
||||
}
|
||||
var helpMessage = ""
|
||||
if (helpMessageForRunningLargeModelOnCPU.isNotEmpty()) {
|
||||
helpMessage += helpMessageForRunningLargeModelOnCPU + "\n\n"
|
||||
helpMessage += "Other possible causes of this issue are: \n"
|
||||
} else {
|
||||
helpMessage += "Possible causes of this issue are: \n";
|
||||
}
|
||||
helpMessage += " - A poor network connection. Please check your network and proxy settings.\n";
|
||||
helpMessage += " - Server overload. Please contact your Tabby server administrator for assistance.\n";
|
||||
if (helpMessageForRunningLargeModelOnCPU.isEmpty()) {
|
||||
helpMessage += " - The running model $model is too large to run on your Tabby server. ";
|
||||
helpMessage += "Please try a smaller model. You can find supported model list by search TabbyML on HuggingFace.\n";
|
||||
}
|
||||
return statsMessages + helpMessage
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val agentService = service<AgentService>()
|
||||
e.presentation.isVisible = agentService.status.value == Agent.Status.ISSUES_EXIST
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread {
|
||||
return ActionUpdateThread.BGT
|
||||
}
|
||||
}
|
||||
|
|
@ -13,10 +13,13 @@ import com.tabbyml.intellijtabby.agent.AgentService
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
open class OpenAuthPage : AnAction() {
|
||||
class OpenAuthPage : AnAction() {
|
||||
private val logger = Logger.getInstance(OpenAuthPage::class.java)
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val agentService = service<AgentService>()
|
||||
agentService.authNotification?.expire()
|
||||
|
||||
val task = object : Task.Modal(
|
||||
e.project,
|
||||
"Tabby Server Authorization",
|
||||
|
|
@ -24,7 +27,6 @@ open class OpenAuthPage : AnAction() {
|
|||
) {
|
||||
lateinit var job: Job
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
val agentService = service<AgentService>()
|
||||
job = agentService.scope.launch {
|
||||
agentService.requestAuth(indicator)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class TriggerCompletion : AnAction() {
|
|||
val completionScheduler = service<CompletionScheduler>()
|
||||
val editor = e.getRequiredData(CommonDataKeys.EDITOR)
|
||||
val offset = editor.caretModel.primaryCaret.offset
|
||||
completionScheduler.schedule(editor, offset, triggerDelay = 0, manually = true)
|
||||
completionScheduler.schedule(editor, offset, manually = true)
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
|
|
|
|||
|
|
@ -33,12 +33,15 @@ class Agent : ProcessAdapter() {
|
|||
READY,
|
||||
DISCONNECTED,
|
||||
UNAUTHORIZED,
|
||||
ISSUES_EXIST,
|
||||
}
|
||||
|
||||
private val statusFlow = MutableStateFlow(Status.NOT_INITIALIZED)
|
||||
val status = statusFlow.asStateFlow()
|
||||
private val authRequiredEventFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||
val authRequiredEvent = authRequiredEventFlow.asSharedFlow()
|
||||
private val currentIssueFlow = MutableStateFlow<String?>(null)
|
||||
val currentIssue = currentIssueFlow.asStateFlow()
|
||||
|
||||
open class AgentException(message: String) : Exception(message)
|
||||
|
||||
|
|
@ -81,20 +84,38 @@ class Agent : ProcessAdapter() {
|
|||
val anonymousUsageTracking: AnonymousUsageTracking? = null,
|
||||
) {
|
||||
data class Server(
|
||||
val endpoint: String,
|
||||
val endpoint: String? = null,
|
||||
val requestHeaders: Map<String, String>? = null,
|
||||
val requestTimeout: Int? = null,
|
||||
)
|
||||
|
||||
data class Completion(
|
||||
val maxPrefixLines: Int,
|
||||
val maxSuffixLines: Int,
|
||||
)
|
||||
val prompt: Prompt? = null,
|
||||
val debounce: Debounce? = null,
|
||||
val timeout: Timeout? = null,
|
||||
) {
|
||||
data class Prompt(
|
||||
val maxPrefixLines: Int? = null,
|
||||
val maxSuffixLines: Int? = null,
|
||||
)
|
||||
|
||||
data class Debounce(
|
||||
val mode: String? = null,
|
||||
val interval: Int? = null,
|
||||
)
|
||||
|
||||
data class Timeout(
|
||||
val auto: Int? = null,
|
||||
val manually: Int? = null,
|
||||
)
|
||||
}
|
||||
|
||||
data class Logs(
|
||||
val level: String,
|
||||
val level: String? = null,
|
||||
)
|
||||
|
||||
data class AnonymousUsageTracking(
|
||||
val disabled: Boolean,
|
||||
val disabled: Boolean? = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -109,8 +130,20 @@ class Agent : ProcessAdapter() {
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun updateConfig(config: Config): Boolean {
|
||||
return request("updateConfig", listOf(config))
|
||||
suspend fun updateConfig(key: String, config: Any): Boolean {
|
||||
return request("updateConfig", listOf(key, config))
|
||||
}
|
||||
|
||||
suspend fun clearConfig(key: String): Boolean {
|
||||
return request("clearConfig", listOf(key))
|
||||
}
|
||||
|
||||
suspend fun getIssues(): List<Map<String, Any>> {
|
||||
return request("getIssues", listOf())
|
||||
}
|
||||
|
||||
suspend fun getServerHealthState(): Map<String, Any>? {
|
||||
return request("getServerHealthState", listOf())
|
||||
}
|
||||
|
||||
data class CompletionRequest(
|
||||
|
|
@ -118,6 +151,7 @@ class Agent : ProcessAdapter() {
|
|||
val language: String,
|
||||
val text: String,
|
||||
val position: Int,
|
||||
val manually: Boolean?,
|
||||
)
|
||||
|
||||
data class CompletionResponse(
|
||||
|
|
@ -130,8 +164,16 @@ class Agent : ProcessAdapter() {
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun getCompletions(request: CompletionRequest): CompletionResponse? {
|
||||
return request("getCompletions", listOf(request))
|
||||
suspend fun requestAuthUrl(): AuthUrlResponse? {
|
||||
return request("requestAuthUrl", listOf())
|
||||
}
|
||||
|
||||
suspend fun waitForAuthToken(code: String) {
|
||||
return request("waitForAuthToken", listOf(code))
|
||||
}
|
||||
|
||||
suspend fun provideCompletions(request: CompletionRequest): CompletionResponse? {
|
||||
return request("provideCompletions", listOf(request))
|
||||
}
|
||||
|
||||
data class LogEventRequest(
|
||||
|
|
@ -148,7 +190,7 @@ class Agent : ProcessAdapter() {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun postEvent(event: LogEventRequest): Boolean {
|
||||
suspend fun postEvent(event: LogEventRequest) {
|
||||
return request("postEvent", listOf(event))
|
||||
}
|
||||
|
||||
|
|
@ -157,14 +199,6 @@ class Agent : ProcessAdapter() {
|
|||
val code: String,
|
||||
)
|
||||
|
||||
suspend fun requestAuthUrl(): AuthUrlResponse? {
|
||||
return request("requestAuthUrl", listOf())
|
||||
}
|
||||
|
||||
suspend fun waitForAuthToken(code: String) {
|
||||
return request("waitForAuthToken", listOf(code))
|
||||
}
|
||||
|
||||
fun close() {
|
||||
streamWriter.close()
|
||||
process.killProcess()
|
||||
|
|
@ -245,8 +279,12 @@ class Agent : ProcessAdapter() {
|
|||
"ready" -> Status.READY
|
||||
"disconnected" -> Status.DISCONNECTED
|
||||
"unauthorized" -> Status.UNAUTHORIZED
|
||||
"issuesExist" -> Status.ISSUES_EXIST
|
||||
else -> Status.NOT_INITIALIZED
|
||||
}
|
||||
if (statusFlow.value !== Status.ISSUES_EXIST) {
|
||||
currentIssueFlow.value = null
|
||||
}
|
||||
}
|
||||
|
||||
"configUpdated" -> {
|
||||
|
|
@ -258,6 +296,11 @@ class Agent : ProcessAdapter() {
|
|||
authRequiredEventFlow.tryEmit(Unit)
|
||||
}
|
||||
|
||||
"newIssue" -> {
|
||||
logger.info("Agent notification $event")
|
||||
currentIssueFlow.value = (event["issue"] as Map<*, *>)["name"] as String?
|
||||
}
|
||||
|
||||
else -> {
|
||||
logger.error("Agent notification, unknown event name: ${event["event"]}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.intellij.notification.Notification
|
|||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.notification.Notifications
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.ApplicationInfo
|
||||
import com.intellij.openapi.application.ReadAction
|
||||
|
|
@ -34,7 +35,12 @@ class AgentService : Disposable {
|
|||
private val logger = Logger.getInstance(AgentService::class.java)
|
||||
private var agent: Agent = Agent()
|
||||
val scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
var authNotification: Notification? = null
|
||||
private set
|
||||
var issueNotification: Notification? = null
|
||||
private set
|
||||
val status get() = agent.status
|
||||
val currentIssue get() = agent.currentIssue
|
||||
|
||||
init {
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
|
|
@ -51,21 +57,26 @@ class AgentService : Disposable {
|
|||
logger.info("Agent init done.")
|
||||
} catch (e: Exception) {
|
||||
logger.error("Agent init failed: $e")
|
||||
anonymousUsageLogger.event("IntelliJInitFailed", mapOf(
|
||||
"client" to client,
|
||||
"error" to e.stackTraceToString()
|
||||
))
|
||||
anonymousUsageLogger.event(
|
||||
"IntelliJInitFailed", mapOf(
|
||||
"client" to client, "error" to e.stackTraceToString()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
settings.state.collect {
|
||||
updateConfig(createAgentConfig(it))
|
||||
if (it.serverEndpoint.isNotBlank()) {
|
||||
updateConfig("server.endpoint", it.serverEndpoint)
|
||||
} else {
|
||||
clearConfig("server.endpoint")
|
||||
}
|
||||
updateConfig("anonymousUsageTracking.disable", it.isAnonymousUsageTrackingDisabled)
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
logger.info("Add authRequired event listener.")
|
||||
agent.authRequiredEvent.collect {
|
||||
logger.info("Will show auth required notification.")
|
||||
val notification = Notification(
|
||||
|
|
@ -73,18 +84,31 @@ class AgentService : Disposable {
|
|||
"Authorization required for Tabby server",
|
||||
NotificationType.WARNING,
|
||||
)
|
||||
notification.addAction(object : OpenAuthPage() {
|
||||
init {
|
||||
getTemplatePresentation().text = "Open Authorization Page..."
|
||||
getTemplatePresentation().description = "Open the authorization web page in your web browser."
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
notification.expire()
|
||||
super.actionPerformed(e)
|
||||
}
|
||||
})
|
||||
notification.addAction(ActionManager.getInstance().getAction("Tabby.OpenAuthPage"))
|
||||
invokeLater {
|
||||
authNotification?.expire()
|
||||
authNotification = notification
|
||||
Notifications.Bus.notify(notification)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
agent.currentIssue.collect { issueName ->
|
||||
val content = when (issueName) {
|
||||
"slowCompletionResponseTime" -> "Completion requests appear to take too much time"
|
||||
"highCompletionTimeoutRate" -> "Most completion requests timed out"
|
||||
else -> return@collect
|
||||
}
|
||||
val notification = Notification(
|
||||
"com.tabbyml.intellijtabby.notification.warning",
|
||||
content,
|
||||
NotificationType.WARNING,
|
||||
)
|
||||
notification.addAction(ActionManager.getInstance().getAction("Tabby.CheckIssueDetail"))
|
||||
invokeLater {
|
||||
issueNotification?.expire()
|
||||
issueNotification = notification
|
||||
Notifications.Bus.notify(notification)
|
||||
}
|
||||
}
|
||||
|
|
@ -114,24 +138,30 @@ class AgentService : Disposable {
|
|||
agent.status.first { it != Agent.Status.NOT_INITIALIZED }
|
||||
}
|
||||
|
||||
private suspend fun updateConfig(config: Agent.Config) {
|
||||
private suspend fun updateConfig(key: String, config: Any) {
|
||||
waitForInitialized()
|
||||
agent.updateConfig(config)
|
||||
agent.updateConfig(key, config)
|
||||
}
|
||||
|
||||
suspend fun getCompletion(editor: Editor, offset: Int): Agent.CompletionResponse? {
|
||||
private suspend fun clearConfig(key: String) {
|
||||
waitForInitialized()
|
||||
agent.clearConfig(key)
|
||||
}
|
||||
|
||||
suspend fun provideCompletion(editor: Editor, offset: Int, manually: Boolean = false): Agent.CompletionResponse? {
|
||||
waitForInitialized()
|
||||
return ReadAction.compute<PsiFile, Throwable> {
|
||||
editor.project?.let { project ->
|
||||
PsiDocumentManager.getInstance(project).getPsiFile(editor.document)
|
||||
}
|
||||
}?.let { file ->
|
||||
agent.getCompletions(
|
||||
agent.provideCompletions(
|
||||
Agent.CompletionRequest(
|
||||
file.virtualFile.path,
|
||||
file.getLanguageId(),
|
||||
editor.document.text,
|
||||
offset
|
||||
offset,
|
||||
manually,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -166,16 +196,26 @@ class AgentService : Disposable {
|
|||
}
|
||||
} else {
|
||||
Notification(
|
||||
"com.tabbyml.intellijtabby.notification.info",
|
||||
"You are already authorized.",
|
||||
NotificationType.INFORMATION
|
||||
"com.tabbyml.intellijtabby.notification.info", "You are already authorized.", NotificationType.INFORMATION
|
||||
)
|
||||
}
|
||||
invokeLater {
|
||||
authNotification?.expire()
|
||||
authNotification = notification
|
||||
Notifications.Bus.notify(notification)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCurrentIssueDetail(): Map<String, Any>? {
|
||||
waitForInitialized()
|
||||
return agent.getIssues().firstOrNull { it["name"] == currentIssue.value }
|
||||
}
|
||||
|
||||
suspend fun getServerHealthState(): Map<String, Any>? {
|
||||
waitForInitialized()
|
||||
return agent.getServerHealthState()
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
agent.close()
|
||||
}
|
||||
|
|
@ -183,15 +223,16 @@ class AgentService : Disposable {
|
|||
companion object {
|
||||
// Language id: https://code.visualstudio.com/docs/languages/identifiers
|
||||
private fun PsiFile.getLanguageId(): String {
|
||||
if (this.language != Language.ANY
|
||||
&& this.language.id.toLowerCasePreservingASCIIRules() !in arrayOf("txt", "text", "textmate")
|
||||
if (this.language != Language.ANY && this.language.id.toLowerCasePreservingASCIIRules() !in arrayOf(
|
||||
"txt",
|
||||
"text",
|
||||
"textmate"
|
||||
)
|
||||
) {
|
||||
if (languageIdMap.containsKey(this.language.id)) {
|
||||
return languageIdMap[this.language.id]!!
|
||||
}
|
||||
return this.language.id.toLowerCasePreservingASCIIRules()
|
||||
.replace("#", "sharp")
|
||||
.replace("++", "pp")
|
||||
return this.language.id.toLowerCasePreservingASCIIRules().replace("#", "sharp").replace("++", "pp")
|
||||
.replace(" ", "")
|
||||
}
|
||||
return if (filetypeMap.containsKey(this.fileType.defaultExtension)) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.intellij.openapi.editor.Editor
|
|||
import com.tabbyml.intellijtabby.agent.AgentService
|
||||
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Service
|
||||
|
|
@ -19,7 +18,7 @@ class CompletionScheduler {
|
|||
var scheduled: CompletionContext? = null
|
||||
private set
|
||||
|
||||
fun schedule(editor: Editor, offset: Int, triggerDelay: Long = 150, manually: Boolean = false) {
|
||||
fun schedule(editor: Editor, offset: Int, manually: Boolean = false) {
|
||||
val agentService = service<AgentService>()
|
||||
val inlineCompletionService = service<InlineCompletionService>()
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
|
|
@ -28,14 +27,10 @@ class CompletionScheduler {
|
|||
if (!manually && !settings.isAutoCompletionEnabled) {
|
||||
return@launch
|
||||
}
|
||||
logger.info("Schedule completion at $offset after $triggerDelay ms.")
|
||||
|
||||
delay(triggerDelay)
|
||||
if (!manually && !settings.isAutoCompletionEnabled) {
|
||||
return@launch
|
||||
}
|
||||
logger.info("Trigger completion at $offset")
|
||||
agentService.getCompletion(editor, offset)?.let {
|
||||
agentService.provideCompletion(editor, offset, manually)?.let {
|
||||
logger.info("Show completion at $offset: $it")
|
||||
inlineCompletionService.show(editor, offset, it)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
|
|||
val settings = service<ApplicationSettingsState>()
|
||||
val agentService = service<AgentService>()
|
||||
updateStatusScope.launch {
|
||||
settings.state.combine(agentService.status) { settings, agentStatus ->
|
||||
Pair(settings, agentStatus)
|
||||
combine(settings.state, agentService.status, agentService.currentIssue) { settings, agentStatus, currentIssue ->
|
||||
Triple(settings, agentStatus, currentIssue)
|
||||
}.collect {
|
||||
updateStatus(it.first, it.second)
|
||||
updateStatus(it.first, it.second, it.third)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +73,7 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
|
|||
val actionManager = ActionManager.getInstance()
|
||||
return arrayOf(
|
||||
actionManager.getAction("Tabby.OpenAuthPage"),
|
||||
actionManager.getAction("Tabby.CheckIssueDetail"),
|
||||
actionManager.getAction("Tabby.ToggleAutoCompletionEnabled"),
|
||||
actionManager.getAction("Tabby.OpenSettings"),
|
||||
)
|
||||
|
|
@ -85,7 +86,7 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
|
|||
)
|
||||
}
|
||||
|
||||
private fun updateStatus(settingsState: ApplicationSettingsState.State, agentStatus: Agent.Status) {
|
||||
private fun updateStatus(settingsState: ApplicationSettingsState.State, agentStatus: Agent.Status, currentIssue: String?) {
|
||||
if (!settingsState.isAutoCompletionEnabled) {
|
||||
icon = AllIcons.Windows.CloseSmall
|
||||
tooltip = "Tabby: Auto completion is disabled"
|
||||
|
|
@ -107,6 +108,14 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
|
|||
icon = AllIcons.General.Warning
|
||||
tooltip = "Tabby: Requires authorization"
|
||||
}
|
||||
Agent.Status.ISSUES_EXIST -> {
|
||||
icon = AllIcons.General.Warning
|
||||
tooltip = when(currentIssue) {
|
||||
"slowCompletionResponseTime" -> "Tabby: Completion requests appear to take too much time"
|
||||
"highCompletionTimeoutRate" -> "Tabby: Most completion requests timed out"
|
||||
else -> "Tabby: Issues exist"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
invokeLater {
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@
|
|||
text="Open Authorization Page..."
|
||||
description="Open the authorization web page in your web browser.">
|
||||
</action>
|
||||
<action id="Tabby.CheckIssueDetail"
|
||||
class="com.tabbyml.intellijtabby.actions.CheckIssueDetail"
|
||||
text="Check Issue Detail..."
|
||||
description="Show detail information for current issue.">
|
||||
</action>
|
||||
<action id="Tabby.ToggleAutoCompletionEnabled"
|
||||
class="com.tabbyml.intellijtabby.actions.ToggleAutoCompletionEnabled">
|
||||
</action>
|
||||
|
|
|
|||
Loading…
Reference in New Issue