Electron-ChatGPT桌面端ChatGPT範例|electron25+vue3聊天AI模板EXE

2023-06-09 06:00:28

基於electron25+vite4+vue3仿製chatgpt使用者端聊天模板ElectronChatGPT

electron-chatgpt 使用最新桌面端技術Electron25.x結合Vite4.x全家桶技術開發跨端模仿ChatGPT智慧聊天程式模板。支援經典+分欄兩種佈局、暗黑+明亮主題模式,整合electron封裝多視窗及通訊功能。

關於veplus元件庫這裡不作過多介紹,之前有過一篇分享文章,大家可以去看看。

https://www.cnblogs.com/xiaoyan2017/p/17170454.html

專案佈局

專案整體大致分為頂部導航工具列+左側對談記錄/操作連結+右側對談區/編輯框等模組。

<template>
    <div class="vegpt__layout flexbox flex-col">
        <!-- //頂部工具列 -->
        <Toolbar />
        
        <div class="ve__layout-body flex1 flexbox">
            <!-- //側邊欄 -->
            <div class="ve__layout-menus flexbox" :class="{'hidden': store.config.collapse}">
                <aside class="ve__layout-aside flexbox flex-col">
                    <ChatNew />
                    <Scrollbar class="flex1" autohide size="4" gap="1">
                        <ChatList />
                    </Scrollbar>
                    <ExtraLink />
                    <Collapse />
                </aside>
            </div>

            <!-- //主體區域 -->
            <div class="ve__layout-main flex1 flexbox flex-col">
                <Main />
            </div>
        </div>
    </div>
</template>

Electron主程序入口

根目錄下新建 electron-main.js 作為主程序入口檔案。

/**
 * 主程序入口
 * @author YXY
 */

const { app, BrowserWindow } = require('electron')

const MultiWindow = require('./src/multiwindow')

// 遮蔽安全警告
// ectron Security Warning (Insecure Content-Security-Policy)
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'

const createWindow = () => {
    let win = new MultiWindow()
    win.createWin({isMainWin: true})
}

app.whenReady().then(() => {
    createWindow()
    app.on('activate', () => {
        if(BrowserWindow.getAllWindows().length === 0) createWindow()
    })
})

app.on('window-all-closed', () => {
    if(process.platform !== 'darwin') app.quit()
})

使用electron的vite外掛,在vite.config.js中設定入口。

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import electron from 'vite-plugin-electron'
import { resolve } from 'path'
import { parseEnv } from './src/utils/env'

export default defineConfig(({ command, mode }) => {
  const viteEnv = loadEnv(mode, process.cwd())
  const env = parseEnv(viteEnv)

  return {
    plugins: [
      vue(),
      electron({
        // 主程序入口檔案
        entry: 'electron-main.js'
      })
    ],
    
    /*構建選項*/
    build: {
      /* minify: 'esbuild', // 打包方式 esbuild(打包快)|terser
      chunkSizeWarningLimit: 2000, // 打包大小警告
      rollupOptions: {
          output: {
              chunkFileNames: 'assets/js/[name]-[hash].js',
              entryFileNames: 'assets/js/[name]-[hash].js',
              assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
          }
      } */
      
      // 如果打包方式是terser,則設定如下
      /* minify: "terser",
      terserOptions: {
        compress: {
          // 去掉所有console和debugger
          // drop_console: true,
          // drop_debugger: true,

          drop_console: command !== 'serve',
          drop_debugger: command !== 'serve',
          //pure_funcs:['console.log'] // 移除console.log
        }
      } */
    },
    esbuild: {
      // 打包去除 console.log 和 debugger
      drop: env.VITE_DROP_CONSOLE && command === 'build' ? ["console", "debugger"] : []
    },

    /*開發伺服器選項*/
    server: {
      //
      port: env.VITE_PORT,
      // ...
    },

    resolve: {
      // 設定別名
      alias: {
        '@': resolve(__dirname, 'src'),
        '@assets': resolve(__dirname, 'src/assets'),
        '@components': resolve(__dirname, 'src/components'),
        '@views': resolve(__dirname, 'src/views')
      }
    }
  }
})

需要注意:由於目前Electron 尚未支援 "type": "module",需要在package.json中去掉,並且設定 "main": "electron-main.js", 入口。

Electron自定義無邊框視窗工具列

建立視窗的時候設定 frame: false 引數,建立的視窗則沒有系統頂部導航欄及邊框。拖拽區域/最大化/最小化及關閉按鈕均需要自定義操作。

通過設定css3屬性 -webkit-app-region: drag ,則可對自定義區域進行拖拽操作,設定後按鈕/連結點選則會失效,這時通過對按鈕或連結設定-webkit-app-region: no-drag就可恢復事件響應。

