This commit is contained in:
刘昊昕
2025-10-17 22:59:41 +08:00
commit ca27603fd2
92 changed files with 7417 additions and 0 deletions

28
.github/workflows/docker.yaml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Build and Push Docker Image
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/xdechat_web:1.1.6

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored Normal file
View File

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

33
Dockerfile Normal file
View File

@@ -0,0 +1,33 @@
# 使用官方的 Node.js 镜像作为构建阶段的基础镜像
FROM node:22 AS builder
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json如果有的话
COPY package*.json ./
# 安装项目依赖
RUN npm install
# 复制项目文件
COPY . .
# 构建 Vite 项目
RUN npm run build
# 使用官方的 Nginx 镜像作为生产阶段的基础镜像
FROM nginx:stable-alpine
# 复制构建输出到 Nginx 的默认静态文件目录
COPY --from=builder /app/dist /usr/share/nginx/html
# 拷贝启动脚本(生成 env.js
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# 声明一个可覆盖的环境变量(默认空)
ENV API_URL=""
EXPOSE 80
CMD ["/entrypoint.sh"]

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
# 1.0
新ui初步完成
# 1.0.1
更改网站名称和icon修复了删除最后一个对话时出现问题的bug更改choosekind的scroll为auto刷新网页时自动创建新的对话
# 1.0.2
修复了删除对话的bug
# 1.1
适配了新的接口
# 1.1.1
fixed some bug
# 1.1.2
增加了新的意图
# 1.1.3
修复了手机端对话框的bug
# 1.1.4
修改后端域名
# 1.1.5
后端域名现在是xdechat
# 1.1.5huitong
后端域名是huitong
# 1.1.6
加了三个意图

10
entrypoint.sh Normal file
View File

@@ -0,0 +1,10 @@
#!/bin/sh
set -e
# 运行时生成 /usr/share/nginx/html/env.js
cat >/usr/share/nginx/html/env.js <<EOF
window.__ENV__ = { API_URL: "${API_URL}" };
EOF
# 启动 nginx前台
exec nginx -g 'daemon off;'

14
index.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en" class="light">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>慧通大模型</title>
</head>
<body>
<div id="app"></div>
<script src="/env.js"></script>
<script type="module" src="/src/main.js"></script>
</body>
</html>

10
nohup.out Normal file
View File

@@ -0,0 +1,10 @@
> my-vue-app@0.0.0 preview
> vite preview
➜ Local: http://localhost:4173/
➜ Network: http://10.198.115.195:4173/
➜ Network: http://198.18.0.1:4173/
➜ Network: http://172.17.0.1:4173/
➜ Network: http://11.45.0.30:4173/
➜ Network: http://10.8.0.2:4173/

3136
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

39
package.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "my-vue-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@microsoft/fetch-event-source": "^2.0.1",
"@rangermauve/fetch-event-source": "^1.0.3",
"@traptitech/markdown-it-katex": "^3.6.0",
"axios": "^1.7.2",
"bootstrap": "^5.3.3",
"bootstrap-vue": "^2.23.1",
"element-plus": "^2.7.6",
"highlight.js": "^11.10.0",
"jquery": "^3.7.1",
"markdown-it": "^14.1.0",
"markdown-it-katex": "^2.0.3",
"markdown-it-link-attributes": "^4.0.1",
"vue": "^3.5.11",
"vue-demi": "^0.14.10",
"vue-router": "^4.4.0",
"vuex": "^4.0.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.5",
"less": "^4.2.0",
"sass": "^1.79.4",
"unplugin-auto-import": "^0.17.6",
"unplugin-icons": "^0.19.0",
"unplugin-vue-components": "^0.27.2",
"vite": "^5.3.1"
}
}

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727490080439" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6860" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M722.438 510.815c-0.246-0.246-0.513-0.454-0.766-0.69l-299.893-299.892c-14.231-14.24-37.291-14.24-51.529 0-14.241 14.231-14.241 37.291 0 51.529l274.851 274.859-274.849 274.851c-14.241 14.237-14.241 37.301 0 51.532 7.116 7.119 16.436 10.682 25.764 10.682 9.321 0 18.644-3.563 25.763-10.682l299.905-299.901c0.246-0.234 0.51-0.438 0.751-0.678 7.129-7.13 10.685-16.469 10.674-25.804 0.006-9.337-3.55-18.676-10.674-25.804z" fill="#272636" p-id="6861"></path></svg>

After

Width:  |  Height:  |  Size: 791 B

1
public/icon/self.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727516507617" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4860" id="mx_n_1727516507618" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M493.75104 585.130667A292.522667 292.522667 0 0 0 786.273707 292.693333a285.269333 285.269333 0 0 0-40.96-146.261333 292.522667 292.522667 0 1 0-251.562667 438.784zM666.380373 676.693333a266.24 266.24 0 0 0-99.498666-18.261333H420.620373a271.36 271.36 0 0 0-73.130666 9.386667 292.522667 292.522667 0 0 0-134.570667 73.216A292.522667 292.522667 0 0 0 128.012373 950.869333c0 40.362667 32.768 73.130667 73.130667 73.130667h585.130667a73.130667 73.130667 0 0 0 73.130666-73.130667 219.392 219.392 0 0 0 0-38.058666A292.522667 292.522667 0 0 0 666.465707 676.693333z" fill="#1296db" p-id="4861"></path></svg>

After

Width:  |  Height:  |  Size: 962 B

48
public/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 317 KiB

138
public/style.less Normal file
View File

@@ -0,0 +1,138 @@
.markdown-body {
background-color: transparent;
font-size: 14px;
p {
white-space: pre-wrap;
}
ol {
list-style-type: decimal;
}
ul {
list-style-type: disc;
}
img{
max-width: 400px;
}
pre code,
pre tt {
line-height: 1.65;
}
.highlight pre,
pre {
background-color: #fff;
}
code.hljs {
padding: 0;
}
.code-block {
&-wrapper {
position: relative;
padding-top: 24px;
}
&-header {
position: absolute;
top: 5px;
right: 0;
width: 100%;
padding: 0 1rem;
display: flex;
justify-content: flex-end;
align-items: center;
color: #b3b3b3;
&__copy {
cursor: pointer;
margin-left: 0.5rem;
user-select: none;
&:hover {
color: #65a665;
}
}
}
}
&.markdown-body-generate>dd:last-child:after,
&.markdown-body-generate>dl:last-child:after,
&.markdown-body-generate>dt:last-child:after,
&.markdown-body-generate>h1:last-child:after,
&.markdown-body-generate>h2:last-child:after,
&.markdown-body-generate>h3:last-child:after,
&.markdown-body-generate>h4:last-child:after,
&.markdown-body-generate>h5:last-child:after,
&.markdown-body-generate>h6:last-child:after,
&.markdown-body-generate>li:last-child:after,
&.markdown-body-generate>ol:last-child li:last-child:after,
&.markdown-body-generate>p:last-child:after,
&.markdown-body-generate>pre:last-child code:after,
&.markdown-body-generate>td:last-child:after,
&.markdown-body-generate>ul:last-child li:last-child:after {
animation: blink 1s steps(5, start) infinite;
color: #000;
content: '_';
font-weight: 700;
margin-left: 3px;
vertical-align: baseline;
}
@keyframes blink {
to {
visibility: hidden;
}
}
}
html.dark {
.markdown-body {
&.markdown-body-generate>dd:last-child:after,
&.markdown-body-generate>dl:last-child:after,
&.markdown-body-generate>dt:last-child:after,
&.markdown-body-generate>h1:last-child:after,
&.markdown-body-generate>h2:last-child:after,
&.markdown-body-generate>h3:last-child:after,
&.markdown-body-generate>h4:last-child:after,
&.markdown-body-generate>h5:last-child:after,
&.markdown-body-generate>h6:last-child:after,
&.markdown-body-generate>li:last-child:after,
&.markdown-body-generate>ol:last-child li:last-child:after,
&.markdown-body-generate>p:last-child:after,
&.markdown-body-generate>pre:last-child code:after,
&.markdown-body-generate>td:last-child:after,
&.markdown-body-generate>ul:last-child li:last-child:after {
color: #65a665;
}
}
.message-reply {
.whitespace-pre-wrap {
white-space: pre-wrap;
color: var(--n-text-color);
}
}
.highlight pre,
pre {
background-color: #282c34;
}
}
@media screen and (max-width: 533px) {
.markdown-body .code-block-wrapper {
padding: unset;
code {
padding: 24px 16px 16px 16px;
}
}
}

1
public/vite.svg Normal file
View File

@@ -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>

After

Width:  |  Height:  |  Size: 1.5 KiB

40
src/App.vue Normal file
View File

@@ -0,0 +1,40 @@
<template>
<Navbar />
<router-view :key="$route.fullPath" />
</template>
<script setup>
import Navbar from "./components/Navbar.vue";
</script>
<style Lang="sass" >
:root {
--primary: #ebebeb;
--dark: #101014;
--sidebar-width: 300px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: var(--light);
overflow-x: hidden;
overflow-y: hidden;
}
.dark body {
background-color: var(--dark);
}
button {
cursor: pointer;
appearance: none;
border: none;
outline: none;
background: none;
}
</style>

1
src/assets/arrowleft.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727489872713" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5897" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M167.517867 513.659733l366.941866 367.586134a36.846933 36.846933 0 0 1 1.058134 50.961066 33.809067 33.809067 0 0 1-48.814934 1.024l-388.872533-389.154133c-16.657067-16.6656-16.648533-43.677867 0.017067-60.334933l389.623466-389.482667a33.489067 33.489067 0 0 1 48.341334 1.0368 36.5056 36.5056 0 0 1-1.053867 50.474667L167.517867 513.664z" fill="#333333" p-id="5898"></path></svg>

After

Width:  |  Height:  |  Size: 713 B

1
src/assets/chat.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M320-520q17 0 28.5-11.5T360-560q0-17-11.5-28.5T320-600q-17 0-28.5 11.5T280-560q0 17 11.5 28.5T320-520Zm160 0q17 0 28.5-11.5T520-560q0-17-11.5-28.5T480-600q-17 0-28.5 11.5T440-560q0 17 11.5 28.5T480-520Zm160 0q17 0 28.5-11.5T680-560q0-17-11.5-28.5T640-600q-17 0-28.5 11.5T600-560q0 17 11.5 28.5T640-520ZM80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z"/></svg>

After

Width:  |  Height:  |  Size: 574 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M440-280h80v-160h160v-80H520v-160h-80v160H280v80h160v160Zm40 200q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>

After

Width:  |  Height:  |  Size: 467 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M383-480 200-664l56-56 240 240-240 240-56-56 183-184Zm264 0L464-664l56-56 240 240-240 240-56-56 183-184Z"/></svg>

