Vue3-ChatGPT:基於vite4.x+vue3+pinia2模仿chatgpt聊天AI例項

xiaoyan2017發表於2023-05-07

前言

隨著chatgpt智慧聊天持續火爆,越來越多的開發者參與其中。今天給大家分享vite4.x搭建簡易網頁版仿ChatGpt聊天程式Vue3ChatGPT

技術框架

  • 編輯工具:Cursor
  • 框架技術:Vue3+Vite4.x+Pinia2
  • 元件庫:VEPlus (基於vue3桌面端元件庫)
  • 國際化多語言:vue-i18n^9.2.2
  • 程式碼高亮:highlight.js^11.7.0
  • 本地儲存:pinia-plugin-persistedstate^3.1.0
  • markdown解析:vue3-markdown-it

vue3-chatgpt 支援兩種佈局模板、dark+light模式、全屏+半屏展示、Markdown語法解析、側邊欄收起等功能。

專案結構

基於vite4.x構建專案,採用vue3 setup語法糖編碼。

main.js入口檔案

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

vite.config.js配置檔案

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

公共佈局模板

整體分為頂部導航欄、左側選單欄、右側主體欄。

<script setup>
    import { computed } from 'vue'
    import { appStore } from '@/store/modules/app'

    // 引入佈局模板
    import Classic from './layout/classic/index.vue'
    import Columns from './layout/columns/index.vue'

    const store = appStore()
    const config = computed(() => store.config)

    const LayoutConfig = {
        classic: Classic,
        columns: Columns
    }
</script>

<template>
    <div class="vegpt__container" :class="{'is-half': store.config.halfScreen}" :style="{'--themeSkin': store.config.skin}">
        <component :is="LayoutConfig[config.layout]" />
    </div>
</template>

<style lang="scss" scoped></style>
<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>
/**
 * 聊天狀態管理
 * @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'] // 設定快取鍵
    } */
})

image.png

如上圖:聊天框採用Input元件實現功能。

<script setup>
    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 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)
        }
    }

    // ...
</script>

OK,基於vue3開發仿製chatgpt例項就先分享這麼多,希望大家能喜歡~~

https://segmentfault.com/a/1190000042710924
https://segmentfault.com/a/1190000040711708

相關文章