commit
28
.github/workflows/docker.yaml
vendored
Normal 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
@@ -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
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
33
Dockerfile
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
39
package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
1
public/icon/arrowright.svg
Normal 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
@@ -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
|
After Width: | Height: | Size: 317 KiB |
138
public/style.less
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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 |
1
src/assets/chatLogSidebar/add_circle.svg
Normal 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 |
1
src/assets/chatLogSidebar/arrow_right.svg
Normal 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 |
1
src/assets/chatLogSidebar/chat_bubble_dm.svg
Normal 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 |
1
src/assets/chatLogSidebar/chat_bubble_lm.svg
Normal 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 |
1
src/assets/chatLogSidebar/double_arrow_dm.svg
Normal 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 |
1
src/assets/chatLogSidebar/double_arrow_lm.svg
Normal 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 |
1
src/assets/chatLogSidebar/message.svg
Normal 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 |
1
src/assets/circuit_database.svg
Normal 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 |
1
src/assets/communication_database.svg
Normal 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 |
1
src/assets/compute_database.svg
Normal 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
@@ -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
@@ -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
@@ -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 |
1
src/assets/doctor_paper.svg
Normal 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
@@ -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
@@ -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 |
BIN
src/assets/fonts/Noto_Sans_SC/NotoSansSC-VariableFont_wght.ttf
Normal file
93
src/assets/fonts/Noto_Sans_SC/OFL.txt
Normal 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.
|
||||
71
src/assets/fonts/Noto_Sans_SC/README.txt
Normal 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 aren’t 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.
|
||||
BIN
src/assets/fonts/Noto_Sans_SC/static/NotoSansSC-Black.ttf
Normal file
BIN
src/assets/fonts/Noto_Sans_SC/static/NotoSansSC-Bold.ttf
Normal file
BIN
src/assets/fonts/Noto_Sans_SC/static/NotoSansSC-ExtraBold.ttf
Normal file
BIN
src/assets/fonts/Noto_Sans_SC/static/NotoSansSC-ExtraLight.ttf
Normal file
BIN
src/assets/fonts/Noto_Sans_SC/static/NotoSansSC-Light.ttf
Normal file
BIN
src/assets/fonts/Noto_Sans_SC/static/NotoSansSC-Medium.ttf
Normal file
BIN
src/assets/fonts/Noto_Sans_SC/static/NotoSansSC-Regular.ttf
Normal file
BIN
src/assets/fonts/Noto_Sans_SC/static/NotoSansSC-SemiBold.ttf
Normal file
BIN
src/assets/fonts/Noto_Sans_SC/static/NotoSansSC-Thin.ttf
Normal file
10
src/assets/fonts/font.css
Normal 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');
|
||||
}
|
||||
1
src/assets/graduate_paper.svg
Normal 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 |
1
src/assets/instruct_design.svg
Normal 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
@@ -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 |
1
src/assets/local_retrieval.svg
Normal 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 |
1
src/assets/local_retrieval_ac.svg
Normal 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
|
After Width: | Height: | Size: 317 KiB |
1
src/assets/message.svg
Normal 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 |
1
src/assets/microelectronics_database.svg
Normal 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 |
1
src/assets/online_retrieval.svg
Normal 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 |
1
src/assets/online_retrieval_ac.svg
Normal 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 |
1
src/assets/organization.svg
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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 |
1
src/assets/system_database.svg
Normal 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
@@ -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
@@ -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 |
1
src/assets/thinkactivate.svg
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
78
src/components/chatHistoryCard.vue
Normal 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>
|
||||
422
src/components/chatInput.vue
Normal 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>
|
||||
163
src/components/chatLogsSidebar.vue
Normal 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>
|
||||
226
src/components/chooseKind.vue
Normal 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>
|
||||
230
src/components/messageCard.vue
Normal 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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
7
src/js/globalVariables.js
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,5 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
}
|
||||
1102
src/style/github-markdown.less
Normal file
206
src/style/highlight.less
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||