Electron25+Vite4.x 模倣 chatgpt クライアント アプリケーションに基づく ElectronChatGPT

Electron25+vue3 デスクトップ チャット AI の例 | Electron は chatgpt を模倣します

最新のフロントエンド技術スタック vite4+vue3+pinia2+vue-router に基づいて、electron25 クロスエンド技術を統合して、デスクトップ模倣 chatgpt チャット プログラムを開発します。クラシック+カラムレイアウト、ダーク+ブライトテーマモード、マルチウィンドウ機能をサポートします。

ここに画像の説明を挿入します

技術的枠組み

エディタ: VScode
使用したテクノロジ: Electron25+Vite4+Vue3+Pinia2
コンポーネント ライブラリ: VEPlus (vue3 コンポーネント ライブラリに基づく)
パッケージ化プラグイン:electron-builder^23.6.0
コード ハイライト:highlight.js^11.7.0
マークダウン プラグイン: vue3-markdown-it
ローカル キャッシュ: pinia-plugin-persistedstate^3.1.0
Electron は vite プラグインを統合します: vite-plugin-electron^0.11.2

ここに画像の説明を挿入します
ここに画像の説明を挿入します
Electron-chatgpt は Electron25 と vite4 を使用します。

ここに画像の説明を挿入します

Electron は vite4 を統合してデスクトップ プログラムを作成します

Electron+vite4 マルチウィンドウ管理 | Electron 新しいマルチウィンドウ

プロジェクト構造ディレクトリ

ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します

レイアウト構造

ここに画像の説明を挿入します

<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-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()
})

次にプラグインをvite.config.js導入し、入口の設定を行います。vite-plugin-electron

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]',
          }
      } */
    },
    esbuild: {
    
    
      // 打包去除 console.log
      drop: env.VITE_DROP_CONSOLE && command === 'build' ? ["console"] : []
    },

    /*开发服务器选项*/
    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')
      }
    }
  }
})

vite-plugin-electronElectron と vite の通信機能を統合するためにプラグインが使用されます。

現在、Electron はこれをサポートしていない"type": "module"ため、package.json削除して "main":"electron-main.js"エントリで構成する必要があります。

電子カスタムボーダレス操作バーウィンドウ

ここに画像の説明を挿入します
ウィンドウ作成時に設定すると、frame: falseフチなしウィンドウを作成できます。-webkit-app-region: dragカスタム操作領域のドラッグ&ドロップ機能を有効にするためのcss3属性を設定します。

新しいコンポーネント/タイトルバー ディレクトリを作成します

ここに画像の説明を挿入します
コントロール.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>


<script setup>
	/**
	 * 封装操作按钮
	 * @author YXY  Q:282310962
	 */
    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>

ここに画像の説明を挿入します
インデックス.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 がシステム トレイ メニューを作成します

ここに画像の説明を挿入します
システム トレイ アイコン ファイルトレイ.ico を作成し、リソース ディレクトリに配置します。
ここに画像の説明を挿入します