After

Width:  |  Height:  |  Size: 229 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#efefef"><path d="M80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z"/></svg>

After

Width:  |  Height:  |  Size: 272 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#111111"><path d="M80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z"/></svg>

After

Width:  |  Height:  |  Size: 272 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#efefef"><path d="m242-200 200-280-200-280h98l200 280-200 280h-98Zm238 0 200-280-200-280h98l200 280-200 280h-98Z"/></svg>

After

Width:  |  Height:  |  Size: 219 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#111111"><path d="m242-200 200-280-200-280h98l200 280-200 280h-98Zm238 0 200-280-200-280h98l200 280-200 280h-98Z"/></svg>

After

Width:  |  Height:  |  Size: 219 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M320-520q17 0 28.5-11.5T360-560q0-17-11.5-28.5T320-600q-17 0-28.5 11.5T280-560q0 17 11.5 28.5T320-520Zm160 0q17 0 28.5-11.5T520-560q0-17-11.5-28.5T480-600q-17 0-28.5 11.5T440-560q0 17 11.5 28.5T480-520Zm160 0q17 0 28.5-11.5T680-560q0-17-11.5-28.5T640-600q-17 0-28.5 11.5T600-560q0 17 11.5 28.5T640-520ZM80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z"/></svg>

After

Width:  |  Height:  |  Size: 574 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="m422-232 207-248H469l29-227-185 267h139l-30 208ZM320-80l40-280H160l360-520h80l-40 320h240L400-80h-80Zm151-390Z"/></svg>

After

Width:  |  Height:  |  Size: 235 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M196-276q-57-60-86.5-133T80-560q0-78 29.5-151T196-844l48 48q-48 48-72 110.5T148-560q0 63 24 125.5T244-324l-48 48Zm96-96q-39-39-59.5-88T212-560q0-51 20.5-100t59.5-88l48 48q-30 27-45 64t-15 76q0 36 15 73t45 67l-48 48ZM280-80l135-405q-16-14-25.5-33t-9.5-42q0-42 29-71t71-29q42 0 71 29t29 71q0 23-9.5 42T545-485L680-80h-80l-26-80H387l-27 80h-80Zm133-160h134l-67-200-67 200Zm255-132-48-48q30-27 45-64t15-76q0-36-15-73t-45-67l48-48q39 39 58 88t22 100q0 51-20.5 100T668-372Zm96 96-48-48q48-48 72-110.5T812-560q0-63-24-125.5T716-796l48-48q57 60 86.5 133T880-560q0 78-28 151t-88 133Z"/></svg>

After

Width:  |  Height:  |  Size: 699 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M40-120v-80h880v80H40Zm120-120q-33 0-56.5-23.5T80-320v-440q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v440q0 33-23.5 56.5T800-240H160Zm0-80h640v-440H160v440Zm0 0v-440 440Z"/></svg>

After

Width:  |  Height:  |  Size: 302 B

1
src/assets/copy.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm160-240v-480 480Z"/></svg>

After

Width:  |  Height:  |  Size: 340 B

1
src/assets/delete.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>

After

Width:  |  Height:  |  Size: 319 B

1
src/assets/dislike.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M240-840h440v520L400-40l-50-50q-7-7-11.5-19t-4.5-23v-14l44-174H120q-32 0-56-24t-24-56v-80q0-7 2-15t4-15l120-282q9-20 30-34t44-14Zm360 80H240L120-480v80h360l-54 220 174-174v-406Zm0 406v-406 406Zm80 34v-80h120v-360H680v-80h200v520H680Z"/></svg>

After

Width:  |  Height:  |  Size: 358 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M240-280h280v-80H240v80Zm400 0h80v-400h-80v400ZM240-440h280v-80H240v80Zm0-160h280v-80H240v80Zm-80 480q-33 0-56.5-23.5T80-200v-560q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v560q0 33-23.5 56.5T800-120H160Zm0-80h640v-560H160v560Zm0 0v-560 560Z"/></svg>

After

Width:  |  Height:  |  Size: 373 B

1
src/assets/feedback.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M480-360q17 0 28.5-11.5T520-400q0-17-11.5-28.5T480-440q-17 0-28.5 11.5T440-400q0 17 11.5 28.5T480-360Zm-40-160h80v-240h-80v240ZM80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z"/></svg>

After

Width:  |  Height:  |  Size: 399 B

1
src/assets/file.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="30px" viewBox="0 -960 960 960" width="30px" fill="#e8eaed"><path d="M319.33-246.67h321.34v-66.66H319.33v66.66Zm0-166.66h321.34V-480H319.33v66.67ZM226.67-80q-27 0-46.84-19.83Q160-119.67 160-146.67v-666.66q0-27 19.83-46.84Q199.67-880 226.67-880H574l226 226v507.33q0 27-19.83 46.84Q760.33-80 733.33-80H226.67Zm314-542.67v-190.66h-314v666.66h506.66v-476H540.67Zm-314-190.66v190.66-190.66 666.66-666.66Z"/></svg>

After

Width:  |  Height:  |  Size: 455 B

View File

@@ -0,0 +1,93 @@
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,71 @@
Noto Sans SC Variable Font
==========================
This download contains Noto Sans SC as both a variable font and static fonts.
Noto Sans SC is a variable font with this axis:
wght
This means all the styles are contained in a single file:
NotoSansSC-VariableFont_wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that arent available as static fonts. Not all apps support variable fonts, and
in those cases you can use the static font files for Noto Sans SC:
static/NotoSansSC-Thin.ttf
static/NotoSansSC-ExtraLight.ttf
static/NotoSansSC-Light.ttf
static/NotoSansSC-Regular.ttf
static/NotoSansSC-Medium.ttf
static/NotoSansSC-SemiBold.ttf
static/NotoSansSC-Bold.ttf
static/NotoSansSC-ExtraBold.ttf
static/NotoSansSC-Black.ttf
Get started
-----------
1. Install the font files you want to use
2. Use your app's font picker to view the font family and all the
available styles
Learn more about variable fonts
-------------------------------
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
https://variablefonts.typenetwork.com
https://medium.com/variable-fonts
In desktop apps
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
Online
https://developers.google.com/fonts/docs/getting_started
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
Installing fonts
MacOS: https://support.apple.com/en-us/HT201749
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
Android Apps
https://developers.google.com/fonts/docs/android
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
License
-------
Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
You can use them in your products & projects print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
license for all details.

10
src/assets/fonts/font.css Normal file
View File

