ElectronChatGPT basado en la aplicación cliente chatgpt de imitación Electron25+Vite4.x

Ejemplo de IA de chat de escritorio Electron25+vue3 | electron imita chatgpt

Basado en la última pila de tecnología front-end vite4 + vue3 + pinia2 + vue-router, integra la tecnología cross-end electron25 para desarrollar un programa de chat chatgpt de imitación de escritorio. Admite diseño clásico + columnas , modo de tema oscuro + brillante y función de ventanas múltiples.

Insertar descripción de la imagen aquí

marco técnico

Editor: VScode
Tecnología utilizada: Electron25+Vite4+Vue3+Pinia2
Biblioteca de componentes: VEPlus (basada en la biblioteca de componentes vue3)
Complemento de empaquetado: electron-builder^23.6.0
Resaltado de código: resaltado.js^11.7.0
complemento de rebajas : vue3- caché
local markdown-it: pinia-plugin-persistedstate^3.1.0
electron integra el complemento vite: vite-plugin-electron^0.11.2

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Electron-chatgpt utiliza electron25 y vite4.

Insertar descripción de la imagen aquí

electron integra vite4 para crear programas de escritorio

electron+vite4 gestión de ventanas múltiples | nueva ventana múltiple electron

Directorio de estructura del proyecto

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

estructura de diseño

Insertar descripción de la imagen aquí

<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 recorre la entrada principal.

Cree un nuevo archivo en el directorio raíz del proyecto electron-main.jspara que sirva como archivo de entrada del proceso principal.

Insertar descripción de la imagen aquí

/**
 * 主进程入口
 * @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()
})

Luego vite.config.jsintroduzca vite-plugin-electronel complemento y configure la entrada.

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-electronEl complemento se utiliza para integrar funciones de comunicación electrónica y vite.

Actualmente, Electron no lo admite "type": "module", por lo que es necesario eliminarlo package.jsony configurarlo con "electron-main.js"la entrada "main": .

ventana de barra de operación sin bordes personalizada de electrones

Insertar descripción de la imagen aquí
Configúrelo al crear la ventana frame: falsepara crear una ventana sin bordes. Configure los atributos css3 -webkit-app-region: dragpara habilitar la función de arrastrar y soltar para áreas de operación personalizadas.

Crear un nuevo directorio de componentes/barra de título

Insertar descripción de la imagen aquí
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>


<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>

Insertar descripción de la imagen aquí
index.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 crea el menú de la bandeja del sistema

Insertar descripción de la imagen aquí
Cree el archivo del icono de la bandeja del sistema tray.ico y colóquelo en el directorio de recursos.
Insertar descripción de la imagen aquí

// 创建托盘图标
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')
    })
}

Nota: Es fácil configurar aquí la ruta del icono incorrecta y el icono no se mostrará. __dirnameSeñale la ruta a su directorio de archivos de proceso principal.
Insertar descripción de la imagen aquí
Por ejemplo, si su archivo de proceso principal está en src/multiwindow/index.js, debe regresar ../../para buscar el directorio de recursos.

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

empaquetado de electrones

Todo está listo y el proyecto puede empezar a funcionar con normalidad. Finalmente, necesitas configurar el embalaje.
Cree un nuevo archivo electron-builder.json para usarlo como archivo de configuración de empaquetado de electrones.

{
    
    
    "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}"
    }
}

Lo anterior es un intercambio de ejemplos del uso de electron25+vue3 para desarrollar un cliente que imite chatgpt. Espero que les guste~~

Plantilla de gestión de backend vue3 + pinia2 | sistema de backend ligero personalizado vue3

programa de chat entre escritorios vue3+tauri | tauri imita la aplicación de chat WeChat

Insertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/yanxinyun1990/article/details/131148077
Recomendado
Clasificación