cauto 1 жил өмнө
commit
4b2230560f

+ 10 - 0
.env

@@ -0,0 +1,10 @@
+# Glob API URL
+VITE_GLOB_API_URL=/api
+
+VITE_APP_API_BASE_URL=http://127.0.0.1:3002/
+
+# Whether long replies are supported, which may result in higher API fees
+VITE_GLOB_OPEN_LONG_REPLY=false
+
+# When you want to use PWA
+VITE_GLOB_APP_PWA=false

+ 1 - 0
.npmrc

@@ -0,0 +1 @@
+strict-peer-dependencies=false

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
+}

+ 18 - 0
README.md

@@ -0,0 +1,18 @@
+# Vue 3 + TypeScript + Vite
+
+This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+## Recommended IDE Setup
+
+- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+
+## Type Support For `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
+
+If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
+
+1. Disable the built-in TypeScript Extension
+   1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
+   2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
+2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Vue + TS</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 47 - 0
package.json

@@ -0,0 +1,47 @@
+{
+  "name": "gpt",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vue-tsc && vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@traptitech/markdown-it-katex": "^3.6.0",
+    "@vueuse/core": "^9.13.0",
+    "highlight.js": "^11.7.0",
+    "html2canvas": "^1.4.1",
+    "katex": "^0.16.4",
+    "naive-ui": "^2.34.3",
+    "pinia": "^2.0.33",
+    "vue": "^3.2.47",
+    "vue-i18n": "^9.2.2",
+    "vue-router": "^4.1.6"
+  },
+  "devDependencies": {
+    "@types/crypto-js": "^4.1.1",
+    "@vitejs/plugin-vue": "^4.1.0",
+    "autoprefixer": "^10.4.14",
+    "axios": "^1.3.4",
+    "crypto-js": "^4.1.1",
+    "eslint": "^8.35.0",
+    "husky": "^8.0.3",
+    "less": "^4.1.3",
+    "lint-staged": "^13.1.2",
+    "markdown-it-link-attributes": "^4.0.1",
+    "npm-run-all": "^4.1.5",
+    "postcss": "^8.4.21",
+    "tailwindcss": "^3.3.1",
+    "typescript": "^4.9.3",
+    "vite": "^4.2.0",
+    "vite-plugin-pwa": "^0.14.4",
+    "vue-tsc": "^1.2.0"
+  },
+  "lint-staged": {
+    "*.{ts,tsx,vue}": [
+      "pnpm lint:fix"
+    ]
+  }
+}

+ 6 - 0
postcss.config.js

@@ -0,0 +1,6 @@
+export default {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+}

+ 1 - 0
public/vite.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 23 - 0
src/App.vue

@@ -0,0 +1,23 @@
+<script setup lang="ts">
+import { NConfigProvider } from 'naive-ui'
+import { useTheme } from '@/hooks/useTheme'
+import { useLanguage } from '@/hooks/useLanguage'
+const { theme, themeOverrides } = useTheme()
+const { language } = useLanguage()
+</script>
+
+<template>
+  <NConfigProvider
+      class="h-full"
+      :theme="theme"
+      :theme-overrides="themeOverrides"
+      :locale="language"
+  >
+    <NaiveProvider>
+      <RouterView />
+    </NaiveProvider>
+  </NConfigProvider>
+</template>
+
+<style scoped>
+</style>

+ 1 - 0
src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 38 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,38 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+defineProps<{ msg: string }>()
+
+const count = ref(0)
+</script>
+
+<template>
+  <h1>{{ msg }}</h1>
+
+  <div class="card">
+    <button type="button" @click="count++">count is {{ count }}</button>
+    <p>
+      Edit
+      <code>components/HelloWorld.vue</code> to test HMR
+    </p>
+  </div>
+
+  <p>
+    Check out
+    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
+      >create-vue</a
+    >, the official Vue + Vite starter
+  </p>
+  <p>
+    Install
+    <a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
+    in your IDE for a better DX
+  </p>
+  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
+</template>
+
+<style scoped>
+.read-the-docs {
+  color: #888;
+}
+</style>

+ 27 - 0
src/hooks/useLanguage.ts

@@ -0,0 +1,27 @@
+import { computed } from 'vue'
+import { enUS, zhCN, zhTW } from 'naive-ui'
+import { useAppStore } from '@/store'
+import { setLocale } from '@/locales'
+
+export function useLanguage() {
+    const appStore = useAppStore()
+
+    const language = computed(() => {
+        switch (appStore.language) {
+            case 'en-US':
+                setLocale('en-US')
+                return enUS
+            case 'zh-CN':
+                setLocale('zh-CN')
+                return zhCN
+            case 'zh-TW':
+                setLocale('zh-TW')
+                return zhTW
+            default:
+                setLocale('zh-CN')
+                return enUS
+        }
+    })
+
+    return { language }
+}

+ 42 - 0
src/hooks/useTheme.ts

@@ -0,0 +1,42 @@
+import type { GlobalThemeOverrides } from 'naive-ui'
+import { computed, watch } from 'vue'
+import { darkTheme, useOsTheme } from 'naive-ui'
+import { useAppStore } from '@/store'
+export function useTheme() {
+    const appStore = useAppStore()
+
+    const OsTheme = useOsTheme()
+
+    const isDark = computed(() => {
+        if (appStore.theme === 'auto')
+            return OsTheme.value === 'dark'
+        else
+            return appStore.theme === 'dark'
+    })
+
+    const theme = computed(() => {
+        return isDark.value ? darkTheme : undefined
+    })
+
+    const themeOverrides = computed<GlobalThemeOverrides>(() => {
+        if (isDark.value) {
+            return {
+                common: {},
+            }
+        }
+        return {}
+    })
+
+    watch(
+        () => isDark.value,
+        (dark) => {
+            if (dark)
+                document.documentElement.classList.add('dark')
+            else
+                document.documentElement.classList.remove('dark')
+        },
+        { immediate: true },
+    )
+
+    return { theme, themeOverrides }
+}

+ 91 - 0
src/locales/en-US.ts

@@ -0,0 +1,91 @@
+export default {
+  common: {
+    add: 'Add',
+    addSuccess: 'Add Success',
+    edit: 'Edit',
+    editSuccess: 'Edit Success',
+    delete: 'Delete',
+    deleteSuccess: 'Delete Success',
+    save: 'Save',
+    saveSuccess: 'Save Success',
+    reset: 'Reset',
+    action: 'Action',
+    export: 'Export',
+    exportSuccess: 'Export Success',
+    import: 'Import',
+    importSuccess: 'Import Success',
+    clear: 'Clear',
+    clearSuccess: 'Clear Success',
+    yes: 'Yes',
+    no: 'No',
+    confirm: 'Confirm',
+    download: 'Download',
+    noData: 'No Data',
+    wrong: 'Something went wrong, please try again later.',
+    success: 'Success',
+    failed: 'Failed',
+    verify: 'Verify',
+    unauthorizedTips: 'Unauthorized, please verify first.',
+  },
+  chat: {
+    newChatButton: 'New Chat',
+    placeholder: 'Ask me anything...(Shift + Enter = line break)',
+    placeholderMobile: 'Ask me anything...',
+    copy: 'Copy',
+    copied: 'Copied',
+    copyCode: 'Copy Code',
+    clearChat: 'Clear Chat',
+    clearChatConfirm: 'Are you sure to clear this chat?',
+    exportImage: 'Export Image',
+    exportImageConfirm: 'Are you sure to export this chat to png?',
+    exportSuccess: 'Export Success',
+    exportFailed: 'Export Failed',
+    usingContext: 'Context Mode',
+    turnOnContext: 'In the current mode, sending messages will carry previous chat records.',
+    turnOffContext: 'In the current mode, sending messages will not carry previous chat records.',
+    deleteMessage: 'Delete Message',
+    deleteMessageConfirm: 'Are you sure to delete this message?',
+    deleteHistoryConfirm: 'Are you sure to clear this history?',
+    clearHistoryConfirm: 'Are you sure to clear chat history?',
+    preview: 'Preview',
+    showRawText: 'Show as raw text',
+  },
+  setting: {
+    setting: 'Setting',
+    general: 'General',
+    advanced: 'Advanced',
+    config: 'Config',
+    avatarLink: 'Avatar Link',
+    name: 'Name',
+    description: 'Description',
+    role: 'Role',
+    resetUserInfo: 'Reset UserInfo',
+    chatHistory: 'ChatHistory',
+    theme: 'Theme',
+    language: 'Language',
+    api: 'API',
+    reverseProxy: 'Reverse Proxy',
+    timeout: 'Timeout',
+    socks: 'Socks',
+    httpsProxy: 'HTTPS Proxy',
+    balance: 'API Balance',
+  },
+  store: {
+    siderButton: 'Prompt Store',
+    local: 'Local',
+    online: 'Online',
+    title: 'Title',
+    description: 'Description',
+    clearStoreConfirm: 'Whether to clear the data?',
+    importPlaceholder: 'Please paste the JSON data here',
+    addRepeatTitleTips: 'Title duplicate, please re-enter',
+    addRepeatContentTips: 'Content duplicate: {msg}, please re-enter',
+    editRepeatTitleTips: 'Title conflict, please revise',
+    editRepeatContentTips: 'Content conflict {msg} , please re-modify',
+    importError: 'Key value mismatch',
+    importRepeatTitle: 'Title repeatedly skipped: {msg}',
+    importRepeatContent: 'Content is repeatedly skipped: {msg}',
+    onlineImportWarning: 'Note: Please check the JSON file source!',
+    downloadError: 'Please check the network status and JSON file validity',
+  },
+}

+ 34 - 0
src/locales/index.ts

@@ -0,0 +1,34 @@
+import type { App } from 'vue'
+import { createI18n } from 'vue-i18n'
+import enUS from './en-US'
+import zhCN from './zh-CN'
+import zhTW from './zh-TW'
+import { useAppStoreWithOut } from '@/store/modules/app'
+import type { Language } from '@/store/modules/app/helper'
+
+const appStore = useAppStoreWithOut()
+
+const defaultLocale = appStore.language || 'zh-CN'
+
+const i18n = createI18n({
+  locale: defaultLocale,
+  fallbackLocale: 'en-US',
+  allowComposition: true,
+  messages: {
+    'en-US': enUS,
+    'zh-CN': zhCN,
+    'zh-TW': zhTW,
+  },
+})
+
+export const t = i18n.global.t
+
+export function setLocale(locale: Language) {
+  i18n.global.locale = locale
+}
+
+export function setupI18n(app: App) {
+  app.use(i18n)
+}
+
+export default i18n

+ 91 - 0
src/locales/zh-CN.ts

@@ -0,0 +1,91 @@
+export default {
+  common: {
+    add: '添加',
+    addSuccess: '添加成功',
+    edit: '编辑',
+    editSuccess: '编辑成功',
+    delete: '删除',
+    deleteSuccess: '删除成功',
+    save: '保存',
+    saveSuccess: '保存成功',
+    reset: '重置',
+    action: '操作',
+    export: '导出',
+    exportSuccess: '导出成功',
+    import: '导入',
+    importSuccess: '导入成功',
+    clear: '清空',
+    clearSuccess: '清空成功',
+    yes: '是',
+    no: '否',
+    confirm: '确定',
+    download: '下载',
+    noData: '暂无数据',
+    wrong: '好像出错了,请稍后再试。',
+    success: '操作成功',
+    failed: '操作失败',
+    verify: '验证',
+    unauthorizedTips: '未经授权,请先进行验证。',
+  },
+  chat: {
+    newChatButton: '新建聊天',
+    placeholder: '来说点什么吧...(Shift + Enter = 换行)',
+    placeholderMobile: '来说点什么...',
+    copy: '复制',
+    copied: '复制成功',
+    copyCode: '复制代码',
+    clearChat: '清空会话',
+    clearChatConfirm: '是否清空会话?',
+    exportImage: '保存会话到图片',
+    exportImageConfirm: '是否将会话保存为图片?',
+    exportSuccess: '保存成功',
+    exportFailed: '保存失败',
+    usingContext: '上下文模式',
+    turnOnContext: '当前模式下, 发送消息会携带之前的聊天记录',
+    turnOffContext: '当前模式下, 发送消息不会携带之前的聊天记录',
+    deleteMessage: '删除消息',
+    deleteMessageConfirm: '是否删除此消息?',
+    deleteHistoryConfirm: '确定删除此记录?',
+    clearHistoryConfirm: '确定清空聊天记录?',
+    preview: '预览',
+    showRawText: '显示原文',
+  },
+  setting: {
+    setting: '设置',
+    general: '总览',
+    advanced: '高级',
+    config: '配置',
+    avatarLink: '头像链接',
+    name: '名称',
+    description: '描述',
+    role: '角色设定',
+    resetUserInfo: '重置用户信息',
+    chatHistory: '聊天记录',
+    theme: '主题',
+    language: '语言',
+    api: 'API',
+    reverseProxy: '反向代理',
+    timeout: '超时',
+    socks: 'Socks',
+    httpsProxy: 'HTTPS Proxy',
+    balance: 'API余额',
+  },
+  store: {
+    siderButton: '提示词商店',
+    local: '本地',
+    online: '在线',
+    title: '标题',
+    description: '描述',
+    clearStoreConfirm: '是否清空数据?',
+    importPlaceholder: '请粘贴 JSON 数据到此处',
+    addRepeatTitleTips: '标题重复,请重新输入',
+    addRepeatContentTips: '内容重复:{msg},请重新输入',
+    editRepeatTitleTips: '标题冲突,请重新修改',
+    editRepeatContentTips: '内容冲突{msg} ,请重新修改',
+    importError: '键值不匹配',
+    importRepeatTitle: '标题重复跳过:{msg}',
+    importRepeatContent: '内容重复跳过:{msg}',
+    onlineImportWarning: '注意:请检查 JSON 文件来源!',
+    downloadError: '请检查网络状态与 JSON 文件有效性',
+  },
+}

+ 91 - 0
src/locales/zh-TW.ts

@@ -0,0 +1,91 @@
+export default {
+  common: {
+    add: '新增',
+    addSuccess: '新增成功',
+    edit: '編輯',
+    editSuccess: '編輯成功',
+    delete: '刪除',
+    deleteSuccess: '刪除成功',
+    save: '儲存',
+    saveSuccess: '儲存成功',
+    reset: '重設',
+    action: '操作',
+    export: '匯出',
+    exportSuccess: '匯出成功',
+    import: '匯入',
+    importSuccess: '匯入成功',
+    clear: '清除',
+    clearSuccess: '清除成功',
+    yes: '是',
+    no: '否',
+    confirm: '確認',
+    download: '下載',
+    noData: '目前無資料',
+    wrong: '發生錯誤,請稍後再試。',
+    success: '操作成功',
+    failed: '操作失敗',
+    verify: '驗證',
+    unauthorizedTips: '未經授權,請先進行驗證。',
+  },
+  chat: {
+    newChatButton: '新建對話',
+    placeholder: '來說點什麼...(Shift + Enter = 換行)',
+    placeholderMobile: '來說點什麼...',
+    copy: '複製',
+    copied: '複製成功',
+    copyCode: '複製代碼',
+    clearChat: '清除對話',
+    clearChatConfirm: '是否清空對話?',
+    exportImage: '儲存對話為圖片',
+    exportImageConfirm: '是否將對話儲存為圖片?',
+    exportSuccess: '儲存成功',
+    exportFailed: '儲存失敗',
+    usingContext: '上下文模式',
+    turnOnContext: '啟用上下文模式,在此模式下,發送訊息會包含之前的聊天記錄。',
+    turnOffContext: '關閉上下文模式,在此模式下,發送訊息不會包含之前的聊天記錄。',
+    deleteMessage: '刪除訊息',
+    deleteMessageConfirm: '是否刪除此訊息?',
+    deleteHistoryConfirm: '確定刪除此紀錄?',
+    clearHistoryConfirm: '確定清除紀錄?',
+    preview: '預覽',
+    showRawText: '顯示原文',
+  },
+  setting: {
+    setting: '設定',
+    general: '總覽',
+    advanced: '高級',
+    config: '設定',
+    avatarLink: '頭貼連結',
+    name: '名稱',
+    description: '描述',
+    role: '角色設定',
+    resetUserInfo: '重設使用者資訊',
+    chatHistory: '紀錄',
+    theme: '主題',
+    language: '語言',
+    api: 'API',
+    reverseProxy: '反向代理',
+    timeout: '逾時',
+    socks: 'Socks',
+    httpsProxy: 'HTTPS Proxy',
+    balance: 'API余額',
+  },
+  store: {
+    siderButton: '提示詞商店',
+    local: '本機',
+    online: '線上',
+    title: '標題',
+    description: '描述',
+    clearStoreConfirm: '是否清除資料?',
+    importPlaceholder: '請將 JSON 資料貼在此處',
+    addRepeatTitleTips: '標題重複,請重新輸入',
+    addRepeatContentTips: '內容重複:{msg},請重新輸入',
+    editRepeatTitleTips: '標題衝突,請重新修改',
+    editRepeatContentTips: '內容衝突{msg} ,請重新修改',
+    importError: '鍵值不符合',
+    importRepeatTitle: '因標題重複跳過:{msg}',
+    importRepeatContent: '因內容重複跳過:{msg}',
+    onlineImportWarning: '注意:請檢查 JSON 檔案來源!',
+    downloadError: '請檢查網路狀態與 JSON 檔案有效性',
+  },
+}

+ 22 - 0
src/main.ts

@@ -0,0 +1,22 @@
+import { createApp } from 'vue'
+import './style.css'
+import App from './App.vue'
+import { setupI18n } from './locales'
+import { setupAssets, setupScrollbarStyle } from './plugins'
+import { setupStore } from './store'
+import { setupRouter } from './router'
+
+async function bootstrap() {
+    const app = createApp(App)
+    setupAssets()
+    setupScrollbarStyle()
+
+    setupStore(app)
+    setupI18n(app)
+    await setupRouter(app)
+
+
+    app.mount('#app')
+}
+
+bootstrap()

+ 18 - 0
src/plugins/assets.ts

@@ -0,0 +1,18 @@
+import 'katex/dist/katex.min.css'
+import '@/styles/lib/tailwind.css'
+import '@/styles/lib/highlight.less'
+import '@/styles/lib/github-markdown.less'
+import '@/styles/global.less'
+
+/** Tailwind's Preflight Style Override */
+function naiveStyleOverride() {
+  const meta = document.createElement('meta')
+  meta.name = 'naive-ui-style'
+  document.head.appendChild(meta)
+}
+
+function setupAssets() {
+  naiveStyleOverride()
+}
+
+export default setupAssets

+ 4 - 0
src/plugins/index.ts

@@ -0,0 +1,4 @@
+import setupAssets from './assets'
+import setupScrollbarStyle from './scrollbarStyle'
+
+export { setupAssets, setupScrollbarStyle }

+ 28 - 0
src/plugins/scrollbarStyle.ts

@@ -0,0 +1,28 @@
+import { darkTheme, lightTheme } from 'naive-ui'
+
+const setupScrollbarStyle = () => {
+  const style = document.createElement('style')
+  const styleContent = `
+    ::-webkit-scrollbar {
+      background-color: transparent;
+      width: ${lightTheme.Scrollbar.common?.scrollbarWidth};
+    }
+    ::-webkit-scrollbar-thumb {
+      background-color: ${lightTheme.Scrollbar.common?.scrollbarColor};
+      border-radius: ${lightTheme.Scrollbar.common?.scrollbarBorderRadius};
+    }
+    html.dark ::-webkit-scrollbar {
+      background-color: transparent;
+      width: ${darkTheme.Scrollbar.common?.scrollbarWidth};
+    }
+    html.dark ::-webkit-scrollbar-thumb {
+      background-color: ${darkTheme.Scrollbar.common?.scrollbarColor};
+      border-radius: ${darkTheme.Scrollbar.common?.scrollbarBorderRadius};
+    }
+  `
+
+  style.innerHTML = styleContent
+  document.head.appendChild(style)
+}
+
+export default setupScrollbarStyle

+ 24 - 0
src/router/index.ts

@@ -0,0 +1,24 @@
+import type { App } from 'vue'
+import type { RouteRecordRaw } from 'vue-router'
+import { createRouter, createWebHashHistory } from 'vue-router'
+import { setupPageGuard } from './permission'
+// import { ChatLayout } from '@/views/chat/layout'
+
+
+const routes: RouteRecordRaw[] = [
+
+]
+
+export const router = createRouter({
+    history: createWebHashHistory(),
+    routes,
+    scrollBehavior: () => ({ left: 0, top: 0 }),
+})
+
+setupPageGuard(router)
+
+export async function setupRouter(app: App) {
+    app.use(router)
+    await router.isReady()
+}
+

+ 26 - 0
src/router/permission.ts

@@ -0,0 +1,26 @@
+import type { Router } from 'vue-router'
+export function setupPageGuard(router: Router) {
+    router.beforeEach(async (to, from, next) => {
+        // const authStore = useAuthStoreWithout()
+        // if (!authStore.session) {
+        //     try {
+        //         const data = await authStore.getSession()
+        //         if (String(data.auth) === 'false' && authStore.token)
+        //             authStore.removeToken()
+        //         if (to.path === '/500')
+        //             next({ name: 'Root' })
+        //         else
+        //             next()
+        //     }
+        //     catch (error) {
+        //         if (to.path !== '/500')
+        //             next({ name: '500' })
+        //         else
+        //             next()
+        //     }
+        // }
+        // else {
+        //     next()
+        // }
+    })
+}

+ 10 - 0
src/store/index.ts

@@ -0,0 +1,10 @@
+import type { App } from 'vue'
+import { createPinia } from 'pinia'
+
+export const store = createPinia()
+
+export function setupStore(app: App) {
+    app.use(store)
+}
+
+export * from './modules'

+ 26 - 0
src/store/modules/app/helper.ts

@@ -0,0 +1,26 @@
+import { ss } from '@/utils/storage'
+
+const LOCAL_NAME = 'appSetting'
+
+export type Theme = 'light' | 'dark' | 'auto'
+
+export type Language = 'zh-CN' | 'zh-TW' | 'en-US'
+
+export interface AppState {
+    siderCollapsed: boolean
+    theme: Theme
+    language: Language
+}
+
+export function defaultSetting(): AppState {
+    return { siderCollapsed: false, theme: 'light', language: 'zh-CN' }
+}
+
+export function getLocalSetting(): AppState {
+    const localSetting: AppState | undefined = ss.get(LOCAL_NAME)
+    return { ...defaultSetting(), ...localSetting }
+}
+
+export function setLocalSetting(setting: AppState): void {
+    ss.set(LOCAL_NAME, setting)
+}

+ 35 - 0
src/store/modules/app/index.ts

@@ -0,0 +1,35 @@
+import { defineStore } from 'pinia'
+import type { AppState, Language, Theme } from './helper'
+import { getLocalSetting, setLocalSetting } from './helper'
+import { store } from '@/store'
+
+
+export const useAppStore = defineStore('app-store', {
+    state: (): AppState => getLocalSetting(),
+    actions: {
+        setSiderCollapsed(collapsed: boolean) {
+            this.siderCollapsed = collapsed
+            this.recordState()
+        },
+
+        setTheme(theme: Theme) {
+            this.theme = theme
+            this.recordState()
+        },
+
+        setLanguage(language: Language) {
+            if (this.language !== language) {
+                this.language = language
+                this.recordState()
+            }
+        },
+
+        recordState() {
+            setLocalSetting(this.$state)
+        },
+    },
+})
+
+export function useAppStoreWithOut() {
+    return useAppStore(store)
+}

+ 1 - 0
src/store/modules/index.ts

@@ -0,0 +1 @@
+export * from './app'

+ 80 - 0
src/style.css

@@ -0,0 +1,80 @@
+:root {
+  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+  line-height: 1.5;
+  font-weight: 400;
+
+  color-scheme: light dark;
+  color: rgba(255, 255, 255, 0.87);
+  background-color: #242424;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-text-size-adjust: 100%;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+body {
+  margin: 0;
+  display: flex;
+  place-items: center;
+  min-width: 320px;
+  min-height: 100vh;
+}
+
+h1 {
+  font-size: 3.2em;
+  line-height: 1.1;
+}
+
+button {
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #1a1a1a;
+  cursor: pointer;
+  transition: border-color 0.25s;
+}
+button:hover {
+  border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+  outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+  padding: 2em;
+}
+
+#app {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+@media (prefers-color-scheme: light) {
+  :root {
+    color: #213547;
+    background-color: #ffffff;
+  }
+  a:hover {
+    color: #747bff;
+  }
+  button {
+    background-color: #f9f9f9;
+  }
+}

+ 10 - 0
src/styles/global.less

@@ -0,0 +1,10 @@
+html,
+body,
+#app {
+	height: 100%;
+}
+
+body {
+	padding-bottom: constant(safe-area-inset-bottom);
+	padding-bottom: env(safe-area-inset-bottom);
+}

+ 1102 - 0
src/styles/lib/github-markdown.less

@@ -0,0 +1,1102 @@
+html.dark {
+  .markdown-body {
+    color-scheme: dark;
+    --color-prettylights-syntax-comment: #8b949e;
+    --color-prettylights-syntax-constant: #79c0ff;
+    --color-prettylights-syntax-entity: #d2a8ff;
+    --color-prettylights-syntax-storage-modifier-import: #c9d1d9;
+    --color-prettylights-syntax-entity-tag: #7ee787;
+    --color-prettylights-syntax-keyword: #ff7b72;
+    --color-prettylights-syntax-string: #a5d6ff;
+    --color-prettylights-syntax-variable: #ffa657;
+    --color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
+    --color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
+    --color-prettylights-syntax-invalid-illegal-bg: #8e1519;
+    --color-prettylights-syntax-carriage-return-text: #f0f6fc;
+    --color-prettylights-syntax-carriage-return-bg: #b62324;
+    --color-prettylights-syntax-string-regexp: #7ee787;
+    --color-prettylights-syntax-markup-list: #f2cc60;
+    --color-prettylights-syntax-markup-heading: #1f6feb;
+    --color-prettylights-syntax-markup-italic: #c9d1d9;
+    --color-prettylights-syntax-markup-bold: #c9d1d9;
+    --color-prettylights-syntax-markup-deleted-text: #ffdcd7;
+    --color-prettylights-syntax-markup-deleted-bg: #67060c;
+    --color-prettylights-syntax-markup-inserted-text: #aff5b4;
+    --color-prettylights-syntax-markup-inserted-bg: #033a16;
+    --color-prettylights-syntax-markup-changed-text: #ffdfb6;
+    --color-prettylights-syntax-markup-changed-bg: #5a1e02;
+    --color-prettylights-syntax-markup-ignored-text: #c9d1d9;
+    --color-prettylights-syntax-markup-ignored-bg: #1158c7;
+    --color-prettylights-syntax-meta-diff-range: #d2a8ff;
+    --color-prettylights-syntax-brackethighlighter-angle: #8b949e;
+    --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
+    --color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
+    --color-fg-default: #c9d1d9;
+    --color-fg-muted: #8b949e;
+    --color-fg-subtle: #6e7681;
+    --color-canvas-default: #0d1117;
+    --color-canvas-subtle: #161b22;
+    --color-border-default: #30363d;
+    --color-border-muted: #21262d;
+    --color-neutral-muted: rgba(110,118,129,0.4);
+    --color-accent-fg: #58a6ff;
+    --color-accent-emphasis: #1f6feb;
+    --color-attention-subtle: rgba(187,128,9,0.15);
+    --color-danger-fg: #f85149;
+  }
+}
+
+html {
+  .markdown-body {
+    color-scheme: light;
+    --color-prettylights-syntax-comment: #6e7781;
+    --color-prettylights-syntax-constant: #0550ae;
+    --color-prettylights-syntax-entity: #8250df;
+    --color-prettylights-syntax-storage-modifier-import: #24292f;
+    --color-prettylights-syntax-entity-tag: #116329;
+    --color-prettylights-syntax-keyword: #cf222e;
+    --color-prettylights-syntax-string: #0a3069;
+    --color-prettylights-syntax-variable: #953800;
+    --color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
+    --color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
+    --color-prettylights-syntax-invalid-illegal-bg: #82071e;
+    --color-prettylights-syntax-carriage-return-text: #f6f8fa;
+    --color-prettylights-syntax-carriage-return-bg: #cf222e;
+    --color-prettylights-syntax-string-regexp: #116329;
+    --color-prettylights-syntax-markup-list: #3b2300;
+    --color-prettylights-syntax-markup-heading: #0550ae;
+    --color-prettylights-syntax-markup-italic: #24292f;
+    --color-prettylights-syntax-markup-bold: #24292f;
+    --color-prettylights-syntax-markup-deleted-text: #82071e;
+    --color-prettylights-syntax-markup-deleted-bg: #ffebe9;
+    --color-prettylights-syntax-markup-inserted-text: #116329;
+    --color-prettylights-syntax-markup-inserted-bg: #dafbe1;
+    --color-prettylights-syntax-markup-changed-text: #953800;
+    --color-prettylights-syntax-markup-changed-bg: #ffd8b5;
+    --color-prettylights-syntax-markup-ignored-text: #eaeef2;
+    --color-prettylights-syntax-markup-ignored-bg: #0550ae;
+    --color-prettylights-syntax-meta-diff-range: #8250df;
+    --color-prettylights-syntax-brackethighlighter-angle: #57606a;
+    --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
+    --color-prettylights-syntax-constant-other-reference-link: #0a3069;
+    --color-fg-default: #24292f;
+    --color-fg-muted: #57606a;
+    --color-fg-subtle: #6e7781;
+    --color-canvas-default: #ffffff;
+    --color-canvas-subtle: #f6f8fa;
+    --color-border-default: #d0d7de;
+    --color-border-muted: hsla(210,18%,87%,1);
+    --color-neutral-muted: rgba(175,184,193,0.2);
+    --color-accent-fg: #0969da;
+    --color-accent-emphasis: #0969da;
+    --color-attention-subtle: #fff8c5;
+    --color-danger-fg: #cf222e;
+  }
+}
+
+.markdown-body {
+  -ms-text-size-adjust: 100%;
+  -webkit-text-size-adjust: 100%;
+  margin: 0;
+  color: var(--color-fg-default);
+  background-color: var(--color-canvas-default);
+  font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
+  font-size: 16px;
+  line-height: 1.5;
+  word-wrap: break-word;
+}
+
+.markdown-body .octicon {
+  display: inline-block;
+  fill: currentColor;
+  vertical-align: text-bottom;
+}
+
+.markdown-body h1:hover .anchor .octicon-link:before,
+.markdown-body h2:hover .anchor .octicon-link:before,
+.markdown-body h3:hover .anchor .octicon-link:before,
+.markdown-body h4:hover .anchor .octicon-link:before,
+.markdown-body h5:hover .anchor .octicon-link:before,
+.markdown-body h6:hover .anchor .octicon-link:before {
+  width: 16px;
+  height: 16px;
+  content: ' ';
+  display: inline-block;
+  background-color: currentColor;
+  -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>");
+  mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>");
+}
+
+.markdown-body details,
+.markdown-body figcaption,
+.markdown-body figure {
+  display: block;
+}
+
+.markdown-body summary {
+  display: list-item;
+}
+
+.markdown-body [hidden] {
+  display: none !important;
+}
+
+.markdown-body a {
+  background-color: transparent;
+  color: var(--color-accent-fg);
+  text-decoration: none;
+}
+
+.markdown-body abbr[title] {
+  border-bottom: none;
+  text-decoration: underline dotted;
+}
+
+.markdown-body b,
+.markdown-body strong {
+  font-weight: var(--base-text-weight-semibold, 600);
+}
+
+.markdown-body dfn {
+  font-style: italic;
+}
+
+.markdown-body h1 {
+  margin: .67em 0;
+  font-weight: var(--base-text-weight-semibold, 600);
+  padding-bottom: .3em;
+  font-size: 2em;
+  border-bottom: 1px solid var(--color-border-muted);
+}
+
+.markdown-body mark {
+  background-color: var(--color-attention-subtle);
+  color: var(--color-fg-default);
+}
+
+.markdown-body small {
+  font-size: 90%;
+}
+
+.markdown-body sub,
+.markdown-body sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+.markdown-body sub {
+  bottom: -0.25em;
+}
+
+.markdown-body sup {
+  top: -0.5em;
+}
+
+.markdown-body img {
+  border-style: none;
+  max-width: 100%;
+  box-sizing: content-box;
+  background-color: var(--color-canvas-default);
+}
+
+.markdown-body code,
+.markdown-body kbd,
+.markdown-body pre,
+.markdown-body samp {
+  font-family: monospace;
+  font-size: 1em;
+}
+
+.markdown-body figure {
+  margin: 1em 40px;
+}
+
+.markdown-body hr {
+  box-sizing: content-box;
+  overflow: hidden;
+  background: transparent;
+  border-bottom: 1px solid var(--color-border-muted);
+  height: .25em;
+  padding: 0;
+  margin: 24px 0;
+  background-color: var(--color-border-default);
+  border: 0;
+}
+
+.markdown-body input {
+  font: inherit;
+  margin: 0;
+  overflow: visible;
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+
+.markdown-body [type=button],
+.markdown-body [type=reset],
+.markdown-body [type=submit] {
+  -webkit-appearance: button;
+}
+
+.markdown-body [type=checkbox],
+.markdown-body [type=radio] {
+  box-sizing: border-box;
+  padding: 0;
+}
+
+.markdown-body [type=number]::-webkit-inner-spin-button,
+.markdown-body [type=number]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+.markdown-body [type=search]::-webkit-search-cancel-button,
+.markdown-body [type=search]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+.markdown-body ::-webkit-input-placeholder {
+  color: inherit;
+  opacity: .54;
+}
+
+.markdown-body ::-webkit-file-upload-button {
+  -webkit-appearance: button;
+  font: inherit;
+}
+
+.markdown-body a:hover {
+  text-decoration: underline;
+}
+
+.markdown-body ::placeholder {
+  color: var(--color-fg-subtle);
+  opacity: 1;
+}
+
+.markdown-body hr::before {
+  display: table;
+  content: "";
+}
+
+.markdown-body hr::after {
+  display: table;
+  clear: both;
+  content: "";
+}
+
+.markdown-body table {
+  border-spacing: 0;
+  border-collapse: collapse;
+  display: block;
+  width: max-content;
+  max-width: 100%;
+  overflow: auto;
+}
+
+.markdown-body td,
+.markdown-body th {
+  padding: 0;
+}
+
+.markdown-body details summary {
+  cursor: pointer;
+}
+
+.markdown-body details:not([open])>*:not(summary) {
+  display: none !important;
+}
+
+.markdown-body a:focus,
+.markdown-body [role=button]:focus,
+.markdown-body input[type=radio]:focus,
+.markdown-body input[type=checkbox]:focus {
+  outline: 2px solid var(--color-accent-fg);
+  outline-offset: -2px;
+  box-shadow: none;
+}
+
+.markdown-body a:focus:not(:focus-visible),
+.markdown-body [role=button]:focus:not(:focus-visible),
+.markdown-body input[type=radio]:focus:not(:focus-visible),
+.markdown-body input[type=checkbox]:focus:not(:focus-visible) {
+  outline: solid 1px transparent;
+}
+
+.markdown-body a:focus-visible,
+.markdown-body [role=button]:focus-visible,
+.markdown-body input[type=radio]:focus-visible,
+.markdown-body input[type=checkbox]:focus-visible {
+  outline: 2px solid var(--color-accent-fg);
+  outline-offset: -2px;
+  box-shadow: none;
+}
+
+.markdown-body a:not([class]):focus,
+.markdown-body a:not([class]):focus-visible,
+.markdown-body input[type=radio]:focus,
+.markdown-body input[type=radio]:focus-visible,
+.markdown-body input[type=checkbox]:focus,
+.markdown-body input[type=checkbox]:focus-visible {
+  outline-offset: 0;
+}
+
+.markdown-body kbd {
+  display: inline-block;
+  padding: 3px 5px;
+  font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
+  line-height: 10px;
+  color: var(--color-fg-default);
+  vertical-align: middle;
+  background-color: var(--color-canvas-subtle);
+  border: solid 1px var(--color-neutral-muted);
+  border-bottom-color: var(--color-neutral-muted);
+  border-radius: 6px;
+  box-shadow: inset 0 -1px 0 var(--color-neutral-muted);
+}
+
+.markdown-body h1,
+.markdown-body h2,
+.markdown-body h3,
+.markdown-body h4,
+.markdown-body h5,
+.markdown-body h6 {
+  margin-top: 24px;
+  margin-bottom: 16px;
+  font-weight: var(--base-text-weight-semibold, 600);
+  line-height: 1.25;
+}
+
+.markdown-body h2 {
+  font-weight: var(--base-text-weight-semibold, 600);
+  padding-bottom: .3em;
+  font-size: 1.5em;
+  border-bottom: 1px solid var(--color-border-muted);
+}
+
+.markdown-body h3 {
+  font-weight: var(--base-text-weight-semibold, 600);
+  font-size: 1.25em;
+}
+
+.markdown-body h4 {
+  font-weight: var(--base-text-weight-semibold, 600);
+  font-size: 1em;
+}
+
+.markdown-body h5 {
+  font-weight: var(--base-text-weight-semibold, 600);
+  font-size: .875em;
+}
+
+.markdown-body h6 {
+  font-weight: var(--base-text-weight-semibold, 600);
+  font-size: .85em;
+  color: var(--color-fg-muted);
+}
+
+.markdown-body p {
+  margin-top: 0;
+  margin-bottom: 10px;
+}
+
+.markdown-body blockquote {
+  margin: 0;
+  padding: 0 1em;
+  color: var(--color-fg-muted);
+  border-left: .25em solid var(--color-border-default);
+}
+
+.markdown-body ul,
+.markdown-body ol {
+  margin-top: 0;
+  margin-bottom: 0;
+  padding-left: 2em;
+}
+
+.markdown-body ol ol,
+.markdown-body ul ol {
+  list-style-type: lower-roman;
+}
+
+.markdown-body ul ul ol,
+.markdown-body ul ol ol,
+.markdown-body ol ul ol,
+.markdown-body ol ol ol {
+  list-style-type: lower-alpha;
+}
+
+.markdown-body dd {
+  margin-left: 0;
+}
+
+.markdown-body tt,
+.markdown-body code,
+.markdown-body samp {
+  font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
+  font-size: 12px;
+}
+
+.markdown-body pre {
+  margin-top: 0;
+  margin-bottom: 0;
+  font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
+  font-size: 12px;
+  word-wrap: normal;
+}
+
+.markdown-body .octicon {
+  display: inline-block;
+  overflow: visible !important;
+  vertical-align: text-bottom;
+  fill: currentColor;
+}
+
+.markdown-body input::-webkit-outer-spin-button,
+.markdown-body input::-webkit-inner-spin-button {
+  margin: 0;
+  -webkit-appearance: none;
+  appearance: none;
+}
+
+.markdown-body::before {
+  display: table;
+  content: "";
+}
+
+.markdown-body::after {
+  display: table;
+  clear: both;
+  content: "";
+}
+
+.markdown-body>*:first-child {
+  margin-top: 0 !important;
+}
+
+.markdown-body>*:last-child {
+  margin-bottom: 0 !important;
+}
+
+.markdown-body a:not([href]) {
+  color: inherit;
+  text-decoration: none;
+}
+
+.markdown-body .absent {
+  color: var(--color-danger-fg);
+}
+
+.markdown-body .anchor {
+  float: left;
+  padding-right: 4px;
+  margin-left: -20px;
+  line-height: 1;
+}
+
+.markdown-body .anchor:focus {
+  outline: none;
+}
+
+.markdown-body p,
+.markdown-body blockquote,
+.markdown-body ul,
+.markdown-body ol,
+.markdown-body dl,
+.markdown-body table,
+.markdown-body pre,
+.markdown-body details {
+  margin-top: 0;
+  margin-bottom: 16px;
+}
+
+.markdown-body blockquote>:first-child {
+  margin-top: 0;
+}
+
+.markdown-body blockquote>:last-child {
+  margin-bottom: 0;
+}
+
+.markdown-body h1 .octicon-link,
+.markdown-body h2 .octicon-link,
+.markdown-body h3 .octicon-link,
+.markdown-body h4 .octicon-link,
+.markdown-body h5 .octicon-link,
+.markdown-body h6 .octicon-link {
+  color: var(--color-fg-default);
+  vertical-align: middle;
+  visibility: hidden;
+}
+
+.markdown-body h1:hover .anchor,
+.markdown-body h2:hover .anchor,
+.markdown-body h3:hover .anchor,
+.markdown-body h4:hover .anchor,
+.markdown-body h5:hover .anchor,
+.markdown-body h6:hover .anchor {
+  text-decoration: none;
+}
+
+.markdown-body h1:hover .anchor .octicon-link,
+.markdown-body h2:hover .anchor .octicon-link,
+.markdown-body h3:hover .anchor .octicon-link,
+.markdown-body h4:hover .anchor .octicon-link,
+.markdown-body h5:hover .anchor .octicon-link,
+.markdown-body h6:hover .anchor .octicon-link {
+  visibility: visible;
+}
+
+.markdown-body h1 tt,
+.markdown-body h1 code,
+.markdown-body h2 tt,
+.markdown-body h2 code,
+.markdown-body h3 tt,
+.markdown-body h3 code,
+.markdown-body h4 tt,
+.markdown-body h4 code,
+.markdown-body h5 tt,
+.markdown-body h5 code,
+.markdown-body h6 tt,
+.markdown-body h6 code {
+  padding: 0 .2em;
+  font-size: inherit;
+}
+
+.markdown-body summary h1,
+.markdown-body summary h2,
+.markdown-body summary h3,
+.markdown-body summary h4,
+.markdown-body summary h5,
+.markdown-body summary h6 {
+  display: inline-block;
+}
+
+.markdown-body summary h1 .anchor,
+.markdown-body summary h2 .anchor,
+.markdown-body summary h3 .anchor,
+.markdown-body summary h4 .anchor,
+.markdown-body summary h5 .anchor,
+.markdown-body summary h6 .anchor {
+  margin-left: -40px;
+}
+
+.markdown-body summary h1,
+.markdown-body summary h2 {
+  padding-bottom: 0;
+  border-bottom: 0;
+}
+
+.markdown-body ul.no-list,
+.markdown-body ol.no-list {
+  padding: 0;
+  list-style-type: none;
+}
+
+.markdown-body ol[type=a] {
+  list-style-type: lower-alpha;
+}
+
+.markdown-body ol[type=A] {
+  list-style-type: upper-alpha;
+}
+
+.markdown-body ol[type=i] {
+  list-style-type: lower-roman;
+}
+
+.markdown-body ol[type=I] {
+  list-style-type: upper-roman;
+}
+
+.markdown-body ol[type="1"] {
+  list-style-type: decimal;
+}
+
+.markdown-body div>ol:not([type]) {
+  list-style-type: decimal;
+}
+
+.markdown-body ul ul,
+.markdown-body ul ol,
+.markdown-body ol ol,
+.markdown-body ol ul {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+.markdown-body li>p {
+  margin-top: 16px;
+}
+
+.markdown-body li+li {
+  margin-top: .25em;
+}
+
+.markdown-body dl {
+  padding: 0;
+}
+
+.markdown-body dl dt {
+  padding: 0;
+  margin-top: 16px;
+  font-size: 1em;
+  font-style: italic;
+  font-weight: var(--base-text-weight-semibold, 600);
+}
+
+.markdown-body dl dd {
+  padding: 0 16px;
+  margin-bottom: 16px;
+}
+
+.markdown-body table th {
+  font-weight: var(--base-text-weight-semibold, 600);
+}
+
+.markdown-body table th,
+.markdown-body table td {
+  padding: 6px 13px;
+  border: 1px solid var(--color-border-default);
+}
+
+.markdown-body table tr {
+  background-color: var(--color-canvas-default);
+  border-top: 1px solid var(--color-border-muted);
+}
+
+.markdown-body table tr:nth-child(2n) {
+  background-color: var(--color-canvas-subtle);
+}
+
+.markdown-body table img {
+  background-color: transparent;
+}
+
+.markdown-body img[align=right] {
+  padding-left: 20px;
+}
+
+.markdown-body img[align=left] {
+  padding-right: 20px;
+}
+
+.markdown-body .emoji {
+  max-width: none;
+  vertical-align: text-top;
+  background-color: transparent;
+}
+
+.markdown-body span.frame {
+  display: block;
+  overflow: hidden;
+}
+
+.markdown-body span.frame>span {
+  display: block;
+  float: left;
+  width: auto;
+  padding: 7px;
+  margin: 13px 0 0;
+  overflow: hidden;
+  border: 1px solid var(--color-border-default);
+}
+
+.markdown-body span.frame span img {
+  display: block;
+  float: left;
+}
+
+.markdown-body span.frame span span {
+  display: block;
+  padding: 5px 0 0;
+  clear: both;
+  color: var(--color-fg-default);
+}
+
+.markdown-body span.align-center {
+  display: block;
+  overflow: hidden;
+  clear: both;
+}
+
+.markdown-body span.align-center>span {
+  display: block;
+  margin: 13px auto 0;
+  overflow: hidden;
+  text-align: center;
+}
+
+.markdown-body span.align-center span img {
+  margin: 0 auto;
+  text-align: center;
+}
+
+.markdown-body span.align-right {
+  display: block;
+  overflow: hidden;
+  clear: both;
+}
+
+.markdown-body span.align-right>span {
+  display: block;
+  margin: 13px 0 0;
+  overflow: hidden;
+  text-align: right;
+}
+
+.markdown-body span.align-right span img {
+  margin: 0;
+  text-align: right;
+}
+
+.markdown-body span.float-left {
+  display: block;
+  float: left;
+  margin-right: 13px;
+  overflow: hidden;
+}
+
+.markdown-body span.float-left span {
+  margin: 13px 0 0;
+}
+
+.markdown-body span.float-right {
+  display: block;
+  float: right;
+  margin-left: 13px;
+  overflow: hidden;
+}
+
+.markdown-body span.float-right>span {
+  display: block;
+  margin: 13px auto 0;
+  overflow: hidden;
+  text-align: right;
+}
+
+.markdown-body code,
+.markdown-body tt {
+  padding: .2em .4em;
+  margin: 0;
+  font-size: 85%;
+  white-space: break-spaces;
+  background-color: var(--color-neutral-muted);
+  border-radius: 6px;
+}
+
+.markdown-body code br,
+.markdown-body tt br {
+  display: none;
+}
+
+.markdown-body del code {
+  text-decoration: inherit;
+}
+
+.markdown-body samp {
+  font-size: 85%;
+}
+
+.markdown-body pre code {
+  font-size: 100%;
+}
+
+.markdown-body pre>code {
+  padding: 0;
+  margin: 0;
+  word-break: normal;
+  white-space: pre;
+  background: transparent;
+  border: 0;
+}
+
+.markdown-body .highlight {
+  margin-bottom: 16px;
+}
+
+.markdown-body .highlight pre {
+  margin-bottom: 0;
+  word-break: normal;
+}
+
+.markdown-body .highlight pre,
+.markdown-body pre {
+  padding: 16px;
+  overflow: auto;
+  font-size: 85%;
+  line-height: 1.45;
+  background-color: var(--color-canvas-subtle);
+  border-radius: 6px;
+}
+
+.markdown-body pre code,
+.markdown-body pre tt {
+  display: inline;
+  max-width: auto;
+  padding: 0;
+  margin: 0;
+  overflow: visible;
+  line-height: inherit;
+  word-wrap: normal;
+  background-color: transparent;
+  border: 0;
+}
+
+.markdown-body .csv-data td,
+.markdown-body .csv-data th {
+  padding: 5px;
+  overflow: hidden;
+  font-size: 12px;
+  line-height: 1;
+  text-align: left;
+  white-space: nowrap;
+}
+
+.markdown-body .csv-data .blob-num {
+  padding: 10px 8px 9px;
+  text-align: right;
+  background: var(--color-canvas-default);
+  border: 0;
+}
+
+.markdown-body .csv-data tr {
+  border-top: 0;
+}
+
+.markdown-body .csv-data th {
+  font-weight: var(--base-text-weight-semibold, 600);
+  background: var(--color-canvas-subtle);
+  border-top: 0;
+}
+
+.markdown-body [data-footnote-ref]::before {
+  content: "[";
+}
+
+.markdown-body [data-footnote-ref]::after {
+  content: "]";
+}
+
+.markdown-body .footnotes {
+  font-size: 12px;
+  color: var(--color-fg-muted);
+  border-top: 1px solid var(--color-border-default);
+}
+
+.markdown-body .footnotes ol {
+  padding-left: 16px;
+}
+
+.markdown-body .footnotes ol ul {
+  display: inline-block;
+  padding-left: 16px;
+  margin-top: 16px;
+}
+
+.markdown-body .footnotes li {
+  position: relative;
+}
+
+.markdown-body .footnotes li:target::before {
+  position: absolute;
+  top: -8px;
+  right: -8px;
+  bottom: -8px;
+  left: -24px;
+  pointer-events: none;
+  content: "";
+  border: 2px solid var(--color-accent-emphasis);
+  border-radius: 6px;
+}
+
+.markdown-body .footnotes li:target {
+  color: var(--color-fg-default);
+}
+
+.markdown-body .footnotes .data-footnote-backref g-emoji {
+  font-family: monospace;
+}
+
+.markdown-body .pl-c {
+  color: var(--color-prettylights-syntax-comment);
+}
+
+.markdown-body .pl-c1,
+.markdown-body .pl-s .pl-v {
+  color: var(--color-prettylights-syntax-constant);
+}
+
+.markdown-body .pl-e,
+.markdown-body .pl-en {
+  color: var(--color-prettylights-syntax-entity);
+}
+
+.markdown-body .pl-smi,
+.markdown-body .pl-s .pl-s1 {
+  color: var(--color-prettylights-syntax-storage-modifier-import);
+}
+
+.markdown-body .pl-ent {
+  color: var(--color-prettylights-syntax-entity-tag);
+}
+
+.markdown-body .pl-k {
+  color: var(--color-prettylights-syntax-keyword);
+}
+
+.markdown-body .pl-s,
+.markdown-body .pl-pds,
+.markdown-body .pl-s .pl-pse .pl-s1,
+.markdown-body .pl-sr,
+.markdown-body .pl-sr .pl-cce,
+.markdown-body .pl-sr .pl-sre,
+.markdown-body .pl-sr .pl-sra {
+  color: var(--color-prettylights-syntax-string);
+}
+
+.markdown-body .pl-v,
+.markdown-body .pl-smw {
+  color: var(--color-prettylights-syntax-variable);
+}
+
+.markdown-body .pl-bu {
+  color: var(--color-prettylights-syntax-brackethighlighter-unmatched);
+}
+
+.markdown-body .pl-ii {
+  color: var(--color-prettylights-syntax-invalid-illegal-text);
+  background-color: var(--color-prettylights-syntax-invalid-illegal-bg);
+}
+
+.markdown-body .pl-c2 {
+  color: var(--color-prettylights-syntax-carriage-return-text);
+  background-color: var(--color-prettylights-syntax-carriage-return-bg);
+}
+
+.markdown-body .pl-sr .pl-cce {
+  font-weight: bold;
+  color: var(--color-prettylights-syntax-string-regexp);
+}
+
+.markdown-body .pl-ml {
+  color: var(--color-prettylights-syntax-markup-list);
+}
+
+.markdown-body .pl-mh,
+.markdown-body .pl-mh .pl-en,
+.markdown-body .pl-ms {
+  font-weight: bold;
+  color: var(--color-prettylights-syntax-markup-heading);
+}
+
+.markdown-body .pl-mi {
+  font-style: italic;
+  color: var(--color-prettylights-syntax-markup-italic);
+}
+
+.markdown-body .pl-mb {
+  font-weight: bold;
+  color: var(--color-prettylights-syntax-markup-bold);
+}
+
+.markdown-body .pl-md {
+  color: var(--color-prettylights-syntax-markup-deleted-text);
+  background-color: var(--color-prettylights-syntax-markup-deleted-bg);
+}
+
+.markdown-body .pl-mi1 {
+  color: var(--color-prettylights-syntax-markup-inserted-text);
+  background-color: var(--color-prettylights-syntax-markup-inserted-bg);
+}
+
+.markdown-body .pl-mc {
+  color: var(--color-prettylights-syntax-markup-changed-text);
+  background-color: var(--color-prettylights-syntax-markup-changed-bg);
+}
+
+.markdown-body .pl-mi2 {
+  color: var(--color-prettylights-syntax-markup-ignored-text);
+  background-color: var(--color-prettylights-syntax-markup-ignored-bg);
+}
+
+.markdown-body .pl-mdr {
+  font-weight: bold;
+  color: var(--color-prettylights-syntax-meta-diff-range);
+}
+
+.markdown-body .pl-ba {
+  color: var(--color-prettylights-syntax-brackethighlighter-angle);
+}
+
+.markdown-body .pl-sg {
+  color: var(--color-prettylights-syntax-sublimelinter-gutter-mark);
+}
+
+.markdown-body .pl-corl {
+  text-decoration: underline;
+  color: var(--color-prettylights-syntax-constant-other-reference-link);
+}
+
+.markdown-body g-emoji {
+  display: inline-block;
+  min-width: 1ch;
+  font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
+  font-size: 1em;
+  font-style: normal !important;
+  font-weight: var(--base-text-weight-normal, 400);
+  line-height: 1;
+  vertical-align: -0.075em;
+}
+
+.markdown-body g-emoji img {
+  width: 1em;
+  height: 1em;
+}
+
+.markdown-body .task-list-item {
+  list-style-type: none;
+}
+
+.markdown-body .task-list-item label {
+  font-weight: var(--base-text-weight-normal, 400);
+}
+
+.markdown-body .task-list-item.enabled label {
+  cursor: pointer;
+}
+
+.markdown-body .task-list-item+.task-list-item {
+  margin-top: 4px;
+}
+
+.markdown-body .task-list-item .handle {
+  display: none;
+}
+
+.markdown-body .task-list-item-checkbox {
+  margin: 0 .2em .25em -1.4em;
+  vertical-align: middle;
+}
+
+.markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox {
+  margin: 0 -1.6em .25em .2em;
+}
+
+.markdown-body .contains-task-list {
+  position: relative;
+}
+
+.markdown-body .contains-task-list:hover .task-list-item-convert-container,
+.markdown-body .contains-task-list:focus-within .task-list-item-convert-container {
+  display: block;
+  width: auto;
+  height: 24px;
+  overflow: visible;
+  clip: auto;
+}
+
+.markdown-body ::-webkit-calendar-picker-indicator {
+  filter: invert(50%);
+}

+ 203 - 0
src/styles/lib/highlight.less

@@ -0,0 +1,203 @@
+html.dark {
+	pre code.hljs {
+		display: block;
+		overflow-x: auto;
+		padding: 1em
+	}
+
+	code.hljs {
+		padding: 3px 5px
+	}
+
+	.hljs {
+		color: #abb2bf;
+		background: #282c34
+	}
+
+	.hljs-keyword,
+	.hljs-operator,
+	.hljs-pattern-match {
+		color: #f92672
+	}
+
+	.hljs-function,
+	.hljs-pattern-match .hljs-constructor {
+		color: #61aeee
+	}
+
+	.hljs-function .hljs-params {
+		color: #a6e22e
+	}
+
+	.hljs-function .hljs-params .hljs-typing {
+		color: #fd971f
+	}
+
+	.hljs-module-access .hljs-module {
+		color: #7e57c2
+	}
+
+	.hljs-constructor {
+		color: #e2b93d
+	}
+
+	.hljs-constructor .hljs-string {
+		color: #9ccc65
+	}
+
+	.hljs-comment,
+	.hljs-quote {
+		color: #b18eb1;
+		font-style: italic
+	}
+
+	.hljs-doctag,
+	.hljs-formula {
+		color: #c678dd
+	}
+
+	.hljs-deletion,
+	.hljs-name,
+	.hljs-section,
+	.hljs-selector-tag,
+	.hljs-subst {
+		color: #e06c75
+	}
+
+	.hljs-literal {
+		color: #56b6c2
+	}
+
+	.hljs-addition,
+	.hljs-attribute,
+	.hljs-meta .hljs-string,
+	.hljs-regexp,
+	.hljs-string {
+		color: #98c379
+	}
+
+	.hljs-built_in,
+	.hljs-class .hljs-title,
+	.hljs-title.class_ {
+		color: #e6c07b
+	}
+
+	.hljs-attr,
+	.hljs-number,
+	.hljs-selector-attr,
+	.hljs-selector-class,
+	.hljs-selector-pseudo,
+	.hljs-template-variable,
+	.hljs-type,
+	.hljs-variable {
+		color: #d19a66
+	}
+
+	.hljs-bullet,
+	.hljs-link,
+	.hljs-meta,
+	.hljs-selector-id,
+	.hljs-symbol,
+	.hljs-title {
+		color: #61aeee
+	}
+
+	.hljs-emphasis {
+		font-style: italic
+	}
+
+	.hljs-strong {
+		font-weight: 700
+	}
+
+	.hljs-link {
+		text-decoration: underline
+	}
+}
+
+html {
+	pre code.hljs {
+		display: block;
+		overflow-x: auto;
+		padding: 1em
+	}
+
+	code.hljs {
+		padding: 3px 5px
+	}
+
+	.hljs {
+		color: #383a42;
+		background: #fafafa
+	}
+
+	.hljs-comment,
+	.hljs-quote {
+		color: #a0a1a7;
+		font-style: italic
+	}
+
+	.hljs-doctag,
+	.hljs-formula,
+	.hljs-keyword {
+		color: #a626a4
+	}
+
+	.hljs-deletion,
+	.hljs-name,
+	.hljs-section,
+	.hljs-selector-tag,
+	.hljs-subst {
+		color: #e45649
+	}
+
+	.hljs-literal {
+		color: #0184bb
+	}
+
+	.hljs-addition,
+	.hljs-attribute,
+	.hljs-meta .hljs-string,
+	.hljs-regexp,
+	.hljs-string {
+		color: #50a14f
+	}
+
+	.hljs-attr,
+	.hljs-number,
+	.hljs-selector-attr,
+	.hljs-selector-class,
+	.hljs-selector-pseudo,
+	.hljs-template-variable,
+	.hljs-type,
+	.hljs-variable {
+		color: #986801
+	}
+
+	.hljs-bullet,
+	.hljs-link,
+	.hljs-meta,
+	.hljs-selector-id,
+	.hljs-symbol,
+	.hljs-title {
+		color: #4078f2
+	}
+
+	.hljs-built_in,
+	.hljs-class .hljs-title,
+	.hljs-title.class_ {
+		color: #c18401
+	}
+
+	.hljs-emphasis {
+		font-style: italic
+	}
+
+	.hljs-strong {
+		font-weight: 700
+	}
+
+	.hljs-link {
+		text-decoration: underline
+	}
+}

+ 3 - 0
src/styles/lib/tailwind.css

@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;

+ 8 - 0
src/typings/env.d.ts

@@ -0,0 +1,8 @@
+/// <reference types="vite/client" />
+
+interface ImportMetaEnv {
+    readonly VITE_GLOB_API_URL: string;
+    readonly VITE_APP_API_BASE_URL: string;
+    readonly VITE_GLOB_OPEN_LONG_REPLY: string;
+    readonly VITE_GLOB_APP_PWA: string;
+}

+ 18 - 0
src/utils/crypto/index.ts

@@ -0,0 +1,18 @@
+import CryptoJS from 'crypto-js'
+
+const CryptoSecret = '__CRYPTO_SECRET__'
+
+export function enCrypto(data: any) {
+    const str = JSON.stringify(data)
+    return CryptoJS.AES.encrypt(str, CryptoSecret).toString()
+}
+
+export function deCrypto(data: string) {
+    const bytes = CryptoJS.AES.decrypt(data, CryptoSecret)
+    const str = bytes.toString(CryptoJS.enc.Utf8)
+
+    if (str)
+        return JSON.parse(str)
+
+    return null
+}

+ 1 - 0
src/utils/storage/index.ts

@@ -0,0 +1 @@
+export * from './local'

+ 70 - 0
src/utils/storage/local.ts

@@ -0,0 +1,70 @@
+import { deCrypto, enCrypto } from '../crypto'
+
+interface StorageData<T = any> {
+    data: T
+    expire: number | null
+}
+
+export function createLocalStorage(options?: { expire?: number | null; crypto?: boolean }) {
+    const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7
+
+    const { expire, crypto } = Object.assign(
+        {
+            expire: DEFAULT_CACHE_TIME,
+            crypto: true,
+        },
+        options,
+    )
+
+    function set<T = any>(key: string, data: T) {
+        const storageData: StorageData<T> = {
+            data,
+            expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
+        }
+
+        const json = crypto ? enCrypto(storageData) : JSON.stringify(storageData)
+        window.localStorage.setItem(key, json)
+    }
+
+    function get(key: string) {
+        const json = window.localStorage.getItem(key)
+        if (json) {
+            let storageData: StorageData | null = null
+
+            try {
+                storageData = crypto ? deCrypto(json) : JSON.parse(json)
+            }
+            catch {
+                // Prevent failure
+            }
+
+            if (storageData) {
+                const { data, expire } = storageData
+                if (expire === null || expire >= Date.now())
+                    return data
+            }
+
+            remove(key)
+            return null
+        }
+    }
+
+    function remove(key: string) {
+        window.localStorage.removeItem(key)
+    }
+
+    function clear() {
+        window.localStorage.clear()
+    }
+
+    return {
+        set,
+        get,
+        remove,
+        clear,
+    }
+}
+
+export const ls = createLocalStorage()
+
+export const ss = createLocalStorage({ expire: null, crypto: false })

+ 1 - 0
src/vite-env.d.ts

@@ -0,0 +1 @@
+/// <reference types="vite/client" />

+ 22 - 0
tailwind.config.js

@@ -0,0 +1,22 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+  darkMode: 'class',
+  content: [
+    './index.html',
+    './src/**/*.{vue,js,ts,jsx,tsx}',
+  ],
+  theme: {
+    extend: {
+      animation: {
+        blink: 'blink 1.2s infinite steps(1, start)',
+      },
+      keyframes: {
+        blink: {
+          '0%, 100%': { 'background-color': 'currentColor' },
+          '50%': { 'background-color': 'transparent' },
+        },
+      },
+    },
+  },
+  plugins: [],
+}

+ 22 - 0
tsconfig.json

@@ -0,0 +1,22 @@
+{
+  "compilerOptions": {
+    "target": "ESNext",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "strict": true,
+    "jsx": "preserve",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "esModuleInterop": true,
+    "lib": ["ESNext", "DOM"],
+    "skipLibCheck": true,
+    "noEmit": true,
+    "paths": {
+      "@/*": ["./src/*"]
+    },
+    "types": ["vite/client", "node", "naive-ui/volar"]
+  },
+  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}

+ 9 - 0
tsconfig.node.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 54 - 0
vite.config.ts

@@ -0,0 +1,54 @@
+import path from 'path'
+import type { PluginOption } from 'vite'
+import { defineConfig, loadEnv } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import { VitePWA } from 'vite-plugin-pwa'
+
+function setupPlugins(env: ImportMetaEnv): PluginOption[] {
+  return [
+    vue(),
+    env.VITE_GLOB_APP_PWA === 'true' && VitePWA({
+      injectRegister: 'auto',
+      manifest: {
+        name: 'chatGPT',
+        short_name: 'chatGPT',
+        icons: [
+          { src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' },
+          { src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png' },
+        ],
+      },
+    }),
+  ]
+}
+
+export default defineConfig((env) => {
+  const viteEnv = loadEnv(env.mode, process.cwd()) as unknown as ImportMetaEnv
+
+  return {
+    resolve: {
+      alias: {
+        '@': path.resolve(process.cwd(), 'src'),
+      },
+    },
+    plugins: setupPlugins(viteEnv),
+    server: {
+      host: '0.0.0.0',
+      port: 1002,
+      open: false,
+      proxy: {
+        '/api': {
+          target: viteEnv.VITE_APP_API_BASE_URL,
+          changeOrigin: true, // 允许跨域
+          rewrite: path => path.replace('/api/', '/'),
+        },
+      },
+    },
+    build: {
+      reportCompressedSize: false,
+      sourcemap: false,
+      commonjsOptions: {
+        ignoreTryCatch: false,
+      },
+    },
+  }
+})