@@ -0,0 +1,10 @@
/*
* 这是NanCunChild于2024/09/28添加的字体选择器
* 如果想添加别的字体就直接加一段font-family和src
* 我好想直接用google font啊受不了了
* 哼,哼,哼,哼啊啊啊~~哼啊啊啊啊啊啊!!!!
*/
@font-face {
font-family: 'NotoSansSC-Regular';
src: url('./Noto_Sans_SC/static/NotoSansSC-Regular.ttf');
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M480-120 200-272v-240L40-600l440-240 440 240v320h-80v-276l-80 44v240L480-120Zm0-332 274-148-274-148-274 148 274 148Zm0 241 200-108v-151L480-360 280-470v151l200 108Zm0-241Zm0 90Zm0 0Z"/></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h480q33 0 56.5 23.5T800-800v640q0 33-23.5 56.5T720-80H240Zm0-80h480v-640h-80v280l-100-60-100 60v-280H240v640Zm0 0v-640 640Zm200-360 100-60 100 60-100-60-100 60Z"/></svg>

After

Width:  |  Height:  |  Size: 344 B

1
src/assets/like.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M720-120H280v-520l280-280 50 50q7 7 11.5 19t4.5 23v14l-44 174h258q32 0 56 24t24 56v80q0 7-2 15t-4 15L794-168q-9 20-30 34t-44 14Zm-360-80h360l120-280v-80H480l54-220-174 174v406Zm0-406v406-406Zm-80-34v80H160v360h120v80H80v-520h200Z"/></svg>

After

Width:  |  Height:  |  Size: 354 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M472-120q-73-1-137.5-13.5t-112-34Q175-189 147.5-218T120-280q0 33 27.5 62t75 50.5q47.5 21.5 112 34T472-120Zm-71-204q-30-3-58-8t-53.5-12q-25.5-7-48-15.5T200-379q19 11 41.5 19.5t48 15.5q25.5 7 53.5 12t58 8Zm79-275q86 0 177.5-26T760-679q-11-29-100.5-55T480-760q-91 0-178.5 25.5T200-679q15 29 104.5 54.5T480-599Zm-61 396q10 23 23 44t30 39q-73-1-137.5-13.5t-112-34Q175-189 147.5-218T120-280v-400q0-33 28.5-62t77.5-51q49-22 114.5-34.5T480-840q74 0 139.5 12.5T734-793q49 22 77.5 51t28.5 62q0 33-28.5 62T734-567q-49 22-114.5 34.5T480-520q-85 0-157-15t-123-44v101q40 37 100 54t121 22q-8 15-13 34.5t-7 43.5q-60-7-111.5-20T200-379v99q14 25 77 47t142 30ZM864-40 756-148q-22 13-46 20.5t-50 7.5q-75 0-127.5-52.5T480-300q0-75 52.5-127.5T660-480q75 0 127.5 52.5T840-300q0 26-7.5 50T812-204L920-96l-56 56ZM660-200q42 0 71-29t29-71q0-42-29-71t-71-29q-42 0-71 29t-29 71q0 42 29 71t71 29Z"/></svg>

After

Width:  |  Height:  |  Size: 992 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#2854C5"><path d="M472-120q-73-1-137.5-13.5t-112-34Q175-189 147.5-218T120-280q0 33 27.5 62t75 50.5q47.5 21.5 112 34T472-120Zm-71-204q-30-3-58-8t-53.5-12q-25.5-7-48-15.5T200-379q19 11 41.5 19.5t48 15.5q25.5 7 53.5 12t58 8Zm79-275q86 0 177.5-26T760-679q-11-29-100.5-55T480-760q-91 0-178.5 25.5T200-679q15 29 104.5 54.5T480-599Zm-61 396q10 23 23 44t30 39q-73-1-137.5-13.5t-112-34Q175-189 147.5-218T120-280v-400q0-33 28.5-62t77.5-51q49-22 114.5-34.5T480-840q74 0 139.5 12.5T734-793q49 22 77.5 51t28.5 62q0 33-28.5 62T734-567q-49 22-114.5 34.5T480-520q-85 0-157-15t-123-44v101q40 37 100 54t121 22q-8 15-13 34.5t-7 43.5q-60-7-111.5-20T200-379v99q14 25 77 47t142 30ZM864-40 756-148q-22 13-46 20.5t-50 7.5q-75 0-127.5-52.5T480-300q0-75 52.5-127.5T660-480q75 0 127.5 52.5T840-300q0 26-7.5 50T812-204L920-96l-56 56ZM660-200q42 0 71-29t29-71q0-42-29-71t-71-29q-42 0-71 29t-29 71q0 42 29 71t71 29Z"/></svg>

After

Width:  |  Height:  |  Size: 992 B

48
src/assets/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 317 KiB

1
src/assets/message.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z"/></svg>

After

Width:  |  Height:  |  Size: 272 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M360-360v-240h240v240H360Zm80-80h80v-80h-80v80Zm-80 320v-80h-80q-33 0-56.5-23.5T200-280v-80h-80v-80h80v-80h-80v-80h80v-80q0-33 23.5-56.5T280-760h80v-80h80v80h80v-80h80v80h80q33 0 56.5 23.5T760-680v80h80v80h-80v80h80v80h-80v80q0 33-23.5 56.5T680-200h-80v80h-80v-80h-80v80h-80Zm320-160v-400H280v400h400ZM480-480Z"/></svg>

After

Width:  |  Height:  |  Size: 435 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-155.5t86-127Q252-817 325-848.5T480-880q83 0 155.5 31.5t127 86q54.5 54.5 86 127T880-480q0 82-31.5 155t-86 127.5q-54.5 54.5-127 86T480-80Zm0-82q26-36 45-75t31-83H404q12 44 31 83t45 75Zm-104-16q-18-33-31.5-68.5T322-320H204q29 50 72.5 87t99.5 55Zm208 0q56-18 99.5-55t72.5-87H638q-9 38-22.5 73.5T584-178ZM170-400h136q-3-20-4.5-39.5T300-480q0-21 1.5-40.5T306-560H170q-5 20-7.5 39.5T160-480q0 21 2.5 40.5T170-400Zm216 0h188q3-20 4.5-39.5T580-480q0-21-1.5-40.5T574-560H386q-3 20-4.5 39.5T380-480q0 21 1.5 40.5T386-400Zm268 0h136q5-20 7.5-39.5T800-480q0-21-2.5-40.5T790-560H654q3 20 4.5 39.5T660-480q0 21-1.5 40.5T654-400Zm-16-240h118q-29-50-72.5-87T584-782q18 33 31.5 68.5T638-640Zm-234 0h152q-12-44-31-83t-45-75q-26 36-45 75t-31 83Zm-200 0h118q9-38 22.5-73.5T376-782q-56 18-99.5 55T204-640Z"/></svg>

After

Width:  |  Height:  |  Size: 976 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#2854C5"><path d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-155.5t86-127Q252-817 325-848.5T480-880q83 0 155.5 31.5t127 86q54.5 54.5 86 127T880-480q0 82-31.5 155t-86 127.5q-54.5 54.5-127 86T480-80Zm0-82q26-36 45-75t31-83H404q12 44 31 83t45 75Zm-104-16q-18-33-31.5-68.5T322-320H204q29 50 72.5 87t99.5 55Zm208 0q56-18 99.5-55t72.5-87H638q-9 38-22.5 73.5T584-178ZM170-400h136q-3-20-4.5-39.5T300-480q0-21 1.5-40.5T306-560H170q-5 20-7.5 39.5T160-480q0 21 2.5 40.5T170-400Zm216 0h188q3-20 4.5-39.5T580-480q0-21-1.5-40.5T574-560H386q-3 20-4.5 39.5T380-480q0 21 1.5 40.5T386-400Zm268 0h136q5-20 7.5-39.5T800-480q0-21-2.5-40.5T790-560H654q3 20 4.5 39.5T660-480q0 21-1.5 40.5T654-400Zm-16-240h118q-29-50-72.5-87T584-782q18 33 31.5 68.5T638-640Zm-234 0h152q-12-44-31-83t-45-75q-26 36-45 75t-31 83Zm-200 0h118q9-38 22.5-73.5T376-782q-56 18-99.5 55T204-640Z"/></svg>

After

Width:  |  Height:  |  Size: 976 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M120-120v-560h160v-160h400v320h160v400H520v-160h-80v160H120Zm80-80h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm160 160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm160 320h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm160 480h80v-80h-80v80Zm0-160h80v-80h-80v80Z"/></svg>

After

Width:  |  Height:  |  Size: 422 B

1
src/assets/paper.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M280-280h280v-80H280v80Zm0-160h400v-80H280v80Zm0-160h400v-80H280v80Zm-80 480q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"/></svg>

After

Width:  |  Height:  |  Size: 350 B

1
src/assets/run_once.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M520-40v-240l-84-80-40 176-276-56 16-80 192 40 64-324-72 28v136h-80v-188l158-68q35-15 51.5-19.5T480-720q21 0 39 11t29 29l40 64q26 42 70.5 69T760-520v80q-66 0-123.5-27.5T540-540l-24 120 84 80v300h-80Zm20-700q-33 0-56.5-23.5T460-820q0-33 23.5-56.5T540-900q33 0 56.5 23.5T620-820q0 33-23.5 56.5T540-740Z"/></svg>

After

Width:  |  Height:  |  Size: 425 B

1
src/assets/send.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M120-160v-640l760 320-760 320Zm80-120 474-200-474-200v140l240 60-240 60v140Zm0 0v-400 400Z"/></svg>

After

Width:  |  Height:  |  Size: 215 B

1
src/assets/system.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727530155932" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7100" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M665.736533 236.407467c0-18.8416-15.223467-34.065067-33.928533-34.065067H223.8464c-18.705067 0-33.928533 15.223467-33.928533 34.065067 0 18.8416 15.223467 34.065067 33.928533 34.065066h407.9616c18.705067-0.068267 33.928533-15.223467 33.928533-34.065066z m0 204.253866c0-18.8416-15.223467-34.065067-33.928533-34.065066H223.8464c-18.705067 0-33.928533 15.223467-33.928533 34.065066 0 18.8416 15.223467 34.065067 33.928533 34.065067h407.9616c18.705067 0 33.928533-15.223467 33.928533-34.065067zM223.8464 610.9184c-18.705067 0-33.928533 15.223467-33.928533 34.065067 0 18.8416 15.223467 34.065067 33.928533 34.065066h203.9808c18.705067 0 33.928533-15.223467 33.928533-34.065066 0-18.8416-15.223467-34.065067-33.928533-34.065067H223.8464z m0 0" p-id="7101"></path><path d="M529.8176 883.3024c0-18.8416-15.223467-34.065067-33.928533-34.065067H153.736533c-18.705067 0-33.928533-15.223467-33.928533-34.065066V134.212267c0-18.8416 15.223467-34.065067 33.928533-34.065067h546.474667c18.705067 0 33.928533 15.223467 33.928533 34.065067v306.449066c0 18.8416 15.223467 34.065067 33.928534 34.065067 18.705067 0 33.928533-15.223467 33.928533-34.065067V100.215467c0-37.546667-30.446933-68.130133-67.857067-68.130134h-614.4C82.3296 32.085333 51.882667 62.600533 51.882667 100.215467V849.237333c0 37.546667 30.446933 68.130133 67.857066 68.130134h376.149334c18.8416 0 33.928533-15.223467 33.928533-34.065067z m0 0" p-id="7102"></path><path d="M970.1376 916.2752h-32.9728v-135.168c0-37.546667-30.5152-68.130133-68.130133-68.130133h-68.130134V610.9184h68.130134c18.8416 0 34.065067-15.223467 34.065066-34.065067 0-18.8416-15.223467-34.065067-34.065066-34.065066H666.897067c-18.8416 0-34.065067 15.223467-34.065067 34.065066 0 18.8416 15.223467 34.065067 34.065067 34.065067h65.9456v102.126933h-68.130134c-37.546667 0-68.130133 30.5152-68.130133 68.130134v135.168H562.517333c-18.8416 0-34.065067 15.223467-34.065066 34.065066 0 18.8416 15.223467 34.065067 34.065066 34.065067h407.552c18.8416 0 34.065067-15.223467 34.065067-34.065067 0.068267-18.909867-15.1552-34.133333-33.9968-34.133333z m-305.3568-135.099733H869.034667v135.168H664.7808v-135.168z m0 0" p-id="7103"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h440l200 200v440q0 33-23.5 56.5T760-120H200Zm0-80h560v-400H600v-160H200v560Zm80-80h400v-80H280v80Zm0-320h200v-80H280v80Zm0 160h400v-80H280v80Zm-80-320v160-160 560-560Z"/></svg>

After

Width:  |  Height:  |  Size: 352 B

1
src/assets/task.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="m438-240 226-226-58-58-169 169-84-84-57 57 142 142ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z"/></svg>

After

Width:  |  Height:  |  Size: 338 B

1
src/assets/think.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M390-120q-51 0-88-35.5T260-241q-60-8-100-53t-40-106q0-21 5.5-41.5T142-480q-11-18-16.5-38t-5.5-42q0-61 40-105.5t99-52.5q3-51 41-86.5t90-35.5q26 0 48.5 10t41.5 27q18-17 41-27t49-10q52 0 89.5 35t40.5 86q59 8 99.5 53T840-560q0 22-5.5 42T818-480q11 18 16.5 38.5T840-400q0 62-40.5 106.5T699-241q-5 50-41.5 85.5T570-120q-25 0-48.5-9.5T480-156q-19 17-42 26.5t-48 9.5Zm130-590v460q0 21 14.5 35.5T570-200q20 0 34.5-16t15.5-36q-21-8-38.5-21.5T550-306q-10-14-7.5-30t16.5-26q14-10 30-7.5t26 16.5q11 16 28 24.5t37 8.5q33 0 56.5-23.5T760-400q0-5-.5-10t-2.5-10q-17 10-36.5 15t-40.5 5q-17 0-28.5-11.5T640-440q0-17 11.5-28.5T680-480q33 0 56.5-23.5T760-560q0-33-23.5-56T680-640q-11 18-28.5 31.5T613-587q-16 6-31-1t-20-23q-5-16 1.5-31t22.5-20q15-5 24.5-18t9.5-30q0-21-14.5-35.5T570-760q-21 0-35.5 14.5T520-710Zm-80 460v-460q0-21-14.5-35.5T390-760q-21 0-35.5 14.5T340-710q0 16 9 29.5t24 18.5q16 5 23 20t2 31q-6 16-21 23t-31 1q-21-8-38.5-21.5T279-640q-32 1-55.5 24.5T200-560q0 33 23.5 56.5T280-480q17 0 28.5 11.5T320-440q0 17-11.5 28.5T280-400q-21 0-40.5-5T203-420q-2 5-2.5 10t-.5 10q0 33 23.5 56.5T280-320q20 0 37-8.5t28-24.5q10-14 26-16.5t30 7.5q14 10 16.5 26t-7.5 30q-14 19-32 33t-39 22q1 20 16 35.5t35 15.5q21 0 35.5-14.5T440-250Zm40-230Z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#2854C5"><path d="M390-120q-51 0-88-35.5T260-241q-60-8-100-53t-40-106q0-21 5.5-41.5T142-480q-11-18-16.5-38t-5.5-42q0-61 40-105.5t99-52.5q3-51 41-86.5t90-35.5q26 0 48.5 10t41.5 27q18-17 41-27t49-10q52 0 89.5 35t40.5 86q59 8 99.5 53T840-560q0 22-5.5 42T818-480q11 18 16.5 38.5T840-400q0 62-40.5 106.5T699-241q-5 50-41.5 85.5T570-120q-25 0-48.5-9.5T480-156q-19 17-42 26.5t-48 9.5Zm130-590v460q0 21 14.5 35.5T570-200q20 0 34.5-16t15.5-36q-21-8-38.5-21.5T550-306q-10-14-7.5-30t16.5-26q14-10 30-7.5t26 16.5q11 16 28 24.5t37 8.5q33 0 56.5-23.5T760-400q0-5-.5-10t-2.5-10q-17 10-36.5 15t-40.5 5q-17 0-28.5-11.5T640-440q0-17 11.5-28.5T680-480q33 0 56.5-23.5T760-560q0-33-23.5-56T680-640q-11 18-28.5 31.5T613-587q-16 6-31-1t-20-23q-5-16 1.5-31t22.5-20q15-5 24.5-18t9.5-30q0-21-14.5-35.5T570-760q-21 0-35.5 14.5T520-710Zm-80 460v-460q0-21-14.5-35.5T390-760q-21 0-35.5 14.5T340-710q0 16 9 29.5t24 18.5q16 5 23 20t2 31q-6 16-21 23t-31 1q-21-8-38.5-21.5T279-640q-32 1-55.5 24.5T200-560q0 33 23.5 56.5T280-480q17 0 28.5 11.5T320-440q0 17-11.5 28.5T280-400q-21 0-40.5-5T203-420q-2 5-2.5 10t-.5 10q0 33 23.5 56.5T280-320q20 0 37-8.5t28-24.5q10-14 26-16.5t30 7.5q14 10 16.5 26t-7.5 30q-14 19-32 33t-39 22q1 20 16 35.5t35 15.5q21 0 35.5-14.5T440-250Zm40-230Z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
src/assets/unlogin.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727487551888" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4933" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M511.999817 511.999817m-511.999817 0a511.999817 511.999817 0 1 0 1023.999634 0 511.999817 511.999817 0 1 0-1023.999634 0Z" fill="#F1F1F1" p-id="4934"></path><path d="M511.999817 716.799744c140.79995 0 264.959905 71.021689 338.687879 179.199936A510.098104 510.098104 0 0 1 511.999817 1023.999634a510.061532 510.061532 0 0 1-338.687879-128.036525A409.23414 409.23414 0 0 1 511.999817 716.799744z" fill="#CCCCCC" p-id="4935"></path><path d="M307.19989 422.399849a217.599922 204.799927 90 1 0 409.599854 0 217.599922 204.799927 90 1 0-409.599854 0Z" fill="#CCCCCC" p-id="4936"></path></svg>

After

Width:  |  Height:  |  Size: 919 B

1
src/assets/upload.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M440-320v-326L336-542l-56-58 200-200 200 200-56 58-104-104v326h-80ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/></svg>

After

Width:  |  Height:  |  Size: 280 B

1
src/assets/user.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#424242"><path d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm246-164q-59 0-99.5-40.5T340-580q0-59 40.5-99.5T480-720q59 0 99.5 40.5T620-580q0 59-40.5 99.5T480-440Zm0 360q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q53 0 100-15.5t86-44.5q-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160Zm0-360q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm0-60Zm0 360Z"/></svg>

After

Width:  |  Height:  |  Size: 751 B

97
src/components/Navbar.vue Normal file
View File

@@ -0,0 +1,97 @@
<template>
<div id="navbar-out">
<div class="navbar-left">
<el-avatar shape="square" :src="logo" />
<span id="navbar-text"><span style="color: #b8001f;">慧通</span>大模型</span>
</div>
<div class="navbar-right">
<!-- 右侧内容 -->
<div class="navbar-ava">
<el-avatar :src="unlogin" :size="30" />
<div class="navbar-user">
<el-dropdown placement="bottom-end">
<span class="name">
{{ userName }}
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="userName !== '未登录'" @click="logout">退出登录</el-dropdown-item>
<el-dropdown-item v-if="userName === '未登录'" @click="jump2Ids">点击登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { jump2Ids, logout } from '../js/util'
import logo from '../assets/logo.svg'
import unlogin from '../assets/unlogin.svg'
const userName = ref("")
console.log(localStorage.getItem("username"))
userName.value = localStorage.getItem("username") !== null ? localStorage.getItem("username") : "未登录";
</script>
<style scoped>
#navbar-out {
height: 48px;
background-color: #f5f5f5;
padding: 10px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.navbar-left,
.navbar-right {
display: flex;
align-items: center;
background-color: transparent;
}
#navbar-text {
font-size: 1.2em;
font-weight: bolder;
height: 40px;
line-height: 40px;
text-align: center;
color: #424242;
margin-left: 20px;
background-color: transparent;
cursor: pointer;
-webkit-user-select: none;
/* Safari */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* IE10+/Edge */
user-select: none;
/* Standard */
}
.navbar-user {
font-size: small;
}
.navbar-ava {
display: flex;
align-items: center;
gap: 10px;
}
.features {
display: flex;
align-items: center;
gap: 10px;
margin-left: 20px;
font-size: 1.2em;
font-weight: bolder;
color: #424242;
}
</style>

