Electron-ChatGPT桌面端ChatGPT实例|electron25+vue3聊天AI模板EXE
基于electron25+vite4+vue3仿制chatgpt客户端聊天模板ElectronChatGPT。
electron-chatgpt 使用最新桌面端技术Electron25.x结合Vite4.x全家桶技术开发跨端模仿ChatGPT智能聊天程序模板。支持经典+分栏两种布局、暗黑+明亮主题模式,集成electron封装多窗口及通讯功能。

技术栈
- 编码工具:vscode
- 框架技术:electron25+vite4+vue3+pinia2
- 组件库:veplus (基于vue3自定义组件库)
- 打包工具:electron-builder^23.6.0
- 调试工具:electron-devtools-installer^3.2.0
- 代码高亮:highlight.js^11.7.0
- markdown组件:vue3-markdown-it
- 本地缓存:pinia-plugin-persistedstate^3.1.0
- electron结合vite插件:vite-plugin-electron^0.11.2

项目结构
基于electron最新版本融合vite4.x技术搭建模仿chatgpt桌面端程序。

如果对electron+vite4创建跨端应用及多开窗口感兴趣,可以去看看之前的这两篇分享文章。
https://www.cnblogs.com/xiaoyan2017/p/17436076.html
https://www.cnblogs.com/xiaoyan2017/p/17442502.html

随着electron快速迭代更新,加上vite极速编译,二者配合创建的应用运行速度超快。










Vue3桌面UI组件库
考虑到项目比较轻量级,所以采用自研vue3组件库ve-plus。

关于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创建系统托盘图标

// 创建系统托盘图标
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: () => {
// app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
// app.exit(0)
}
}, */
{
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 和 app.exit(0) 都可退出应用。
// 前者可以被打断并触发一些事件,而后者将强制应用程序退出而不触发任何事件或允许应用程序取消操作。
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')
}) // 开启托盘闪烁
// this.flashTray(true)
}

托盘图标、右键菜单图标及打包图标均在resource目录下。

Electron打包脚本electron-builder
在根目录新建一个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": "ElectronVite4Vue3"
},
"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}"
}
}
Electron主渲染进程通讯传值
由于electron主渲染进程一般都是单窗口之间进行传值。如果需要在多个窗口间传值,如切换主题功能,则需要在渲染进程发送请求,主进程监听后再发送请求给渲染进程(App.vue中监听)。

<div
class="toolbar__item"
:title="`切换 暗黑/明亮 模式(当前 ${appState.config.isDark ? '暗黑' : '明亮'}模式)`"
@click="changeMode"
>
<Icon :name="appState.config.isDark ? 've-icon-sunny' : 've-icon-yewan'" />
</div> // 主题切换
const changeMode = () => {
appState.config.isDark = !appState.config.isDark
ipcRenderer.send('win__postData', appState.config.isDark)
}
在主进程中使用ipcMain.on监听。
// 主/渲染进程传参
ipcMain.on('win__postData', (event, args) => {
mainWin.webContents.send('win__postData', args)
})
然后在渲染进程App.vue页面监听并处理通讯传值。
/**
* 接收主进程发送的事件
*/
ipcRenderer.on('win__postData', (e, data) => {
console.log('——+——+——receive multiwin data:', data) switch(data.type) {
// 退出登录
case 'WIN_LOGOUT':
appState.$reset()
break;
// 布局切换
case 'CHANGE_LAYOUT':
appState.config.layout = data.value
break;
// 切换主题
case 'CHANGE_MODE':
appState.config.isDark = data.value
appState.changeDark()
break;
// 侧边栏收缩
case 'CHANGE_COLLAPSE':
appState.config.collapse = data.value
break;
}
})
这样就能简单实现多窗口传值了。如果大家有其他方法,欢迎一起交流学习哈~
Ok,基于electron25+vue3开发桌面端仿chatgpt聊天实例就先分享到这里,希望对大家有所帮助
最后附上一个Vue3+Tauri跨端聊天项目
https://www.cnblogs.com/xiaoyan2017/p/16830689.html