// 创建托盘图标
createTray() {
    
    
    console.log('——+——+——Start Create Tray!')
    console.log(__dirname)
    console.log(join(process.env.ROOT, 'resource/tray.ico'))
    
    const trayMenu = Menu.buildFromTemplate([
        {
    
    
            label: '打开主界面',
            icon: join(process.env.ROOT, 'resource/home.png'),
            click: () => {
    
    
                try {
    
    
                    for(let i in this.group) {
    
    
                        let win = this.getWin(i)
                        if(!win) return
                        // 是否主窗口
                        if(this.group[i].isMainWin) {
    
    
                            if(win.isMinimized()) win.restore()
                            win.show()
                        }
                    }
                } catch (error) {
    
    
                    console.log(error)
                }
            }
        },
        {
    
    
            label: '设置中心',
            icon: join(process.env.ROOT, 'resource/setting.png'),
            click: () => {
    
    
                for(let i in this.group) {
    
    
                    let win = this.getWin(i)
                    if(win) win.webContents.send('win__ipcData', {
    
     type: 'CREATE_WIN_SETTING', value: null })
                }
            },
        },
        {
    
    
            label: '锁屏',
            icon: join(process.env.ROOT, 'resource/lock.png'),
            click: () => null,
        },
        {
    
    
            label: '关闭托盘闪烁',
            click: () => {
    
    
                this.flashTray(false)
            }
        },
        {
    
    type: 'separator'},
        {
    
    
            label: '关于',
            click: () => {
    
    
                for(let i in this.group) {
    
    
                    let win = this.getWin(i)
                    if(win) win.webContents.send('win__ipcData', {
    
     type: 'CREATE_WIN_ABOUT', value: null })
                }
            }
        },
        {
    
    
            label: '关闭应用并退出',
            icon: join(process.env.ROOT, 'resource/quit.png'),
            click: () => {
    
    
                dialog.showMessageBox(this.main, {
    
    
                    title: '询问',
                    message: '确定要退出应用程序吗?',
                    buttons: ['取消', '最小化托盘', '退出应用'],
                    type: 'error',
                    noLink: false,  // true传统按钮样式  false链接样式
                    cancelId: 0
                }).then(res => {
    
    
                    console.log(res)

                    const index = res.response
                    if(index == 0) {
    
    
                        console.log('取消')
                    }if(index == 1) {
    
    
                        console.log('最小化托盘')
                        for(let i in this.group) {
    
    
                            let win = this.getWin(i)
                            if(win) win.hide()
                        }
                    }else if(index == 2) {
    
    
                        console.log('退出应用')

                        try {
    
    
                            for(let i in this.group) {
    
    
                                let win = this.getWin(i)
                                if(win) win.webContents.send('win__ipcData', {
    
     type: 'WIN_LOGOUT', value: null })
                            }
                            app.quit()
                        } catch (error) {
    
    
                            console.log(error)
                        }
                    }
                })
            }
        }
    ])
    this.tray = new Tray(this.trayIco1)
    this.tray.setContextMenu(trayMenu)
    this.tray.setToolTip(app.name)
    this.tray.on('double-click', () => {
    
    
        console.log('double clicked')
    })
}

注: ここで間違ったアイコン パスを設定すると、アイコンが表示されなくなります。__dirnameメイン プロセス ファイル ディレクトリへのパスを指定します。
ここに画像の説明を挿入します
たとえば、メイン プロセス ファイルが src/multiwindow/index.js にある場合は、../../リソース ディレクトリに戻る必要があります。

// 根目录路径
process.env.ROOT = join(__dirname, '../../')

電子パッケージング

すべての準備が完了し、プロジェクトは通常どおり実行を開始できます。最後に、パッケージ化を構成する必要があります。
Electron パッケージング構成ファイルとして使用する新しい Electron-builder.json ファイルを作成します。

{
    
    
    "productName": "Electron-ChatGPT",
    "appId": "com.yxy.electron-chatgpt-vue3",
    "copyright": "Copyright © 2023-present Andy",
    "compression": "maximum",
    "asar": true,
    "directories": {
    
    
        "output": "release/${version}"
    },
    "nsis": {
    
    
        "oneClick": false,
        "allowToChangeInstallationDirectory": true,
        "perMachine": true,
        "deleteAppDataOnUninstall": true,
        "createDesktopShortcut": true,
        "createStartMenuShortcut": true,
        "shortcutName": "ElectronChatGPT"
    },
    "win": {
    
    
        "icon": "./resource/shortcut.ico",
        "artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}",
        "target": [
            {
    
    
                "target": "nsis",
                "arch": ["ia32"]
            }
        ]
    },
    "mac": {
    
    
        "icon": "./resource/shortcut.icns",
        "artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}"
    },
    "linux": {
    
    
        "icon": "./resource",
        "artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}"
    }
}

上記は、electron25+vue3 を使用して chatgpt を模倣したクライアントを開発する例の一部です。

vue3+pinia2 バックエンド管理テンプレート|vue3 カスタム軽量バックエンド システム

vue3+tauri クロスデスクトップ チャット プログラム | tauri は WeChat チャット アプリケーションを模倣します

ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/yanxinyun1990/article/details/131148077