Based on vue3+pinia2 imitating ChatGPT chat example|vite4.x imitating chatgpt interface

Using vue3+pinia2 to develop imitation chatgpt interface chat instance Vue3-Chatgpt

Based on Vue3.x+Pinia2+VueRouter+Vue3-Markdown and other technologies, a ChatGPT-like web-side chat program is built. Supports classic + column interface layout, light/dark mode, full screen + half screen display, Markdown syntax parsing, sidebar hiding and other functions.

Insert image description here
Insert image description here

technical framework

  • Editing tool: Cursor
  • Framework technology: Vue3+Vite4.x+Pinia2
  • Component library: VEPlus (based on vue3 desktop component library)
  • International multi-language: vue-i18n^9.2.2
  • Code highlighting: highlight.js^11.7.0
  • Local storage: pinia-plugin-persistedstate^3.1.0
  • markdown analysis: vue3-markdown-it

Insert image description here
Insert image description here

Project directory structure

Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here

vite.config.js configuration

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

// https://vitejs.dev/config/
export default defineConfig(({
    
     mode }) => {
    
    
	const viteEnv = loadEnv(mode, process.cwd())
	const env = parseEnv(viteEnv)

	return {
    
    
		plugins: [vue()],

		// base: '/',
		// mode: 'development', // development|production

		/*构建选项*/
		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 和 debugger
			drop: env.VITE_DROP_CONSOLE ? ['console', 'debugger'] : []
		},

		/*开发服务器选项*/
		server: {
    
    
			// 端口
			port: env.VITE_PORT,
			// 是否浏览器自动打开
			open: env.VITE_OPEN,
			// 开启https
			https: env.VITE_HTTPS,
			// 代理配置
			proxy: {
    
    
				// ...
			}
		},

		resolve: {
    
    
			// 设置别名
			alias: {
    
    
				'@': resolve(__dirname, 'src'),
				'@assets': resolve(__dirname, 'src/assets'),
				'@components': resolve(__dirname, 'src/components'),
				'@views': resolve(__dirname, 'src/views'),
				// 解决vue-i18n警告提示:You are running the esm-bundler build of vue-i18n.
				'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
			}
		}
	}
})

main.js main entrance

import {
    
     createApp } from 'vue'
import App from './App.vue'

// 引入Router和Store
import Router from './router'
import Store from './store'

// 引入插件配置
import Plugins from './plugins'

const app = createApp(App)

app
.use(Router)
.use(Store)
.use(Plugins)
.mount('#app')

vue3.x component library

The component library used in the project is based on the vue3 custom UI component library Ve-plus. A lightweight component library that supports 40+ components.
Insert image description here

Insert image description here
Install components

yarn add ve-plus
npm i ve-plus --save

https://blog.csdn.net/yanxinyun1990/article/details/129312570

overall arrangement

The project supports 2 layout modes, and is divided into three modules: top bar + side bar + main content.
Insert image description here

Insert image description here

<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>
<template>
	<div class="vegpt__editor">
		<div class="vegpt__editor-inner">
			<Flex :gap="0">
				<Popover placement="top" trigger="click" width="150">
					<Button class="btn" type="link" icon="ve-icon-yuyin1" v-tooltip="{content: '发送语音', theme: 'light', arrow: false}"></Button>
					<template #content>
						<div class="flexbox flex-alignc flex-col" style="padding: 15px 0;">
							<Icon name="ve-icon-yuyin" size="40" color="#0fa27e" />
							<p class="fs-12 mb-15 c-999">网络不给力</p>
							<Button size="small"><i style="background:#f00;border-radius:50%;box-shadow:0 1px 2px #999;margin-right:5px;height:8px;width:8px;"></i>开始讲话</Button>
						</div>
					</template>
				</Popover>
				<Button class="btn" type="link" v-tooltip="{content: '发送图片', theme: 'light', arrow: false}">
					<Icon name="ve-icon-photo" size="16" cursor />
					<input ref="uploadImgRef" type="file" title="" accept="image/*" @change="handleUploadImage" />
				</Button>
				<Input
					class="flex1"
					ref="editorRef"
					v-model="editorText"
					type="textarea"
					:autosize="{maxRows: 4}"
					clearable
					placeholder="Prompt..."
					@keydown="handleKeydown"
					@clear="handleClear"
					style="margin: 0 5px;"
				/>
				<Button class="btn" type="link" icon="ve-icon-submit" @click="handleSubmit"></Button>
			</Flex>
		</div>
	</div>