117
src/components/Text.vue Normal file
View File

@@ -0,0 +1,117 @@
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, onUpdated, ref } from 'vue'
import MarkdownIt from 'markdown-it'
import mdKatex from '@traptitech/markdown-it-katex'
import mila from 'markdown-it-link-attributes'
import hljs from 'highlight.js'
import 'katex/dist/katex.min.css'
import { copyToClip } from "../js/copy"
interface Props {
text?: string
}
const props = defineProps<Props>()
const textRef = ref<HTMLElement>()
const mdi = new MarkdownIt({
html: false,
linkify: true,
highlight(code, language) {
const validLang = !!(language && hljs.getLanguage(language))
if (validLang) {
const lang = language ?? ''
return highlightBlock(hljs.highlight(code, { language: lang }).value, lang)
}
return highlightBlock(hljs.highlightAuto(code).value, '')
},
})
mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } })
mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' })
const wrapClass = computed(() => {
return [
'text-wrap',
'min-w-[20px]', 'max-w-[810px]',
'rounded-md',
'px-3 py-2',
]
})
const text = computed(() => {
let value = props.text ?? ''
console.log(value)
value = value.replace(/\$\$/g, `\n$$$$`)
value = value.replace(/\\\(/g, '$$')
value = value.replace(/\\\)/g, '$$')
value = value.replace(/\\\[/g, '\n$$$$')
value = value.replace(/\\\]/g, '\n$$$$')
console.log(value)
return mdi.render(value)
return value
})
function highlightBlock(str: string, lang?: string) {
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">复制代码</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`
}
function addCopyEvents() {
if (textRef.value) {
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy')
copyBtn.forEach((btn) => {
btn.addEventListener('click', () => {
const code = btn.parentElement?.nextElementSibling?.textContent
if (code) {
copyToClip(code).then(() => {
btn.textContent = '复制成功'
setTimeout(() => {
btn.textContent = '复制代码'
}, 1000)
})
}
})
})
}
}
function removeCopyEvents() {
if (textRef.value) {
const copyBtn = textRef.value.querySelectorAll('.code-block-header__copy')
copyBtn.forEach((btn) => {
btn.removeEventListener('click', () => { })
})
}
}
onMounted(() => {
addCopyEvents()
})
onUpdated(() => {
addCopyEvents()
})
onUnmounted(() => {
removeCopyEvents()
})
</script>
<template>
<div class="text-black" :class="wrapClass">
<div ref="textRef" class="leading-relaxed break-words">
<div class="markdown-body " style="--color-fg-default:#24292f" v-html="text" />
</div>
</div>
</template>
<style lang="less">
@import url(/src/style/style.less);
@import url(/src/style/highlight.less);
@import url(/src/style/github-markdown.less);
</style>

View File

