使用electron开发桌面AI应用


最终目标

创建类似360、腾讯电脑管家的气泡悬浮球,并且实现拖动,点击气泡框显示AI聊天对话框,可以和AI进行通话。并创建系统托盘应用图标,实现应用显示、隐藏,退出功能。

使用工具

1、electron-vite脚手架  2、MaxKB  3、Tray

实现过程

1、使用electron-vite脚手架工具搭建项目框架

npm create @quick-start/electron@latest

electron-vite官网地址

目录结构如下,主要是src目录下的,main是主进程,主要作用是创建electron应用窗体,并且实现一些监听,操作应用,preload是预加载进程,可以将主进程的一些东西暴露给渲染进程,作为桥梁的作用;renderer是作为渲染进程,是具体的页面显示,这里使用的是vue,所以renderer下面其实就是完整的vue工程。

2、首先是创建应用,在主进程创建BrowserWindow实例,定义应用宽高,type类型选择toolbar工具栏应用,frame设置false为无边框。

// Create the browser window.
  mainWindow = new BrowserWindow({
    // width: 900,
    // height: 670,
    show: false,
    transparent: true,
    backgroundColor: '#00000000',
    autoHideMenuBar: true,
    width: 80,
    height: 80,
    type: 'toolbar',    //创建的窗口类型为工具栏窗口
    frame: false,   //要创建无边框窗口
    resizable: false, //禁止窗口大小缩放
    // alwaysOnTop: true,
    // show: false,
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      nodeIntegration: true, // 或 process.env.NODE_ENV !== 'production',
      contextIsolation: false,
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false
    }
  })

 设置框体在桌面屏幕位置

 const { left, top } = { left: screen.getPrimaryDisplay().workAreaSize.width - 90, top: screen.getPrimaryDisplay().workAreaSize.height - 90 }
  mainWindow.setPosition(left, top) // 设置悬浮球位置
  mainWindow.setVisibleOnAllWorkspaces(true)  // 显示在所有工作区

3、创建系统托盘,使用electron自带的Tray类,并且定义托盘操作栏菜单,并且定义托盘图标双击事件。


import icon from '../../resources/icon.png?asset'


//
创建系统托盘图标和菜单 tray = new Tray(icon); const trayMenu = Menu.buildFromTemplate([ { label: '显示气泡框', click: () => { mainWindow.show(); } }, { label: '隐藏气泡框', click: () => { mainWindow.hide(); } }, { type: 'separator' }, { label: '退出', click: () => { if (tray) tray.destroy(); app.exit(); } } ]); tray.setContextMenu(trayMenu); tray.setToolTip('AI助手'); tray.on('double-click', () => { if(mainWindow.isVisible()){ mainWindow.hide() }else{ mainWindow.show() } });

4、编写渲染进程页面代码

首先设置body背景透明,使用脚手架创建的工程会设置body的背景色,这里我们需要删掉,或者直接在index.html页面设置body背景透明

为了让窗体能够拖动,设置样式-webkit-app-region: drag;这样设置了之后整个页面的点击事件将会失效,所以设置app节点样式为no-drag,并且body设置内边框,这样窗体边缘可以拖动,中间区域也不影响点击事件。

App.vue页面绘制页面

<script setup lang="ts">
  import { ref } from 'vue'

  const chatVisible = ref(false)

  //打开聊天窗口
  function openChatDialog() {
    chatVisible.value = !chatVisible.value
    //和主进程通信修改窗体大小
    window.electron.ipcRenderer.send('openChat')
  }

  //关闭聊天窗口
  function closeChatDialog() {
    chatVisible.value = !chatVisible.value
    window.electron.ipcRenderer.send('closeChat')
  }
</script>

<template>
  <div class="ball" @click="openChatDialog" v-if="!chatVisible" title="AI助手">
    AI
  </div>
  <div v-else class="chat">
    <span class="close-icon" @click="closeChatDialog" title="关闭">X</span>
    <iframe
      src="http://10.88.99.12:8980/ui/chat/f1fe745724ef2929?mode=mobile"
      style="width: 100%; height: 100%;"
      frameborder="0"
      allow="microphone">
    </iframe>
  </div>
</template>
<style scoped lang="scss">
  .ball {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: rgba(15, 14, 62, 0.7);
  }
  .chat {
    background-color: #fff;
    height: 97vh;
    width: 96vw;

    .close-icon{
      color:black;
      font-size: 20px;
      cursor: pointer;
      position: absolute;
      right:55px;
      top:22px;
    }
  }
</style>

在主进程添加监听渲染进程的点击监听事件,监听放在app.whenReady里面

 //动态改变窗体大小——显示聊天框
  ipcMain.on('openChat', () => {
    let position = mainWindow.getPosition()
    logger.info('窗体位置:'+position)
    mainWindow.setContentSize(400, 700)
    // const { left, top } = { left: screen.getPrimaryDisplay().workAreaSize.width - 420, top: screen.getPrimaryDisplay().workAreaSize.height - 720 }
    mainWindow.setPosition(position[0]-320, position[1]-620) // 设置悬浮球位置
    logger.info('ok:打开聊天框')
    console.log('ok:打开聊天框')
  })



  //动态改变窗体大小——显示气泡框
  ipcMain.on('closeChat', () => {
    let position = mainWindow.getPosition()
    logger.info('窗体位置:'+position)
    mainWindow.setContentSize(80, 80)
    // const { left, top } = { left: screen.getPrimaryDisplay().workAreaSize.width - 90, top: screen.getPrimaryDisplay().workAreaSize.height - 90 }
    mainWindow.setPosition(position[0]+320, position[1]+620) // 设置悬浮球位置
    logger.info('ok:关闭聊天框')
    console.log('ok:关闭聊天框')
  })

5、这里AI使用的是MaxKB,嵌入方式是MaxKB使用iframe的方式,这里由于electron的csp规则限制,需要在渲染进程的index.html页面修改csp安全策略,将MaxKB的地址添加进去,并且最好使用https,我这里使用的http会有警告。

 6、运行效果

 可以拖动,点击之后显示AI聊天对话框

点击X可以关闭对话框,显示为气泡框。系统托盘显示图标。

 右键图标可以显示操作菜单,并且双击图标可以快速显示、隐藏气泡框。

 7、打包

运行build:win可以打windows包,还可以打mac和linux的包。node环境建议22.0.0以上,不然打包的时候会报错,最终会形成一个安装包,双击安装在桌面创建一个应用图标。