Skip to content

feat: Floating window #553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open

Conversation

zhangmo8
Copy link
Collaborator

@zhangmo8 zhangmo8 commented Jun 23, 2025

Pull Request Description (中文)

你的功能请求是否与某个问题有关?请描述一下。

Link #244

桌面应用程序的 UI/UX 更改

deepchat floating window base infra

目前的功能:

  • 点击唤起 deepchat
  • 拖拽
  • 设置关闭悬浮框
20250623_185328.mp4

Summary by CodeRabbit

  • New Features

    • Introduced a floating button feature, providing a persistent, interactive button overlay within the application.
    • The floating button supports click actions, dynamic configuration updates, and can be repositioned on the screen.
    • Added a new Vue-based floating button UI with animated feedback and dynamic opacity control.
    • Users can interact with the floating button to trigger application actions, even when the main window is hidden.
  • Chores

    • Updated build configuration to support multiple entry points for the floating button feature.
    • Added configuration setting to disable data tracking in the development environment.

Copy link
Contributor

coderabbitai bot commented Jun 23, 2025

Walkthrough

This update introduces a floating button feature to an Electron application. It adds new main process presenters and window management for the floating button, integrates IPC communication, and provides a Vue-based renderer for the button UI. The Electron build configuration is updated for multiple entry points, and new TypeScript types and preload scripts are included for secure API exposure.

Changes

File(s) Change Summary
.vscode/settings.json Added "vue-vine.dataTrack": false and fixed JSON trailing comma.
electron.vite.config.ts Updated build config: multiple entry points for preload and renderer, including new floating button files.
src/main/events.ts Added FLOATING_BUTTON_EVENTS constant for floating button-related IPC events.
src/main/index.ts Integrated floating button lifecycle, IPC event handling, and window visibility management in main process.
src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts New class for floating button window creation, control, and state management.
src/main/presenter/floatingButtonPresenter/index.ts New presenter class for floating button lifecycle, config, and visibility management.
src/main/presenter/floatingButtonPresenter/types.ts Added TypeScript interfaces and default config for floating button feature.
src/main/presenter/index.ts Presenter class now instantiates and holds a FloatingButtonPresenter.
src/preload/floating-preload.ts New preload script exposing secure floating button API to renderer via contextBridge or window.
src/preload/index.d.ts Extended global Window type with floatingButtonAPI.
src/renderer/floating/FloatingButton.vue New Vue 3 component: interactive floating button with click, hover, and config update handling.
src/renderer/floating/env.d.ts Type declarations for Vue SFCs and floating button API on window.
src/renderer/floating/index.html New HTML entry for floating button renderer with transparent, minimal layout.
src/renderer/floating/main.ts Vue app entry point mounting FloatingButton component to DOM.
tsconfig.web.json Added floating button source files and env.d.ts to compilation include paths.

Sequence Diagram(s)

sequenceDiagram
    participant ElectronMain as Electron Main Process
    participant FloatingButtonPresenter
    participant FloatingButtonWindow
    participant PreloadScript
    participant Renderer as Floating Button Renderer (Vue)

    ElectronMain->>FloatingButtonPresenter: initialize()
    FloatingButtonPresenter->>FloatingButtonWindow: create()
    FloatingButtonWindow-->>FloatingButtonPresenter: window ready
    FloatingButtonPresenter-->>ElectronMain: floating button ready

    Renderer->>PreloadScript: window.floatingButtonAPI.onClick()
    PreloadScript->>ElectronMain: IPC: FLOATING_BUTTON_EVENTS.CLICKED
    ElectronMain->>FloatingButtonPresenter: handle click event
    FloatingButtonPresenter->>ElectronMain: toggle main window visibility

    ElectronMain->>FloatingButtonPresenter: onMainWindowVisibilityChanged()
    FloatingButtonPresenter->>FloatingButtonWindow: show/hide as needed

    FloatingButtonPresenter->>FloatingButtonWindow: updateConfig(newConfig)
    FloatingButtonWindow-->>Renderer: send config update (via IPC)
    Renderer->>PreloadScript: onConfigUpdate(callback)
    PreloadScript-->>Renderer: callback(config)
Loading

Poem