@@ -0,0 +1,78 @@
<template>
<div class="card" :class="{ 'ac': isac }">
<img :src="message" height="18px">
<span class="text">
{{ text }}
</span>
<img :src="deleteIcon" height="18px" :class="{ 'hide': !isac }" @click.stop="deleteMessage" />
</div>
</template>
<script setup>
import { defineProps } from 'vue';
import { defineEmits } from 'vue';
import message from '../assets/message.svg'
import deleteIcon from '../assets/delete.svg'
const emit = defineEmits(['delete-message']);
const deleteMessage = () => {
emit('delete-message', props.idx);
};
const props = defineProps({
text: {
type: String,
required: true
},
isac: {
type: Boolean,
required: true,
},
idx: {
type: Number,
required: true
}
})
</script>
<style scoped>
.card {
border-radius: 5px;
border: 1px rgba(128, 128, 128, 0.3) solid;
margin: 10px;
text-align: left;
font-size: 16px;
padding: 10px;
display: flex;
align-items: center;
transition: all 0.3ms;
}
.text {
margin-left: 10px;
width: 140px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: 'NotoSansSC-Regular';
}
.ac {
background-color: rgba(128, 128, 128, 0.3) !important;
transition: all 0.3ms;
}
.card:hover {
background-color: rgba(128, 128, 128, 0.1);
}
.hide {
display: none;
}
* {
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,422 @@
<template>
<div class="fileBox" v-if="session.file !== ''">
<div class="fileicon">
<img :src="fileIcon">
</div>
<div class="desc">
{{ getSubstringAfterFirstUnderscore(session.file) }}
<br>
<span class="filetype">
PDF
</span>
</div>
</div>
<input type="file" ref="fileInput" accept=".pdf" style="display: none;" @change="handleFileChange">
<div class="chatInputComponent">
<div>
<button v-show="session.type.endsWith('paper')" class="upload" @click="uploadFile">
<img :src="uploadIcon" alt="上传" />
</button>
</div>
<div v-show="!session.type.endsWith('paper')">
<el-tooltip class="box-item" effect="dark" content="深度思考" placement="top">
<div class="thinkicon no-select" @click="handleThink">
<img :src="thinkIcon" alt="思考" style="fill:#424242">
</div>
</el-tooltip>
</div>
<div v-show="!session.type.endsWith('paper')">
<el-tooltip class="box-item" effect="dark" content="本地知识库检索" placement="top"
v-show="!session.type.endsWith('paper')">
<div class="lricon no-select" @click="handlelr">
<img :src="localRrtrievalIcon" alt="本机检索" style="fill:#424242">
</div>
</el-tooltip>
</div>
<div v-show="!session.type.endsWith('paper')">
<el-tooltip class="box-item" effect="dark" content="搜索模式" placement="top"
v-show="!session.type.endsWith('paper')">
<div class="oricon no-select" @click="handleor">
<img :src="onlineRrtrievalIcon" alt="网络检索" style="fill:#424242">
</div>
</el-tooltip>
</div>
<div class="chatInputContainer">
<textarea name="chatinput" class="chatInput" :style="{ height: textareaHeight }" ref="chatInputArea"
@input="onInput" @keydown="handleKeydown" :placeholder="props.session.type === '' ? '请选择类别' : placeholders[props.session.type] || '问点什么'"
rows="1"></textarea>
</div>
<button class="send" @click="onSend" :disabled="isInputOccupied || props.session.type === ''">
<img :src="sendIcon" alt="发送" />
</button>
</div>
</template>
<script setup>
import { defineEmits, ref, nextTick } from 'vue'
import { scrollToBottom, scrollToBottomWithAnimation } from "../js/util.js";
import $, { data } from 'jquery'
import { useStore } from 'vuex';
const store = useStore();
import { computed, watch } from 'vue';
const isInputOccupied = computed(() => store.state.isInputOccupied);
import { defineProps } from 'vue';
import { Session } from "../js/session.js";
import { uploadFileFunc } from '../js/api';
import fileIcon from '../assets/file.svg'
import uploadIcon from '../assets/upload.svg'
import sendIcon from '../assets/send.svg'
import thinkIcon from '../assets/think.svg'
import thinkActivateIcon from '../assets/thinkactivate.svg'
import localRrtrievalIcon from '../assets/local_retrieval.svg'
import onlineRrtrievalIcon from '../assets/online_retrieval.svg'
import localRetrievalAc from '../assets/local_retrieval_ac.svg'
import onlineRetrievalAc from '../assets/online_retrieval_ac.svg'
const props = defineProps({
session: {
type: Session,
required: true
}
})
const toast = (message, type) => {
ElMessage({
message: message,
type,
plain: true,
duration: 1000
})
}
const placeholders = {
generate_plan_report: `请输入报告或者方案名称并给出一些内容要点。例如西安电子科技大学关于深入学习贯彻习近平总书记在中国人民大学考察时重要讲话精神的实施方案。1. 传承红色基因扎根中国大地办大学走出一条建设中国特色、世界一流大学的新路2.思政课的本质是讲道理,要注重方式方法,把道理讲深、讲透、讲活,老师要用心教,学生要用心悟,达到沟通心灵、启智润心、激扬斗志。`,
generate_speech: `请输入致辞主题并给出一些内容要点。例如以信息化带动学校治理体系与治理能力现代化1.建设高质量教育体系2.以人事制度为牵引的两体制两机制综合改革`
}
const chatInputArea = ref(null)
const textareaHeight = ref("auto")
const fileInput = ref(null)
const think = ref(false);
const lr = ref(false);
const or = ref(false);
const id = computed(() => props.session.id)
function uploadFile() {
fileInput.value.click();
}
function handleThink(e, n = false) {
if (think.value || n) {
think.value = false;
$('.thinkicon img').attr("src", thinkIcon);
$('.thinkicon').css('background-color', 'transparent')
props.session.r1_model = false;
return;
}
think.value = true;
props.session.r1_model = true;
$('.thinkicon img').attr("src", thinkActivateIcon);
$('.thinkicon').css('background-color', '#00b7ff54')
}
function handlelr(e, n = false) {
if (lr.value || n) {
lr.value = false;
$('.lricon img').attr("src", localRrtrievalIcon);
$('.lricon').css('background-color', 'transparent')
props.session.local_retrieval = false;
return;
}
lr.value = true;
props.session.local_retrieval = true;
$('.lricon img').attr("src", localRetrievalAc);
$('.lricon').css('background-color', '#00b7ff54')
}
function handleor(e, n = false) {
if (or.value || n) {
or.value = false;
$('.oricon').css('background-color', 'transparent')
$('.oricon img').attr("src", onlineRrtrievalIcon);
props.session.online_retrieval = false;
return;
}
or.value = true;
props.session.online_retrieval = true;
$('.oricon img').attr("src", onlineRetrievalAc);
$('.oricon').css('background-color', '#00b7ff54')
}
watch(id, () => {
handleThink("", true);
handlelr("", true);
handleor("", true);
})
const type = computed(() => props.session.type)
watch(type, () => {
if (type.value === "generate_plan_report" || type.value === "generate_speech" || type.value === 'hr_service') {
handlelr("")
}
})
function handleFileChange() {
const file = event.target.files[0];
if (file) {
if (file.type !== 'application/pdf') {
toast("上传文件必须为PDF", "error")
fileInput.value.value = ''
return;
}
if (file.size > (20 * 1024 * 1024)) {
toast("文件不可大于20MB", "error")
fileInput.value.value = ''
return;
}
toast("请稍等,文件正在上传", "message")
uploadFileFunc(file)
.then((res) => {
toast("文件上传成功", "success")
props.session.file = res.filename
})
.catch((e) => {
toast("文件上传失败", "error")
console.log(e)
})
}
}
function onInput() {
textareaHeight.value = 'auto';
nextTick(() => {
const scrollHeight = chatInputArea.value.scrollHeight;
textareaHeight.value = `${scrollHeight - 16}px`;
});
}
function getSubstringAfterFirstUnderscore(str) {
const underscoreIndex = str.indexOf('_');
if (underscoreIndex === -1) {
// If there is no underscore in the string, return an empty string or handle accordingly
return '';
}
return str.slice(underscoreIndex + 1);
}
const emit = defineEmits(['send-message']);
function onSend() {
if (isInputOccupied.value || props.session.type === '') return;
const input = $('.chatInput')
if (input.val() === "") {
return;
}
emit('send-message', input.val());
input[0].value = "";
store.dispatch('setInputOccupied', true);
setTimeout(() => {
scrollToBottomWithAnimation('message-list')
}, 0)
onInput();
}
function handleKeydown(event) {
if (event.shiftKey && event.key === 'Enter') {
//这里需要记得阻止不然会有两次换行。同时让onInput处理因为本质上用户输入被截断了
$('.chatInput')[0].value += '\n';
onInput();
event.preventDefault();
} else if (event.key === 'Enter') {
onSend();
event.preventDefault(); // 阻止默认行为(例如换行)
}
}
</script>
<style Lang="sass" scoped>
.no-select {
user-select: none;
/* Standard syntax */
-webkit-user-select: none;
/* Safari */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* Internet Explorer/Edge */
cursor: pointer;
}
.chatInputComponent {
display: flex;
align-items: center;
bottom: 28px;
min-height: 42px;
max-height: 180px;
border-radius: 32px;
background-color: #F6F6F6;
width: calc(100% - 20px);
border: 1px gray solids;
padding: 10px;
display: flex
}
.chatInputContainer {
display: flex;
width: 100%;
height: auto;
max-height: 25dvh;
}
.chatInput {
display: flex;
flex-grow: 1;
padding: 0排序10px 8px 20px;
resize: none;
width: 95%;
min-height: 32px;
height: 32px;
max-height: 140px;
font-size: 48px;
border: 0px;
background-color: transparent;
overflow: auto;
font-family: 'NotoSansSC-Regular';
font-size: 18px;
color: #141414;
scrollbar-width: none;
outline: none;
}
.chatInput::-webkit-scrollbar {
scrollbar-width: none;
}
textarea::-webkit-scrollbar {
width: 1em;
}
.send {
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
width: 48px;
height: 48px;
border: 0;
background-color: #117554;
color: whi;
border-radius: 25px;
font-weight: bolder;
color: white;
rotate: 2;
transition: 0.5s;
}
.send:disabled {
background-color: #666666;
cursor: not-allowed;
}
.send:hover {
background-color: #054b33;
scale: 1.05;
transition: 0.5s;
}
.upload {
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
width: 40px;
height: 40px;
border: 0;
background-color: transparent;
border-radius: 25px;
font-weight: bolder;
color: white;
rotate: 2;
transition: 0.5s;
}
.upload:hover {
background-color: #dfdfdf;
cursor: pointer;
}
.fileBox {
display: flex;
padding: 5px;
background-color: rgb(246, 246, 246);
margin-bottom: 5px;
border-radius: 5px;
width: 200px;
align-items: center;
height: 45px;
justify-content: space-between;
}
.fileicon {
background-color: #cb3b3b;
border-radius: 5px;
height: 35px;
margin-left: 5px;
padding-top: 2px;
}
.thinkicon,
.lricon,
.oricon {
border-radius: 25px;
height: 40px;
width: 30px;
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.5s ease;
/* 过渡效果 */
margin-left: 2px;
}
.thinkicon:hover,
.lricon:hover,
.oricon:hover {
background-color: #dfdfdf !important;
cursor: pointer;
}
.thinkicon-ac,
.lricon-ac,
.oricon-ac {
border-radius: 25px;
height: 40px;
width: 40px;
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: #00b7ff54;
}
.desc {
flex-grow: 1;
padding-left: 10px;
overflow: hidden;
text-overflow: ellipsis;
font-size: small;
white-space: nowrap;
}
.filetype {
font-weight: bolder;
color: #989898;
}
</style>

View File

@@ -0,0 +1,163 @@
<template>
<aside class="chatLogSideBarContainer">
<div class="chatLogSideBar">
<div class="collapseArrowArea">
<div class="collapseArrowBar">
<div class="collapseArrowContainer">
<img class="collapseArrowImg" alt="Collapse"
src="../assets/chatLogSidebar/double_arrow_lm.svg" />
</div>
</div>
</div>
<div class="chatLogArea" id="chatLogArea">
<div class="chatLogBar" v-for="(log, index) in chatLogs" :key="index">
<div class="chatLogItemImgContainer">
<img class="chatLogItemImg" src="../assets/chatLogSidebar/chat_bubble_lm.svg" />
</div>
<div class="chatLogItemText">{{ log.chatLogTitle }}</div>
</div>
</div>
<div class="extraSettingsArea">
</div>
</div>
</aside>
</template>
<script setup>
import { ref } from 'vue';
// 定义一个聊天记录数组
const chatLogs = ref([
{ chatLogTitle: 'New' },
{ chatLogTitle: 'Looooooooooooooooooooooooooooooooog' },
{ chatLogTitle: 'Chat 3' },
{ chatLogTitle: 'OHHHHHHHH' }
]);
</script>
<style scoped>
.chatLogSideBarContainer {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
left: 5px;
top: 5px;
width: 280px;
height: 100%;
background-color: #e0e0e0;
}
.chatLogSideBar {
display: flex;
align-items: center;
flex-direction: column;
width: 250px;
height: 880px;
margin: 2px;
}
.collapseArrowArea {
display: flex;
justify-content: center;
align-items: center;
width: 250px;
height: auto;
margin: 2px;
}
.chatLogArea {
display: flex;
flex-direction: column;
width: 240px;
height: 640px;
margin: 2px;
}
.extraSettingsArea {
display: flex;
position: absolute;
bottom: 10px;
width: 240px;
height: 150px;
margin: 2px;
}
.collapseArrowBar {
display: flex;
justify-content: right;
align-items: center;
height: 46px;
width: 235px;
margin: 2px;
border-radius: 24px;
transition: 0.6s;
}
.collapseArrowBar:hover {
background-color: #e0e0e0;
transition: 0.6s;
}
.collapseArrowContainer {
display: flex;
justify-content: center;
align-items: center;
height: 38px;
width: 38px;
margin-right: 5px;
}
.collapseArrowImg {
display: flex;
height: 32px;
width: 32px;
}
.chatLogBar {
display: flex;
justify-content: left;
align-items: center;
width: 235px;
height: 46px;
margin: 2px;
background-color: transparent;
border-radius: 100px;
}
.chatLogBar:hover {
background-color: #c5c5c5;
cursor: pointer;
}
.chatLogItemImgContainer {
display: flex;
justify-content: center;
align-items: center;
width: 32px;
height: 32px;
margin: 2px 8px 2px 12px;
}
.chatLogItemImg {
display: flex;
width: 21px;
height: 21px;
}
.chatLogItemText {
font-size: 15px;
max-width: 160px;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,226 @@
<template>
<div class="choose">
<div class="title no-select">
欢迎使用<br>慧通大模型
</div>
<div class="feature-box no-select">
<div class="feature">
<div class="teach tag active" @click="chooseKind(0, 'teach')">
</div>
<div class="learn tag" @click="chooseKind(1, 'learn')">
</div>
<div class="admin tag" @click="chooseKind(2, 'admin')">
</div>
<div class="review tag" @click="chooseKind(3, 'review')">
</div>
</div>
</div>
<div class="btns">
<div v-for="(item, index) in allKinds[option]" :key="index">
<div class="choose-card no-select" @click=choose(item.key)>
<div class="img">
<img :src="imageMap[item.key]" height="30px" draggable="false">
</div>
{{ item.desc }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue';
import { getKinds } from '../js/api';
import system_database from '../assets/system_database.svg'
import compute_database from '../assets/compute_database.svg'
import circuit_database from '../assets/circuit_database.svg'
import communication_database from '../assets/communication_database.svg'
import microelectronics_database from '../assets/microelectronics_database.svg'
import instruct_design from '../assets/instruct_design.svg'
import chat from '../assets/chat.svg'
import paper from '../assets/paper.svg'
import $ from 'jquery';
import task from '../assets/task.svg'
import graduate_paper from '../assets/graduate_paper.svg'
import doctor_paper from '../assets/doctor_paper.svg'
import origanzation from '../assets/organization.svg'
const imageMap = {
system_inquiry: system_database,
compute_qa: compute_database,
circuit_qa: circuit_database,
communication_qa: communication_database,
microelectronics_qa: microelectronics_database,
chat: chat,
paper: paper,
instruction_design_generation: instruct_design,
test_question_generation: task,
undergraduate_paper: paper,
academic_graduate_paper: graduate_paper,
engineer_graduate_paper: graduate_paper,
academic_doctor_paper: doctor_paper,
engineer_doctor_paper: doctor_paper,
organization_department: origanzation,
graduate_evaluation: graduate_paper,
generate_plan_report: doctor_paper,
generate_speech: doctor_paper,
hr_service: doctor_paper,
};
const emit = defineEmits(['choose-key']);
const allKinds = ref([])
const option = ref(0)
function chooseKind(idx, className) {
option.value = idx;
$('.tag').removeClass('active');
$(`.${className}`).addClass('active');
}
async function fetchKinds() {
try {
const res = await getKinds();
if (res === "err") {
console.log("获取类型失败的错误处理")
} else {
allKinds.value = res.data;
console.log(allKinds.value)
}
} catch (error) {
console.error('获取聊天记录失败:', error);
}
}
fetchKinds();
function choose(key) {
const sendDataToParent = () => {
emit('choose-key', key);
};
sendDataToParent();
}
</script>
<style scoped>
.choose {
overflow-y: auto;
max-height: 600px;
overflow-x: hidden;
}
.title {
margin-top: 80px;
text-align: center;
font-size: 30px;
font-weight: bolder;
color: rgb(155, 155, 155);
}
.choose-card {
width: 100px;
height: 140px;
margin: 20px;
text-align: center;
background-color: rgb(241, 240, 240);
border-radius: 5px;
box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
transition: transform 0.3s ease;
}
.choose-card:hover {
transform: scale(1.1);
}
.dark .choose-card {
background-color: #4f525a;
color: white;
}
.btns {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.no-select {
user-select: none;
/* Standard syntax */
-webkit-user-select: none;
/* Safari */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* Internet Explorer/Edge */
cursor: pointer;
}
.feature {
display: flex;
justify-content: space-between;
align-items: center;
border: 2px solid rgb(58, 139, 255);
border-radius: 50px;
width: 100%;
max-width: 400px;
box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px;
}
.feature-box {
margin-top: 10px;
width: 100%;
height: 40px;
display: flex;
justify-content: center;
}
.tag {
width: 25%;
height: 40px;
line-height: 40px;
text-align: center;
background-color: rgb(58, 139, 255, 0.5);
color: rgb(0, 106, 255);
font-weight: bolder;
font-size: 1.2em;
}
.tag:hover {
background-color: rgb(58, 139, 255, 0.7);
cursor: pointer;
}
.teach {
border-radius: 50px 0 0 50px;
border-right: 2px solid rgb(58, 139, 255);
}
.active {
background-color: rgb(58, 139, 255);
color: white;
}
.admin {
border-left: 2px solid rgb(58, 139, 255);
}
.review {
border-radius: 0 50px 50px 0;
border-left: 2px solid rgb(58, 139, 255);
}
</style>

View File

@@ -0,0 +1,230 @@
<template>
<div class="message" :class="{ 'message-user': role === 'user' }">
<div class="avatorContainer" :class="{ 'hide': role === 'user' }">
<img :src="logo" class="avator unselectable">
</div>
<div class="avatorContainer" :class="{ 'hide': role === 'assistant' }">
<img :src="user" class="avator unselectable">
</div>
<!-- <div class="text" :class="{ 'text-user': role === 'user', 'text-bot': role === 'assistant' }" @mouseover="showExtra[idx] = true" @mouseout="temp(); console.log('移除')">-->
<div class="text" :class="{ 'text-user': role === 'user', 'text-bot': role === 'assistant' }">
<Text :text="text" />
<!-- <div class="icons" v-if="role === 'assistant' && showExtra[idx]" @mouseover="showExtra[idx] = true; toast('111')">-->
<div class="icons" v-if="role === 'assistant'">
<button class="copy actions" @click="copyToClip(text.replace(regex, '')); toast('复制成功')"
title="复制">
<img :src="copy" class="avator unselectable icon" title="复制">
</button>
<button class="like actions" title="点赞" @click="like()">
<img :src="likeIcon" class="avator unselectable icon">
</button>
<button class="copy actions" title="点踩" @click="dislike">
<img :src="dislikeIcon" class="avator unselectable icon">
</button>
<button class="copy actions" title="反馈" @click="feedbackDialog = true">
<img :src="feedbackIcon" class="avator unselectable icon">
</button>
</div>
</div>
</div>
<el-dialog v-model="feedbackDialog" title="反馈" width="500" align-center>
<el-input v-model="feedbackMessage" maxlength="100" :rows="5" style="" placeholder="请输入反馈信息" show-word-limit
type="textarea" resize="none" />
<br>
<br>
<el-button type="primary" @click="feedback">提交反馈</el-button>
</el-dialog>
</template>
<script setup>
import { defineProps, ref, watch } from 'vue';
import Text from './Text.vue';
import { copyToClip } from "../js/copy.ts";
import { addFeedback } from "../js/api.js";
import logo from '../assets/logo.svg'
import user from '../assets/user.svg'
import copy from '../assets/copy.svg'
import likeIcon from '../assets/like.svg'
import dislikeIcon from '../assets/dislike.svg'
import feedbackIcon from '../assets/feedback.svg'
const feedbackDialog = ref(false)
const feedbackMessage = ref("")
const toast = (message) => {
ElMessage({
message: message,
type: 'success',
plain: true,
duration: 1000
})
}
const regex = /\n\n----------\n##### 当前对话次数: \d+\/\d+,对话次数过多可能会导致模型回复不准确\n$/g;
const props = defineProps({
text: {
type: String,
required: true
},
role: {
type: String,
required: true
},
idx: {
type: Number,
required: true
},
messages: {
type: Array,
required: true
}
})
function like() {
addFeedback(JSON.stringify(props.messages), "like", '')
.then((res) => { console.log(res); toast("点赞成功") })
.catch((err) => { console.log(err) })
}
function dislike() {
addFeedback(JSON.stringify(props.messages), "dislike", '')
.then((res) => { console.log(res); toast("反馈成功") })
.catch((err) => { console.log(err) })
}
function feedback() {
if (feedbackMessage.value === '') {
toast("反馈内容不能为空")
return;
}
addFeedback(JSON.stringify(props.messages), "feedback", feedbackMessage.value)
.then((res) => { console.log(res); toast("反馈成功") })
.catch((err) => { console.log(err) })
feedbackMessage.value = ""
feedbackDialog.value = false
}
</script>
<style scoped>
.message-user {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.hide {
display: none !important;
}
.text {
display: inline-block;
margin: 10px 10px 2px 10px;
padding: 20px 25px;
border-radius: 28px;
max-width: calc(100% - 60px);
overflow: auto;
}
div {
font-family: 'NotoSansSC-Regular';
}
.text-bot {
background-color: rgb(235, 235, 235);
}
.text-user {
margin-top: 5px;
background-color: rgb(210, 249, 209);
}
.dark .text-user {
background-color: rgb(25, 78, 45);
}
.dark .text-user * {
color: #bcc3ca;
}
.dark .text-bot {
background-color: #1e1e20;
}
.dark .text-bot * {
color: #bcc3ca;
}
.unselectable {
-webkit-user-select: none;
/* Safari */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* IE 10+ 和 Edge */
user-select: none;
/* 标准语法 */
pointer-events: none;
}
.avatorContainer {
display: flex;
justify-content: left;
align-items: flex-end;
width: auto;
height: 42px;
margin: 2px;
}
.avator {
display: flex;
width: 38px;
height: 38px;
border-radius: 5px
}
.message {
width: 100%;
}
.icons {
display: flex;
flex-direction: row;
margin-top: 15px;
margin-bottom: 2px;
z-index: 8;
}
.icon {
height: 16px;
width: 16px;
}
.actions {
display: flex;
justify-content: center;
align-items: center;
padding: 2px 5px;
transition: transform 0.3s ease;
cursor: pointer;
margin-right: 10px;
-webkit-user-select: none;
/* Safari */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* IE 10+ 和 Edge */
user-select: none;
/* 标准语法 */
border: 0px;
border-radius: 10px;
}
.actions:hover {
transform: scale(1.05);
}
</style>

197
src/js/api.js Normal file
View File

@@ -0,0 +1,197 @@
import {
backendUrl
} from "./globalVariables"
import {
fetchEventSource
} from '@microsoft/fetch-event-source';
import {
scrollToBottom,
scrollToBottomWithAnimation
} from "./util.js";
import store from '../store'
async function getKinds() {
try {
const response = await fetch(backendUrl + "/kinds");
if (response.status === 200) {
const data = await response.json();
console.log(data);
return data;
} else {
return "err"
}
} catch (err) {
console.error(err);
return "err";
}
}
async function fetchStream(session) {
console.log("open:open")
const controller = new AbortController();
const payload = {
messages: session.messages.slice(0, session.messages.length - 1),
stream: true,
intention: session.type,
record_id: session.id,
local_retrieval: session.local_retrieval,
online_retrieval: session.online_retrieval,
r1_model: session.r1_model,
}
if (session.file !== "") {
payload.file_url = session.file;
}
let cnt = 0;
const url = `${backendUrl}/v1/chat/completions`;
new fetchEventSource(url, {
method: "POST",
signal: controller.signal,
body: JSON.stringify(payload),
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
},
onopen(response) {
// 连接打开时的逻辑
if (response.status !== 200) {
controller.abort()
const err = response.json();
err.then(res => res).then(res => {
console.log(res)
session.messages[session.messages.length - 1].content = '```json\n' + JSON.stringify(res, null, 2) + '\n```'
setTimeout(() => {
scrollToBottom("message-list")
}, 0)
messageEnd(session)
}).catch(err => {
session.messages[session.messages.length - 1].content = '```json\n' + JSON.stringify({
error: response.status
}, null, 2) + '\n```'
setTimeout(() => {
scrollToBottom("message-list")
}, 0)
messageEnd(session)
});
}
},
onmessage(event) {
cnt++;
let receive = JSON.parse(event.data);
if (cnt === 1) {
session.messages[session.messages.length - 1].content = receive.choices[0].delta.content;
setTimeout(() => {
scrollToBottomWithAnimation("message-list")
}, 0)
} else {
session.messages[session.messages.length - 1].content += receive.choices[0].delta.content;
setTimeout(() => {
scrollToBottom("message-list")
}, 0)
}
},
onclose() {
console.log('close');
messageEnd(session)
},
})
}
function messageEnd(session) {
store.dispatch('setInputOccupied', false);
session.topic = session.messages[1].content;
const now = new Date();
fetch(backendUrl + '/add-record', {
method: "POST",
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
},
body: JSON.stringify({
id: session.id,
datetime: now.toISOString(),
record: JSON.stringify(session)
})
})
}
function deleteMessage(id) {
return new Promise((resolve, reject) => {
fetch(backendUrl + `/delete-record?id=${id}`, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
},
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json(); // 假设返回的是 JSON 数据
})
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});
}
function addFeedback(record, type, content) {
return new Promise((resolve, reject) => {
fetch(backendUrl + `/add`, {
method: "POST",
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
},
body: JSON.stringify({
record,
type,
content
})
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json(); // 假设返回的是 JSON 数据
})
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});
}
function uploadFileFunc(file) {
const formData = new FormData();
formData.append('file', file);
return new Promise((resolve, reject) => {
fetch(backendUrl + `/upload`, {
method: "POST",
body: formData,
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
},
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});
}
export {
getKinds,
fetchStream,
deleteMessage,
addFeedback,
uploadFileFunc
}

26
src/js/chatHistory.js Normal file
View File

@@ -0,0 +1,26 @@
import {
backendUrl
} from "./globalVariables"
async function getChatHistory() {
try {
const response = await fetch(backendUrl + "/get-record", {
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
},
});
if (response.status === 200) {
const data = await response.json();
return data.data;
} else if (response.status === 401) {} else {
return "err"
}
} catch (err) {
console.error(err);
return "err";
}
}
export {
getChatHistory
}

18
src/js/copy.ts Normal file
View File

@@ -0,0 +1,18 @@
export function copyToClip(text: string) {
return new Promise((resolve, reject) => {
try {
const input: HTMLTextAreaElement = document.createElement('textarea')
input.setAttribute('readonly', 'readonly')
input.value = text
document.body.appendChild(input)
input.select()
if (document.execCommand('copy'))
document.execCommand('copy')
document.body.removeChild(input)
resolve(text)
}
catch (error) {
reject(error)
}
})
}

View File

@@ -0,0 +1,7 @@
const backendUrl = window.__ENV__.API_URL && window.__ENV__.API_URL !== '' ? window.__ENV__.API_URL : "http://localhost:3000";
// const backendUrl = "http://liu.steeshacn:2222"
// const backendUrl = "http://127.0.0.1:2222"
export {
backendUrl
}

39
src/js/session.js Normal file
View File

@@ -0,0 +1,39 @@
import {
generateRandomString,
getUid
} from './util';
class Session {
constructor(id, uid, lastUpdate, messages, topic, type, file) {
if (arguments.length === 0) {
this.id = generateRandomString(21)
this.uid = getUid();
this.lastUpdate = Date.now();
this.topic = "新的聊天";
this.type = "";
this.messages = [{
role: 'assistant',
content: '请问你有什么需要帮助的吗'
}],
this.file = "";
this.r1_model = false;
this.local_retrieval = false;
this.online_retrieval = false;
} else {
this.id = id;
this.uid = uid;
this.lastUpdate = lastUpdate;
this.messages = messages;
this.topic = topic;
this.type = type;
this.file = file;
this.r1_model = false;
this.local_retrieval = false;
this.online_retrieval = false;
}
}
}
export {
Session
}

