JetBrains 插件空状态页 Feedback Support 按钮实现:从 VS Code 对齐到 IntelliJ Popup 落地
2026/9/23 17:33:36 网站建设 项目流程

JetBrains 插件空状态页 Feedback & Support 按钮实现:从 VS Code 对齐到 IntelliJ Popup 落地

【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode

导读

本文基于 Kilo 仓库内.kilo/plans/1780256109130-stellar-forest.md这一份 JetBrains 插件功能实现计划展开,完整讲解如何把 VS Code 端空状态页(empty state)的Feedback & Support按钮与反馈弹层移植到 JetBrains 插件,实现 GitHub Issues、Discord、Customer Support 三个跳转入口,并满足点击外部 / 按 Esc 关闭、非模态、不抢焦点等交互要求。文章不仅逐条展开计划的 6 个实施步骤(本地化、UI 扩展、Popup 构建、测试、发布说明、验证),还对照当前仓库中已落地的源码(EmptySessionFeedback.ktEmptySessionPanel.ktEmptySessionPanelTest.kt及 i18n 资源),给出计划与最终实现之间的差异,读者可据此在 JetBrains 插件前端复现或继续改进同类功能。

一、功能目标与整体背景

该计划的目标非常明确:把 VS Code 端空状态页上的Feedback & Support入口原样搬到 JetBrains 插件中,弹出一个**非模态(non-modal)**小弹层,提供三个与 VS Code 完全一致的目的地:

  • GitHub Issues:https://github.com/Kilo-Org/kilocode/issues/new/choose
  • Discord:https://kilo.ai/discord
  • Customer Support:https://kilo.ai/support

弹层必须满足两个交互约束:点击弹层外部时隐藏按下 Escape 时隐藏

从仓库现状看,该计划已经落地:packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionFeedback.kt完整实现了按钮、弹层内容与三个 URL 常量,且feedback.urls()返回的正是上述三个 URL;对应测试 EmptySessionPanelTest.kt 中也有test feedback popup content opens expected destinationstest feedback discord action has icon两个用例守护行为。

二、调研发现:入口定位与 VS Code 参考实现

计划的第一步是定位落点与参考实现,仓库现状印证了全部结论:

  1. JetBrains 空状态组件位于 EmptySessionPanel.kt(计划书写时为.../session/ui/EmptySessionPanel.kt,落地时位于ui/empty/子包)。它已经渲染了 Kilo logo、欢迎文案、最近会话列表(Recents)和Show History按钮,是添加 Feedback 按钮的正确位置。从源码可见该面板是一个BorderLayoutPanel,南侧(SOUTH)区域由Stack.vertical垂直堆叠Centerizer(historyButton)Centerizer(feedback.button)构成,Feedback 按钮正好排在Show History之下、保持居中布局。

  2. VS Code 侧三件套为参考基准:

    • 按钮:packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx(计划标注位置)
    • 弹层内容:FeedbackDialog.tsx——SolidJS 组件,内含 Kilo Logo、feedback.dialog.message文案,以及 GitHub(primary 变体 + github 图标)、Discord(secondary + discord 图标)、Support(secondary + help 图标)三个全宽按钮,外加一个 ghost 变体的 Cancel 按钮;点击任一动作通过vscode.postMessage({ type: "openExternal", url })打开外部链接并关闭弹层。
    • 样式:packages/kilo-vscode/webview-ui/src/styles/welcome.css
  3. IntelliJ Popup API已在本地 IntelliJ 源码(计划注明$INTELLIJ_REPO)中确认可用:

    • JBPopupFactory.getInstance().createComponentPopupBuilder(content, focusComponent)
    • setModalContext(false):非模态上下文
    • setRequestFocus(false)/setFocusable(false):让弹层不抢焦点
    • setCancelOnClickOutside(true):点击外部关闭
    • setCancelKeyEnabled(true):Esc 关闭
    • setCancelOnWindowDeactivation(true)/setCancelOnOtherWindowOpen(true):窗口失活或其它窗口打开时清理弹层

    需要特别说明的是,最终实现采用了createBalloonBuilder而非计划中的createComponentPopupBuilder:在 EmptySessionFeedback.kt 中,弹层由JBPopupFactory.getInstance().createBalloonBuilder(content)构建,并通过.setHideOnClickOutside(true).setHideOnKeyOutside(true).setHideOnAction(true).setHideOnFrameResize(true)实现点击外部、键盘(Esc)、动作触发与窗口缩放时的隐藏——与计划的交互目标一致,但 API 形态不同,这是实现相对计划的一处实际偏差。

  4. 平台图标(AllIcons)可用性

    • Feedback 按钮:AllIcons.Ide.Feedback
    • GitHub 动作:AllIcons.Vcs.Vendors.Github
    • Support/帮助动作:AllIcons.Actions.HelpAllIcons.General.ContextHelp
    • Discord 没有平台图标:需要借用 VS Code / kilo-ui 现有的 Discord SVG 素材,改造成 IntelliJ 兼容的 SVG 颜色,必要时提供深色变体。仓库中已落地两个资源文件:discord.svg 与 discord_dark.svg,并通过IconLoader.getIcon("/icons/discord.svg", EmptySessionPanel::class.java)加载。