不過設定-webkit-app-region: drag,點選滑鼠右鍵,會出現上圖系統選單,經過一番偵錯,windows下可以暫時通過如下方法遮蔽右鍵選單。

// 遮蔽系統右鍵選單
win.hookWindowMessage(278, () => {
    win.setEnabled(false)
    setTimeout(() => {
        win.setEnabled(true)
    }, 100)

    return true
})

components/titlebar目錄自定義工具列條。

control.vue自定義最大化/最小化/關閉按鈕

<template>
    <div class="vegpt__control ve__nodrag">
        <div class="vegpt__control-btns" :style="{'color': color}">
            <slot />
            <div v-if="isTrue(minimizable)" class="btn win-btn win-min" @click="handleMin"><i class="iconfont ve-icon-minimize"></i></div>
            <div v-if="isTrue(maximizable) && winCfg.window.resizable" class="btn win-btn win-maxmin" @click="handleRestore">
                <i class="iconfont" :class="isMaximized ? 've-icon-maxrestore' : 've-icon-maximize'"></i>
            </div>
            <div v-if="isTrue(closable)" class="btn win-btn win-close" @click="handleQuit"><i class="iconfont ve-icon-close"></i></div>
        </div>
    </div>
</template>
<template>
    <div class="vegpt__control ve__nodrag">
        <div class="vegpt__control-btns" :style="{'color': color}">
            <slot />
            <div v-if="isTrue(minimizable)" class="btn win-btn win-min" @click="handleMin"><i class="iconfont ve-icon-minimize"></i></div>
            <div v-if="isTrue(maximizable) && winCfg.window.resizable" class="btn win-btn win-maxmin" @click="handleRestore">
                <i class="iconfont" :class="isMaximized ? 've-icon-maxrestore' : 've-icon-maximize'"></i>
            </div>
            <div v-if="isTrue(closable)" class="btn win-btn win-close" @click="handleQuit"><i class="iconfont ve-icon-close"></i></div>
        </div>
    </div>
</template>


<script setup>
    import { onMounted, ref } from 'vue'
    import { winCfg, setWin } from '@/multiwindow/actions'
    import { appStore } from '@/pinia/modules/app'
    import { isTrue } from '@/utils'

    const appState = appStore()

    const props = defineProps({
        // 標題顏色
        color: String,

        // 視窗是否可以最小化
        minimizable: { type: [Boolean, String], default: true },
        // 視窗是否可以最大化
        maximizable: { type: [Boolean, String], default: true },
        // 視窗是否可以關閉
        closable: { type: [Boolean, String], default: true }
    })

    // 是否最大化
    let isMaximized = ref(false)

    onMounted(() => {
        window.electronAPI.invoke('win__isMaximized').then(data => {
            console.log(data)
            isMaximized.value = data
        })
        window.electronAPI.receive('win__hasMaximized', (e, data) => {
            console.log(data)
            isMaximized.value = data
        })
    })

    // 最小化
    const handleMin = () => {
        window.electronAPI.send('win__minimize')
    }
    // 最大化/還原
    const handleRestore = () => {
        window.electronAPI.invoke('win__max2min').then(data => {
            console.log(data)
            isMaximized.value = data
        })
    }
    // 關閉表單
    const handleQuit = () => {
        if(winCfg.window.isMainWin) {
            MessageBox.confirm('應用提示', '是否最小化到托盤, 不退出程式?', {
                type: 'warning',
                cancelText: '最小化至托盤',
                confirmText: '殘忍退出',
                confirmType: 'danger',
                width: 300,
                callback: action => {
                    if(action == 'confirm') {
                        appState.$reset()
                        setWin('close')
                    }else if(action == 'cancel') {
                        setWin('hide', winCfg.window.id)
                    }
                }
            })
        }else {
            setWin('close', winCfg.window.id)
        }
    }
</script>

在 index.vue 中引入 control.vue 操作按鈕,並支援自定義左側、標題等功能。

<template>
    <div class="vegpt__titlebar" :class="{'fixed': isTrue(fixed), 'transparent fixed': isTrue(transparent)}">
        <div class="vegpt__titlebar-wrapper flexbox flex-alignc ve__drag" :style="{'background': bgcolor, 'color': color, 'z-index': zIndex}">
            <slot name="left">
                <img src="/logo.png" height="20" style="margin-left: 10px;" />
            </slot>
            <div class="vegpt__titlebar-title" :class="{'center': isTrue(center)}">
                <slot name="title">{{ title || winCfg.window.title || env.VITE_APPTITLE }}</slot>
            </div>

            <!-- 控制按鈕 -->
            <Control :minimizable="minimizable" :maximizable="maximizable" :closable="closable">
                <slot name="btn" />
            </Control>
        </div>
    </div>
</template>

Electron建立系統托盤圖示

托盤圖示、右鍵選單圖示及打包圖示均在resource目錄下。