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.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
open class OpenAuthPage : AnAction() {
|
class OpenAuthPage : AnAction() {
|
||||||
private val logger = Logger.getInstance(OpenAuthPage::class.java)
|
private val logger = Logger.getInstance(OpenAuthPage::class.java)
|
||||||
|
|
||||||
override fun actionPerformed(e: AnActionEvent) {
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
val agentService = service<AgentService>()
|
||||||
|
agentService.authNotification?.expire()
|
||||||
|
|
||||||
val task = object : Task.Modal(
|
val task = object : Task.Modal(
|
||||||
e.project,
|
e.project,
|
||||||
"Tabby Server Authorization",
|
"Tabby Server Authorization",
|
||||||
|
|
@ -24,7 +27,6 @@ open class OpenAuthPage : AnAction() {
|
||||||
) {
|
) {
|
||||||
lateinit var job: Job
|
lateinit var job: Job
|
||||||
override fun run(indicator: ProgressIndicator) {
|
override fun run(indicator: ProgressIndicator) {
|
||||||
val agentService = service<AgentService>()
|
|
||||||
job = agentService.scope.launch {
|
job = agentService.scope.launch {
|
||||||
agentService.requestAuth(indicator)
|
agentService.requestAuth(indicator)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class TriggerCompletion : AnAction() {
|
||||||
val completionScheduler = service<CompletionScheduler>()
|
val completionScheduler = service<CompletionScheduler>()
|
||||||
val editor = e.getRequiredData(CommonDataKeys.EDITOR)
|
val editor = e.getRequiredData(CommonDataKeys.EDITOR)
|
||||||
val offset = editor.caretModel.primaryCaret.offset
|
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) {
|
override fun update(e: AnActionEvent) {
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,15 @@ class Agent : ProcessAdapter() {
|
||||||
READY,
|
READY,
|
||||||
DISCONNECTED,
|
DISCONNECTED,
|
||||||
UNAUTHORIZED,
|
UNAUTHORIZED,
|
||||||
|
ISSUES_EXIST,
|
||||||
}
|
}
|
||||||
|
|
||||||
private val statusFlow = MutableStateFlow(Status.NOT_INITIALIZED)
|
private val statusFlow = MutableStateFlow(Status.NOT_INITIALIZED)
|
||||||
val status = statusFlow.asStateFlow()
|
val status = statusFlow.asStateFlow()
|
||||||
private val authRequiredEventFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
private val authRequiredEventFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||||
val authRequiredEvent = authRequiredEventFlow.asSharedFlow()
|
val authRequiredEvent = authRequiredEventFlow.asSharedFlow()
|
||||||
|
private val currentIssueFlow = MutableStateFlow<String?>(null)
|
||||||
|
val currentIssue = currentIssueFlow.asStateFlow()
|
||||||
|
|
||||||
open class AgentException(message: String) : Exception(message)
|
open class AgentException(message: String) : Exception(message)
|
||||||
|
|
||||||
|
|
@ -81,20 +84,38 @@ class Agent : ProcessAdapter() {
|
||||||
val anonymousUsageTracking: AnonymousUsageTracking? = null,
|
val anonymousUsageTracking: AnonymousUsageTracking? = null,
|
||||||
) {
|
) {
|
||||||
data class Server(
|
data class Server(
|
||||||
val endpoint: String,
|
val endpoint: String? = null,
|
||||||
|
val requestHeaders: Map<String, String>? = null,
|
||||||
|
val requestTimeout: Int? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Completion(
|
data class Completion(
|
||||||
val maxPrefixLines: Int,
|
val prompt: Prompt? = null,
|
||||||
val maxSuffixLines: Int,
|
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(
|
data class Logs(
|
||||||
val level: String,
|
val level: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class AnonymousUsageTracking(
|
data class AnonymousUsageTracking(
|
||||||
val disabled: Boolean,
|
val disabled: Boolean? = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,8 +130,20 @@ class Agent : ProcessAdapter() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateConfig(config: Config): Boolean {
|
suspend fun updateConfig(key: String, config: Any): Boolean {
|
||||||
return request("updateConfig", listOf(config))
|
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(
|
data class CompletionRequest(
|
||||||
|
|
@ -118,6 +151,7 @@ class Agent : ProcessAdapter() {
|
||||||
val language: String,
|
val language: String,
|
||||||
val text: String,
|
val text: String,
|
||||||
val position: Int,
|
val position: Int,
|
||||||
|
val manually: Boolean?,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class CompletionResponse(
|
data class CompletionResponse(
|
||||||
|
|
@ -130,8 +164,16 @@ class Agent : ProcessAdapter() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCompletions(request: CompletionRequest): CompletionResponse? {
|
suspend fun requestAuthUrl(): AuthUrlResponse? {
|
||||||
return request("getCompletions", listOf(request))
|
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(
|
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))
|
return request("postEvent", listOf(event))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,14 +199,6 @@ class Agent : ProcessAdapter() {
|
||||||
val code: String,
|
val code: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun requestAuthUrl(): AuthUrlResponse? {
|
|
||||||
return request("requestAuthUrl", listOf())
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun waitForAuthToken(code: String) {
|
|
||||||
return request("waitForAuthToken", listOf(code))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun close() {
|
fun close() {
|
||||||
streamWriter.close()
|
streamWriter.close()
|
||||||
process.killProcess()
|
process.killProcess()
|
||||||
|
|
@ -245,8 +279,12 @@ class Agent : ProcessAdapter() {
|
||||||
"ready" -> Status.READY
|
"ready" -> Status.READY
|
||||||
"disconnected" -> Status.DISCONNECTED
|
"disconnected" -> Status.DISCONNECTED
|
||||||
"unauthorized" -> Status.UNAUTHORIZED
|
"unauthorized" -> Status.UNAUTHORIZED
|
||||||
|
"issuesExist" -> Status.ISSUES_EXIST
|
||||||
else -> Status.NOT_INITIALIZED
|
else -> Status.NOT_INITIALIZED
|
||||||
}
|
}
|
||||||
|
if (statusFlow.value !== Status.ISSUES_EXIST) {
|
||||||
|
currentIssueFlow.value = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"configUpdated" -> {
|
"configUpdated" -> {
|
||||||
|
|
@ -258,6 +296,11 @@ class Agent : ProcessAdapter() {
|
||||||
authRequiredEventFlow.tryEmit(Unit)
|
authRequiredEventFlow.tryEmit(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"newIssue" -> {
|
||||||
|
logger.info("Agent notification $event")
|
||||||
|
currentIssueFlow.value = (event["issue"] as Map<*, *>)["name"] as String?
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
logger.error("Agent notification, unknown event name: ${event["event"]}")
|
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.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.AnActionEvent
|
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
|
||||||
|
|
@ -34,7 +35,12 @@ class AgentService : Disposable {
|
||||||
private val logger = Logger.getInstance(AgentService::class.java)
|
private val logger = Logger.getInstance(AgentService::class.java)
|
||||||
private var agent: Agent = Agent()
|
private var agent: Agent = Agent()
|
||||||
val scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
val scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
var authNotification: Notification? = null
|
||||||
|
private set
|
||||||
|
var issueNotification: Notification? = null
|
||||||
|
private set
|
||||||
val status get() = agent.status
|
val status get() = agent.status
|
||||||
|
val currentIssue get() = agent.currentIssue
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val settings = service<ApplicationSettingsState>()
|
val settings = service<ApplicationSettingsState>()
|
||||||
|
|
@ -51,21 +57,26 @@ class AgentService : Disposable {
|
||||||
logger.info("Agent init done.")
|
logger.info("Agent init done.")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.error("Agent init failed: $e")
|
logger.error("Agent init failed: $e")
|
||||||
anonymousUsageLogger.event("IntelliJInitFailed", mapOf(
|
anonymousUsageLogger.event(
|
||||||
"client" to client,
|
"IntelliJInitFailed", mapOf(
|
||||||
"error" to e.stackTraceToString()
|
"client" to client, "error" to e.stackTraceToString()
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
settings.state.collect {
|
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 {
|
scope.launch {
|
||||||
logger.info("Add authRequired event listener.")
|
|
||||||
agent.authRequiredEvent.collect {
|
agent.authRequiredEvent.collect {
|
||||||
logger.info("Will show auth required notification.")
|
logger.info("Will show auth required notification.")
|
||||||
val notification = Notification(
|
val notification = Notification(
|
||||||
|
|
@ -73,18 +84,31 @@ class AgentService : Disposable {
|
||||||
"Authorization required for Tabby server",
|
"Authorization required for Tabby server",
|
||||||
NotificationType.WARNING,
|
NotificationType.WARNING,
|
||||||
)
|
)
|
||||||
notification.addAction(object : OpenAuthPage() {
|
notification.addAction(ActionManager.getInstance().getAction("Tabby.OpenAuthPage"))
|
||||||
init {
|
invokeLater {
|
||||||
getTemplatePresentation().text = "Open Authorization Page..."
|
authNotification?.expire()
|
||||||
getTemplatePresentation().description = "Open the authorization web page in your web browser."
|
authNotification = notification
|
||||||
|
Notifications.Bus.notify(notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun actionPerformed(e: AnActionEvent) {
|
scope.launch {
|
||||||
notification.expire()
|
agent.currentIssue.collect { issueName ->
|
||||||
super.actionPerformed(e)
|
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 {
|
invokeLater {
|
||||||
|
issueNotification?.expire()
|
||||||
|
issueNotification = notification
|
||||||
Notifications.Bus.notify(notification)
|
Notifications.Bus.notify(notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -114,24 +138,30 @@ class AgentService : Disposable {
|
||||||
agent.status.first { it != Agent.Status.NOT_INITIALIZED }
|
agent.status.first { it != Agent.Status.NOT_INITIALIZED }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateConfig(config: Agent.Config) {
|
private suspend fun updateConfig(key: String, config: Any) {
|
||||||
waitForInitialized()
|
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()
|
waitForInitialized()
|
||||||
return ReadAction.compute<PsiFile, Throwable> {
|
return ReadAction.compute<PsiFile, Throwable> {
|
||||||
editor.project?.let { project ->
|
editor.project?.let { project ->
|
||||||
PsiDocumentManager.getInstance(project).getPsiFile(editor.document)
|
PsiDocumentManager.getInstance(project).getPsiFile(editor.document)
|
||||||
}
|
}
|
||||||
}?.let { file ->
|
}?.let { file ->
|
||||||
agent.getCompletions(
|
agent.provideCompletions(
|
||||||
Agent.CompletionRequest(
|
Agent.CompletionRequest(
|
||||||
file.virtualFile.path,
|
file.virtualFile.path,
|
||||||
file.getLanguageId(),
|
file.getLanguageId(),
|
||||||
editor.document.text,
|
editor.document.text,
|
||||||
offset
|
offset,
|
||||||
|
manually,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -166,16 +196,26 @@ class AgentService : Disposable {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Notification(
|
Notification(
|
||||||
"com.tabbyml.intellijtabby.notification.info",
|
"com.tabbyml.intellijtabby.notification.info", "You are already authorized.", NotificationType.INFORMATION
|
||||||
"You are already authorized.",
|
|
||||||
NotificationType.INFORMATION
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
invokeLater {
|
invokeLater {
|
||||||
|
authNotification?.expire()
|
||||||
|
authNotification = notification
|
||||||
Notifications.Bus.notify(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() {
|
override fun dispose() {
|
||||||
agent.close()
|
agent.close()
|
||||||
}
|
}
|
||||||
|
|
@ -183,15 +223,16 @@ class AgentService : Disposable {
|
||||||
companion object {
|
companion object {
|
||||||
// Language id: https://code.visualstudio.com/docs/languages/identifiers
|
// Language id: https://code.visualstudio.com/docs/languages/identifiers
|
||||||
private fun PsiFile.getLanguageId(): String {
|
private fun PsiFile.getLanguageId(): String {
|
||||||
if (this.language != Language.ANY
|
if (this.language != Language.ANY && this.language.id.toLowerCasePreservingASCIIRules() !in arrayOf(
|
||||||
&& this.language.id.toLowerCasePreservingASCIIRules() !in arrayOf("txt", "text", "textmate")
|
"txt",
|
||||||
|
"text",
|
||||||
|
"textmate"
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
if (languageIdMap.containsKey(this.language.id)) {
|
if (languageIdMap.containsKey(this.language.id)) {
|
||||||
return languageIdMap[this.language.id]!!
|
return languageIdMap[this.language.id]!!
|
||||||
}
|
}
|
||||||
return this.language.id.toLowerCasePreservingASCIIRules()
|
return this.language.id.toLowerCasePreservingASCIIRules().replace("#", "sharp").replace("++", "pp")
|
||||||
.replace("#", "sharp")
|
|
||||||
.replace("++", "pp")
|
|
||||||
.replace(" ", "")
|
.replace(" ", "")
|
||||||
}
|
}
|
||||||
return if (filetypeMap.containsKey(this.fileType.defaultExtension)) {
|
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.agent.AgentService
|
||||||
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
|
@ -19,7 +18,7 @@ class CompletionScheduler {
|
||||||
var scheduled: CompletionContext? = null
|
var scheduled: CompletionContext? = null
|
||||||
private set
|
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 agentService = service<AgentService>()
|
||||||
val inlineCompletionService = service<InlineCompletionService>()
|
val inlineCompletionService = service<InlineCompletionService>()
|
||||||
val settings = service<ApplicationSettingsState>()
|
val settings = service<ApplicationSettingsState>()
|
||||||
|
|
@ -28,14 +27,10 @@ class CompletionScheduler {
|
||||||
if (!manually && !settings.isAutoCompletionEnabled) {
|
if (!manually && !settings.isAutoCompletionEnabled) {
|
||||||
return@launch
|
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")
|
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)
|
inlineCompletionService.show(editor, offset, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,10 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
|
||||||
val settings = service<ApplicationSettingsState>()
|
val settings = service<ApplicationSettingsState>()
|
||||||
val agentService = service<AgentService>()
|
val agentService = service<AgentService>()
|
||||||
updateStatusScope.launch {
|
updateStatusScope.launch {
|
||||||
settings.state.combine(agentService.status) { settings, agentStatus ->
|
combine(settings.state, agentService.status, agentService.currentIssue) { settings, agentStatus, currentIssue ->
|
||||||
Pair(settings, agentStatus)
|
Triple(settings, agentStatus, currentIssue)
|
||||||
}.collect {
|
}.collect {
|
||||||
updateStatus(it.first, it.second)
|
updateStatus(it.first, it.second, it.third)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -73,6 +73,7 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
|
||||||
val actionManager = ActionManager.getInstance()
|
val actionManager = ActionManager.getInstance()
|
||||||
return arrayOf(
|
return arrayOf(
|
||||||
actionManager.getAction("Tabby.OpenAuthPage"),
|
actionManager.getAction("Tabby.OpenAuthPage"),
|
||||||
|
actionManager.getAction("Tabby.CheckIssueDetail"),
|
||||||
actionManager.getAction("Tabby.ToggleAutoCompletionEnabled"),
|
actionManager.getAction("Tabby.ToggleAutoCompletionEnabled"),
|
||||||
actionManager.getAction("Tabby.OpenSettings"),
|
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) {
|
if (!settingsState.isAutoCompletionEnabled) {
|
||||||
icon = AllIcons.Windows.CloseSmall
|
icon = AllIcons.Windows.CloseSmall
|
||||||
tooltip = "Tabby: Auto completion is disabled"
|
tooltip = "Tabby: Auto completion is disabled"
|
||||||
|
|
@ -107,6 +108,14 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
|
||||||
icon = AllIcons.General.Warning
|
icon = AllIcons.General.Warning
|
||||||
tooltip = "Tabby: Requires authorization"
|
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 {
|
invokeLater {
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,11 @@
|
||||||
text="Open Authorization Page..."
|
text="Open Authorization Page..."
|
||||||
description="Open the authorization web page in your web browser.">
|
description="Open the authorization web page in your web browser.">
|
||||||
</action>
|
</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"
|
<action id="Tabby.ToggleAutoCompletionEnabled"
|
||||||
class="com.tabbyml.intellijtabby.actions.ToggleAutoCompletionEnabled">
|
class="com.tabbyml.intellijtabby.actions.ToggleAutoCompletionEnabled">
|
||||||
</action>
|
</action>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue