feat(intellij): add check connection button in settings page. (#809)
* feat(intellij): add check connection in settings page. * fix(intellij): update notification message. * fix(intellij): fix language mapping for file extension.release-fix-intellij-update-support-version-range
parent
d47dac9041
commit
76679bc249
|
|
@ -0,0 +1 @@
|
|||
*.wasm filter=lfs diff=lfs merge=lfs -text
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1b69c5af834fd23053238e484c7fe9ed2f121d5b1fe32242af78576d67e49f1e
|
||||
size 240169
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:72d0f97ba6c3134d7873ec5c9d0fd3c1f5137f4eac4dda0709993d92809e62b6
|
||||
size 474189
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1190cddd839b78c2aec737573399a71c23fe9a546d3543f86304c4c68ca73852
|
||||
size 990787
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:273f9ce6f2c595ad4e63b3195513b61974ae1ec513efcce39da1afa90574ef38
|
||||
size 844087
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:060422a330f9c819a10e7310788d336dbcb53cc6a4be0e91d40f644564080f97
|
||||
size 1182114
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:17382e1a69bd628107e8dfe37d31d57f7ba948e5f2da77e56a8aa010488dc5ae
|
||||
size 186526
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
"repository": "https://github.com/TabbyML/tabby",
|
||||
"scripts": {
|
||||
"preupgrade-agent": "cd ../tabby-agent && yarn build",
|
||||
"upgrade-agent": "rimraf ./node_scripts && cpy ../tabby-agent/dist/cli.js ./node_scripts/ --flat --rename=tabby-agent.js"
|
||||
"upgrade-agent": "rimraf ./node_scripts && cpy ../tabby-agent/dist/cli.js ./node_scripts/ --flat --rename=tabby-agent.js && cpy ../tabby-agent/dist/wasm/* ./node_scripts/wasm/ --flat"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cpy-cli": "^4.2.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tabbyml.intellijtabby.actions
|
||||
|
||||
import com.intellij.ide.BrowserUtil
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
|
|
@ -10,7 +9,6 @@ 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 com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
|
||||
|
|
@ -23,17 +21,25 @@ class CheckIssueDetail : AnAction() {
|
|||
|
||||
agentService.scope.launch {
|
||||
val detail = agentService.getCurrentIssueDetail() ?: return@launch
|
||||
val serverHealthState = agentService.getServerHealthState()
|
||||
val agentConfig = agentService.getConfig()
|
||||
logger.info("Show issue detail: $detail, $serverHealthState, $agentConfig")
|
||||
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, agentConfig)
|
||||
invokeLater {
|
||||
Messages.showMessageDialog(message, title, Messages.getInformationIcon())
|
||||
if (detail["name"] == "connectionFailed") {
|
||||
invokeLater {
|
||||
val messages = "<html>" + (detail["message"] as String?)?.replace("\n", "<br/>") + "</html>"
|
||||
Messages.showErrorDialog(messages, "Cannot Connect to Tabby Server")
|
||||
}
|
||||
return@launch
|
||||
} else {
|
||||
val serverHealthState = agentService.getServerHealthState()
|
||||
val agentConfig = agentService.getConfig()
|
||||
logger.info("Show issue detail: $detail, $serverHealthState, $agentConfig")
|
||||
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, agentConfig)
|
||||
invokeLater {
|
||||
Messages.showInfoMessage(message, title)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,8 @@ import com.intellij.psi.PsiDocumentManager
|
|||
import com.intellij.psi.PsiFile
|
||||
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
||||
import io.ktor.util.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@Service
|
||||
class AgentService : Disposable {
|
||||
|
|
@ -89,14 +86,20 @@ class AgentService : Disposable {
|
|||
}
|
||||
|
||||
scope.launch {
|
||||
settings.state.collect {
|
||||
if (it.serverEndpoint.isNotBlank()) {
|
||||
updateConfig("server.endpoint", it.serverEndpoint)
|
||||
} else {
|
||||
clearConfig("server.endpoint")
|
||||
}
|
||||
updateClientProperties("user", "intellij.triggerMode", it.completionTriggerMode)
|
||||
updateConfig("anonymousUsageTracking.disable", it.isAnonymousUsageTrackingDisabled)
|
||||
settings.serverEndpointState.collect {
|
||||
setEndpoint(it)
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
settings.completionTriggerModeState.collect {
|
||||
updateClientProperties("user", "intellij.triggerMode", it)
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
settings.isAnonymousUsageTrackingDisabledState.collect {
|
||||
updateConfig("anonymousUsageTracking.disable", it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -117,20 +120,43 @@ class AgentService : Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
scope.launch {
|
||||
agent.status.collect { status ->
|
||||
if (status == Agent.Status.READY) {
|
||||
completionResponseWarningShown = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 message = when (issueName) {
|
||||
"connectionFailed" -> "Cannot connect to Tabby server"
|
||||
"slowCompletionResponseTime" -> if (!completionResponseWarningShown) {
|
||||
completionResponseWarningShown = true
|
||||
"Completion requests appear to take too much time"
|
||||
} else {
|
||||
return@collect
|
||||
}
|
||||
|
||||
"highCompletionTimeoutRate" -> if (!completionResponseWarningShown) {
|
||||
completionResponseWarningShown = true
|
||||
"Most completion requests timed out"
|
||||
} else {
|
||||
return@collect
|
||||
}
|
||||
|
||||
else -> {
|
||||
invokeLater {
|
||||
issueNotification?.expire()
|
||||
}
|
||||
return@collect
|
||||
}
|
||||
}
|
||||
if (completionResponseWarningShown) {
|
||||
return@collect
|
||||
}
|
||||
completionResponseWarningShown = true
|
||||
val notification = Notification(
|
||||
"com.tabbyml.intellijtabby.notification.warning",
|
||||
content,
|
||||
message,
|
||||
NotificationType.WARNING,
|
||||
)
|
||||
notification.addAction(ActionManager.getInstance().getAction("Tabby.CheckIssueDetail"))
|
||||
|
|
@ -202,6 +228,14 @@ class AgentService : Disposable {
|
|||
agent.clearConfig(key)
|
||||
}
|
||||
|
||||
suspend fun setEndpoint(endpoint: String) {
|
||||
if (endpoint.isNotBlank()) {
|
||||
updateConfig("server.endpoint", endpoint)
|
||||
} else {
|
||||
clearConfig("server.endpoint")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getConfig(): Agent.Config {
|
||||
waitForInitialized()
|
||||
return agent.getConfig()
|
||||
|
|
@ -287,22 +321,24 @@ 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"
|
||||
)
|
||||
return if (this.language != Language.ANY &&
|
||||
this.language.id.isNotBlank() &&
|
||||
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")
|
||||
languageIdMap[this.language.id] ?: this.language.id
|
||||
.toLowerCasePreservingASCIIRules()
|
||||
.replace("#", "sharp")
|
||||
.replace("++", "pp")
|
||||
.replace(" ", "")
|
||||
}
|
||||
return if (filetypeMap.containsKey(this.fileType.defaultExtension)) {
|
||||
filetypeMap[this.fileType.defaultExtension]!!
|
||||
} else {
|
||||
this.fileType.defaultExtension.toLowerCasePreservingASCIIRules()
|
||||
val ext = this.fileType.defaultExtension.ifBlank {
|
||||
this.virtualFile.name.substringAfterLast(".")
|
||||
}
|
||||
if (ext.isNotBlank()) {
|
||||
filetypeMap[ext] ?: ext.toLowerCasePreservingASCIIRules()
|
||||
} else {
|
||||
"plaintext"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,12 @@ class CompletionProvider {
|
|||
clear()
|
||||
val job = agentService.scope.launch {
|
||||
logger.info("Trigger completion at $offset")
|
||||
agentService.provideCompletion(editor, offset, manually)?.let {
|
||||
logger.info("Show completion at $offset: $it")
|
||||
inlineCompletionService.show(editor, offset, it)
|
||||
agentService.provideCompletion(editor, offset, manually).let {
|
||||
ongoingCompletionFlow.value = null
|
||||
if (it != null) {
|
||||
logger.info("Show completion at $offset: $it")
|
||||
inlineCompletionService.show(editor, offset, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
ongoingCompletionFlow.value = CompletionContext(editor, offset, job)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
package com.tabbyml.intellijtabby.settings
|
||||
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.invokeLater
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.ui.components.JBCheckBox
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.JBRadioButton
|
||||
|
|
@ -7,7 +14,12 @@ import com.intellij.ui.components.JBTextField
|
|||
import com.intellij.util.ui.FormBuilder
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.tabbyml.intellijtabby.agent.Agent
|
||||
import com.tabbyml.intellijtabby.agent.AgentService
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.swing.ButtonGroup
|
||||
import javax.swing.JButton
|
||||
import javax.swing.JPanel
|
||||
|
||||
private fun FormBuilder.addCopyableTooltip(text: String): FormBuilder {
|
||||
|
|
@ -26,6 +38,82 @@ private fun FormBuilder.addCopyableTooltip(text: String): FormBuilder {
|
|||
|
||||
class ApplicationSettingsPanel {
|
||||
private val serverEndpointTextField = JBTextField()
|
||||
private val serverEndpointCheckConnectionButton = JButton("Check connection").apply {
|
||||
addActionListener {
|
||||
val parentComponent = this@ApplicationSettingsPanel.mainPanel
|
||||
val agentService = service<AgentService>()
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
|
||||
val task = object : Task.Modal(
|
||||
null,
|
||||
parentComponent,
|
||||
"Check Connection",
|
||||
true
|
||||
) {
|
||||
lateinit var job: Job
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
job = agentService.scope.launch {
|
||||
indicator.isIndeterminate = true
|
||||
indicator.text = "Checking connection..."
|
||||
settings.serverEndpoint = serverEndpointTextField.text
|
||||
agentService.setEndpoint(serverEndpointTextField.text)
|
||||
when (agentService.status.value) {
|
||||
Agent.Status.READY -> {
|
||||
invokeLater(ModalityState.stateForComponent(parentComponent)) {
|
||||
Messages.showInfoMessage(
|
||||
parentComponent,
|
||||
"Successfully connected to the Tabby server.",
|
||||
"Check Connection Completed"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Agent.Status.UNAUTHORIZED -> {
|
||||
agentService.requestAuth(indicator)
|
||||
if (agentService.status.value == Agent.Status.READY) {
|
||||
invokeLater(ModalityState.stateForComponent(parentComponent)) {
|
||||
Messages.showInfoMessage(
|
||||
parentComponent,
|
||||
"Successfully connected to the Tabby server.",
|
||||
"Check Connection Completed"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
invokeLater(ModalityState.stateForComponent(parentComponent)) {
|
||||
Messages.showErrorDialog(
|
||||
parentComponent,
|
||||
"Failed to connect to the Tabby server.",
|
||||
"Check Connection Failed"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val detail = agentService.getCurrentIssueDetail()
|
||||
if (detail?.get("name") == "connectionFailed") {
|
||||
invokeLater(ModalityState.stateForComponent(parentComponent)) {
|
||||
val errorMessage = (detail["message"] as String?)?.replace("\n", "<br/>") ?: ""
|
||||
val messages = "<html>Failed to connect to the Tabby server:<br/>${errorMessage}</html>"
|
||||
Messages.showErrorDialog(parentComponent, messages, "Check Connection Failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while (job.isActive) {
|
||||
indicator.checkCanceled()
|
||||
Thread.sleep(100)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
ProgressManager.getInstance().run(task)
|
||||
}
|
||||
}
|
||||
private val serverEndpointPanel = FormBuilder.createFormBuilder()
|
||||
.addComponent(serverEndpointTextField)
|
||||
.addCopyableTooltip(
|
||||
|
|
@ -37,6 +125,7 @@ class ApplicationSettingsPanel {
|
|||
</html>
|
||||
""".trimIndent()
|
||||
)
|
||||
.addComponent(serverEndpointCheckConnectionButton)
|
||||
.panel
|
||||
|
||||
private val nodeBinaryTextField = JBTextField()
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ import com.intellij.openapi.components.Service
|
|||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.Storage
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@Service
|
||||
@State(
|
||||
|
|
@ -18,29 +19,41 @@ class ApplicationSettingsState : PersistentStateComponent<ApplicationSettingsSta
|
|||
enum class TriggerMode {
|
||||
@SerializedName("manual")
|
||||
MANUAL,
|
||||
|
||||
@SerializedName("automatic")
|
||||
AUTOMATIC,
|
||||
}
|
||||
|
||||
private val completionTriggerModeFlow = MutableStateFlow(TriggerMode.AUTOMATIC)
|
||||
val completionTriggerModeState = completionTriggerModeFlow.asStateFlow()
|
||||
var completionTriggerMode: TriggerMode = TriggerMode.AUTOMATIC
|
||||
set(value) {
|
||||
field = value
|
||||
stateFlow.value = this.data
|
||||
completionTriggerModeFlow.value = value
|
||||
}
|
||||
|
||||
private val serverEndpointFlow = MutableStateFlow("")
|
||||
val serverEndpointState = serverEndpointFlow.asStateFlow()
|
||||
var serverEndpoint: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
stateFlow.value = this.data
|
||||
serverEndpointFlow.value = value
|
||||
}
|
||||
|
||||
private val nodeBinaryFlow = MutableStateFlow("")
|
||||
val nodeBinaryState = nodeBinaryFlow.asStateFlow()
|
||||
var nodeBinary: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
stateFlow.value = this.data
|
||||
nodeBinaryFlow.value = value
|
||||
}
|
||||
|
||||
private val isAnonymousUsageTrackingDisabledFlow = MutableStateFlow(false)
|
||||
val isAnonymousUsageTrackingDisabledState = isAnonymousUsageTrackingDisabledFlow.asStateFlow()
|
||||
var isAnonymousUsageTrackingDisabled: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
stateFlow.value = this.data
|
||||
isAnonymousUsageTrackingDisabledFlow.value = value
|
||||
}
|
||||
|
||||
data class State(
|
||||
|
|
@ -58,8 +71,19 @@ class ApplicationSettingsState : PersistentStateComponent<ApplicationSettingsSta
|
|||
isAnonymousUsageTrackingDisabled = isAnonymousUsageTrackingDisabled,
|
||||
)
|
||||
|
||||
private val stateFlow = MutableStateFlow(data)
|
||||
val state = stateFlow.asStateFlow()
|
||||
val state = combine(
|
||||
completionTriggerModeState,
|
||||
serverEndpointState,
|
||||
nodeBinaryState,
|
||||
isAnonymousUsageTrackingDisabledState,
|
||||
) { completionTriggerMode, serverEndpoint, nodeBinary, isAnonymousUsageTrackingDisabled ->
|
||||
State(
|
||||
completionTriggerMode = completionTriggerMode,
|
||||
serverEndpoint = serverEndpoint,
|
||||
nodeBinary = nodeBinary,
|
||||
isAnonymousUsageTrackingDisabled = isAnonymousUsageTrackingDisabled,
|
||||
)
|
||||
}.stateIn(CoroutineScope(Dispatchers.IO), SharingStarted.Eagerly, this.data)
|
||||
|
||||
override fun getState(): ApplicationSettingsState {
|
||||
return this
|
||||
|
|
|
|||
Loading…
Reference in New Issue