</template>
import {
    
     ref, watch } from 'vue'
import {
    
     guid } from '@/utils'
import {
    
     chatStore } from '@/store/modules/chat'

const props = defineProps({
    
    
	value: {
    
     type: [String, Number] }
})
const emit = defineEmits(['clear'])

const chatState = chatStore()

const uploadImgRef = ref()
const editorRef = ref()
const editorText = ref(props.value)

// ...

// 发送会话
const handleSubmit = () => {
    
    
	editorRef.value.focus()
	if(!editorText.value) return

	let data = {
    
    
		type: 'text',
		role: 'User',
		key: guid(),
		content: editorText.value
	}
	chatState.addSession(data)
	// 清空
	editorText.value = ''
}
const handleKeydown = (e) => {
    
    
	// ctrl+enter
	if(e.ctrlKey && e.keyCode == 13) {
    
    
		handleSubmit()
	}
}
const handleClear = () => {
    
    
	emit('clear')
}
// 选择图片
const handleUploadImage = () => {
    
    
	let file = uploadImgRef.value.files[0]
	if(!file) return
	let size = Math.floor(file.size / 1024)
	console.log(size)
	if(size > 2*1024) {
    
    
		Message.danger('图片大小不能超过2M')
		uploadImgRef.value.value = ''
		return false
	}
	let reader = new FileReader()
	reader.readAsDataURL(file)
	reader.onload = function() {
    
    
		let img = this.result

		let data = {
    
    
			type: 'image',
			role: 'User',
			key: guid(),
			content: img
		}
		chatState.addSession(data)
	}
}

Insert image description here

/**
 * 聊天状态管理
 * @author YXY  Q:282310962
 */

import {
    
     defineStore } from 'pinia'
import {
    
     guid, isEmpty } from '@/utils'

export const chatStore = defineStore('chat', {
    
    
	state: () => ({
    
    
		// 聊天会话记录
		sessionId: '',
		session: []
	}),
	getters: {
    
    },
	actions: {
    
    
		// 创建新会话
		createSession(ssid) {
    
    
			this.sessionId = ssid
			this.session.push({
    
    
				sessionId: ssid,
				title: '',
				data: []
			})
		},

		// 新增会话
		addSession(message) {
    
    
			// 判断当前会话uuid是否存在,不存在创建新会话
			if(!this.sessionId) {
    
    
				const ssid = guid()
				this.createSession(ssid)
			}
			this.session.map(item => {
    
    
				if(item.sessionId == this.sessionId) {
    
    
					if(!item.title) {
    
    
						item.title = message.content
					}
					item.data.push(message)
				}
			})
			// ...
		},

		// 获取会话
		getSession() {
    
    
			return this.session.find(item => item.sessionId == this.sessionId)
		},

		// 移除会话
		removeSession(ssid) {
    
    
			const index = this.session.findIndex(item => item?.sessionId === ssid)
			if(index > -1) {
    
    
				this.session.splice(index, 1)
			}
			this.sessionId = ''
		},
		// 删除某一条会话
		deleteSession(ssid) {
    
    
			// ...
		},

		// 清空会话
		clearSession() {
    
    
			this.session = []
			this.sessionId = ''
		}
	},
	// 本地持久化存储(默认存储localStorage)
	persist: true
	/* persist: {
		// key: 'chatStore', // 不设置则是默认app
		storage: localStorage,
		paths: ['aa', 'bb'] // 设置缓存键
	} */
})

Okay, I will share it here based on vue3+vite4+pinia2 to imitate chatgpt chat.

Tauri-Vue3 chat example | Tauri cross-end chat
uniapp-ttlive short video chat | uniapp+uview imitation Douyin example

Insert image description here

Guess you like

Origin blog.csdn.net/yanxinyun1990/article/details/130539403