🐇
A button now floats, so round and bright,
Dancing on screens, both day and night.
With Vue for its charm and Electron for might,
It pulses with promise, a whimsical sight.
Clicks and configs, all handled with care—
The floating friend is always there!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8ad206 and 3189c9a.

📒 Files selected for processing (2)
  • electron.vite.config.ts (2 hunks)
  • src/renderer/floating/FloatingButton.vue (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • electron.vite.config.ts
  • src/renderer/floating/FloatingButton.vue
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (5)
src/preload/floating-preload.ts (1)

16-20: Consider using event constants for consistency.

The hardcoded event name 'floating-button-config-update' should use a constant from the events module for consistency and maintainability.

+import { FLOATING_BUTTON_EVENTS } from '@/events';

-    ipcRenderer.on('floating-button-config-update', (_event, config) => {
+    ipcRenderer.on(FLOATING_BUTTON_EVENTS.CONFIG_UPDATE, (_event, config) => {
-    ipcRenderer.removeAllListeners('floating-button-config-update');
+    ipcRenderer.removeAllListeners(FLOATING_BUTTON_EVENTS.CONFIG_UPDATE);
src/renderer/floating/FloatingButton.vue (1)

28-49: Consider using Vue's reactivity instead of manual DOM manipulation.

The click animation uses direct DOM manipulation which goes against Vue's reactive paradigm. Consider using a reactive state for better maintainability.

+const isClicked = ref(false)

const handleClick = () => {
-  // 点击反馈动画
-  if (floatingButton.value) {
-    floatingButton.value.style.transform = 'scale(0.9)'
-    setTimeout(() => {
-      if (floatingButton.value) {
-        floatingButton.value.style.transform = ''
-      }
-    }, 150)
-  }
+  // 点击反馈动画
+  isClicked.value = true
+  setTimeout(() => {
+    isClicked.value = false
+  }, 150)

And update the template:

-      :class="{ 'floating-button-pulse': isPulsing }"
+      :class="{
+        'floating-button-pulse': isPulsing,
+        'floating-button-clicked': isClicked
+      }"
src/main/presenter/floatingButtonPresenter/index.ts (1)

110-112: Add parentheses for clarity in boolean expression.

The current expression might be confusing due to operator precedence. Consider adding parentheses to make the intent clearer.

  public isAvailable(): boolean {
-   return this.config.enabled && this.floatingWindow?.exists() || false;
+   return (this.config.enabled && this.floatingWindow?.exists()) || false;
  }

Note: The || false is redundant since the expression already returns a boolean.

  public isAvailable(): boolean {
-   return this.config.enabled && this.floatingWindow?.exists() || false;
+   return this.config.enabled && (this.floatingWindow?.exists() ?? false);
  }
src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts (2)

181-199: Remove redundant 'bottom-right' case.

The 'bottom-right' case has identical logic to the default case, making it redundant.

      case 'bottom-left':
        x = this.config.offset.x;
        y = workAreaSize.height - this.config.size.height - this.config.offset.y;
        break;
-     case 'bottom-right':
      default:
        x = workAreaSize.width - this.config.size.width - this.config.offset.x;
        y = workAreaSize.height - this.config.size.height - this.config.offset.y;
        break;

71-75: Consider making DevTools optional in development mode.

Opening DevTools automatically on every launch in development mode can be disruptive, especially since the comment indicates it may interfere with dragging functionality.

      if (isDev) {
        await this.window.loadURL('http://localhost:5173/floating/');
-       // 开发模式下可选择性打开开发者工具(暂时禁用,避免影响拖拽)
-       this.window.webContents.openDevTools({ mode: 'detach' });
+       // 开发模式下可通过环境变量控制是否打开开发者工具
+       if (process.env.FLOATING_BUTTON_DEVTOOLS === 'true') {
+         this.window.webContents.openDevTools({ mode: 'detach' });
+       }
      } else {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97e9f61 and 72e998c.

📒 Files selected for processing (14)
  • .vscode/settings.json (1 hunks)
  • electron.vite.config.ts (2 hunks)
  • src/main/events.ts (1 hunks)
  • src/main/index.ts (8 hunks)
  • src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts (1 hunks)
  • src/main/presenter/floatingButtonPresenter/index.ts (1 hunks)
  • src/main/presenter/floatingButtonPresenter/types.ts (1 hunks)
  • src/main/presenter/index.ts (3 hunks)
  • src/preload/floating-preload.ts (1 hunks)
  • src/preload/index.d.ts (1 hunks)
  • src/renderer/floating/FloatingButton.vue (1 hunks)
  • src/renderer/floating/env.d.ts (1 hunks)
  • src/renderer/floating/index.html (1 hunks)
  • src/renderer/floating/main.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/renderer/floating/env.d.ts

[error] 6-6: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 6-6: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

src/main/presenter/floatingButtonPresenter/types.ts

[error] 40-40: void is confusing outside a return type or a type parameter.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts

[error] 208-210: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)

🪛 GitHub Check: build-check (x64)
src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts

[failure] 3-3:
'fs' is declared but its value is never read.

🪛 GitHub Actions: PR Check
src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts

[error] 3-3: TypeScript error TS6133: 'fs' is declared but its value is never read.

🔇 Additional comments (23)
.vscode/settings.json (1)

6-7: Disable Vue-Vine telemetry in workspace settings

Good addition to turn off data tracking for the Vue-Vine extension. The comma after the previous entry is valid, and the JSON remains well-formed.

src/main/events.ts (1)

167-173: LGTM! Clean integration of floating button events.

The new FLOATING_BUTTON_EVENTS constant follows the established naming convention and provides appropriate event types for the floating button feature. The events are well-defined and logically structured.

src/renderer/floating/main.ts (1)

2-6: LGTM! Standard Vue application bootstrap.

The Vue application setup is correctly implemented, creating an app instance with the FloatingButton component and mounting it to the DOM.

src/main/presenter/index.ts (3)

22-22: LGTM! Proper import of FloatingButtonPresenter.

The import follows the established pattern and integrates cleanly with the existing presenter imports.


56-56: LGTM! Consistent property declaration.

The property declaration follows the same pattern as other presenters in the class.


84-84: LGTM! Proper presenter initialization.

The FloatingButtonPresenter is correctly initialized in the constructor following the established pattern. The initialization doesn't require dependencies, which is appropriate for this presenter.

src/renderer/floating/index.html (3)

1-12: LGTM! Proper HTML structure and meta configuration.

The HTML structure is well-formed with appropriate meta tags and viewport settings for a floating button renderer.


8-21: LGTM! Excellent CSS styling for floating UI.

The CSS reset and styling is perfect for a floating button:

  • Global reset with box-sizing: border-box
  • Transparent background for seamless desktop integration
  • Hidden overflow to prevent scrollbars
  • Full viewport dimensions

This ensures the floating button will render cleanly without visual artifacts.


24-25: LGTM! Proper Vue app mounting setup.

The div element provides the correct mounting point for the Vue application, and the module script correctly references the main.ts entry point.

electron.vite.config.ts (2)

36-44: LGTM! Proper multi-entry configuration for Electron preload.

The addition of multiple entry points in the preload build configuration correctly supports the floating button feature architecture.


88-90: LGTM! Renderer entry points properly configured.

The floating renderer entry point is correctly added alongside existing entries, enabling proper build and loading of the floating button UI.

src/renderer/floating/env.d.ts (1)

10-19: LGTM! Well-defined API interface for floating button.

The global window interface extension properly defines the floating button API with appropriate method signatures for IPC communication.

src/preload/floating-preload.ts (2)

4-27: LGTM! Secure API implementation with proper error handling.

The floating button API is well-structured with appropriate error handling for IPC communication. The method implementations correctly use ipcRenderer for communication with the main process.


29-42: LGTM! Proper security implementation for API exposure.

The context isolation detection and fallback approach correctly handles both secure and legacy Electron contexts while maintaining appropriate error handling.

src/renderer/floating/FloatingButton.vue (2)

71-82: LGTM! Proper lifecycle management for API integration.

The component correctly sets up and cleans up API listeners in the mounted/unmounted hooks, following Vue best practices.


85-168: LGTM! Excellent CSS implementation with smooth animations.

The styling includes well-crafted animations (pulse, shine, hover effects) and a properly positioned tooltip. The gradient background and transitions create a polished user experience.

src/main/presenter/floatingButtonPresenter/types.ts (2)

1-20: LGTM! Comprehensive configuration interface.

The FloatingButtonConfig interface provides well-defined properties with clear documentation, covering all necessary aspects of floating button configuration.


43-56: LGTM! Sensible default configuration values.

The default configuration provides reasonable values for all properties, with the button positioned in the bottom-right corner and appropriate sizing and opacity settings.

src/main/index.ts (4)

109-115: LGTM! Good error handling for non-critical feature.

The floating button initialization is properly wrapped in try-catch, preventing failures from affecting the main application flow.


128-158: Well-structured window visibility handling.

The handleShowHiddenWindow function properly handles window state transitions with appropriate checks for destroyed windows. The mustShow parameter elegantly handles the different behaviors needed for tray clicks vs other triggers.


321-337: Correct handling of floating button in app lifecycle.

The modified window-all-closed handler properly excludes floating button windows from the quit decision, ensuring the app behaves correctly when only the floating button remains.


45-47: ```shell
#!/bin/bash

Locate the presenter index file and display its init() definition

INDEX_FILE=$(find src/main/presenter -maxdepth 1 -type f -name "index.ts")
echo "Presenter index file: $INDEX_FILE"
rg -A 5 -n "init\s*()" "$INDEX_FILE"


</details>
<details>
<summary>src/main/presenter/floatingButtonPresenter/index.ts (1)</summary>

`63-91`: **Well-implemented configuration update logic.**

The `updateConfig` method properly handles state transitions between enabled/disabled states, with appropriate error handling and logging. The immediate show after enabling provides good user feedback.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@@ -9,6 +9,7 @@ declare global {
getPathForFile(file: File): string
getWindowId(): number | null
getWebContentsId(): number
}
},
floatingButtonAPI: typeof floatingButtonAPI;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the missing import for floatingButtonAPI type reference.

The type declaration typeof floatingButtonAPI references an undefined variable. This will cause a TypeScript compilation error since floatingButtonAPI is not imported or defined in this file.

You need to either import the floatingButtonAPI or define the type explicitly:

+import { floatingButtonAPI } from '../preload/floating-preload'
+
 declare global {
   interface Window {
     electron: ElectronAPI
     api: {
       copyText(text: string): void
       copyImage(image: string): void
       getPathForFile(file: File): string
       getWindowId(): number | null
       getWebContentsId(): number
     },
     floatingButtonAPI: typeof floatingButtonAPI;
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
floatingButtonAPI: typeof floatingButtonAPI;
import { floatingButtonAPI } from '../preload/floating-preload'
declare global {
interface Window {
electron: ElectronAPI
api: {
copyText(text: string): void
copyImage(image: string): void
getPathForFile(file: File): string
getWindowId(): number | null
getWebContentsId(): number
},
floatingButtonAPI: typeof floatingButtonAPI;
}
}
🤖 Prompt for AI Agents
In src/preload/index.d.ts at line 13, the type reference `typeof
floatingButtonAPI` is used without importing or defining `floatingButtonAPI`,
causing a TypeScript error. Fix this by adding an import statement for
`floatingButtonAPI` from its module at the top of the file, or alternatively,
define the type explicitly in this file if importing is not suitable.

@@ -0,0 +1,6 @@
import '../src/assets/main.css'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the incorrect CSS import path.

The CSS import path appears to be incorrect. From src/renderer/floating/main.ts, the path ../src/assets/main.css would resolve to src/src/assets/main.css, which likely doesn't exist.

Apply this fix:

-import '../src/assets/main.css'
+import '../../assets/main.css'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import '../src/assets/main.css'
import '../../assets/main.css'
🤖 Prompt for AI Agents
In src/renderer/floating/main.ts at line 1, the CSS import path
'../src/assets/main.css' is incorrect because it resolves to
'src/src/assets/main.css'. Update the import path to correctly point to the
assets directory relative to this file, likely by changing it to
'../../assets/main.css' or the correct relative path that leads to
'src/assets/main.css'.

Comment on lines +3 to +8
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
const component: DefineComponent<{}, {}, any>
export default component
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix TypeScript type definition for Vue components.

The empty object type {} should be replaced with more specific types to improve type safety.

-  const component: DefineComponent<{}, {}, any>
+  const component: DefineComponent<Record<string, never>, Record<string, never>, any>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
const component: DefineComponent<{}, {}, any>
export default component
}
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
const component: DefineComponent<Record<string, never>, Record<string, never>, any>
export default component
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 6-6: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 6-6: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

🤖 Prompt for AI Agents
In src/renderer/floating/env.d.ts between lines 3 and 8, the TypeScript type
definition for Vue components uses empty object types {} which lack specificity
and reduce type safety. Replace these empty object types with more precise types
such as Record<string, unknown> or appropriate generic parameters to better
represent props and other component options, thereby improving type safety in
the Vue component declarations.

Comment on lines +63 to +69
// 配置更新处理
const handleConfigUpdate = (config: any) => {
// 根据配置更新按钮样式
if (config.opacity !== undefined && document.body) {
document.body.style.opacity = config.opacity.toString()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety for configuration parameter.

The config parameter uses any type, reducing type safety. Consider defining a proper interface.

-const handleConfigUpdate = (config: any) => {
+interface FloatingButtonConfig {
+  opacity?: number;
+  // Add other config properties as needed
+}
+
+const handleConfigUpdate = (config: FloatingButtonConfig) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 配置更新处理
const handleConfigUpdate = (config: any) => {
// 根据配置更新按钮样式
if (config.opacity !== undefined && document.body) {
document.body.style.opacity = config.opacity.toString()
}
}
// 配置更新处理
interface FloatingButtonConfig {
opacity?: number;
// Add other config properties as needed
}
const handleConfigUpdate = (config: FloatingButtonConfig) => {
// 根据配置更新按钮样式
if (config.opacity !== undefined && document.body) {
document.body.style.opacity = config.opacity.toString()
}
}
🤖 Prompt for AI Agents
In src/renderer/floating/FloatingButton.vue around lines 63 to 69, the config
parameter is typed as any, which reduces type safety. Define a proper TypeScript
interface for the config object specifying expected properties like opacity,
then update the handleConfigUpdate function to use this interface instead of any
to improve type safety and code clarity.

Comment on lines +34 to +41
export interface FloatingButtonEvents {
/** 悬浮按钮被点击 */
'floating-button-clicked': void;
/** 悬浮按钮显示状态改变 */
'floating-button-visibility-changed': { visible: boolean };
/** 悬浮按钮位置改变 */
'floating-button-position-changed': { x: number; y: number };
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix void type usage in event interface.

Using void outside of return types is confusing and should be replaced with undefined for better clarity.

export interface FloatingButtonEvents {
  /** 悬浮按钮被点击 */
-  'floating-button-clicked': void;
+  'floating-button-clicked': undefined;
  /** 悬浮按钮显示状态改变 */
  'floating-button-visibility-changed': { visible: boolean };
  /** 悬浮按钮位置改变 */
  'floating-button-position-changed': { x: number; y: number };
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 40-40: void is confusing outside a return type or a type parameter.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

🤖 Prompt for AI Agents
In src/main/presenter/floatingButtonPresenter/types.ts between lines 34 and 41,
replace the use of 'void' as the type for the 'floating-button-clicked' event
with 'undefined' to improve clarity and correctness, since 'void' is intended
for function return types and 'undefined' better represents the absence of a
value in this context.

Comment on lines +117 to 126
// 设置悬浮按钮点击事件的 IPC 处理器
// 先移除可能存在的旧监听器
ipcMain.removeAllListeners(FLOATING_BUTTON_EVENTS.CLICKED)
ipcMain.on(FLOATING_BUTTON_EVENTS.CLICKED, () => {
try {
// 触发内置事件处理器
handleShowHiddenWindow(true)
} catch (error) {
}
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error logging in the empty catch block.

The empty catch block at line 125 could hide errors. Consider logging errors to help with debugging.

    } catch (error) {
+     console.error('Error handling floating button click:', error)
    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 设置悬浮按钮点击事件的 IPC 处理器
// 先移除可能存在的旧监听器
ipcMain.removeAllListeners(FLOATING_BUTTON_EVENTS.CLICKED)
ipcMain.on(FLOATING_BUTTON_EVENTS.CLICKED, () => {
try {
// 触发内置事件处理器
handleShowHiddenWindow(true)
} catch (error) {
}
})
// 设置悬浮按钮点击事件的 IPC 处理器
// 先移除可能存在的旧监听器
ipcMain.removeAllListeners(FLOATING_BUTTON_EVENTS.CLICKED)
ipcMain.on(FLOATING_BUTTON_EVENTS.CLICKED, () => {
try {
// 触发内置事件处理器
handleShowHiddenWindow(true)
} catch (error) {
console.error('Error handling floating button click:', error)
}
})
🤖 Prompt for AI Agents
In src/main/index.ts around lines 117 to 126, the catch block inside the
ipcMain.on event handler is empty, which can hide errors and make debugging
difficult. Modify the catch block to log the caught error using a suitable
logging method or console.error to ensure any exceptions thrown by
handleShowHiddenWindow(true) are recorded for troubleshooting.

Comment on lines +160 to 172
// 监听窗口显示/隐藏状态变化
const handleWindowVisibilityChange = () => {
const allWindows = presenter.windowPresenter.getAllWindows()
const hasVisibleWindow = allWindows.some(win => !win.isDestroyed() && win.isVisible())
floatingButtonPresenter.onMainWindowVisibilityChanged(hasVisibleWindow)
}

// 监听窗口显示事件
app.on('browser-window-created', (_, window) => {
window.on('show', handleWindowVisibilityChange)
window.on('hide', handleWindowVisibilityChange)
window.on('closed', handleWindowVisibilityChange)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential duplicate event listeners on windows.

The current implementation attaches event listeners every time a browser window is created. If a window is reused or if the same window triggers multiple browser-window-created events, this could lead to duplicate listeners and multiple calls to handleWindowVisibilityChange.

Consider tracking which windows already have listeners attached:

+ const windowsWithListeners = new WeakSet<BrowserWindow>();

  app.on('browser-window-created', (_, window) => {
+   if (windowsWithListeners.has(window)) {
+     return;
+   }
+   windowsWithListeners.add(window);
    window.on('show', handleWindowVisibilityChange)
    window.on('hide', handleWindowVisibilityChange)
    window.on('closed', handleWindowVisibilityChange)
  })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 监听窗口显示/隐藏状态变化
const handleWindowVisibilityChange = () => {
const allWindows = presenter.windowPresenter.getAllWindows()
const hasVisibleWindow = allWindows.some(win => !win.isDestroyed() && win.isVisible())
floatingButtonPresenter.onMainWindowVisibilityChanged(hasVisibleWindow)
}
// 监听窗口显示事件
app.on('browser-window-created', (_, window) => {
window.on('show', handleWindowVisibilityChange)
window.on('hide', handleWindowVisibilityChange)
window.on('closed', handleWindowVisibilityChange)
})
// 监听窗口显示/隐藏状态变化
const handleWindowVisibilityChange = () => {
const allWindows = presenter.windowPresenter.getAllWindows()
const hasVisibleWindow = allWindows.some(win => !win.isDestroyed() && win.isVisible())
floatingButtonPresenter.onMainWindowVisibilityChanged(hasVisibleWindow)
}
const windowsWithListeners = new WeakSet<BrowserWindow>();
// 监听窗口显示事件
app.on('browser-window-created', (_, window) => {
if (windowsWithListeners.has(window)) {
return;
}
windowsWithListeners.add(window);
window.on('show', handleWindowVisibilityChange)
window.on('hide', handleWindowVisibilityChange)
window.on('closed', handleWindowVisibilityChange)
})
🤖 Prompt for AI Agents
In src/main/index.ts around lines 160 to 172, the event listeners for 'show',
'hide', and 'closed' are attached every time a new browser window is created,
which can cause duplicate listeners if windows are reused or multiple
'browser-window-created' events occur for the same window. To fix this,
implement a mechanism to track windows that already have listeners attached,
such as using a WeakSet or a Map, and only add listeners if they have not been
added before, preventing duplicate event handling.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts (2)

193-194: Remove redundant case clause.

The 'bottom-right' case is immediately followed by the default case that handles the same logic, making it redundant.

      case 'bottom-left':
        x = this.config.offset.x;
        y = workAreaSize.height - this.config.size.height - this.config.offset.y;
        break;
-     case 'bottom-right':
      default:
        x = workAreaSize.width - this.config.size.width - this.config.offset.x;
        y = workAreaSize.height - this.config.size.height - this.config.offset.y;
        break;

72-73: Make devTools opening configurable instead of automatic.

Automatically opening devTools may not always be desired and could interfere with testing the floating window behavior.

  if (isDev) {
    await this.window.loadURL('http://localhost:5173/floating/');
-   // 开发模式下可选择性打开开发者工具(暂时禁用,避免影响拖拽)
-   this.window.webContents.openDevTools({ mode: 'detach' });
+   // Optionally open devTools based on environment variable
+   if (process.env.FLOATING_DEVTOOLS === 'true') {
+     this.window.webContents.openDevTools({ mode: 'detach' });
+   }
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72e998c and a8ad206.

📒 Files selected for processing (3)
  • src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts (1 hunks)
  • src/preload/floating-preload.ts (1 hunks)
  • tsconfig.web.json (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tsconfig.web.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/preload/floating-preload.ts
🧰 Additional context used
🪛 Biome (1.9.4)
src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts

[error] 207-209: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build-check (x64)
🔇 Additional comments (1)
src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts (1)

6-228: Well-designed class architecture for floating window management.

The FloatingButtonWindow class provides good encapsulation with clear separation of concerns between window management, configuration, and state tracking. The event handling setup and cleanup logic are properly implemented.

Comment on lines +91 to +99
public show(): void {
if (!this.window) {
return;
}

this.window.show();
this.state.isVisible = true;
logger.debug('FloatingButtonWindow shown');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve window state validation in show/hide methods.

The methods only check if window is null but don't validate if the window is still valid (not destroyed).

  public show(): void {
-   if (!this.window) {
+   if (!this.exists()) {
      return;
    }

    this.window.show();
    this.state.isVisible = true;
    logger.debug('FloatingButtonWindow shown');
  }

  public hide(): void {
-   if (!this.window) {
+   if (!this.exists()) {
      return;
    }

    this.window.hide();
    this.state.isVisible = false;
    logger.debug('FloatingButtonWindow hidden');
  }

Also applies to: 104-112

🤖 Prompt for AI Agents
In src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts around
lines 91 to 99 and 104 to 112, the show and hide methods only check if the
window object exists but do not verify if the window is still valid or has been
destroyed. Update these methods to also check if the window is not destroyed
before proceeding. This can be done by adding a condition to verify the window's
validity state, preventing operations on a destroyed window.

Comment on lines +60 to +62
webSecurity: false, // 开发模式下允许跨域
devTools: true, // 开发模式下启用开发者工具
sandbox: false // 禁用沙盒模式,确保预加载脚本能正常工作
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Address security vulnerabilities in webPreferences.

The current configuration disables important security features that should only be relaxed in development mode.

  webPreferences: {
    nodeIntegration: false,
    contextIsolation: true,
    preload: preloadPath,
-   webSecurity: false, // 开发模式下允许跨域
-   devTools: true, // 开发模式下启用开发者工具
-   sandbox: false // 禁用沙盒模式,确保预加载脚本能正常工作
+   webSecurity: isDev ? false : true, // Only disable in development
+   devTools: isDev, // Only enable in development
+   sandbox: !isDev // Enable sandbox in production for better security
  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts around
lines 60 to 62, the webPreferences settings disable key security features like
webSecurity and sandbox unconditionally. Modify the code to enable these
security features by default and only disable them when running in development
mode, ensuring production builds maintain proper security configurations.

Comment on lines +134 to +145
if (config.size) {
this.window.setSize(this.config.size.width, this.config.size.height);
this.state.bounds.width = this.config.size.width;
this.state.bounds.height = this.config.size.height;
}

if (config.position || config.offset) {
const position = this.calculatePosition();
this.window.setPosition(position.x, position.y);
this.state.bounds.x = position.x;
this.state.bounds.y = position.y;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for window property updates.

State updates should only occur after successful window operations to maintain consistency.

      if (config.size) {
-       this.window.setSize(this.config.size.width, this.config.size.height);
-       this.state.bounds.width = this.config.size.width;
-       this.state.bounds.height = this.config.size.height;
+       try {
+         this.window.setSize(this.config.size.width, this.config.size.height);
+         this.state.bounds.width = this.config.size.width;
+         this.state.bounds.height = this.config.size.height;
+       } catch (error) {
+         logger.error('Failed to update window size:', error);
+       }
      }

      if (config.position || config.offset) {
-       const position = this.calculatePosition();
-       this.window.setPosition(position.x, position.y);
-       this.state.bounds.x = position.x;
-       this.state.bounds.y = position.y;
+       try {
+         const position = this.calculatePosition();
+         this.window.setPosition(position.x, position.y);
+         this.state.bounds.x = position.x;
+         this.state.bounds.y = position.y;
+       } catch (error) {
+         logger.error('Failed to update window position:', error);
+       }
      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (config.size) {
this.window.setSize(this.config.size.width, this.config.size.height);
this.state.bounds.width = this.config.size.width;
this.state.bounds.height = this.config.size.height;
}
if (config.position || config.offset) {
const position = this.calculatePosition();
this.window.setPosition(position.x, position.y);
this.state.bounds.x = position.x;
this.state.bounds.y = position.y;
}
if (config.size) {
try {
this.window.setSize(this.config.size.width, this.config.size.height);
this.state.bounds.width = this.config.size.width;
this.state.bounds.height = this.config.size.height;
} catch (error) {
logger.error('Failed to update window size:', error);
}
}
if (config.position || config.offset) {
try {
const position = this.calculatePosition();
this.window.setPosition(position.x, position.y);
this.state.bounds.x = position.x;
this.state.bounds.y = position.y;
} catch (error) {
logger.error('Failed to update window position:', error);
}
}
🤖 Prompt for AI Agents
In src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts around
lines 134 to 145, the code updates the state properties immediately after
calling window methods without verifying if those calls succeeded. To fix this,
add error handling around the window.setSize and window.setPosition calls, and
only update the state.bounds properties if the respective window method
completes successfully. This ensures state consistency with the actual window
properties.

Comment on lines +37 to +39
const preloadPath = isDev
? path.join(process.cwd(), 'out/preload/floating.mjs')
: path.join(__dirname, '../../preload/floating.mjs');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Validate preload script paths to prevent runtime failures.

The hardcoded paths for the preload script may not exist in all deployment scenarios. Consider adding path existence validation or using more robust path resolution.

  const isDev = process.env.NODE_ENV === 'development';
- const preloadPath = isDev 
-   ? path.join(process.cwd(), 'out/preload/floating.mjs')
-   : path.join(__dirname, '../../preload/floating.mjs');
+ const preloadPath = isDev 
+   ? path.join(process.cwd(), 'out/preload/floating.mjs')
+   : path.join(__dirname, '../../preload/floating.mjs');
+ 
+ // Validate preload script exists
+ if (!require('fs').existsSync(preloadPath)) {
+   throw new Error(`Preload script not found: ${preloadPath}`);
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const preloadPath = isDev
? path.join(process.cwd(), 'out/preload/floating.mjs')
: path.join(__dirname, '../../preload/floating.mjs');
const isDev = process.env.NODE_ENV === 'development';
const preloadPath = isDev
? path.join(process.cwd(), 'out/preload/floating.mjs')
: path.join(__dirname, '../../preload/floating.mjs');
// Validate preload script exists
if (!require('fs').existsSync(preloadPath)) {
throw new Error(`Preload script not found: ${preloadPath}`);
}
🤖 Prompt for AI Agents
In src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts around
lines 37 to 39, the preload script paths are hardcoded and may not exist in all
deployment environments, risking runtime failures. Add validation to check if
the resolved preloadPath exists on the filesystem before using it, and handle
the case where it does not (e.g., log an error or fallback to a default path).
Alternatively, improve path resolution by dynamically determining the correct
preload script location based on the environment or configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant