feat: 引入prettier
This commit is contained in:
3
web/.prettierignore
Normal file
3
web/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# .prettierignore
|
||||||
|
auto-imports.d.ts
|
||||||
|
components.d.ts
|
||||||
8
web/.prettierrc.json
Normal file
8
web/.prettierrc.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": false,
|
||||||
|
"printWidth": 80,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"ignorePath": ".prettierignore"
|
||||||
|
}
|
||||||
@@ -1,39 +1,36 @@
|
|||||||
import antfu from "@antfu/eslint-config"
|
import antfu from "@antfu/eslint-config";
|
||||||
|
|
||||||
export default antfu(
|
export default antfu({
|
||||||
{
|
formatters: {
|
||||||
formatters: {
|
/**
|
||||||
/**
|
* Format CSS, LESS, SCSS files, also the `<style>` blocks in Vue
|
||||||
* Format CSS, LESS, SCSS files, also the `<style>` blocks in Vue
|
* By default uses Prettier
|
||||||
* By default uses Prettier
|
*/
|
||||||
*/
|
css: true,
|
||||||
css: true,
|
/**
|
||||||
/**
|
* Format HTML files
|
||||||
* Format HTML files
|
* By default uses Prettier
|
||||||
* By default uses Prettier
|
*/
|
||||||
*/
|
html: true,
|
||||||
html: true,
|
/**
|
||||||
/**
|
* Format Markdown files
|
||||||
* Format Markdown files
|
* Supports Prettier and dprint
|
||||||
* Supports Prettier and dprint
|
* By default uses Prettier
|
||||||
* By default uses Prettier
|
*/
|
||||||
*/
|
markdown: "prettier"
|
||||||
markdown: "prettier",
|
|
||||||
},
|
|
||||||
stylistic: {
|
|
||||||
indent: 2,
|
|
||||||
quotes: "double",
|
|
||||||
semi: true,
|
|
||||||
},
|
|
||||||
vue: true,
|
|
||||||
ignores: ["node_modules", "dist"],
|
|
||||||
rules: {
|
|
||||||
"vue/html-self-closing": "off",
|
|
||||||
"antfu/top-level-function": "off",
|
|
||||||
"ts/no-unsafe-function-type": "off",
|
|
||||||
"no-console": "off",
|
|
||||||
"unused-imports/no-unused-vars": "warn",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
stylistic: {
|
||||||
)
|
indent: 2,
|
||||||
|
quotes: "double",
|
||||||
|
semi: true
|
||||||
|
},
|
||||||
|
vue: true,
|
||||||
|
ignores: ["node_modules", "dist"],
|
||||||
|
rules: {
|
||||||
|
"vue/html-self-closing": "off",
|
||||||
|
"antfu/top-level-function": "off",
|
||||||
|
"ts/no-unsafe-function-type": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"unused-imports/no-unused-vars": "warn"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="zh-CN">
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
<head>
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<title>chat</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
</head>
|
<title>chat</title>
|
||||||
<body>
|
</head>
|
||||||
<div id="app"></div>
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<body>
|
||||||
</body>
|
<div id="app"></div>
|
||||||
</html>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -34,8 +34,11 @@
|
|||||||
"@vitejs/plugin-vue": "^6.0.0",
|
"@vitejs/plugin-vue": "^6.0.0",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
"eslint": "^9.29.0",
|
"eslint": "^9.29.0",
|
||||||
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"eslint-plugin-format": "^1.0.1",
|
"eslint-plugin-format": "^1.0.1",
|
||||||
|
"eslint-plugin-prettier": "^5.5.1",
|
||||||
"naive-ui": "^2.42.0",
|
"naive-ui": "^2.42.0",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"unplugin-vue-components": "^0.28.0",
|
"unplugin-vue-components": "^0.28.0",
|
||||||
"vite": "^7.0.0",
|
"vite": "^7.0.0",
|
||||||
|
|||||||
50
web/pnpm-lock.yaml
generated
50
web/pnpm-lock.yaml
generated
@@ -72,12 +72,21 @@ importers:
|
|||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.29.0
|
specifier: ^9.29.0
|
||||||
version: 9.29.0(jiti@2.4.2)
|
version: 9.29.0(jiti@2.4.2)
|
||||||
|
eslint-config-prettier:
|
||||||
|
specifier: ^10.1.5
|
||||||
|
version: 10.1.5(eslint@9.29.0(jiti@2.4.2))
|
||||||
eslint-plugin-format:
|
eslint-plugin-format:
|
||||||
specifier: ^1.0.1
|
specifier: ^1.0.1
|
||||||
version: 1.0.1(eslint@9.29.0(jiti@2.4.2))
|
version: 1.0.1(eslint@9.29.0(jiti@2.4.2))
|
||||||
|
eslint-plugin-prettier:
|
||||||
|
specifier: ^5.5.1
|
||||||
|
version: 5.5.1(eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2))(prettier@3.6.2)
|
||||||
naive-ui:
|
naive-ui:
|
||||||
specifier: ^2.42.0
|
specifier: ^2.42.0
|
||||||
version: 2.42.0(vue@3.5.17(typescript@5.8.3))
|
version: 2.42.0(vue@3.5.17(typescript@5.8.3))
|
||||||
|
prettier:
|
||||||
|
specifier: ^3.6.2
|
||||||
|
version: 3.6.2
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ~5.8.3
|
specifier: ~5.8.3
|
||||||
version: 5.8.3
|
version: 5.8.3
|
||||||
@@ -1453,6 +1462,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^9.5.0
|
eslint: ^9.5.0
|
||||||
|
|
||||||
|
eslint-config-prettier@10.1.5:
|
||||||
|
resolution: {integrity: sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
eslint: '>=7.0.0'
|
||||||
|
|
||||||
eslint-flat-config-utils@2.1.0:
|
eslint-flat-config-utils@2.1.0:
|
||||||
resolution: {integrity: sha512-6fjOJ9tS0k28ketkUcQ+kKptB4dBZY2VijMZ9rGn8Cwnn1SH0cZBoPXT8AHBFHxmHcLFQK9zbELDinZ2Mr1rng==}
|
resolution: {integrity: sha512-6fjOJ9tS0k28ketkUcQ+kKptB4dBZY2VijMZ9rGn8Cwnn1SH0cZBoPXT8AHBFHxmHcLFQK9zbELDinZ2Mr1rng==}
|
||||||
|
|
||||||
@@ -1544,6 +1559,20 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^9.0.0
|
eslint: ^9.0.0
|
||||||
|
|
||||||
|
eslint-plugin-prettier@5.5.1:
|
||||||
|
resolution: {integrity: sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==}
|
||||||
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/eslint': '>=8.0.0'
|
||||||
|
eslint: '>=8.0.0'
|
||||||
|
eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0'
|
||||||
|
prettier: '>=3.0.0'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/eslint':
|
||||||
|
optional: true
|
||||||
|
eslint-config-prettier:
|
||||||
|
optional: true
|
||||||
|
|
||||||
eslint-plugin-regexp@2.9.0:
|
eslint-plugin-regexp@2.9.0:
|
||||||
resolution: {integrity: sha512-9WqJMnOq8VlE/cK+YAo9C9YHhkOtcEtEk9d12a+H7OSZFwlpI6stiHmYPGa2VE0QhTzodJyhlyprUaXDZLgHBw==}
|
resolution: {integrity: sha512-9WqJMnOq8VlE/cK+YAo9C9YHhkOtcEtEk9d12a+H7OSZFwlpI6stiHmYPGa2VE0QhTzodJyhlyprUaXDZLgHBw==}
|
||||||
engines: {node: ^18 || >=20}
|
engines: {node: ^18 || >=20}
|
||||||
@@ -2433,8 +2462,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
prettier@3.6.1:
|
prettier@3.6.2:
|
||||||
resolution: {integrity: sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==}
|
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -4282,6 +4311,10 @@ snapshots:
|
|||||||
'@eslint/compat': 1.3.1(eslint@9.29.0(jiti@2.4.2))
|
'@eslint/compat': 1.3.1(eslint@9.29.0(jiti@2.4.2))
|
||||||
eslint: 9.29.0(jiti@2.4.2)
|
eslint: 9.29.0(jiti@2.4.2)
|
||||||
|
|
||||||
|
eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)):
|
||||||
|
dependencies:
|
||||||
|
eslint: 9.29.0(jiti@2.4.2)
|
||||||
|
|
||||||
eslint-flat-config-utils@2.1.0:
|
eslint-flat-config-utils@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
@@ -4327,7 +4360,7 @@ snapshots:
|
|||||||
eslint: 9.29.0(jiti@2.4.2)
|
eslint: 9.29.0(jiti@2.4.2)
|
||||||
eslint-formatting-reporter: 0.0.0(eslint@9.29.0(jiti@2.4.2))
|
eslint-formatting-reporter: 0.0.0(eslint@9.29.0(jiti@2.4.2))
|
||||||
eslint-parser-plain: 0.1.1
|
eslint-parser-plain: 0.1.1
|
||||||
prettier: 3.6.1
|
prettier: 3.6.2
|
||||||
synckit: 0.9.3
|
synckit: 0.9.3
|
||||||
|
|
||||||
eslint-plugin-import-lite@0.3.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3):
|
eslint-plugin-import-lite@0.3.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3):
|
||||||
@@ -4407,6 +4440,15 @@ snapshots:
|
|||||||
tinyglobby: 0.2.14
|
tinyglobby: 0.2.14
|
||||||
yaml-eslint-parser: 1.3.0
|
yaml-eslint-parser: 1.3.0
|
||||||
|
|
||||||
|
eslint-plugin-prettier@5.5.1(eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2))(prettier@3.6.2):
|
||||||
|
dependencies:
|
||||||
|
eslint: 9.29.0(jiti@2.4.2)
|
||||||
|
prettier: 3.6.2
|
||||||
|
prettier-linter-helpers: 1.0.0
|
||||||
|
synckit: 0.11.8
|
||||||
|
optionalDependencies:
|
||||||
|
eslint-config-prettier: 10.1.5(eslint@9.29.0(jiti@2.4.2))
|
||||||
|
|
||||||
eslint-plugin-regexp@2.9.0(eslint@9.29.0(jiti@2.4.2)):
|
eslint-plugin-regexp@2.9.0(eslint@9.29.0(jiti@2.4.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.4.2))
|
'@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.4.2))
|
||||||
@@ -5465,7 +5507,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-diff: 1.3.0
|
fast-diff: 1.3.0
|
||||||
|
|
||||||
prettier@3.6.1: {}
|
prettier@3.6.2: {}
|
||||||
|
|
||||||
pretty-ms@9.2.0:
|
pretty-ms@9.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { GlobalThemeOverrides } from "naive-ui"
|
import type { GlobalThemeOverrides } from "naive-ui";
|
||||||
import { zhCN } from "naive-ui"
|
import { zhCN } from "naive-ui";
|
||||||
import { useWebSocketStore } from "@/services"
|
import { useWebSocketStore } from "@/services";
|
||||||
|
|
||||||
const websocketStore = useWebSocketStore()
|
const websocketStore = useWebSocketStore();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
websocketStore.connect()
|
websocketStore.connect();
|
||||||
})
|
});
|
||||||
|
|
||||||
const themeOverrides: GlobalThemeOverrides = {
|
const themeOverrides: GlobalThemeOverrides = {
|
||||||
common: {
|
common: {
|
||||||
@@ -17,12 +17,12 @@ const themeOverrides: GlobalThemeOverrides = {
|
|||||||
primaryColorSuppl: "#00bfff",
|
primaryColorSuppl: "#00bfff",
|
||||||
fontWeightStrong: "600",
|
fontWeightStrong: "600",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
borderRadiusSmall: "5px",
|
borderRadiusSmall: "5px"
|
||||||
},
|
},
|
||||||
Button: {
|
Button: {
|
||||||
textColor: "#0094c5",
|
textColor: "#0094c5"
|
||||||
},
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export { default as ExclamationTriangleIcon } from "./svg/heroicons/ExclamationTriangleIcon.svg?component"
|
export { default as ExclamationTriangleIcon } from "./svg/heroicons/ExclamationTriangleIcon.svg?component";
|
||||||
export { default as microphone } from "./svg/heroicons/MicrophoneIcon.svg?component"
|
export { default as microphone } from "./svg/heroicons/MicrophoneIcon.svg?component";
|
||||||
export { default as PaperAirplaneIcon } from "./svg/heroicons/PaperAirplaneIcon.svg?component"
|
export { default as PaperAirplaneIcon } from "./svg/heroicons/PaperAirplaneIcon.svg?component";
|
||||||
export { default as TrashIcon } from "./svg/heroicons/TrashIcon.svg?component"
|
export { default as TrashIcon } from "./svg/heroicons/TrashIcon.svg?component";
|
||||||
|
|||||||
@@ -6,8 +6,14 @@ const { avatar } = defineProps<{
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NImage
|
<NImage
|
||||||
:src="avatar" object-fit="cover" :preview-disabled="true" width="64" height="64" class="!block !w-16 !min-w-16 !h-16" :img-props="{
|
:src="avatar"
|
||||||
class: 'rounded-lg !block !w-16 !min-w-16 !h-16',
|
width="64"
|
||||||
|
height="64"
|
||||||
|
:preview-disabled="true"
|
||||||
|
object-fit="cover"
|
||||||
|
class="!block !w-16 !min-w-16 !h-16"
|
||||||
|
:img-props="{
|
||||||
|
class: 'rounded-lg !block !w-16 !min-w-16 !h-16'
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ const md = markdownit({
|
|||||||
if (lang && hljs.getLanguage(lang)) {
|
if (lang && hljs.getLanguage(lang)) {
|
||||||
try {
|
try {
|
||||||
return hljs.highlight(str, { language: lang }).value;
|
return hljs.highlight(str, { language: lang }).value;
|
||||||
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||||
|
} catch (__) {
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
||||||
catch (__) { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""; // use external default escaping
|
return ""; // use external default escaping
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// // 计算代码块宽度
|
// // 计算代码块宽度
|
||||||
@@ -35,9 +35,9 @@ const codeWidth = computed(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="markdown-body w-full text-base break-words whitespace-normal"
|
class="markdown-body w-full text-base break-words whitespace-normal"
|
||||||
:style="{ '--code-width': `${codeWidth}px` }" v-html="md.render(content)"
|
:style="{ '--code-width': `${codeWidth}px` }"
|
||||||
>
|
v-html="md.render(content)"
|
||||||
</div>
|
></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export interface ICommonResponse<T> {
|
export interface ICommonResponse<T> {
|
||||||
code: number
|
code: number;
|
||||||
msg: string
|
msg: string;
|
||||||
data: T
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IMsgOnlyResponse = ICommonResponse<{ msg: string }>
|
export type IMsgOnlyResponse = ICommonResponse<{ msg: string }>;
|
||||||
|
|
||||||
export * from "./chat_service"
|
export * from "./chat_service";
|
||||||
|
|||||||
@@ -11,15 +11,23 @@ const { onlineCount } = storeToRefs(chatStore);
|
|||||||
<div class="h-screen flex overflow-hidden">
|
<div class="h-screen flex overflow-hidden">
|
||||||
<div class="flex-none w-[200px] h-full flex flex-col">
|
<div class="flex-none w-[200px] h-full flex flex-col">
|
||||||
<router-link class="w-full my-6 cursor-pointer" to="/">
|
<router-link class="w-full my-6 cursor-pointer" to="/">
|
||||||
<NImage class="w-full object-cover" :src="logo" alt="logo" :preview-disabled="true" />
|
<NImage
|
||||||
|
class="w-full object-cover"
|
||||||
|
:src="logo"
|
||||||
|
alt="logo"
|
||||||
|
:preview-disabled="true"
|
||||||
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
class="w-full h-[52px] px-8 flex items-center cursor-pointer" :class="$route.path === '/'
|
class="w-full h-[52px] px-8 flex items-center cursor-pointer"
|
||||||
? [
|
:class="
|
||||||
'bg-[rgba(37,99,235,0.04)] text-[#0094c5] border-r-2 border-[#0094c5]',
|
$route.path === '/'
|
||||||
]
|
? [
|
||||||
: []
|
'bg-[rgba(37,99,235,0.04)] text-[#0094c5] border-r-2 border-[#0094c5]'
|
||||||
" to="/"
|
]
|
||||||
|
: []
|
||||||
|
"
|
||||||
|
to="/"
|
||||||
>
|
>
|
||||||
聊天
|
聊天
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { createPinia } from 'pinia'
|
import { createPinia } from "pinia";
|
||||||
import { createApp } from 'vue'
|
import { createApp } from "vue";
|
||||||
import App from './App.vue'
|
import App from "./App.vue";
|
||||||
|
|
||||||
import router from './router'
|
import router from "./router";
|
||||||
import './style.css'
|
import "./style.css";
|
||||||
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia();
|
||||||
const app = createApp(App)
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(pinia)
|
app.use(pinia);
|
||||||
app.use(router)
|
app.use(router);
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount("#app");
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
|
||||||
import BasicLayout from '@/layouts/BasicLayout.vue'
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
import { resetDescription, setTitle } from '@/utils'
|
import { resetDescription, setTitle } from "@/utils";
|
||||||
import community from '@/views/CommunityView.vue'
|
import community from "@/views/CommunityView.vue";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: "/",
|
||||||
component: BasicLayout,
|
component: BasicLayout,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: "",
|
||||||
name: 'community',
|
name: "community",
|
||||||
component: community,
|
component: community,
|
||||||
meta: {
|
meta: {
|
||||||
title: '社区',
|
title: "社区"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
})
|
});
|
||||||
|
|
||||||
// // 权限检查函数,检查并决定是否允许访问
|
// // 权限检查函数,检查并决定是否允许访问
|
||||||
// const checkPermission: NavigationGuard = (to, from, next) => {
|
// const checkPermission: NavigationGuard = (to, from, next) => {
|
||||||
@@ -64,17 +64,17 @@ const router = createRouter({
|
|||||||
// // 添加导航守卫
|
// // 添加导航守卫
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
setTitle(to.meta.title as string)
|
setTitle(to.meta.title as string);
|
||||||
resetDescription()
|
resetDescription();
|
||||||
|
|
||||||
// context.loadingBar?.start();
|
// context.loadingBar?.start();
|
||||||
// 在每个路由导航前执行权限检查
|
// 在每个路由导航前执行权限检查
|
||||||
// checkPermission(to, from, next);
|
// checkPermission(to, from, next);
|
||||||
next()
|
next();
|
||||||
})
|
});
|
||||||
|
|
||||||
// router.afterEach(() => {
|
// router.afterEach(() => {
|
||||||
// context.loadingBar?.finish();
|
// context.loadingBar?.finish();
|
||||||
// });
|
// });
|
||||||
|
|
||||||
export default router
|
export default router;
|
||||||
|
|||||||
@@ -5,27 +5,33 @@ import { context } from "@/utils";
|
|||||||
const BaseClientService = axios.create();
|
const BaseClientService = axios.create();
|
||||||
|
|
||||||
// 添加请求拦截器
|
// 添加请求拦截器
|
||||||
BaseClientService.interceptors.request.use((config) => {
|
BaseClientService.interceptors.request.use(
|
||||||
// 在发送请求之前做些什么
|
(config) => {
|
||||||
return config;
|
// 在发送请求之前做些什么
|
||||||
}, (e) => {
|
return config;
|
||||||
// 对请求错误做些什么
|
},
|
||||||
return Promise.reject(e);
|
(e) => {
|
||||||
});
|
// 对请求错误做些什么
|
||||||
|
|
||||||
// 添加响应拦截器
|
|
||||||
BaseClientService.interceptors.response.use((response) => {
|
|
||||||
// 2xx 范围内的状态码都会触发该函数。
|
|
||||||
// 对响应数据做点什么
|
|
||||||
return response;
|
|
||||||
}, (e) => {
|
|
||||||
// “50”开头统一处理
|
|
||||||
if (e.response?.status >= 500 && e.response?.status < 600) {
|
|
||||||
context.message?.error("服务器开小差了,请稍后再试");
|
|
||||||
return Promise.reject(e);
|
return Promise.reject(e);
|
||||||
}
|
}
|
||||||
return Promise.reject(e);
|
);
|
||||||
});
|
|
||||||
|
// 添加响应拦截器
|
||||||
|
BaseClientService.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
// 2xx 范围内的状态码都会触发该函数。
|
||||||
|
// 对响应数据做点什么
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(e) => {
|
||||||
|
// “50”开头统一处理
|
||||||
|
if (e.response?.status >= 500 && e.response?.status < 600) {
|
||||||
|
context.message?.error("服务器开小差了,请稍后再试");
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/** 基础URL */
|
/** 基础URL */
|
||||||
export const BaseUrl = "/v1";
|
export const BaseUrl = "/v1";
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export class ChatService {
|
|||||||
accessToken: string,
|
accessToken: string,
|
||||||
request: IChatWithLLMRequest,
|
request: IChatWithLLMRequest,
|
||||||
onProgress: (content: string) => void,
|
onProgress: (content: string) => void,
|
||||||
getUsageInfo: (object: UsageInfo) => void = () => { },
|
getUsageInfo: (object: UsageInfo) => void = () => {}
|
||||||
) {
|
) {
|
||||||
let response;
|
let response;
|
||||||
let buffer = "";
|
let buffer = "";
|
||||||
@@ -20,10 +20,10 @@ export class ChatService {
|
|||||||
response = await fetch("/v1/chat/completions", {
|
response = await fetch("/v1/chat/completions", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
body: JSON.stringify(request),
|
body: JSON.stringify(request)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -37,8 +37,7 @@ export class ChatService {
|
|||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader!.read();
|
const { done, value } = await reader!.read();
|
||||||
|
|
||||||
if (done)
|
if (done) break;
|
||||||
break;
|
|
||||||
|
|
||||||
// 将二进制数据转为字符串并存入缓冲区
|
// 将二进制数据转为字符串并存入缓冲区
|
||||||
buffer += decoder.decode(value);
|
buffer += decoder.decode(value);
|
||||||
@@ -52,8 +51,7 @@ export class ChatService {
|
|||||||
// 处理每一行
|
// 处理每一行
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const trimmedLine = line.trim();
|
const trimmedLine = line.trim();
|
||||||
if (!trimmedLine)
|
if (!trimmedLine) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (trimmedLine.startsWith("data: ")) {
|
if (trimmedLine.startsWith("data: ")) {
|
||||||
const jsonStr = trimmedLine.slice(6);
|
const jsonStr = trimmedLine.slice(6);
|
||||||
@@ -77,18 +75,21 @@ export class ChatService {
|
|||||||
|
|
||||||
// 处理使用信息
|
// 处理使用信息
|
||||||
if (data.usage) {
|
if (data.usage) {
|
||||||
const { prompt_tokens, completion_tokens, total_tokens } = data.usage;
|
const { prompt_tokens, completion_tokens, total_tokens } =
|
||||||
getUsageInfo({ prompt_tokens, completion_tokens, total_tokens });
|
data.usage;
|
||||||
|
getUsageInfo({
|
||||||
|
prompt_tokens,
|
||||||
|
completion_tokens,
|
||||||
|
total_tokens
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
console.error("JSON解析失败:", err);
|
console.error("JSON解析失败:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
console.error("Error:", err);
|
console.error("Error:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from "./base_service"
|
export * from "./base_service";
|
||||||
export * from "./chat_service"
|
export * from "./chat_service";
|
||||||
export * from "./websocket"
|
export * from "./websocket";
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ export const useWebSocketStore = defineStore("websocket", () => {
|
|||||||
|
|
||||||
let pingIntervalId: NodeJS.Timeout | undefined;
|
let pingIntervalId: NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
if (pingIntervalId)
|
if (pingIntervalId) clearInterval(pingIntervalId);
|
||||||
clearInterval(pingIntervalId);
|
|
||||||
pingIntervalId = setInterval(() => send("ping"), 30 * 1000);
|
pingIntervalId = setInterval(() => send("ping"), 30 * 1000);
|
||||||
|
|
||||||
if (websocket.value) {
|
if (websocket.value) {
|
||||||
@@ -61,6 +60,6 @@ export const useWebSocketStore = defineStore("websocket", () => {
|
|||||||
connected,
|
connected,
|
||||||
send,
|
send,
|
||||||
close,
|
close,
|
||||||
connect,
|
connect
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ export const useAsrStore = defineStore("asr", () => {
|
|||||||
if (webSocketStore.connected) {
|
if (webSocketStore.connected) {
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
webSocketStore.send(data);
|
webSocketStore.send(data);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
webSocketStore.websocket?.send(data);
|
webSocketStore.websocket?.send(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,8 +52,7 @@ export const useAsrStore = defineStore("asr", () => {
|
|||||||
* 开始录音
|
* 开始录音
|
||||||
*/
|
*/
|
||||||
const startRecording = async () => {
|
const startRecording = async () => {
|
||||||
if (isRecording.value)
|
if (isRecording.value) return;
|
||||||
return;
|
|
||||||
messages.value = [];
|
messages.value = [];
|
||||||
// 确保 WebSocket 已连接
|
// 确保 WebSocket 已连接
|
||||||
if (!webSocketStore.connected) {
|
if (!webSocketStore.connected) {
|
||||||
@@ -62,8 +60,7 @@ export const useAsrStore = defineStore("asr", () => {
|
|||||||
// 等待连接建立
|
// 等待连接建立
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
const check = () => {
|
const check = () => {
|
||||||
if (webSocketStore.connected)
|
if (webSocketStore.connected) resolve();
|
||||||
resolve();
|
|
||||||
else setTimeout(check, 100);
|
else setTimeout(check, 100);
|
||||||
};
|
};
|
||||||
check();
|
check();
|
||||||
@@ -73,11 +70,14 @@ export const useAsrStore = defineStore("asr", () => {
|
|||||||
// 获取麦克风音频流
|
// 获取麦克风音频流
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
// 创建音频上下文,采样率16kHz
|
// 创建音频上下文,采样率16kHz
|
||||||
audioContext = new (window.AudioContext || (window as any).webkitAudioContext)({
|
audioContext = new (window.AudioContext ||
|
||||||
sampleRate: 16000,
|
(window as any).webkitAudioContext)({
|
||||||
|
sampleRate: 16000
|
||||||
});
|
});
|
||||||
// 用Blob方式创建AudioWorklet模块的URL
|
// 用Blob方式创建AudioWorklet模块的URL
|
||||||
const blob = new Blob([audioProcessorCode], { type: "application/javascript" });
|
const blob = new Blob([audioProcessorCode], {
|
||||||
|
type: "application/javascript"
|
||||||
|
});
|
||||||
const processorUrl = URL.createObjectURL(blob);
|
const processorUrl = URL.createObjectURL(blob);
|
||||||
// 加载AudioWorklet模块
|
// 加载AudioWorklet模块
|
||||||
await audioContext.audioWorklet.addModule(processorUrl);
|
await audioContext.audioWorklet.addModule(processorUrl);
|
||||||
@@ -89,7 +89,7 @@ export const useAsrStore = defineStore("asr", () => {
|
|||||||
workletNode = new AudioWorkletNode(audioContext, "audio-processor", {
|
workletNode = new AudioWorkletNode(audioContext, "audio-processor", {
|
||||||
numberOfInputs: 1,
|
numberOfInputs: 1,
|
||||||
numberOfOutputs: 1,
|
numberOfOutputs: 1,
|
||||||
channelCount: 1,
|
channelCount: 1
|
||||||
});
|
});
|
||||||
// 监听来自AudioWorklet的音频数据
|
// 监听来自AudioWorklet的音频数据
|
||||||
workletNode.port.onmessage = (event) => {
|
workletNode.port.onmessage = (event) => {
|
||||||
@@ -104,8 +104,7 @@ export const useAsrStore = defineStore("asr", () => {
|
|||||||
mediaStreamSource.connect(workletNode);
|
mediaStreamSource.connect(workletNode);
|
||||||
workletNode.connect(audioContext.destination);
|
workletNode.connect(audioContext.destination);
|
||||||
isRecording.value = true;
|
isRecording.value = true;
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
// 麦克风权限失败或AudioWorklet加载失败
|
// 麦克风权限失败或AudioWorklet加载失败
|
||||||
console.error("需要麦克风权限才能录音", err);
|
console.error("需要麦克风权限才能录音", err);
|
||||||
}
|
}
|
||||||
@@ -115,8 +114,7 @@ export const useAsrStore = defineStore("asr", () => {
|
|||||||
* 停止录音
|
* 停止录音
|
||||||
*/
|
*/
|
||||||
const stopRecording = () => {
|
const stopRecording = () => {
|
||||||
if (!isRecording.value)
|
if (!isRecording.value) return;
|
||||||
return;
|
|
||||||
|
|
||||||
// 通知后端录音结束
|
// 通知后端录音结束
|
||||||
sendMessage(JSON.stringify({ type: "asr_end" }));
|
sendMessage(JSON.stringify({ type: "asr_end" }));
|
||||||
@@ -124,7 +122,7 @@ export const useAsrStore = defineStore("asr", () => {
|
|||||||
// 停止所有音轨
|
// 停止所有音轨
|
||||||
if (mediaStreamSource?.mediaStream) {
|
if (mediaStreamSource?.mediaStream) {
|
||||||
const tracks = mediaStreamSource.mediaStream.getTracks();
|
const tracks = mediaStreamSource.mediaStream.getTracks();
|
||||||
tracks.forEach(track => track.stop());
|
tracks.forEach((track) => track.stop());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 断开音频节点
|
// 断开音频节点
|
||||||
@@ -149,6 +147,6 @@ export const useAsrStore = defineStore("asr", () => {
|
|||||||
messages,
|
messages,
|
||||||
startRecording,
|
startRecording,
|
||||||
stopRecording,
|
stopRecording,
|
||||||
sendMessage,
|
sendMessage
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import type { IChatWithLLMRequest, ModelInfo, ModelListInfo, UsageInfo } from "@/interfaces";
|
import type {
|
||||||
|
IChatWithLLMRequest,
|
||||||
|
ModelInfo,
|
||||||
|
ModelListInfo,
|
||||||
|
UsageInfo
|
||||||
|
} from "@/interfaces";
|
||||||
import { ChatService } from "@/services";
|
import { ChatService } from "@/services";
|
||||||
|
|
||||||
export const useChatStore = defineStore("chat", () => {
|
export const useChatStore = defineStore("chat", () => {
|
||||||
const token = ("sk-fkGVZBrAqvIxLjlF3b5f19EfBb63486c90Fa5a1fBd7076Ee");
|
const token = "sk-fkGVZBrAqvIxLjlF3b5f19EfBb63486c90Fa5a1fBd7076Ee";
|
||||||
// 默认模型
|
// 默认模型
|
||||||
const modelInfo = ref<ModelInfo | null>(null);
|
const modelInfo = ref<ModelInfo | null>(null);
|
||||||
// 历史消息
|
// 历史消息
|
||||||
@@ -16,23 +21,25 @@ export const useChatStore = defineStore("chat", () => {
|
|||||||
const chatWithLLM = async (
|
const chatWithLLM = async (
|
||||||
request: IChatWithLLMRequest,
|
request: IChatWithLLMRequest,
|
||||||
onProgress: (content: string) => void, // 接收进度回调
|
onProgress: (content: string) => void, // 接收进度回调
|
||||||
getUsageInfo: (object: UsageInfo) => void = () => { },
|
getUsageInfo: (object: UsageInfo) => void = () => {}
|
||||||
) => {
|
) => {
|
||||||
if (completing.value)
|
if (completing.value) throw new Error("正在响应中");
|
||||||
throw new Error("正在响应中");
|
|
||||||
|
|
||||||
completing.value = true; // 开始请求
|
completing.value = true; // 开始请求
|
||||||
try {
|
try {
|
||||||
await ChatService.ChatWithLLM(token, request, (content) => {
|
await ChatService.ChatWithLLM(
|
||||||
onProgress(content);
|
token,
|
||||||
}, (object: UsageInfo) => {
|
request,
|
||||||
getUsageInfo(object);
|
(content) => {
|
||||||
});
|
onProgress(content);
|
||||||
}
|
},
|
||||||
catch (error) {
|
(object: UsageInfo) => {
|
||||||
|
getUsageInfo(object);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
console.error("请求失败:", error);
|
console.error("请求失败:", error);
|
||||||
}
|
} finally {
|
||||||
finally {
|
|
||||||
completing.value = false;
|
completing.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -40,12 +47,11 @@ export const useChatStore = defineStore("chat", () => {
|
|||||||
// 添加消息到历史记录
|
// 添加消息到历史记录
|
||||||
const addMessageToHistory = (message: string) => {
|
const addMessageToHistory = (message: string) => {
|
||||||
const content = message.trim();
|
const content = message.trim();
|
||||||
if (!content)
|
if (!content) return;
|
||||||
return;
|
|
||||||
|
|
||||||
historyMessages.value.push({
|
historyMessages.value.push({
|
||||||
role: "user",
|
role: "user",
|
||||||
content,
|
content
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -54,39 +60,51 @@ export const useChatStore = defineStore("chat", () => {
|
|||||||
historyMessages.value = [];
|
historyMessages.value = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(historyMessages, (newVal) => {
|
watch(
|
||||||
// 当历史消息变化时,发送请求
|
historyMessages,
|
||||||
if (newVal.length > 0) {
|
(newVal) => {
|
||||||
const lastMessage = newVal[newVal.length - 1];
|
// 当历史消息变化时,发送请求
|
||||||
if (lastMessage.role === "user" && modelInfo.value) {
|
if (newVal.length > 0) {
|
||||||
chatWithLLM({
|
const lastMessage = newVal[newVal.length - 1];
|
||||||
messages: newVal,
|
if (lastMessage.role === "user" && modelInfo.value) {
|
||||||
model: modelInfo.value?.model_id,
|
chatWithLLM(
|
||||||
}, (content) => {
|
{
|
||||||
// 处理进度回调
|
messages: newVal,
|
||||||
if (
|
model: modelInfo.value?.model_id
|
||||||
historyMessages.value.length === 0
|
},
|
||||||
|| historyMessages.value[historyMessages.value.length - 1].role !== "assistant"
|
(content) => {
|
||||||
) {
|
// 处理进度回调
|
||||||
historyMessages.value.push({
|
if (
|
||||||
role: "assistant",
|
historyMessages.value.length === 0 ||
|
||||||
content: "",
|
historyMessages.value[historyMessages.value.length - 1].role !==
|
||||||
});
|
"assistant"
|
||||||
}
|
) {
|
||||||
historyMessages.value[historyMessages.value.length - 1].content = content;
|
historyMessages.value.push({
|
||||||
}, (usageInfo: UsageInfo) => {
|
role: "assistant",
|
||||||
// 处理使用usage信息回调
|
content: ""
|
||||||
// 如果最后一条消息是助手的回复,则更新使用信息
|
});
|
||||||
if (
|
}
|
||||||
historyMessages.value.length > 0
|
historyMessages.value[historyMessages.value.length - 1].content =
|
||||||
&& historyMessages.value[historyMessages.value.length - 1].role === "assistant"
|
content;
|
||||||
) {
|
},
|
||||||
historyMessages.value[historyMessages.value.length - 1].usage = usageInfo;
|
(usageInfo: UsageInfo) => {
|
||||||
}
|
// 处理使用usage信息回调
|
||||||
});
|
// 如果最后一条消息是助手的回复,则更新使用信息
|
||||||
|
if (
|
||||||
|
historyMessages.value.length > 0 &&
|
||||||
|
historyMessages.value[historyMessages.value.length - 1].role ===
|
||||||
|
"assistant"
|
||||||
|
) {
|
||||||
|
historyMessages.value[historyMessages.value.length - 1].usage =
|
||||||
|
usageInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, { deep: true });
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
// 模型列表
|
// 模型列表
|
||||||
const modelList = ref<ModelListInfo[]>([]);
|
const modelList = ref<ModelListInfo[]>([]);
|
||||||
@@ -96,11 +114,21 @@ export const useChatStore = defineStore("chat", () => {
|
|||||||
try {
|
try {
|
||||||
const response = await ChatService.GetModelList();
|
const response = await ChatService.GetModelList();
|
||||||
modelList.value = response.data.data;
|
modelList.value = response.data.data;
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
console.error("获取模型列表失败:", error);
|
console.error("获取模型列表失败:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { token, completing, chatWithLLM, historyMessages, addMessageToHistory, clearHistoryMessages, getModelList, modelList, modelInfo, onlineCount };
|
return {
|
||||||
|
token,
|
||||||
|
completing,
|
||||||
|
chatWithLLM,
|
||||||
|
historyMessages,
|
||||||
|
addMessageToHistory,
|
||||||
|
clearHistoryMessages,
|
||||||
|
getModelList,
|
||||||
|
modelList,
|
||||||
|
modelInfo,
|
||||||
|
onlineCount
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
export const useUserStore = defineStore("user", () => {
|
export const useUserStore = defineStore("user", () => {
|
||||||
return {
|
return {};
|
||||||
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
@import 'tailwindcss';
|
@import "tailwindcss";
|
||||||
@import 'highlight.js/styles/github.css';
|
@import "highlight.js/styles/github.css";
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { LoadingBarApiInjection } from "naive-ui/es/loading-bar/src/LoadingBarProvider"
|
import type { LoadingBarApiInjection } from "naive-ui/es/loading-bar/src/LoadingBarProvider";
|
||||||
import type { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider"
|
import type { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider";
|
||||||
import type { NotificationApiInjection } from "naive-ui/es/notification/src/NotificationProvider"
|
import type { NotificationApiInjection } from "naive-ui/es/notification/src/NotificationProvider";
|
||||||
|
|
||||||
export const context: {
|
export const context: {
|
||||||
message?: MessageApiInjection
|
message?: MessageApiInjection;
|
||||||
notification?: NotificationApiInjection
|
notification?: NotificationApiInjection;
|
||||||
loadingBar?: LoadingBarApiInjection
|
loadingBar?: LoadingBarApiInjection;
|
||||||
} = {}
|
} = {};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export * from "./context"
|
export * from "./context";
|
||||||
export * from "./media"
|
export * from "./media";
|
||||||
export * from "./pcm"
|
export * from "./pcm";
|
||||||
export * from "./title"
|
export * from "./title";
|
||||||
export * from "./title"
|
export * from "./title";
|
||||||
export * from "./url"
|
export * from "./url";
|
||||||
|
|||||||
@@ -2,41 +2,34 @@
|
|||||||
export const matchMedia = (
|
export const matchMedia = (
|
||||||
type: "sm" | "md" | "lg" | string,
|
type: "sm" | "md" | "lg" | string,
|
||||||
matchFunc?: Function,
|
matchFunc?: Function,
|
||||||
mismatchFunc?: Function,
|
mismatchFunc?: Function
|
||||||
) => {
|
) => {
|
||||||
if (type === "sm") {
|
if (type === "sm") {
|
||||||
if (window.matchMedia("(max-width: 767.98px)").matches) {
|
if (window.matchMedia("(max-width: 767.98px)").matches) {
|
||||||
/* 窗口小于或等于 */
|
/* 窗口小于或等于 */
|
||||||
matchFunc?.();
|
matchFunc?.();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
mismatchFunc?.();
|
mismatchFunc?.();
|
||||||
}
|
}
|
||||||
}
|
} else if (type === "md") {
|
||||||
else if (type === "md") {
|
|
||||||
if (window.matchMedia("(max-width: 992px)").matches) {
|
if (window.matchMedia("(max-width: 992px)").matches) {
|
||||||
/* 窗口小于或等于 */
|
/* 窗口小于或等于 */
|
||||||
matchFunc?.();
|
matchFunc?.();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
mismatchFunc?.();
|
mismatchFunc?.();
|
||||||
}
|
}
|
||||||
}
|
} else if (type === "lg") {
|
||||||
else if (type === "lg") {
|
|
||||||
if (window.matchMedia("(max-width: 1200px)").matches) {
|
if (window.matchMedia("(max-width: 1200px)").matches) {
|
||||||
/* 窗口小于或等于 */
|
/* 窗口小于或等于 */
|
||||||
matchFunc?.();
|
matchFunc?.();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
mismatchFunc?.();
|
mismatchFunc?.();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (window.matchMedia(`(max-width: ${type}px)`).matches) {
|
if (window.matchMedia(`(max-width: ${type}px)`).matches) {
|
||||||
/* 窗口小于或等于 */
|
/* 窗口小于或等于 */
|
||||||
matchFunc?.();
|
matchFunc?.();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
mismatchFunc?.();
|
mismatchFunc?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
export const convertToPCM16 = (float32Array: Float32Array): Uint8Array => {
|
export const convertToPCM16 = (float32Array: Float32Array): Uint8Array => {
|
||||||
const int16Buffer = new Int16Array(float32Array.length)
|
const int16Buffer = new Int16Array(float32Array.length);
|
||||||
for (let i = 0; i < float32Array.length; i++) {
|
for (let i = 0; i < float32Array.length; i++) {
|
||||||
int16Buffer[i] = Math.max(-1, Math.min(1, float32Array[i])) * 0x7FFF
|
int16Buffer[i] = Math.max(-1, Math.min(1, float32Array[i])) * 0x7FFF;
|
||||||
}
|
}
|
||||||
const buffer = new ArrayBuffer(int16Buffer.length * 2)
|
const buffer = new ArrayBuffer(int16Buffer.length * 2);
|
||||||
const view = new DataView(buffer)
|
const view = new DataView(buffer);
|
||||||
for (let i = 0; i < int16Buffer.length; i++) {
|
for (let i = 0; i < int16Buffer.length; i++) {
|
||||||
view.setInt16(i * 2, int16Buffer[i], true)
|
view.setInt16(i * 2, int16Buffer[i], true);
|
||||||
}
|
}
|
||||||
return new Uint8Array(buffer)
|
return new Uint8Array(buffer);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import { useTitle } from "@vueuse/core"
|
import { useTitle } from "@vueuse/core";
|
||||||
|
|
||||||
const DEFAULT_TITLE = "Agent"
|
const DEFAULT_TITLE = "Agent";
|
||||||
|
|
||||||
const DEFAULT_DESCRIPTION = document
|
const DEFAULT_DESCRIPTION = document
|
||||||
.querySelector("meta[name='description']")
|
.querySelector("meta[name='description']")
|
||||||
?.getAttribute("content")
|
?.getAttribute("content");
|
||||||
|
|
||||||
export function setTitle(title?: string) {
|
export function setTitle(title?: string) {
|
||||||
useTitle().value = (title ? `${title} | ` : "") + DEFAULT_TITLE
|
useTitle().value = (title ? `${title} | ` : "") + DEFAULT_TITLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetDescription() {
|
export function resetDescription() {
|
||||||
document
|
document
|
||||||
.querySelector("meta[name='description']")
|
.querySelector("meta[name='description']")
|
||||||
?.setAttribute("content", DEFAULT_DESCRIPTION!)
|
?.setAttribute("content", DEFAULT_DESCRIPTION!);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setDescription(description?: string) {
|
export function setDescription(description?: string) {
|
||||||
if (!description)
|
if (!description) return;
|
||||||
return
|
|
||||||
document
|
document
|
||||||
.querySelector("meta[name='description']")
|
.querySelector("meta[name='description']")
|
||||||
?.setAttribute("content", `${description} | ${DEFAULT_TITLE}`)
|
?.setAttribute("content", `${description} | ${DEFAULT_TITLE}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
/** 直接当前页面跳转到指定url */
|
/** 直接当前页面跳转到指定url */
|
||||||
export const jump = (url: string) => {
|
export const jump = (url: string) => {
|
||||||
window.location.href = url
|
window.location.href = url;
|
||||||
}
|
};
|
||||||
|
|
||||||
/** 在新标签页中跳转到指定url */
|
/** 在新标签页中跳转到指定url */
|
||||||
export const jumpBlank = (url: string) => {
|
export const jumpBlank = (url: string) => {
|
||||||
window.open(url, "_blank")
|
window.open(url, "_blank");
|
||||||
}
|
};
|
||||||
|
|
||||||
/** 将对象转换为url查询字符串 */
|
/** 将对象转换为url查询字符串 */
|
||||||
export const queryFormat = (query: Record<string, any>) => {
|
export const queryFormat = (query: Record<string, any>) => {
|
||||||
const params = new URLSearchParams(query)
|
const params = new URLSearchParams(query);
|
||||||
return params.toString() ? `?${params}` : ""
|
return params.toString() ? `?${params}` : "";
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
import type { SelectGroupOption, SelectOption } from "naive-ui";
|
import type { SelectGroupOption, SelectOption } from "naive-ui";
|
||||||
import { throttle } from "lodash-es";
|
import { throttle } from "lodash-es";
|
||||||
import AIAvatar from "@/assets/ai_avatar.png";
|
import AIAvatar from "@/assets/ai_avatar.png";
|
||||||
import { ExclamationTriangleIcon, microphone, PaperAirplaneIcon, TrashIcon } from "@/assets/Icons";
|
import {
|
||||||
|
ExclamationTriangleIcon,
|
||||||
|
microphone,
|
||||||
|
PaperAirplaneIcon,
|
||||||
|
TrashIcon
|
||||||
|
} from "@/assets/Icons";
|
||||||
import UserAvatar from "@/assets/user_avatar.jpg";
|
import UserAvatar from "@/assets/user_avatar.jpg";
|
||||||
import markdown from "@/components/markdown.vue";
|
import markdown from "@/components/markdown.vue";
|
||||||
import { useAsrStore, useChatStore } from "@/stores";
|
import { useAsrStore, useChatStore } from "@/stores";
|
||||||
@@ -10,7 +15,8 @@ import { useAsrStore, useChatStore } from "@/stores";
|
|||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
const asrStore = useAsrStore();
|
const asrStore = useAsrStore();
|
||||||
|
|
||||||
const { historyMessages, completing, modelList, modelInfo } = storeToRefs(chatStore);
|
const { historyMessages, completing, modelList, modelInfo } =
|
||||||
|
storeToRefs(chatStore);
|
||||||
const { isRecording } = storeToRefs(asrStore);
|
const { isRecording } = storeToRefs(asrStore);
|
||||||
|
|
||||||
const inputData = ref("");
|
const inputData = ref("");
|
||||||
@@ -22,40 +28,43 @@ const selectedModelId = computed({
|
|||||||
get: () => modelInfo.value?.model_id ?? null,
|
get: () => modelInfo.value?.model_id ?? null,
|
||||||
set: (id: string | null) => {
|
set: (id: string | null) => {
|
||||||
for (const vendor of modelList.value) {
|
for (const vendor of modelList.value) {
|
||||||
const found = vendor.models.find(model => model.model_id === id);
|
const found = vendor.models.find((model) => model.model_id === id);
|
||||||
if (found) {
|
if (found) {
|
||||||
modelInfo.value = found;
|
modelInfo.value = found;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
modelInfo.value = null;
|
modelInfo.value = null;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听模型列表变化,更新选项
|
// 监听模型列表变化,更新选项
|
||||||
watch(() => modelList.value, (newVal) => {
|
watch(
|
||||||
if (newVal) {
|
() => modelList.value,
|
||||||
options.value = newVal.map(vendor => ({
|
(newVal) => {
|
||||||
type: "group",
|
if (newVal) {
|
||||||
label: vendor.vendor,
|
options.value = newVal.map((vendor) => ({
|
||||||
key: vendor.vendor,
|
type: "group",
|
||||||
children: vendor.models.map(model => ({
|
label: vendor.vendor,
|
||||||
label: model.model_name,
|
key: vendor.vendor,
|
||||||
value: model.model_id,
|
children: vendor.models.map((model) => ({
|
||||||
type: model.model_type,
|
label: model.model_name,
|
||||||
})),
|
value: model.model_id,
|
||||||
}));
|
type: model.model_type
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
if (newVal.length > 0 && newVal[0].models.length > 0) {
|
if (newVal.length > 0 && newVal[0].models.length > 0) {
|
||||||
modelInfo.value = newVal[0].models[0];
|
modelInfo.value = newVal[0].models[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, { immediate: true, deep: true });
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
// 发送消息
|
// 发送消息
|
||||||
const handleSendMessage = () => {
|
const handleSendMessage = () => {
|
||||||
if (inputData.value.trim() === "")
|
if (inputData.value.trim() === "") return;
|
||||||
return;
|
|
||||||
chatStore.addMessageToHistory(inputData.value);
|
chatStore.addMessageToHistory(inputData.value);
|
||||||
inputData.value = "";
|
inputData.value = "";
|
||||||
};
|
};
|
||||||
@@ -64,8 +73,7 @@ const handleSendMessage = () => {
|
|||||||
const toggleRecording = throttle(() => {
|
const toggleRecording = throttle(() => {
|
||||||
if (isRecording.value) {
|
if (isRecording.value) {
|
||||||
asrStore.stopRecording();
|
asrStore.stopRecording();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
asrStore.startRecording();
|
asrStore.startRecording();
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
@@ -84,7 +92,9 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-8 !pr-4 h-full w-full flex flex-col gap-4 border-l-[24px] border-l-[#FAFAFA] text-base">
|
<div
|
||||||
|
class="p-8 !pr-4 h-full w-full flex flex-col gap-4 border-l-[24px] border-l-[#FAFAFA] text-base"
|
||||||
|
>
|
||||||
<!-- 历史消息区 -->
|
<!-- 历史消息区 -->
|
||||||
<NScrollbar ref="scrollbarRef" class="flex-1 pr-4 relative">
|
<NScrollbar ref="scrollbarRef" class="flex-1 pr-4 relative">
|
||||||
<div class="flex items-start mb-4">
|
<div class="flex items-start mb-4">
|
||||||
@@ -93,20 +103,34 @@ onMounted(() => {
|
|||||||
</span>
|
</span>
|
||||||
<div class="text-base w-full max-w-full ml-2 flex flex-col items-start">
|
<div class="text-base w-full max-w-full ml-2 flex flex-col items-start">
|
||||||
<span class="text-base font-bold mb-4">助手:</span>
|
<span class="text-base font-bold mb-4">助手:</span>
|
||||||
<span class="text-base">你好,我是你的智能助手,请问有什么可以帮助你的吗?</span>
|
<span class="text-base"
|
||||||
|
>你好,我是你的智能助手,请问有什么可以帮助你的吗?</span
|
||||||
|
>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(msg, idx) in historyMessages" :key="idx" class="flex items-start mb-4">
|
<div
|
||||||
<span v-if="msg.role === 'user'" class="rounded-lg overflow-hidden !w-16 !min-w-16 !h-16">
|
v-for="(msg, idx) in historyMessages"
|
||||||
|
:key="idx"
|
||||||
|
class="flex items-start mb-4"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="msg.role === 'user'"
|
||||||
|
class="rounded-lg overflow-hidden !w-16 !min-w-16 !h-16"
|
||||||
|
>
|
||||||
<avatar :avatar="UserAvatar" />
|
<avatar :avatar="UserAvatar" />
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="rounded-lg overflow-hidden">
|
<span v-else class="rounded-lg overflow-hidden">
|
||||||
<avatar :avatar="AIAvatar" />
|
<avatar :avatar="AIAvatar" />
|
||||||
</span>
|
</span>
|
||||||
<div class="text-base w-full max-w-full ml-2 flex flex-col items-start">
|
<div class="text-base w-full max-w-full ml-2 flex flex-col items-start">
|
||||||
<span class="text-base font-bold">{{ msg.role === 'user' ? '你:' : '助手:' }}</span>
|
<span class="text-base font-bold">{{
|
||||||
<div v-if="msg.role !== 'user'" class="text-[12px] text-[#7A7A7A] mb-[2px]">
|
msg.role === "user" ? "你:" : "助手:"
|
||||||
|
}}</span>
|
||||||
|
<div
|
||||||
|
v-if="msg.role !== 'user'"
|
||||||
|
class="text-[12px] text-[#7A7A7A] mb-[2px]"
|
||||||
|
>
|
||||||
Tokens: <span class="mr-1">{{ msg.usage?.total_tokens }}</span>
|
Tokens: <span class="mr-1">{{ msg.usage?.total_tokens }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full max-w-full">
|
<div class="w-full max-w-full">
|
||||||
@@ -124,23 +148,34 @@ onMounted(() => {
|
|||||||
</NScrollbar>
|
</NScrollbar>
|
||||||
<!-- 输入框 -->
|
<!-- 输入框 -->
|
||||||
<NInput
|
<NInput
|
||||||
v-model:value="inputData" type="textarea" placeholder="输入内容,Enter发送,Shift+Enter换行" :autosize="{
|
v-model:value="inputData"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="输入内容,Enter发送,Shift+Enter换行"
|
||||||
|
:autosize="{
|
||||||
minRows: 3,
|
minRows: 3,
|
||||||
maxRows: 15,
|
maxRows: 15
|
||||||
}" @keyup.enter="handleSendMessage"
|
}"
|
||||||
|
@keyup.enter="handleSendMessage"
|
||||||
/>
|
/>
|
||||||
<!-- 操作区 -->
|
<!-- 操作区 -->
|
||||||
<div class="flex justify-between items-center gap-2">
|
<div class="flex justify-between items-center gap-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<NSelect
|
<NSelect
|
||||||
v-model:value="selectedModelId" label-field="label" value-field="value" children-field="children"
|
v-model:value="selectedModelId"
|
||||||
filterable :options="options"
|
label-field="label"
|
||||||
|
value-field="value"
|
||||||
|
children-field="children"
|
||||||
|
filterable
|
||||||
|
:options="options"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<NPopconfirm
|
<NPopconfirm
|
||||||
:positive-button-props="{ type: 'error' }" positive-text="清除" negative-text="取消"
|
:positive-button-props="{ type: 'error' }"
|
||||||
@positive-click="chatStore.clearHistoryMessages" @negative-click="() => { }"
|
positive-text="清除"
|
||||||
|
negative-text="取消"
|
||||||
|
@positive-click="chatStore.clearHistoryMessages"
|
||||||
|
@negative-click="() => {}"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<ExclamationTriangleIcon class="!w-6 !h-6 text-[#d03050]" />
|
<ExclamationTriangleIcon class="!w-6 !h-6 text-[#d03050]" />
|
||||||
@@ -158,7 +193,11 @@ onMounted(() => {
|
|||||||
{{ isRecording ? "停止输入" : "语音输入" }}
|
{{ isRecording ? "停止输入" : "语音输入" }}
|
||||||
<microphone class="!w-4 !h-4 ml-1" />
|
<microphone class="!w-4 !h-4 ml-1" />
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton :disabled="isRecording" :loading="completing" @click="handleSendMessage">
|
<NButton
|
||||||
|
:disabled="isRecording"
|
||||||
|
:loading="completing"
|
||||||
|
@click="handleSendMessage"
|
||||||
|
>
|
||||||
发送
|
发送
|
||||||
<PaperAirplaneIcon class="!w-4 !h-4 ml-1" />
|
<PaperAirplaneIcon class="!w-4 !h-4 ml-1" />
|
||||||
</NButton>
|
</NButton>
|
||||||
|
|||||||
Reference in New Issue
Block a user