118
src/js/util.js Normal file
View File

@@ -0,0 +1,118 @@
import {
backendUrl
} from "./globalVariables";
function generateRandomString(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
function getUid() {
return "111";
}
function scrollToBottom(className) {
const div = document.getElementsByClassName(className)[0];
div.scrollTop = div.scrollHeight;
}
function scrollToBottomWithAnimation(className, duration = 500) {
const element = document.getElementsByClassName(className)[0];
const start = element.scrollTop;
const end = element.scrollHeight;
const change = end - start;
const increment = 20;
// 当前动画的开始时间
let startTime;
// 动画函数
function animateScroll(timestamp) {
if (!startTime) startTime = timestamp;
// 计算动画进度
const elapsed = timestamp - startTime;
// 使用easeInOut函数计算当前滚动位置
const position = easeInOut(elapsed, start, change, duration);
element.scrollTop = position;
// 如果动画时间未结束,继续执行动画
if (elapsed < duration) {
requestAnimationFrame(animateScroll);
} else {
// 确保动画结束时精确设置到底部
element.scrollTop = end;
}
}
// 缓动函数 - 使用简单的easeInOut公式使动画更平滑
function easeInOut(currentTime, start, change, duration) {
currentTime /= duration / 2;
if (currentTime < 1) {
return change / 2 * currentTime * currentTime + start;
}
currentTime -= 1;
return -change / 2 * (currentTime * (currentTime - 2) - 1) + start;
}
// 开始执行动画
requestAnimationFrame(animateScroll);
}
function jump2Ids() {
// window.location.href = "https://ids.xidi an.edu.cn/authserver/login?service=https://xdechat.xidian.edu.cn/"
let url = new URL(window.location.href);
url.searchParams.set('ticket', '114514');
window.location.href = url.toString();
}
function login(ticket) {
fetch(backendUrl + '/login?ticket=' + ticket)
.then(res => res.json())
.then((res) => {
localStorage.setItem('uid', res.uid);
localStorage.setItem('username', res.userName)
localStorage.setItem('access_token', res.access_token)
const currentUrl = window.location.href
const url = new URL(currentUrl);
url.search = '';
window.location.href = url.href;
})
.catch((e) => {
console.log(e)
})
}
function logout() {
localStorage.clear();
// window.location.href = "https://ids.xidian.edu.cn/authserver/logout?service=https://xdechat.xidian.edu.cn/"
window.location.reload();
}
function isValid() {
fetch(backendUrl + '/valid', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
},
})
.then(res => {
if (res.status === 401) {
jump2Ids()
}
})
}
export {
getUid,
generateRandomString,
scrollToBottom,
scrollToBottomWithAnimation,
login,
jump2Ids,
logout,
isValid
}

10
src/main.js Normal file
View File

@@ -0,0 +1,10 @@
import {
createApp
} from 'vue'
import App from './App.vue'
import './style.css'
import router from './router'
import './assets/fonts/font.css'
import store from './store'
createApp(App).use(store).use(router).mount('#app')

24
src/router/index.js Normal file
View File

@@ -0,0 +1,24 @@
import {
createRouter,
createWebHistory
} from 'vue-router'
import Home from '../views/Home.vue'
import Test from '../views/Test.vue'
const routes = [{
path: '/',
name: 'home',
component: Home
},
{
path: '/test',
name: 'test',
component: Test
},
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router

23
src/store/index.js Normal file
View File

@@ -0,0 +1,23 @@
import {
createStore
} from 'vuex';
export default createStore({
state() {
return {
isInputOccupied: false,
};
},
mutations: {
SET_INPUT_OCCUPIED(state, isOccupied) {
state.isInputOccupied = isOccupied;
},
},
actions: {
setInputOccupied({
commit
}, isOccupied) {
commit('SET_INPUT_OCCUPIED', isOccupied);
},
},
});

5
src/style.css Normal file
View File

@@ -0,0 +1,5 @@
* {
margin: 0;
padding: 0;
}

File diff suppressed because it is too large Load Diff

206
src/style/highlight.less Normal file
View File

@@ -0,0 +1,206 @@
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;
&::-webkit-scrollbar {
height: 4px;
}
}
.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
}
}

141
src/style/style.less Normal file
View File