Electron-ChatGPT桌面端ChatGPT实例|electron25+vue3聊天AI模板EXE的更多相关文章
- ve-plus:基于 vue3.x 桌面端UI组件库|vue3组件库
VE-Plus 自研轻量级 vue3.js 桌面pc端UI组件库 经过一个多月的筹划及开发,今天给大家带来一款全新的Vue3桌面端UI组件库VEPlus.新增了35+常用的组件,采用vue3 setu ...
- vue3系列:vue3.0自定义全局弹层V3Layer|vue3.x pc桌面端弹窗组件
基于Vue3.0开发PC桌面端自定义对话框组件V3Layer. 前两天有分享一个vue3.0移动端弹出层组件,今天分享的是最新开发的vue3.0版pc端弹窗组件. V3Layer 一款使用vue3.0 ...
- Electron-Vue3-Vadmin后台系统|vite2+electron桌面端权限管理系统
基于vite2.x+electron12桌面端后台管理系统Vite2ElectronVAdmin. 继上一次分享vite2整合electron搭建后台框架,这次带来的是最新开发的跨桌面中后台权限管理系 ...
- Next.js+React聊天室|Next仿微信桌面端|next.js聊天实例
一.项目介绍 next-webchat 基于Next.js+React.js+Redux+Antd+RScroll+RLayer等技术构建的PC桌面端仿微信聊天项目.实现了消息/表情发送.图片/视频预 ...
- chatGPT 桌面版安装教程
概述 标题党了,首先声明 ChatGPT 官方没有桌面版,目前市面上很多的桌面应用也在是浏览器上包了一层,而且大多做的比较粗糙,不太好用,所以都不太推荐. 作为一名骨灰级的程序员,ChatGPT 的重 ...
- Tauri-Vue3桌面端聊天室|tauri+vite3仿微信|tauri聊天程序EXE
基于tauri+vue3.js+vite3跨桌面端仿微信聊天实例TauriVue3Chat. tauri-chat 运用最新tauri+vue3+vite3+element-plus+v3layer等 ...
- AngularJS 和 Electron 构建桌面应用
译]使用 AngularJS 和 Electron 构建桌面应用 原文: Creating Desktop Applications With AngularJS and GitHub Electro ...
- 重复造轮子系列——基于FastReport设计打印模板实现桌面端WPF套打和商超POS高度自适应小票打印
重复造轮子系列——基于FastReport设计打印模板实现桌面端WPF套打和商超POS高度自适应小票打印 一.引言 桌面端系统经常需要对接各种硬件设备,比如扫描器.读卡器.打印机等. 这里介绍下桌面端 ...
- Vue3.0网页版聊天|Vue3.x+ElementPlus仿微信/QQ界面|vue3聊天实例
一.项目简介 基于vue3.x+vuex+vue-router+element-plus+v3layer+v3scroll等技术构建的仿微信web桌面端聊天实战项目Vue3-Webchat.基本上实现 ...
- APP 性能分析工作台——你的最佳桌面端性能分析助手
目前 MARS-App 性能分析工作台版本为开发者提供Fastbot桌面版的服务. 旨在帮助开发者们更快.更便捷地开启智能测试之旅,成倍提升稳定性测试的效率. 作者:字节跳动终端技术--王凯 背景 F ...
随机推荐
- 非常小的一个东西,Spring依赖注入Bean类型的8种情况
大家好,我是三友~~ 今天来讲一个可能看似没有用但是实际又有点用的一个小东西,那就是@Autowired支持注入哪些Bean的类型. 为啥要讲这个呢? 故事说起来可能就比较长了. 不过长话可以短说,仅 ...
- Windows的压缩文件夹(zip/cab)
https://weibo.com/1114096665/DtHXgvnva #windows10# 硬要把zip.cab文件当文件夹,不爽怎么解决? 删除注册表 "HKEY_CLASSES ...
- MySQL 中索引是如何实现的,有哪些类型的索引,如何进行优化索引
MySQL 中的索引 前言 索引的实现 哈希索引 全文索引 B+ 树索引 索引的分类 聚簇索引(clustered index) 非聚簇索引(non-clustered index) 联合索引 覆盖索 ...
- uniapp H5图片编辑器(安卓/iOS适用)
箭头绘制参考了:https://blog.csdn.net/qq_45939676/article/details/127425426 这位大佬的文章 gitee地址: https://gitee.c ...
- Centos9网卡配置
Centos9 网卡配置文件已修改,如下 [root@bogon ~]# cat /etc/NetworkManager/system-connections/ens18.nmconnection [ ...
- python入门教程之十五获取对象属性的几种方法
当我们拿到一个对象的引用时,如何知道这个对象是什么类型.有哪些方法呢? 使用type() 首先,我们来判断对象类型,使用type()函数: 基本类型都可以用type()判断: >>> ...
- [Chrome]插件的导出与导入[转载]
1 文由 毕竟是在墙内,暂时又没有有效的FQ工具,换设备了,但需要使用原先旧设备下载的插件.只能出此下策liao~ 2 导出插件 step1 Chrome: 进入插件管理界面 Chrome: 更多工具 ...
- 巧用Nginx配置解决跨域问题
页面nginx配置 1,前端页面放在域名根目录,比如,http://www.xuecheng.com/ ,对应的nginx配置: #门户 location / { alias D:/Z_lhy/Spr ...
- 执行计划display_cursor函数
问题描述:关于oracle查看真实的执行计划,使用select * from table(dbms_xplan.display_cursor(null,null));的方式来获取执行计划 参考文档:h ...
- LangChain vs Semantic Kernel
每当向他人介绍 Semantic Kernel, 会得到的第一个问题就是 Semantic Kernel 类似于LangChain吗,或者是c# 版本的LangChain吗? 为了全面而不想重复的回答 ...