三、实施步骤详解(计划原文 + 源码对照)

步骤 1:添加本地化 Bundle Keys

计划要求在frontend/src/main/resources/messages/KiloBundle.properties添加基英文键:

Key
feedback.buttonFeedback and Support
feedback.dialog.messageWe'd love to hear your feedback or help with any issues you're experiencing.
feedback.dialog.githubReport an issue on GitHub
feedback.dialog.discordJoin our Discord community
feedback.dialog.supportCustomer Support

现有 Cancel 文案若已存在则复用,否则仅在必要时新增common.cancel=Cancel。仓库现状确认这些键已写入 KiloBundle.properties(第 54–58 行),且消息目录下已存在 18 个本地化变体文件(KiloBundle_ar.propertiesKiloBundle_zh_CN.propertiesKiloBundle_ja.propertiesKiloBundle_ko.propertiesKiloBundle_de.propertiesKiloBundle_fr.properties等),计划要求在能力允许范围内复用 VS Code i18n 的既有翻译,并以英文回退兜底。实际按钮文案在实现中通过KiloBundle.message("feedback.button")读取,测试test feedback button uses localized text and icon也据此断言。

步骤 2:扩展 EmptySessionPanel UI

计划对新增按钮提出了明确的外观规格,要求与 VS Code 概念对齐:

  • 反馈图标 +Feedback & Support文本
  • 虚线、链接色边框
  • 透明背景
  • 手型光标
  • 悬停时填充链接色并反转前景/背景以形成对比

样式必须使用 IntelliJ 主题 API 而非硬编码颜色:链接色用JBUI.CurrentTheme.Link.Foreground.ENABLED、悬停可用...HOVERED;背景取自既有会话样式或UiStyle.Colors.editorBackground();间距用UiStyle.Gap/JBUI.Borders;圆角在绘制时用UiStyle.Arc.component()JBUI.getInt("Button.arc", 6)。按钮需放在Show History下方、保持居中布局,Swing 变更保持在 EDT 上执行并维持 retained-component 行为。

落地实现中,EmptySessionPanel.kt 的做法是让FeedbackButton直接继承面板内部的ShowHistoryButton(一个自定义JButton子类),因此天然继承了其既有行为:isFocusable = falseisContentAreaFilled = falseisBorderPainted = falseisOpaque = false、手型光标,以及paintComponent中基于JBUI.CurrentTheme.ActionButton.hoverBackground()JBUI.getInt("Button.arc", 6)的圆角悬停填充。feedback.button在构造时挂上addActionListener { popup() },并作为Centerizer包裹项加入南侧 actions 栈(见上文源码第 153–159 行)。

步骤 3:实现 Feedback Popup(前端 Swing 代码)

计划给出的弹层内容结构,与 VS Code 弹层逐一对应:

  • 顶部 Kilo logo,使用既有/icons/kilo-content.svg
  • 消息文案
  • GitHub、Discord、Customer Support 三个动作按钮/行,且每个动作都必须有图标:
    • GitHub →AllIcons.Vcs.Vendors.Github
    • Discord → 借用的 Discord SVG
    • Customer Support →AllIcons.Actions.HelpAllIcons.General.ContextHelp
  • 可选 Cancel 按钮(Esc / 点击外部已能关闭)

弹层构建(计划版)配置项如下,均来自 IntelliJ Popup API:

JBPopupFactory.createComponentPopupBuilder(content, null) .setModalContext(false) // 非模态 .setRequestFocus(false) // 不请求焦点 .setFocusable(false) // 不可聚焦(除非需要 Tab 遍历) .setCancelOnClickOutside(true) // 点击外部关闭 .setCancelKeyEnabled(true) // Esc 关闭 .setCancelOnWindowDeactivation(true) // 窗口失活清理 .setCancelOnOtherWindowOpen(true) // 其他窗口打开清理 .setResizable(false) .setMovable(false) .showUnderneathOf(feedbackButton) // 展示在按钮下方

打开链接统一使用BrowserUtil.browse(url),并在打开 URL 后关闭弹层。Discord 图标资源按插件既有命名模式放入frontend/src/main/resources/icons/(如discord.svgdiscord_dark.svg),用IconLoader.getIcon("/icons/discord.svg", EmptySessionPanel::class.java)或小型 Kilo 自有图标对象加载。