@@ -0,0 +1,141 @@
// *{
// transition: height 0.3s ease-in-out;
// }
.markdown-body {
background-color: transparent !important;
font-size: 14px !important;
p {
white-space: pre-wrap;
}
ol {
list-style-type: decimal;
}
ul {
list-style-type: disc;
}
img{
max-width: 400px;
}
pre code,
pre tt {
line-height: 1.65;
}
.highlight pre,
pre {
background-color: #fff;
}
code.hljs {
padding: 0;
}
.code-block {
&-wrapper {
position: relative;
padding-top: 24px;
}
&-header {
position: absolute;
top: 5px;
right: 0;
width: 100%;
padding: 0 1rem;
display: flex;
justify-content: flex-end;
align-items: center;
color: #b3b3b3;
&__copy {
cursor: pointer;
margin-left: 0.5rem;
user-select: none;
&:hover {
color: #65a665;
}
}
}
}
&.markdown-body-generate>dd:last-child:after,
&.markdown-body-generate>dl:last-child:after,
&.markdown-body-generate>dt:last-child:after,
&.markdown-body-generate>h1:last-child:after,
&.markdown-body-generate>h2:last-child:after,
&.markdown-body-generate>h3:last-child:after,
&.markdown-body-generate>h4:last-child:after,
&.markdown-body-generate>h5:last-child:after,
&.markdown-body-generate>h6:last-child:after,
&.markdown-body-generate>li:last-child:after,
&.markdown-body-generate>ol:last-child li:last-child:after,
&.markdown-body-generate>p:last-child:after,
&.markdown-body-generate>pre:last-child code:after,
&.markdown-body-generate>td:last-child:after,
&.markdown-body-generate>ul:last-child li:last-child:after {
animation: blink 1s steps(5, start) infinite;
color: #000;
content: '_';
font-weight: 700;
margin-left: 3px;
vertical-align: baseline;
}
@keyframes blink {
to {
visibility: hidden;
}
}
}
html.dark {
.markdown-body {
&.markdown-body-generate>dd:last-child:after,
&.markdown-body-generate>dl:last-child:after,
&.markdown-body-generate>dt:last-child:after,
&.markdown-body-generate>h1:last-child:after,
&.markdown-body-generate>h2:last-child:after,
&.markdown-body-generate>h3:last-child:after,
&.markdown-body-generate>h4:last-child:after,
&.markdown-body-generate>h5:last-child:after,
&.markdown-body-generate>h6:last-child:after,
&.markdown-body-generate>li:last-child:after,
&.markdown-body-generate>ol:last-child li:last-child:after,
&.markdown-body-generate>p:last-child:after,
&.markdown-body-generate>pre:last-child code:after,
&.markdown-body-generate>td:last-child:after,
&.markdown-body-generate>ul:last-child li:last-child:after {
color: #9e9e9e;
}
}
.message-reply {
.whitespace-pre-wrap {
white-space: pre-wrap;
color: var(--n-text-color);
}
}
.highlight pre,
pre {
background-color: #282c34;
}
}
@media screen and (max-width: 533px) {
.markdown-body .code-block-wrapper {
padding: unset;
code {
padding: 24px 16px 16px 16px;
}
}
}

320
src/views/Home.vue Normal file
View File

@@ -0,0 +1,320 @@
<template>
<div class="container" :style="{height: componentHeight}">
<aside :class="{ 'sidebar-collapsed': isCollapsed, 'sidebar': !isCollapsed }">
<div class="new-chat" @click="newChat">
创建新对话
</div>
<chatHistoryCard v-for="(item, index) in session_list" :key="index" :text="item.topic"
:isac="activateId === item.id" :idx="index" @click="choice(item.id, index)"
@delete-message="handleDeleteMessage" />
</aside>
<aside class="sidebar-btn">
<div class="toggleSidebar" @click="toggleSidebar">
<img class="toggleSidebar-left" :src="arrowLeft" width="70%" height="70%" alt="">
</div>
</aside>
<div class="main-content">
<div class="responsive-element">
<div class="content">
<div class="message-list" v-if="ac_session.type !== ''">
<messageCard v-for="(item, index) in ac_session.messages" :key="item.id" :text="item.content"
:role="item.role" :idx="index" :messages="ac_session.messages" />
</div>
<div class="choose-type" v-if="ac_session.type === ''">
<chooseKind @choose-key="handleChoose" />
</div>
</div>
<div>
<chatInput @send-message="onSend" :session="ac_session" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, watch } from "vue";
import $ from 'jquery'
import { getChatHistory } from '../js/chatHistory'
import { Session } from '../js/session'
import { fetchStream, deleteMessage } from "../js/api";
import chatHistoryCard from '../components/chatHistoryCard.vue'
import messageCard from "../components/messageCard.vue";
import chooseKind from "../components/chooseKind.vue"
import chatInput from "../components/chatInput.vue";
import { getUid, scrollToBottomWithAnimation, login, isValid } from "../js/util";
import arrowLeft from '../assets/arrowleft.svg'
import { onMounted, onUnmounted } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const componentHeight = ref('100vh')
const setComponentHeight = () => {
componentHeight.value = window.innerHeight - 50 + 'px';
}
onMounted(() => {
setComponentHeight();
window.addEventListener('resize', setComponentHeight);
})
onUnmounted(() => {
window.removeEventListener('resize', setComponentHeight);
})
const isCollapsed = ref(false); // 使用ref来创建响应式变量
const activateId = ref("")
onMounted(() => {
store.dispatch('setInputOccupied', false);
const queryParams = new URLSearchParams(window.location.search);
const ticket = queryParams.get('ticket');
if (ticket !== null) {
console.log('Ticket:', ticket);
login(ticket)
} else {
isValid()
}
const screenWidth = window.innerWidth;
if (screenWidth <= 768) {
isCollapsed.value = true
$('.toggleSidebar-left').css('transform', 'scaleX(-1)')
}
})
function toggleSidebar() {
isCollapsed.value = !isCollapsed.value; // 切换侧边栏状态
if (isCollapsed.value === true) {
$('.toggleSidebar-left').css('transform', 'scaleX(-1)')
} else {
$('.toggleSidebar-left').css('transform', 'scaleX(1)')
}
}
const session_list = ref([])
let ac_session = reactive(new Session())
async function fetchChatHistory() {
try {
const chatHistory = await getChatHistory();
if (chatHistory === "err") {
console.log("获取聊天记录失败后的错误处理")
} else if (chatHistory.length === 0) {
session_list.value.push(new Session());
} else {
for (let i of chatHistory) {
let record = JSON.parse(i.record)
let file = i.file === undefined ? "" : i.file;
session_list.value.unshift(new Session(i.id, i.uid, record.lastUpdate, record.messages, record.topic, record.type, file));
}
}
activateId.value = session_list.value[0].id;
ac_session = session_list.value[0];
newChat();
} catch (error) {
console.error('获取聊天记录失败:', error);
}
}
fetchChatHistory();
function newChat() {
session_list.value.unshift(new Session())
activateId.value = session_list.value[0].id
ac_session = session_list.value[0];
}
function choice(id, index) {
activateId.value = id;
ac_session = session_list.value[index];
}
const receiveChoose = ref('');
const handleChoose = (data) => {
receiveChoose.value = data;
console.log(receiveChoose.value)
ac_session.type = receiveChoose.value;
};
const toSendMessage = ref('');
const onSend = (data) => {
toSendMessage.value = data;
ac_session.messages.push({
role: 'user',
content: toSendMessage.value
})
ac_session.messages.push({
role: 'assistant',
content: '思考中...',
})
setTimeout(() => {
scrollToBottomWithAnimation("message-list")
}, 0)
fetchStream(ac_session);
};
function min(a, b) {
return a < b ? a : b;
}
const handleDeleteMessage = (idx) => {
deleteMessage(session_list.value[idx].id)
.then(res => {
const toast = () => {
ElMessage({
message: '删除成功',
type: 'success',
plain: true,
duration: 1000
})
}
toast()
})
.catch(e => {
console.log(e)
})
session_list.value.splice(idx, 1);
if (session_list.value.length === 0) {
console.log(111)
session_list.value.push(new Session());
}
choice(session_list.value[min(idx, session_list.value.length - 1)].id, min(idx, session_list.value.length - 1))
}
</script>
<style scoped>
.container {
display: flex;
overflow: hidden;
}
.sidebar {
flex-shrink: 0;
width: 200px !important;
background-color: #eeeeee;
transition: all 0.3s;
overflow-y: auto;
}
.dark .sidebar {
background-color: #18181c;
color: #c9d1d9;
}
.sidebar-collapsed {
width: 0;
transform: translateX(-200px);
transition: ease-in-out 0.3s;
background-color: #eeeeee;
}
.main-content {
flex: 1;
display: flex;
justify-content: center;
height: 100%;
min-width: calc(100% - 200px);
padding: 0;
background-color: white;
}
.dark .main-content{
background-color: #101014;
}
.responsive-element {
width: 90%;
max-width: 90%;
padding: 20px;
box-sizing: border-box;
margin: 0 auto;
display: flex;
flex-direction: column;
height: 100%;
}
@media (min-width: 768px) {
.responsive-element {
width: 60%;
}
}
.message-list {
height: 100%;
overflow-y: auto;
width: 100%;
scrollbar-width: none;
overflow-x: hidden;
}
.message-list::-webkit-scrollbar {
scrollbar-width: none;
}
.content {
flex: 1;
overflow-y: auto;
width: 100%;
padding-bottom: 20px;
overflow-x: hidden;
}
.sidebar-btn {
display: flex;
align-items: center;
justify-content: center;
width: 0;
}
.toggleSidebar {
width: 30px;
height: 30px;
background-color: #eeeeee;
border-radius: 0 50% 50% 0;
display: flex;
align-items: center;
justify-content: center;
transform: translateX(10px);
z-index: 100;
}
.dark .toggleSidebar {
background-color: #818185;
}
.toggleSidebar-left {
width: 30px;
transition: all 0.3s;
}
.new-chat {
border-radius: 5px;
border: 1px gray dashed;
margin: 10px;
text-align: center;
font-size: small;
padding: 5px;
cursor: pointer
}
.new-chat:hover {
background-color: rgba(128, 128, 128, 0.1);
}
.choose-type {
top: 0;
width: 100%;
}
.font_notoSansSC {
font-family: 'NotoSansSC-Regular';
font-size: 18px;
color: #141414;
}
</style>

12
src/views/Test.vue Normal file
View File

@@ -0,0 +1,12 @@
<template>
<chatLogsSidebar />
</template>
<script setup>
import chatLogsSidebar from "../components/chatLogsSidebar.vue";
</script>
<style lang="sass" scoped>
</style>

27
vite.config.js Normal file
View File

@@ -0,0 +1,27 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),],
server:{
host: '0.0.0.0',
proxy: {
'/api': {
target: 'http://10.8.0.9:8888',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})