源码现状对照:EmptySessionFeedback.kt 的content(open)静态方法完整实现了上述结构:顶部 logo(点击打开https://kilocode.ai,且为手型光标)、居中消息、三个ActionButton竖排堆叠(GitHub 用AllIcons.Vcs.Vendors.Github、Discord 用DISCORD_ICON、Support 用AllIcons.Actions.Help)。popup()方法采用createBalloonBuilder变体实现(见第二节的差异说明),调用链为:点击按钮 →popup()→ 若已有弹层则先hide()→ 构建 content →popup.show(point, Balloon.Position.below)展示在按钮正下方;点击动作行时执行注入的browse(url)回调(生产环境为BrowserUtil::browse,测试环境可注入假实现)并hide()。弹层通过Disposer与面板生命周期绑定,dispose()时隐藏弹层,避免泄漏。

步骤 4:测试覆盖(EmptySessionPanelTest.kt)

计划要求的测试点,在 EmptySessionPanelTest.kt 中均有对应用例:

  • 按钮文案使用 i18ntest feedback button uses localized text and icon断言panel.feedbackText() == KiloBundle.message("feedback.button")且图标非空。
  • 手型光标与边框语义test action controls use hand cursor and no show history outline断言feedbackCursor() == Cursor.HAND_CURSORfeedbackBorderPainted()为 false。
  • 可注入浏览回调:面板构造参数browse: (String) -> Unit = BrowserUtil::browse(EmptySessionPanel.kt 第 62 行)即为测试预留的注入点,feedbackContent { opened.add(it) }把 URL 收集进内存列表,测试因此无需真正启动浏览器。
  • 三个动作标签与 URL 映射test feedback popup content opens expected destinations通过UIUtil.uiTraverser遍历弹层内容中的所有JButton,断言三个按钮文案依次为feedback.dialog.github / feedback.dialog.discord / feedback.dialog.support的本地化值,逐个doClick()后断言收集到的 URL 与panel.feedbackUrls()完全一致(即与 VS Code 相同的三个 URL)。
  • Discord 必须有图标test feedback discord action has icon定位文案为 Discord 的按钮并断言icon != null,防止回归成纯文本行。
  • 最小模式(minimal)test minimal mode shows only logo and feedback断言在 worktree/subagent 编辑器标签等最小化表面,仅保留 logo 与 feedback,隐藏历史、描述与最近会话;test full mode shows logo feedback history description and recents则验证完整模式。

测试基类为BasePlatformTestCase,通过FakeAppRpcApiFakeSessionRpcApiFakeWorkspaceRpcApi构造最小可运行环境,flush()runBlocking { delay(100); UIUtil.dispatchAllInvocationEvents() }派发 EDT 事件。

步骤 5:发布说明(Release Note)

由于这是 JetBrains 插件的用户可见功能,计划要求新增 patch changeset(放在.changeset/,除非仓库存在 JetBrains 专属的发布说明机制取代 changesets),建议文案:

Add Feedback & Support to the JetBrains empty session screen.

步骤 6:验证命令

计划给出的验证流程,均在packages/kilo-jetbrains/目录下执行:

# 1) 聚焦前端/JetBrains 测试 ./gradlew :frontend:test --tests "ai.kilocode.client.session.ui.EmptySessionPanelTest" # 2) JetBrains 类型检查 bun run typecheck # 或 ./gradlew typecheck

若仅改动前端 UI 且测试通过,则无需后端/SDK 代码生成。测试全名中的类已按落地后的包路径ai.kilocode.client.session.ui.EmptySessionPanelTest核对无误。

四、约束与边界(Notes)

计划明确划定了实现边界,防止范围蔓延:

  • 不得引入 Kotlin UI DSL、Compose 或 JCEF:弹层必须用 Swing + IntelliJ Platform API 完成,保持与既有前端技术栈一致。
  • 不得修改 shared opencode 文件:本功能全部工作限制在packages/kilo-jetbrains/内(外加一个 changeset)。这与仓库的架构隔离规则相符。
  • 优先使用平台图标,但 Discord 必须显式借用/新增图标资源,因为AllIcons不提供 Discord 图标,而弹层需要在视觉上与 VS Code 的按钮集保持一致。

五、实现现状小结

对照仓库当前状态,这份计划已完整落地,且实现细节与计划存在两处值得关注的差异(均为从源码结构观察到的结论):

  1. Popup API 形态不同:计划推演的是createComponentPopupBuilder系列 API,最终实现改用JBPopupFactory.createBalloonBuilder,借助 Balloon 的setHideOnClickOutside(true)setHideOnKeyOutside(true)setHideOnAction(true)setHideOnFrameResize(true)达成同样的“点击外部/Esc 关闭、动作后关闭”语义,并通过Disposer.register(popup) { balloon = null }维护弹层引用生命周期。
  2. 路径与包结构变化:计划中的EmptySessionPanel.kt实际落在.../session/ui/empty/子包,测试类名与./gradlew过滤参数相应更新。

对于希望复刻或扩展该功能的开发者,可沿这条阅读路径深入:EmptySessionPanel.kt(按钮挂载与 retained-component 行为)→ EmptySessionFeedback.kt(弹层内容与 Balloon 构建)→ EmptySessionPanelTest.kt(行为契约与 URL 映射)→ KiloBundle.properties(文案键)→ discord.svg(借用的图标资源),并可与 VS Code 侧的 FeedbackDialog.tsx 对照理解两端交互与视觉的一致性目标。

【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询