'frontendadd'
12
frontend/src/App.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<SwitchLanguage />
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import SwitchLanguage from "./components/SwitchLanguage.vue";
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
||||
27
frontend/src/api/model/detailModel.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export interface PurchaseListResult {
|
||||
list: Array<PurchaseInfo>
|
||||
}
|
||||
export interface PurchaseInfo {
|
||||
adminName: string
|
||||
index: string
|
||||
pdName: string
|
||||
pdNum: string
|
||||
pdType: string
|
||||
purchaseNum: number
|
||||
updateTime: Date
|
||||
}
|
||||
|
||||
export interface ProjectListResult {
|
||||
[x: string]: number
|
||||
code: any
|
||||
data: any
|
||||
msg: any
|
||||
// list: Array<ProjectInfo>;
|
||||
}
|
||||
export interface ProjectInfo {
|
||||
adminName: string
|
||||
adminPhone: string
|
||||
index: number
|
||||
name: string
|
||||
updateTime: Date
|
||||
}
|
||||
26
frontend/src/api/model/listModel.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export interface ListResult {
|
||||
list: Array<ListModel>;
|
||||
}
|
||||
export interface ListModel {
|
||||
adminName: string;
|
||||
amount: string;
|
||||
contractType: number;
|
||||
index: number;
|
||||
name: string;
|
||||
no: string;
|
||||
paymentType: number;
|
||||
status: number;
|
||||
updateTime: Date;
|
||||
}
|
||||
|
||||
export interface CardListResult {
|
||||
list: Array<CardList>;
|
||||
}
|
||||
export interface CardList {
|
||||
banner: string;
|
||||
description: string;
|
||||
index: number;
|
||||
isSetup: boolean;
|
||||
name: string;
|
||||
type: number;
|
||||
}
|
||||
32
frontend/src/api/model/permissionModel.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export interface MenuListResult {
|
||||
list: Array<RouteItem>;
|
||||
}
|
||||
|
||||
export type Component<T = any> =
|
||||
| ReturnType<typeof defineComponent>
|
||||
| (() => Promise<typeof import('*.vue')>)
|
||||
| (() => Promise<T>);
|
||||
|
||||
export interface RouteItem {
|
||||
path: string;
|
||||
name: string;
|
||||
component?: Component | string;
|
||||
components?: Component;
|
||||
redirect?: string;
|
||||
meta: RouteMeta;
|
||||
children?: Array<RouteItem>;
|
||||
}
|
||||
export interface RouteMeta {
|
||||
title: string;
|
||||
icon?: string;
|
||||
expanded?: boolean;
|
||||
orderNo?: number;
|
||||
hidden?: boolean;
|
||||
hiddenBreadcrumb?: boolean;
|
||||
single?: boolean;
|
||||
keepAlive?: boolean;
|
||||
frameSrc?: string;
|
||||
frameBlank?: boolean;
|
||||
}
|
||||
22
frontend/src/api/user.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { ProjectListResult } from './model/detailModel'
|
||||
import { request } from '../utils/request'
|
||||
// 登录获取Token
|
||||
export function authLogin(data: any) {
|
||||
return request.post<ProjectListResult>({
|
||||
url: `/login`,
|
||||
data
|
||||
})
|
||||
}
|
||||
// 发送聊天请求
|
||||
export function chat(data: any) {
|
||||
return request.post<ProjectListResult>({
|
||||
url: `/chat`,
|
||||
data
|
||||
})
|
||||
}
|
||||
// 获取模型列表
|
||||
export function getModelList() {
|
||||
return request.get<ProjectListResult>({
|
||||
url: `/model`
|
||||
})
|
||||
}
|
||||
BIN
frontend/src/assets/background.png
Normal file
|
After Width: | Height: | Size: 822 KiB |
BIN
frontend/src/assets/chart/in-arrow.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
frontend/src/assets/chart/model.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
frontend/src/assets/chart/out-arrow.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
frontend/src/assets/chart/round-arrow.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
frontend/src/assets/chart/round-lage-arrow.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
frontend/src/assets/chart/sit.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
frontend/src/assets/chart/stand.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
frontend/src/assets/header-logo.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
frontend/src/assets/home/assistant.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
frontend/src/assets/home/recommend.png
Normal file
|
After Width: | Height: | Size: 525 B |
BIN
frontend/src/assets/layout/next.png
Normal file
|
After Width: | Height: | Size: 286 B |
BIN
frontend/src/assets/layout/vector.png
Normal file
|
After Width: | Height: | Size: 672 B |
BIN
frontend/src/assets/login/background.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
frontend/src/assets/login/password.png
Normal file
|
After Width: | Height: | Size: 433 B |
BIN
frontend/src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
125
frontend/src/components/ReasoningView/index.less
Normal file
@@ -0,0 +1,125 @@
|
||||
.reasoning-box {
|
||||
width: 100%;
|
||||
|
||||
.problem-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 24px;
|
||||
|
||||
.title-box {
|
||||
width: 100%;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 4px;
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.reasoning-item {
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
column-gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
|
||||
img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.active_item {
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px;
|
||||
font-size: 14px;
|
||||
height: 24px;
|
||||
margin: 0;
|
||||
color: rgba(73, 252, 255, 0.80);
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(24, 91, 197, 1);
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 213, 250, 1) inset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 3px;
|
||||
background: #0C3870;
|
||||
|
||||
img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.reasoning-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.reasoning-text {
|
||||
width: calc(100% - 46px);
|
||||
border-radius: 6px;
|
||||
background: rgba(3, 52, 90, 0.5);
|
||||
border: 1px solid #0061A6;
|
||||
box-shadow: 0px 0px 16px 0px rgba(0, 149, 230, 1) inset;
|
||||
padding: 16px;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
box-sizing: border-box;
|
||||
|
||||
.show-box {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background: rgba(42, 163, 255, 0.6);
|
||||
border: 2px solid #0FFBFF;
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
height: 46px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.el-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #093D7C;
|
||||
border: 1px solid #025389;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.complete-box {
|
||||
font-size: 14px;
|
||||
color: #2F88BF;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
153
frontend/src/components/ReasoningView/index.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div class="reasoning-box">
|
||||
<div
|
||||
class="problem-item"
|
||||
v-for="(key, num) in props.reasoningList"
|
||||
:key="num"
|
||||
:id="`${num}title`"
|
||||
>
|
||||
<div class="title-box">
|
||||
<p>{{generateTitle('问题设定')}}:</p>
|
||||
<span>{{ key.title }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="reasoning-item"
|
||||
v-for="(item, index) in key.children"
|
||||
:key="index"
|
||||
:id="`${num}${index}content`"
|
||||
>
|
||||
<div class="title">
|
||||
<img src="../../assets/logo.png" alt="" />
|
||||
<span v-if="item.title">{{generateTitle(item.title =='Planner'?'角色':'模型名称')}}({{ item.title }})</span>
|
||||
<p
|
||||
class="active_item"
|
||||
v-if="reasonStatus.index === index && reasonStatus.show"
|
||||
>
|
||||
<img src="../../assets/layout/vector.png" alt="" />
|
||||
{{generateTitle('推理中')}}...
|
||||
</p>
|
||||
</div>
|
||||
<div class="reasoning-content">
|
||||
<div
|
||||
class="reasoning-text"
|
||||
:class="{
|
||||
active: reasonStatus.index === index && reasonStatus.show
|
||||
}"
|
||||
>
|
||||
<div
|
||||
style="font-size: 14px"
|
||||
:class="{ 'show-box': current[index] }"
|
||||
>
|
||||
<MdPreview style="background: linear-gradient(to bottom,#d5e6f4 0%,#afceea 50%,#c2d9ef 100%);" :editorId="id" :modelValue="item.content" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon-box">
|
||||
<el-icon color="#fff" @click="changeShow(index)"
|
||||
><component :is="getIcon(index)"></component
|
||||
></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="complete-box"
|
||||
v-if="completeList[num].show && num === completeList[num].index"
|
||||
>
|
||||
{{ generateTitle('推理完成,如果希望了解更多可继续在下面框中提问或新建对话。')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, defineExpose, defineEmits, defineProps } from 'vue'
|
||||
import { Plus, Minus } from '@element-plus/icons-vue'
|
||||
import { MdPreview } from 'md-editor-v3'
|
||||
import {generateTitle} from '../../utils/i18n'
|
||||
import 'md-editor-v3/lib/style.css'
|
||||
const props: any = defineProps({
|
||||
reasoningList: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
})
|
||||
const id = 'preview-only'
|
||||
const emits = defineEmits(['completeFun'])
|
||||
const current = ref<Array<any>>([])
|
||||
const reasonStatus = ref({
|
||||
index: 0,
|
||||
show: false
|
||||
})
|
||||
const completeList = ref<Array<any>>([])
|
||||
const getIcon = (index: number) => {
|
||||
return current.value[index] ? Plus : Minus
|
||||
}
|
||||
const changeShow = (index: number) => {
|
||||
current.value[index] = !current.value[index]
|
||||
}
|
||||
// const reasoningList = ref<Array<any>>([])
|
||||
const getAnswer = async () => {
|
||||
emits('completeFun')
|
||||
}
|
||||
// function extractSynthesisProcess() {
|
||||
// talkList.value
|
||||
// .split(">>>>>>>> USING AUTO REPLY...")
|
||||
// .forEach((item: any, index: number) => {
|
||||
// if (index) {
|
||||
// let data = item.split(
|
||||
// "\nsynthesis_scientist (to Generate_Group_Admin):\n"
|
||||
// )[1];
|
||||
// if (data) {
|
||||
// reasoningList.value[reasoningList.value.length - 1].content = data;
|
||||
// }
|
||||
// } else {
|
||||
// let data = item.split("Next speaker: ");
|
||||
// reasoningList.value[
|
||||
// reasoningList.value.length - 1
|
||||
// ].title = `模型名称(${data[data.length - 1].replace(/\n/g, "")})`;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// const getData = (e: any) => {
|
||||
// let regex = /Next speaker: (.*)/;
|
||||
// let regexnew = /\(to (.*)\)/;
|
||||
// reasonStatus.value.show = true;
|
||||
// if (e.includes(">>>>>>>> USING AUTO REPLY...")) {
|
||||
// reasoningList.value[reasoningList.value.length - 1].title = oldLine.value[oldLine.value.length - 1].match(regex)[1];
|
||||
// startStatus.value = true;
|
||||
// } else if (startStatus.value) {
|
||||
// if (
|
||||
// e.includes(
|
||||
// `${reasoningList.value[reasoningList.value.length - 1].title} (to`
|
||||
// )
|
||||
// ) {
|
||||
// reasoningList.value[reasoningList.value.length - 1].title = e.match(regexnew)[1];
|
||||
// } else {
|
||||
// console.log(e,e.split("**TERMINATE**"),'111');
|
||||
|
||||
// if (e.split("**TERMINATE**")[1]) {
|
||||
// reasoningList.value[reasoningList.value.length - 1].content +=
|
||||
// e.split("**TERMINATE**")[0];
|
||||
// reasonStatus.value.show = false;
|
||||
// endShow.value = true;
|
||||
// } else if (e.split("`TERMINATE`")[1]) {
|
||||
// reasoningList.value[reasoningList.value.length - 1].content +=
|
||||
// e.split("`TERMINATE`")[0];
|
||||
// reasonStatus.value.show = false;
|
||||
// endShow.value = true;
|
||||
// } else {
|
||||
// reasoningList.value[reasoningList.value.length - 1].content += e;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// oldLine.value.push(e);
|
||||
|
||||
// }
|
||||
defineExpose({
|
||||
getAnswer,
|
||||
reasonStatus,
|
||||
completeList
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
@import './index.less';
|
||||
</style>
|
||||
177
frontend/src/components/StepsView.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="list-box">
|
||||
<div
|
||||
class="item-box"
|
||||
v-for="(item, index) in props.reasoningList"
|
||||
:key="index"
|
||||
>
|
||||
<div class="title" @click="toTitle(index)">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class="ul">
|
||||
<div
|
||||
@click="toContent(index, num)"
|
||||
v-for="(key, num) in item.children"
|
||||
:key="num"
|
||||
>
|
||||
<div v-if="key.title !== 'Planner'" class="li">
|
||||
<div
|
||||
class="p-box"
|
||||
:class="{
|
||||
active_item:
|
||||
index === props.reasoningList.length - 1 &&
|
||||
num === item.children.length - 1,
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="
|
||||
index === props.reasoningList.length - 1 &&
|
||||
num === item.children.length - 1
|
||||
"
|
||||
src="../assets/layout/vector.png"
|
||||
alt=""
|
||||
/>
|
||||
<span>{{ key.title }}</span>
|
||||
</div>
|
||||
<img
|
||||
class="next"
|
||||
v-if="num + 1 != item.children.length"
|
||||
src="../assets/layout/next.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
|
||||
const props: any = defineProps({
|
||||
reasoningList: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
});
|
||||
const toTitle = (index: number) => {
|
||||
let id = `${index}title`;
|
||||
let view: any = document.getElementById(id);
|
||||
view.scrollIntoView({ behavior: "smooth" });
|
||||
};
|
||||
const toContent = (index: number, num: number) => {
|
||||
let id = `${index}${num}content`;
|
||||
let view: any = document.getElementById(id);
|
||||
view.scrollIntoView({ behavior: "smooth" });
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.list-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
row-gap: 18px;
|
||||
height: calc(100% - 70px);
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #1a87ca;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: #1a87ca;
|
||||
}
|
||||
.item-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 80%;
|
||||
height: 1px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(37, 237, 242, 0) 0%,
|
||||
rgba(98, 250, 248, 0.2) 32.5%,
|
||||
rgba(98, 250, 248, 0.2) 68%,
|
||||
rgba(37, 237, 242, 0) 100%
|
||||
);
|
||||
}
|
||||
&:last-child::before {
|
||||
height: 0;
|
||||
}
|
||||
.title {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 15px;
|
||||
color: #fff;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ul {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
row-gap: 8px;
|
||||
|
||||
.li {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
row-gap: 8px;
|
||||
.p-box {
|
||||
max-width: 90%;
|
||||
padding: 10px 10px;
|
||||
font-size: 14px;
|
||||
min-height: 36px;
|
||||
color: #fff;
|
||||
border-radius: 36px;
|
||||
border: 1px solid rgba(24, 91, 197, 1);
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 213, 250, 1) inset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 3px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
span {
|
||||
width: 100%;
|
||||
word-wrap: break-word;
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
.active_item {
|
||||
background: rgba(25, 240, 255, 0.2);
|
||||
}
|
||||
.next {
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
55
frontend/src/components/SwitchLanguage.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dropdown class="language-box" placement="bottom-start">
|
||||
<el-button>{{
|
||||
state.curLanguage === "en" ? state.curLanguage : "中"
|
||||
}}</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="zh('zh-cn')">中文</el-dropdown-item>
|
||||
<el-dropdown-item @click="en('en')">English</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import { reactive } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { locale } = useI18n();
|
||||
|
||||
const state = reactive({
|
||||
curLanguage: "zh-cn",
|
||||
});
|
||||
const zh = (e: any) => {
|
||||
state.curLanguage = e;
|
||||
locale.value = state.curLanguage;
|
||||
};
|
||||
const en = (e: any) => {
|
||||
state.curLanguage = e;
|
||||
locale.value = state.curLanguage;
|
||||
};
|
||||
</script>
|
||||
<style scoped lang='less'>
|
||||
.language-box {
|
||||
position: fixed;
|
||||
top: 6px;
|
||||
left: 32px;
|
||||
.el-button {
|
||||
background: none;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
width: 24px;
|
||||
padding: 0;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid #3ba4ff;
|
||||
color: #3ba4ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
118
frontend/src/components/TextareaView.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="textarea-content">
|
||||
<el-input
|
||||
v-model="chatData.message"
|
||||
@keydown="handleKeydown"
|
||||
:placeholder="generateTitle('在此输入您的问题或需求,有问必答,Shift+Enter换行')"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: props.minRows }"
|
||||
:disabled="disableStatus"
|
||||
>
|
||||
</el-input>
|
||||
<div class="btn-box">
|
||||
<el-button type="primary" @click="submitFun" :disabled="disableStatus"
|
||||
><el-icon color="#fff" size="16"><Promotion /></el-icon>{{generateTitle('发送')}}</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, defineProps, defineEmits, defineExpose } from 'vue'
|
||||
import { Promotion } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {generateTitle} from '../utils/i18n'
|
||||
|
||||
// import { chat } from "../api/user";
|
||||
const handleKeydown = (event: any) => {
|
||||
if (!event.shiftKey && event.keyCode == 13) {
|
||||
event.cancelBubble = true
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
const props = defineProps({
|
||||
minRows: {
|
||||
type: Number,
|
||||
default: 6
|
||||
}
|
||||
})
|
||||
|
||||
const chatData = ref<any>({
|
||||
chat_id: '',
|
||||
message: ''
|
||||
})
|
||||
//如何在室温条件下合成CsPbBr3
|
||||
const disableStatus = ref(false)
|
||||
const emits = defineEmits(['submitFun'])
|
||||
const submitFun = async () => {
|
||||
if (!chatData.value.message) {
|
||||
ElMessage.error('请输入问题')
|
||||
return
|
||||
}
|
||||
if (disableStatus.value) {
|
||||
ElMessage.error('回答输出中,暂不能再次提问')
|
||||
return
|
||||
}
|
||||
chatData.value.chat_id = new Date().getTime()
|
||||
emits('submitFun', JSON.stringify(chatData.value))
|
||||
chatData.value = {
|
||||
chat_id: '',
|
||||
message: ''
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
disableStatus
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.textarea-content {
|
||||
position: relative;
|
||||
.btn-box {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
.el-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 4px;
|
||||
}
|
||||
}
|
||||
:deep(.el-textarea__inner) {
|
||||
width: 100%;
|
||||
border-radius: 6px;
|
||||
background: rgba(0, 30, 70, 0.5);
|
||||
border: 1px solid rgba(59, 164, 255, 1);
|
||||
box-shadow: 0px 0px 16px 0px rgba(0, 149, 230, 1) inset;
|
||||
padding: 16px;
|
||||
font-size: 14px;
|
||||
color: rgba(157, 215, 248, 1);
|
||||
box-sizing: border-box;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 1px solid #3ba4ff;
|
||||
}
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
color: rgba(157, 215, 248, 1);
|
||||
}
|
||||
|
||||
&:-moz-placeholder {
|
||||
color: rgba(157, 215, 248, 1);
|
||||
}
|
||||
|
||||
&::-moz-placeholder {
|
||||
color: rgba(157, 215, 248, 1);
|
||||
}
|
||||
|
||||
&::-ms-input-placeholder {
|
||||
color: rgba(157, 215, 248, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
136
frontend/src/components/ValidCode.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div class="CharacterVerification" :style="{'width' : width + 'px','height' : height + 'px'}" ref="CharacterVerification">
|
||||
<canvas
|
||||
@click="refresh"
|
||||
:width="width"
|
||||
:height="height"
|
||||
ref="verifyCanvas"
|
||||
:style="{ cursor: 'pointer' }"
|
||||
></canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, onMounted } from "vue";
|
||||
export default defineComponent({
|
||||
name: "CharacterVerification",
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 40,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "number", //图形验证码默认类型blend:数字字母混合类型、number:纯数字、letter:纯字母
|
||||
},
|
||||
},
|
||||
setup(props, {expose}) {
|
||||
const numArr = "0,1,2,3,4,5,6,7,8,9".split(",");
|
||||
const letterArr =
|
||||
"a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z".split(
|
||||
","
|
||||
);
|
||||
const code = ref("");
|
||||
const CharacterVerification = ref<HTMLDivElement | null>(null);
|
||||
const verifyCanvas = ref<HTMLCanvasElement | null>(null);
|
||||
let ctx;
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
})
|
||||
const refresh = () => {
|
||||
code.value = "";
|
||||
if (verifyCanvas.value?.getContext) {
|
||||
ctx = verifyCanvas.value.getContext("2d") as CanvasRenderingContext2D;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.textBaseline = "middle";
|
||||
|
||||
ctx.fillStyle = randomColor(180, 240);
|
||||
ctx.fillRect(0, 0, props.width, props.height);
|
||||
|
||||
if (props.type == "blend") {
|
||||
var txtArr = numArr.concat(letterArr);
|
||||
} else if (props.type == "number") {
|
||||
var txtArr = numArr;
|
||||
} else {
|
||||
var txtArr = letterArr;
|
||||
}
|
||||
|
||||
for (var i = 1; i <= 4; i++) {
|
||||
var txt = txtArr[randomNum(0, txtArr.length)];
|
||||
code.value += txt;
|
||||
ctx.font = randomNum(props.height / 2, props.height) + "px SimHei"; //随机生成字体大小
|
||||
ctx.fillStyle = randomColor(50, 160);
|
||||
ctx.shadowOffsetX = randomNum(-3, 3);
|
||||
ctx.shadowOffsetY = randomNum(-3, 3);
|
||||
ctx.shadowBlur = randomNum(-3, 3);
|
||||
ctx.shadowColor = "rgba(0, 0, 0, 0.3)";
|
||||
var x = (props.width / 5) * i;
|
||||
var y = props.height / 2;
|
||||
var deg = randomNum(-30, 30);
|
||||
ctx.translate(x, y);
|
||||
ctx.rotate((deg * Math.PI) / 180);
|
||||
ctx.fillText(txt, 0, 0);
|
||||
ctx.rotate((-deg * Math.PI) / 180);
|
||||
ctx.translate(-x, -y);
|
||||
}
|
||||
for (var i = 0; i < 4; i++) {
|
||||
ctx.strokeStyle = randomColor(40, 180);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(randomNum(0, props.width), randomNum(0, props.height));
|
||||
ctx.lineTo(randomNum(0, props.width), randomNum(0, props.height));
|
||||
ctx.stroke();
|
||||
}
|
||||
for (var i = 0; i < props.width / 4; i++) {
|
||||
ctx.fillStyle = randomColor(0, 255);
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
randomNum(0, props.width),
|
||||
randomNum(0, props.height),
|
||||
1,
|
||||
0,
|
||||
2 * Math.PI
|
||||
);
|
||||
ctx.fill();
|
||||
}
|
||||
};
|
||||
|
||||
const randomColor = (min: number, max: number) => {
|
||||
var r = randomNum(min, max);
|
||||
var g = randomNum(min, max);
|
||||
var b = randomNum(min, max);
|
||||
return "rgb(" + r + "," + g + "," + b + ")";
|
||||
};
|
||||
const randomNum = (min: number, max: number) => {
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
};
|
||||
const validate = (iptCode: string) => {
|
||||
var newIptCode = iptCode.toLowerCase();
|
||||
var v_code = code.value.toLowerCase();
|
||||
if (newIptCode == v_code) {
|
||||
return true;
|
||||
} else {
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
expose({ validate });
|
||||
|
||||
return {
|
||||
CharacterVerification,
|
||||
verifyCanvas,
|
||||
refresh,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
610
frontend/src/components/collapseView/index.less
Normal file
@@ -0,0 +1,610 @@
|
||||
.is-sidebar-open {
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
.aside-box {
|
||||
// height: calc(100vh - 175px);
|
||||
overflow: inherit;
|
||||
position: relative;
|
||||
|
||||
.collapse-demo {
|
||||
width: 36px;
|
||||
height: calc(100% - 260px);
|
||||
background: #093e7a;
|
||||
border: 1px solid rgba(2, 83, 137, 1);
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
// transition: all .25s ease;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
.collapse-title {
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(2, 83, 137, 1);
|
||||
background: rgba(9, 61, 124, 1);
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: -12px;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
width: 230px;
|
||||
padding: 0;
|
||||
background: rgba(0, 37, 78, 1);
|
||||
|
||||
.collapse-title {
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
background: rgba(33, 78, 131, 1);
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
box-sizing: border-box;
|
||||
padding-left: 30px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.user-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 10px;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
left: 0;
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-box {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 6px;
|
||||
width: 230px;
|
||||
height: 230px;
|
||||
// background: url('../../assets/chart/图片9.png') no-repeat;
|
||||
// background-size: cover;
|
||||
|
||||
.body-box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.out-round-list {
|
||||
width: 230px;
|
||||
height: 230px;
|
||||
position: relative;
|
||||
|
||||
.out-round-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
position: absolute;
|
||||
|
||||
// &:nth-child(1) {
|
||||
// top: 89px;
|
||||
// left: 117px;
|
||||
// transform: translateX(-50%) rotate(0deg);
|
||||
// }
|
||||
&:nth-child(1) {
|
||||
top: 0;
|
||||
left: 88px;
|
||||
transform: translateX(-50%) rotate(0deg);
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
top: 119px;
|
||||
left: -30px;
|
||||
transform: translateY(-50%) rotate(-87deg);
|
||||
|
||||
.round-arrow-box {
|
||||
transform: rotate(4deg);
|
||||
|
||||
.round-lage-arrow {
|
||||
top: 10px;
|
||||
left: -10px;
|
||||
}
|
||||
|
||||
.round-arrow {
|
||||
top: 26px;
|
||||
left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.model-box {
|
||||
transform: rotate(84deg);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
bottom: -8px;
|
||||
left: 85px;
|
||||
transform: translateX(-50%) rotate(208deg);
|
||||
|
||||
.round-arrow-box {
|
||||
transform: rotate(6deg);
|
||||
|
||||
.round-lage-arrow {
|
||||
top: 24px;
|
||||
left: -8px;
|
||||
}
|
||||
|
||||
.round-arrow {
|
||||
top: 40px;
|
||||
left: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
.model-box {
|
||||
transform: rotate(151deg);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
bottom: 26px;
|
||||
right: -83px;
|
||||
transform: translateX(-50%) rotate(136deg);
|
||||
|
||||
.round-arrow-box {
|
||||
transform: rotate(2deg);
|
||||
|
||||
.round-lage-arrow {
|
||||
top: 21px;
|
||||
left: -7px;
|
||||
}
|
||||
|
||||
.round-arrow {
|
||||
top: 38px;
|
||||
left: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
.model-box {
|
||||
transform: rotate(223deg);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(5) {
|
||||
top: 62px;
|
||||
right: -27px;
|
||||
transform: translateY(-50%) rotate(82deg);
|
||||
|
||||
.round-arrow-box {
|
||||
transform: rotate(-15deg);
|
||||
|
||||
.round-lage-arrow {
|
||||
top: 29px;
|
||||
left: -11px;
|
||||
}
|
||||
|
||||
.round-arrow {
|
||||
top: 43px;
|
||||
left: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.model-box {
|
||||
transform: rotate(-84deg);
|
||||
}
|
||||
}
|
||||
|
||||
.round-arrow-box {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
transform: rotate(0deg);
|
||||
position: relative;
|
||||
|
||||
.round-lage-arrow {
|
||||
width: 61px;
|
||||
height: 34px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 19px;
|
||||
left: -16px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.round-arrow {
|
||||
width: 50px;
|
||||
height: 28px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 33px;
|
||||
left: 20px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.model-box {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: url('../../assets/chart/model.png') no-repeat;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 6px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.type-box {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
line-height: 20px;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
background: #deebf7;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.model-title {
|
||||
font-size: 12px;
|
||||
color: #97d7fb;
|
||||
transform: scale(0.7);
|
||||
margin-top: -3px;
|
||||
}
|
||||
}
|
||||
.chart-active {
|
||||
|
||||
.type-box {
|
||||
color: #fff;
|
||||
background: #133158;
|
||||
text-align: center;
|
||||
z-index: 9;
|
||||
}
|
||||
.model-title {
|
||||
color: #333;
|
||||
}
|
||||
.chart-bg {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 2.5px;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
border-radius: 50%;
|
||||
background: rgb(181 210 236);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.center-list {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
.center-round-list {
|
||||
position: relative;
|
||||
|
||||
.center-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 6px;
|
||||
position: absolute;
|
||||
|
||||
.stand {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.out-arrow {
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.in-arrow {
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.sit {
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
.center-item {
|
||||
transform: rotate(0deg);
|
||||
top: 40px;
|
||||
left: 72px;
|
||||
}
|
||||
|
||||
.sit {
|
||||
position: absolute;
|
||||
left: 34px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
|
||||
.in-arrow {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 0;
|
||||
transform: rotate(68deg);
|
||||
}
|
||||
|
||||
|
||||
.out-arrow {
|
||||
position: absolute;
|
||||
left: 9px;
|
||||
top: 0px;
|
||||
transform: rotate(70deg);
|
||||
}
|
||||
|
||||
.stand {
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.center-item {
|
||||
transform: rotate(-71deg) rotateY(-175deg);
|
||||
top: 56px;
|
||||
left: 47px;
|
||||
|
||||
}
|
||||
|
||||
.sit {
|
||||
position: absolute;
|
||||
left: 29px;
|
||||
top: -3px;
|
||||
transform: rotate(-70deg) rotateY(166deg);
|
||||
}
|
||||
|
||||
|
||||
.in-arrow {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 0;
|
||||
transform: rotate(68deg);
|
||||
}
|
||||
|
||||
|
||||
.out-arrow {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: -2px;
|
||||
transform: rotate(70deg);
|
||||
}
|
||||
|
||||
.stand {
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
top: -1px;
|
||||
transform: rotate(-71deg) rotateY(172deg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
.center-item {
|
||||
transform: rotate(-136deg) rotateY(-175deg);
|
||||
top: 117px;
|
||||
left: 39px;
|
||||
}
|
||||
|
||||
.sit {
|
||||
position: absolute;
|
||||
left: 32px;
|
||||
top: 0px;
|
||||
transform: rotate(-141deg) rotateY(166deg);
|
||||
}
|
||||
|
||||
|
||||
.in-arrow {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 0;
|
||||
transform: rotate(68deg);
|
||||
}
|
||||
|
||||
|
||||
.out-arrow {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: -2px;
|
||||
transform: rotate(70deg);
|
||||
}
|
||||
|
||||
.stand {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 4px;
|
||||
transform: rotate(-112deg) rotateY(172deg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
.center-item {
|
||||
transform: rotate(-211deg) rotateY(-175deg);
|
||||
top: 143px;
|
||||
left: 109px;
|
||||
}
|
||||
|
||||
.sit {
|
||||
position: absolute;
|
||||
left: 32px;
|
||||
top: -3px;
|
||||
transform: rotate(-225deg) rotateY(166deg);
|
||||
}
|
||||
|
||||
|
||||
.in-arrow {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 0;
|
||||
transform: rotate(68deg);
|
||||
}
|
||||
|
||||
|
||||
.out-arrow {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: -2px;
|
||||
transform: rotate(70deg);
|
||||
}
|
||||
|
||||
.stand {
|
||||
position: absolute;
|
||||
left: 4px;
|
||||
top: 0px;
|
||||
transform: rotate(-200deg) rotateY(172deg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&:nth-child(5) {
|
||||
.center-item {
|
||||
transform: rotate(-299deg);
|
||||
top: 56px;
|
||||
left: 131px;
|
||||
}
|
||||
|
||||
.sit {
|
||||
position: absolute;
|
||||
left: 32px;
|
||||
top: -4px;
|
||||
transform: rotate(-420deg);
|
||||
}
|
||||
|
||||
|
||||
.in-arrow {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 0;
|
||||
transform: rotate(68deg);
|
||||
}
|
||||
|
||||
|
||||
.out-arrow {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: -2px;
|
||||
transform: rotate(70deg);
|
||||
}
|
||||
|
||||
.stand {
|
||||
position: absolute;
|
||||
left: 9px;
|
||||
top: -4px;
|
||||
transform: rotate(-60deg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-list {
|
||||
position: absolute;
|
||||
top: 89px;
|
||||
left: 89px;
|
||||
|
||||
.model-box {
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
border-radius: 50%;
|
||||
background: url('../../assets/chart/model.png') no-repeat;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
padding-top: 6px;
|
||||
|
||||
.type-box {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
line-height: 20px;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
background: #deebf7;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.model-title {
|
||||
font-size: 12px;
|
||||
color: #97d7fb;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.model-box {
|
||||
position: relative;
|
||||
}
|
||||
.chart-active {
|
||||
|
||||
.type-box {
|
||||
color: #fff;
|
||||
background: #133158;
|
||||
text-align: center;
|
||||
z-index: 9;
|
||||
}
|
||||
.model-title {
|
||||
color: #333;
|
||||
}
|
||||
.chart-bg {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 2.5px;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
border-radius: 50%;
|
||||
background: rgb(181 210 236);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
162
frontend/src/components/collapseView/index.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div
|
||||
class="aside-box"
|
||||
:class="{ 'is-sidebar-open': isCollapse }"
|
||||
width="230px"
|
||||
>
|
||||
<div>
|
||||
<div class="collapse-demo" :class="{ active: isCollapse }" width="230px">
|
||||
<div class="collapse-title">
|
||||
<div class="icon-box" @click="changeStatusFun">
|
||||
<el-icon size="14" color="rgba(157, 215, 248, 1)"
|
||||
><component :is="getIcon()"></component
|
||||
></el-icon>
|
||||
</div>
|
||||
<div class="title">{{ generateTitle("模型调用流程") }}</div>
|
||||
</div>
|
||||
<div style="height: 100%" v-show="isCollapse">
|
||||
<steps-view :reasoningList="reasoningList" />
|
||||
</div>
|
||||
<!-- <div class="user-box">
|
||||
<img src="../../assets/logo.png" alt="" />
|
||||
<span v-show="isCollapse">Jayson</span>
|
||||
</div> -->
|
||||
<div class="chart-box" v-show="isCollapse">
|
||||
<div class="body-box">
|
||||
<div class="out-round-list">
|
||||
<div
|
||||
class="out-round-item"
|
||||
v-for="(item, index) in chartList.slice(1, 6)"
|
||||
:key="index"
|
||||
>
|
||||
<div class="round-arrow-box">
|
||||
<div class="round-lage-arrow">
|
||||
<img src="../../assets/chart/round-lage-arrow.png" alt="" />
|
||||
</div>
|
||||
<div class="round-arrow" v-if="index != 4">
|
||||
<img src="../../assets/chart/round-arrow.png" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-box" :class="{'chart-active': getStatus(item.title)}">
|
||||
<div
|
||||
class="chart-bg"
|
||||
v-if="getStatus(item.title)"
|
||||
></div>
|
||||
<div class="type-box">{{ item.type }}</div>
|
||||
<div class="model-title">{{ item.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center-list">
|
||||
<div
|
||||
class="center-round-list"
|
||||
v-for="(item, index) in chartList.slice(1, 6)"
|
||||
:key="item"
|
||||
>
|
||||
<div class="center-item">
|
||||
<img class="sit" src="../../assets/chart/sit.png" alt="" />
|
||||
<img
|
||||
class="in-arrow"
|
||||
src="../../assets/chart/in-arrow.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
class="out-arrow"
|
||||
src="../../assets/chart/out-arrow.png"
|
||||
alt=""
|
||||
v-if="index != 4"
|
||||
/>
|
||||
<img
|
||||
class="stand"
|
||||
src="../../assets/chart/stand.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-list">
|
||||
<div class="model-box" :class="{'chart-active': getStatus(chartList[0].title)}">
|
||||
<div
|
||||
class="chart-bg"
|
||||
v-if="getStatus(chartList[0].title)"
|
||||
></div>
|
||||
<div class="type-box">{{ chartList[0].type }}</div>
|
||||
<div class="model-title">
|
||||
{{ chartList[0].title.substr(0, 4) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import StepsView from "../StepsView.vue";
|
||||
import { ref, defineProps, defineExpose, defineEmits } from "vue";
|
||||
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
|
||||
import { generateTitle } from "../../utils/i18n";
|
||||
const getIcon = () => {
|
||||
return isCollapse.value ? ArrowLeft : ArrowRight;
|
||||
};
|
||||
const props = defineProps({
|
||||
reasoningList: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
});
|
||||
const chartList = ref<Array<any>>([
|
||||
{
|
||||
type: "G1",
|
||||
title: "Planner",
|
||||
},
|
||||
{
|
||||
type: "G2",
|
||||
title: "Retrieval",
|
||||
},
|
||||
{
|
||||
type: "G3",
|
||||
title: "Generate",
|
||||
},
|
||||
{
|
||||
type: "G4",
|
||||
title: "Converter",
|
||||
},
|
||||
{
|
||||
type: "G5",
|
||||
title: "Executor",
|
||||
},
|
||||
{
|
||||
type: "G6",
|
||||
title: "Optimize",
|
||||
},
|
||||
]);
|
||||
const emits = defineEmits(["changeStatusFun"]);
|
||||
const getStatus = (val: any) => {
|
||||
let arr: any = props.reasoningList;
|
||||
if (arr.length) {
|
||||
let children: any = arr[arr.length - 1].children;
|
||||
if (!children.length) {
|
||||
return false;
|
||||
}
|
||||
let title = children[children.length - 1].title.split(":")[0].toLowerCase();
|
||||
return title === val.toLowerCase();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const changeStatusFun = () => {
|
||||
isCollapse.value = !isCollapse.value;
|
||||
emits("changeStatusFun", isCollapse.value);
|
||||
};
|
||||
const isCollapse = ref<boolean>(true);
|
||||
defineExpose({
|
||||
isCollapse,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "./index.less";
|
||||
</style>
|
||||
6
frontend/src/constants/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// 通用请求头
|
||||
export enum ContentTypeEnum {
|
||||
Json = 'application/json;charset=UTF-8',
|
||||
FormURLEncoded = 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
FormData = 'multipart/form-data;charset=UTF-8'
|
||||
}
|
||||
31
frontend/src/lang/en.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
export default {
|
||||
routers:{
|
||||
logOut:'Log Out',
|
||||
restore:'Restore data',
|
||||
'智能协作无界,材料创新无限':'Collaborative Intelligence, Revolutionary Materials',
|
||||
'问题设定':'Problem Setting',
|
||||
'模型名称':'Model Name',
|
||||
'新建对话':'Create New Conversation',
|
||||
'推理完成,如果希望了解更多可继续在下面框中提问或新建对话。':'The reasoning is complete. If you wish to learn more, you can continue to ask questions or create a new conversation in the box below.',
|
||||
'推理中':'In Reasoning',
|
||||
'角色':'Role',
|
||||
'模型调用流程':'Model Calling Process',
|
||||
'发送':'send',
|
||||
'您好,多智能体MARS为您服务':'Hello,multi-agent MARS is at your service',
|
||||
'推荐问题':'Recommended Questions',
|
||||
'在此输入您的问题或需求,有问必答,Shift+Enter换行':'Enter your question or requirement here, answer all questions, Shift+Enter line break',
|
||||
'MARS模型创制系统':'MARS Model Creation System',
|
||||
'账号':'account',
|
||||
'密码':'password',
|
||||
'验证码':'code',
|
||||
'登录':'login',
|
||||
'请输入账号':'Please enter your account',
|
||||
'请输入密码':'Please enter your password',
|
||||
'请输入验证码':'Please enter your code',
|
||||
'验证码错误':'code error',
|
||||
'请输入问题':'Please enter the question',
|
||||
'回答输出中,暂不能再次提问':'Answer output, cannot be asked again temporarily',
|
||||
}
|
||||
|
||||
}
|
||||
31
frontend/src/lang/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
// element-plus 中的语言配置
|
||||
import elementEnLocale from 'element-plus/es/locale/lang/en'
|
||||
import elementZhLocale from 'element-plus/es/locale/lang/zh-cn'
|
||||
|
||||
// 自己的语言配置
|
||||
import enLocale from './en'
|
||||
import zhLocale from './zh'
|
||||
|
||||
// 语言配置整合
|
||||
const messages = {
|
||||
en:{
|
||||
...enLocale,
|
||||
...elementEnLocale
|
||||
},
|
||||
'zh-cn':{
|
||||
...zhLocale,
|
||||
elementZhLocale
|
||||
}
|
||||
}
|
||||
|
||||
// 创建 i18n
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
globalInjection:true, // 全局模式,可以直接使用 $t
|
||||
locale: 'zh-cn',
|
||||
messages: messages
|
||||
})
|
||||
|
||||
export default i18n
|
||||
30
frontend/src/lang/zh.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export default {
|
||||
routers:{
|
||||
logOut:'退出登录',
|
||||
restore:'还原数据',
|
||||
'智能协作无界,材料创新无限':'智能协作无界,材料创新无限',
|
||||
'问题设定':'问题设定',
|
||||
'模型名称':'模型名称',
|
||||
'新建对话':'新建对话',
|
||||
'推理完成,如果希望了解更多可继续在下面框中提问或新建对话。':'推理完成,如果希望了解更多可继续在下面框中提问或新建对话。',
|
||||
'推理中':'推理中',
|
||||
'角色':'角色',
|
||||
'模型调用流程':'模型调用流程',
|
||||
'发送':'发送',
|
||||
'您好,多智能体MARS为您服务':'您好,多智能体MARS为您服务',
|
||||
'推荐问题':'推荐问题',
|
||||
'在此输入您的问题或需求,有问必答,Shift+Enter换行':'在此输入您的问题或需求,有问必答,Shift+Enter换行',
|
||||
'MARS模型创制系统':'MARS模型创制系统',
|
||||
'账号':'账号',
|
||||
'密码':'密码',
|
||||
'验证码':'验证码',
|
||||
'登录':'登录',
|
||||
'请输入账号':'请输入账号',
|
||||
'请输入密码':'请输入密码',
|
||||
'请输入验证码':'请输入验证码',
|
||||
'验证码错误':'验证码错误',
|
||||
'请输入问题':'请输入问题',
|
||||
'回答输出中,暂不能再次提问':'回答输出中,暂不能再次提问',
|
||||
}
|
||||
|
||||
}
|
||||
19
frontend/src/layout/index.less
Normal file
@@ -0,0 +1,19 @@
|
||||
.common-layout {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
.el-main {
|
||||
// width: 100vw;
|
||||
height: calc(100vh - 40px);
|
||||
}
|
||||
|
||||
.feeter-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
color: rgba(47, 136, 191, 1);
|
||||
}
|
||||
}
|
||||
18
frontend/src/layout/index.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div class="common-layout">
|
||||
<el-container>
|
||||
<el-main style="padding: 40px 32px 32px">
|
||||
<router-view></router-view>
|
||||
</el-main>
|
||||
<el-footer style="height: 40px">
|
||||
<div class="feeter-box">{{ generateTitle('智能协作无界,材料创新无限') }}</div>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {generateTitle} from '../utils/i18n'
|
||||
</script>
|
||||
<style scoped>
|
||||
@import "./index.less";
|
||||
</style>
|
||||
19
frontend/src/main.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import App from './App.vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import router from "./router"
|
||||
import i18n from './lang/index'
|
||||
const app = createApp(App)
|
||||
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
app.use(createPinia())
|
||||
app.use(ElementPlus)
|
||||
app.mount('#app')
|
||||
58
frontend/src/router/index.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { createRouter, createWebHashHistory } from "vue-router"
|
||||
|
||||
import Home from '../view/home/index.vue'
|
||||
import Login from '../view/login/index.vue'
|
||||
import Reasoning from '../view/reasoning/index.vue'
|
||||
import LayoutView from '../layout/index.vue'
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: Login,
|
||||
meta: {
|
||||
title: '登录'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
redirect: '/home',
|
||||
component: LayoutView,
|
||||
meta: {
|
||||
title: '主页'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/home',
|
||||
component: Home,
|
||||
meta: {
|
||||
title: '主页'
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/reasoning',
|
||||
redirect: '/reasoning',
|
||||
component: LayoutView,
|
||||
meta: {
|
||||
title: '推理中'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/reasoning',
|
||||
component: Reasoning,
|
||||
meta: {
|
||||
title: '推理中'
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes
|
||||
});
|
||||
|
||||
|
||||
export default router;
|
||||
13
frontend/src/store/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// import { defineStore } from 'pinia'
|
||||
// export const useUserStore = defineStore('user', {
|
||||
// state: () => ({
|
||||
// token: '', // 登录token
|
||||
// }),
|
||||
// getters: {},
|
||||
// actions: {
|
||||
// },
|
||||
// persist: {
|
||||
// key: 'user',
|
||||
// paths: ['token']
|
||||
// }
|
||||
// })
|
||||
44
frontend/src/style.css
Normal file
@@ -0,0 +1,44 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
background: url('./assets/background.png') no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
#app {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
99
frontend/src/types/axios.d.ts
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { AxiosRequestConfig } from 'axios'
|
||||
|
||||
/**
|
||||
* Axios请求配置
|
||||
*/
|
||||
export interface RequestOptions {
|
||||
/**
|
||||
* 接口地址
|
||||
*
|
||||
* 例: http://www.baidu.com/api
|
||||
*/
|
||||
apiUrl?: string
|
||||
/**
|
||||
* 是否自动添加接口前缀
|
||||
*
|
||||
* 例: http://www.baidu.com/api
|
||||
* urlPrefix: 'api'
|
||||
*/
|
||||
isJoinPrefix?: boolean
|
||||
/**
|
||||
* 接口前缀
|
||||
*/
|
||||
urlPrefix?: string
|
||||
/**
|
||||
* POST请求的时候添加参数到Url中
|
||||
*/
|
||||
joinParamsToUrl?: boolean
|
||||
/**
|
||||
* 格式化提交参数时间
|
||||
*/
|
||||
formatDate?: boolean
|
||||
/**
|
||||
* 是否需要对响应数据进行处理
|
||||
*/
|
||||
isTransformResponse?: boolean
|
||||
/**
|
||||
* 是否返回原生响应头
|
||||
*
|
||||
* 例: 需要获取响应头时使用该属性
|
||||
*/
|
||||
isReturnNativeResponse?: boolean
|
||||
/**
|
||||
* 是否忽略请求取消令牌
|
||||
*
|
||||
* 如果启用,则重复请求时不进行处理
|
||||
*
|
||||
* 如果禁用,则重复请求时会取消当前请求
|
||||
*/
|
||||
ignoreCancelToken?: boolean
|
||||
/**
|
||||
* 自动对请求添加时间戳参数
|
||||
*/
|
||||
joinTime?: boolean
|
||||
/**
|
||||
* 是否携带Token
|
||||
*/
|
||||
withToken?: boolean
|
||||
externalUrlStatus?: Number
|
||||
/**
|
||||
* 重试配置
|
||||
*/
|
||||
retry?: {
|
||||
/**
|
||||
* 重试次数
|
||||
*/
|
||||
count: number
|
||||
/**
|
||||
* 隔多久重试
|
||||
*
|
||||
* 单位: 毫秒
|
||||
*/
|
||||
delay: number
|
||||
}
|
||||
/**
|
||||
* 接口级节流
|
||||
*
|
||||
* 单位: 毫秒
|
||||
*/
|
||||
throttle?: {
|
||||
delay: number
|
||||
}
|
||||
/**
|
||||
* 接口级防抖
|
||||
*
|
||||
* 单位: 毫秒
|
||||
*/
|
||||
debounce?: {
|
||||
delay: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface Result<T = any> {
|
||||
code: number
|
||||
data: T
|
||||
}
|
||||
|
||||
export interface AxiosRequestConfigRetry extends AxiosRequestConfig {
|
||||
retryCount?: number
|
||||
}
|
||||
5
frontend/src/types/env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface ImportMetaEnv {
|
||||
readonly VITE_IS_REQUEST_PROXY: string;
|
||||
readonly VITE_API_URL: string;
|
||||
readonly VITE_API_URL_PREFIX: string;
|
||||
}
|
||||
16
frontend/src/types/globals.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Vue
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
declare type ClassName = { [className: string]: any } | ClassName[] | string
|
||||
|
||||
declare module '*.svg' {
|
||||
const CONTENT: string
|
||||
export default CONTENT
|
||||
}
|
||||
|
||||
declare type Recordable<T = any> = Record<string, T>
|
||||
15
frontend/src/types/interface.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
export interface MenuRoute {
|
||||
path: string
|
||||
title?: string
|
||||
name?: string
|
||||
icon?:
|
||||
| string
|
||||
| {
|
||||
render: () => void
|
||||
}
|
||||
redirect?: string
|
||||
children: MenuRoute[]
|
||||
meta: any
|
||||
}
|
||||
|
||||
export type ClassName = { [className: string]: any } | ClassName[] | string
|
||||
60
frontend/src/utils/agent.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { reactive } from "vue";
|
||||
interface ViolationBOType {
|
||||
[key: string]: string;
|
||||
}
|
||||
const Agent: ViolationBOType = reactive({
|
||||
// 1
|
||||
Converter_Group_Admin: "Converter: Converter_Group_Admin",
|
||||
scheme_converter: "Converter: scheme_converter",
|
||||
converter_critic: "Converter: converter_critic",
|
||||
mergrid_ploter: "Converter: mergrid_ploter",
|
||||
scheme_code_writer: "Converter: scheme_code_writer",
|
||||
scheme_code_critic: "Converter: scheme_code_critic",
|
||||
// 2
|
||||
|
||||
expriment_code_writer: "Executor: expriment_code_writer",
|
||||
data_collector: "Executor: data_collector",
|
||||
collector_code_writer: "Executor: collector_code_writer",
|
||||
Inner_Executor_Admin: "Executor: Inner_Executor_Admin",
|
||||
// Inner_Executor_Admin: "Executor: Executor_Group_Admin",
|
||||
// 3
|
||||
Generate_Group_Admin: "Generate: Generate_Group_Admin",
|
||||
structure_scientist: "Generate: structure_scientist",
|
||||
property_scientist: "Generate: property_scientist",
|
||||
application_scientist: "Generate: application_scientist",
|
||||
synthesis_scientist: "Generate: synthesis_scientist",
|
||||
scheme_critic: "Generate: scheme_critic",
|
||||
|
||||
// 4
|
||||
analysis_executor: "Optimize: analysis_executor",
|
||||
analysis_pl_uv: "Optimize: analysis_pl_uv",
|
||||
analysis_picturer: "Optimize: analysis_picturer",
|
||||
Experiment_Optimizer: "Optimize: Experiment_Optimizer",
|
||||
optimizer_critic: "Optimize: optimizer_critic",
|
||||
Analysis_Group_Admin: "Optimize: Analysis_Group_Admin",
|
||||
|
||||
//
|
||||
Outer_Retrieval_Admin: "Retrieval: Outer_Retrieval_Admin",
|
||||
Outer_Converter_Admin: "Converter: Outer_Converter_Admin",
|
||||
Outer_Executor_Admin: "Executor: Outer_Executor_Admin",
|
||||
experiment_executor: "Executor: experiment_executor",
|
||||
Outer_Generate_Admin: "Generate: Outer_Generate_Admin",
|
||||
Outer_Analysis_Admin: "Optimize: Outer_Analysis_Admin",
|
||||
vector_code_executor: "Retrieval: vector_code_executor",
|
||||
graphrag_code_executor: "Retrieval: graphrag_code_executor",
|
||||
web_code_executor: "Retrieval: web_code_executor",
|
||||
web_summary: "Retrieval: web_summary",
|
||||
|
||||
// 5
|
||||
Inner_Retrieval_Admin: "Retrieval: Inner_Retrieval_Admin",
|
||||
// Inner_Retrieval_Admin: "Retrieval: Retrieval_Group_Admin",
|
||||
vector_searcher: "Retrieval: vector_searcher",
|
||||
graphrag_searcher: "Retrieval: graphrag_searcher",
|
||||
web_searcher: "Retrieval: web_searcher",
|
||||
Planer: "Planner",
|
||||
Planner: "Planner",
|
||||
})
|
||||
export function getAgent(data: any) {
|
||||
let rData = Object.keys(Agent).filter(k => k.toLowerCase() === data.toLowerCase())[0]
|
||||
return Agent[rData] || ''
|
||||
}
|
||||
10
frontend/src/utils/i18n.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import i18n from "../lang/index"
|
||||
export const generateTitle=(title:string)=>{
|
||||
//i18n.global.te('routers.' + title) 只能在ts文件中用
|
||||
const hasKey = i18n.global.te('routers.' + title)
|
||||
if (hasKey) {
|
||||
const translatedTitle = i18n.global.t('routers.' + title)
|
||||
return translatedTitle
|
||||
}
|
||||
return title
|
||||
}
|
||||
324
frontend/src/utils/request/Axios.ts
Normal file
@@ -0,0 +1,324 @@
|
||||
import axios, {
|
||||
AxiosError,
|
||||
AxiosInstance,
|
||||
AxiosRequestConfig,
|
||||
AxiosRequestHeaders,
|
||||
AxiosResponse,
|
||||
InternalAxiosRequestConfig
|
||||
} from 'axios'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import debounce from 'lodash/debounce'
|
||||
import isFunction from 'lodash/isFunction'
|
||||
import throttle from 'lodash/throttle'
|
||||
import { stringify } from 'qs'
|
||||
|
||||
import { ContentTypeEnum } from '../../constants'
|
||||
import { AxiosRequestConfigRetry, RequestOptions, Result } from '../../types/axios'
|
||||
|
||||
import { AxiosCanceler } from './AxiosCancel'
|
||||
import { CreateAxiosOptions } from './AxiosTransform'
|
||||
|
||||
// const store = useSettingStore();
|
||||
// let tokenUpdateing = false;
|
||||
// // 创建一个 axios 实例
|
||||
// const service = axios.create({
|
||||
// timeout: 5000,
|
||||
// });
|
||||
// const reqLists = ref<any>([]);
|
||||
/**
|
||||
* Axios 模块
|
||||
*/
|
||||
export class VAxios {
|
||||
/**
|
||||
* Axios实例句柄
|
||||
* @private
|
||||
*/
|
||||
private instance: AxiosInstance
|
||||
|
||||
/**
|
||||
* Axios配置
|
||||
* @private
|
||||
*/
|
||||
private readonly options: CreateAxiosOptions
|
||||
|
||||
constructor(options: CreateAxiosOptions) {
|
||||
this.options = options
|
||||
this.instance = axios.create(options)
|
||||
this.setupInterceptors()
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Axios实例
|
||||
* @param config
|
||||
* @private
|
||||
*/
|
||||
private createAxios(config: CreateAxiosOptions): void {
|
||||
this.instance = axios.create(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据处理类
|
||||
* @private
|
||||
*/
|
||||
private getTransform() {
|
||||
const { transform } = this.options
|
||||
return transform
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Axios实例
|
||||
*/
|
||||
getAxios(): AxiosInstance {
|
||||
return this.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置Axios
|
||||
* @param config
|
||||
*/
|
||||
configAxios(config: CreateAxiosOptions) {
|
||||
if (!this.instance) return
|
||||
this.createAxios(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置公共头部信息
|
||||
* @param headers
|
||||
*/
|
||||
setHeader(headers: Record<string, string>): void {
|
||||
if (!this.instance) return
|
||||
Object.assign(this.instance.defaults.headers, headers)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置拦截器
|
||||
* @private
|
||||
*/
|
||||
private setupInterceptors() {
|
||||
const transform = this.getTransform()
|
||||
if (!transform) return
|
||||
|
||||
const {
|
||||
requestInterceptors,
|
||||
requestInterceptorsCatch,
|
||||
responseInterceptors,
|
||||
responseInterceptorsCatch
|
||||
} = transform
|
||||
const axiosCanceler = new AxiosCanceler()
|
||||
this.instance.defaults.withCredentials = false // 解决内网多api跨越问题
|
||||
// 请求拦截器
|
||||
this.instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||||
// 如果忽略取消令牌,则不会取消重复的请求
|
||||
// @ts-ignore
|
||||
const { ignoreCancelToken } = config.requestOptions
|
||||
const ignoreCancel = ignoreCancelToken ?? this.options.requestOptions?.ignoreCancelToken
|
||||
if (!ignoreCancel) axiosCanceler.addPending(config)
|
||||
|
||||
if (requestInterceptors && isFunction(requestInterceptors)) {
|
||||
config = requestInterceptors(config, this.options) as InternalAxiosRequestConfig
|
||||
}
|
||||
|
||||
return config
|
||||
}, undefined)
|
||||
|
||||
// 请求错误处理
|
||||
if (requestInterceptorsCatch && isFunction(requestInterceptorsCatch)) {
|
||||
this.instance.interceptors.request.use(undefined, requestInterceptorsCatch)
|
||||
}
|
||||
|
||||
// 响应结果处理
|
||||
this.instance.interceptors.response.use(async (res: AxiosResponse) => {
|
||||
if (res) axiosCanceler.removePending(res.config)
|
||||
if (
|
||||
responseInterceptors &&
|
||||
isFunction(responseInterceptors) &&
|
||||
responseInterceptors(res).data.code
|
||||
) {
|
||||
res = responseInterceptors(res)
|
||||
}
|
||||
return res
|
||||
}, undefined)
|
||||
|
||||
// 响应错误处理
|
||||
if (responseInterceptorsCatch && isFunction(responseInterceptorsCatch)) {
|
||||
this.instance.interceptors.response.use(undefined, (error) =>
|
||||
responseInterceptorsCatch(error, this.instance)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持 FormData 请求格式
|
||||
* @param config
|
||||
*/
|
||||
supportFormData(config: AxiosRequestConfig) {
|
||||
const headers = config.headers || (this.options.headers as AxiosRequestHeaders)
|
||||
const contentType = headers?.['Content-Type'] || headers?.['content-type']
|
||||
|
||||
if (
|
||||
contentType !== ContentTypeEnum.FormURLEncoded ||
|
||||
!Reflect.has(config, 'data') ||
|
||||
config.method?.toUpperCase() === 'GET'
|
||||
) {
|
||||
return config
|
||||
}
|
||||
|
||||
return {
|
||||
...config,
|
||||
data: stringify(config.data, { arrayFormat: 'brackets' })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持 params 序列化
|
||||
* @param config
|
||||
*/
|
||||
supportParamsStringify(config: AxiosRequestConfig) {
|
||||
const headers = config.headers || this.options.headers
|
||||
const contentType = headers?.['Content-Type'] || headers?.['content-type']
|
||||
|
||||
if (contentType === ContentTypeEnum.FormURLEncoded || !Reflect.has(config, 'params')) {
|
||||
return config
|
||||
}
|
||||
|
||||
return {
|
||||
...config,
|
||||
paramsSerializer: (params: any) => stringify(params, { arrayFormat: 'brackets' })
|
||||
}
|
||||
}
|
||||
|
||||
get<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
return this.request({ ...config, method: 'GET' }, options)
|
||||
}
|
||||
|
||||
post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
return this.request({ ...config, method: 'POST' }, options)
|
||||
}
|
||||
|
||||
put<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
return this.request({ ...config, method: 'PUT' }, options)
|
||||
}
|
||||
|
||||
delete<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
return this.request({ ...config, method: 'DELETE' }, options)
|
||||
}
|
||||
|
||||
patch<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||
return this.request({ ...config, method: 'PATCH' }, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件封装
|
||||
* @param key 文件所属的key
|
||||
* @param file 文件
|
||||
* @param config 请求配置
|
||||
* @param options
|
||||
*/
|
||||
upload<T = any>(
|
||||
key: string,
|
||||
file: File,
|
||||
config: AxiosRequestConfig,
|
||||
options?: RequestOptions
|
||||
): Promise<T> {
|
||||
const params: FormData = config.params ?? new FormData()
|
||||
params.append(key, file)
|
||||
|
||||
return this.request(
|
||||
{
|
||||
...config,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': ContentTypeEnum.FormData
|
||||
},
|
||||
params
|
||||
},
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求封装
|
||||
* @param config
|
||||
* @param options
|
||||
*/
|
||||
request<T = any>(config: AxiosRequestConfigRetry, options?: RequestOptions): Promise<T> {
|
||||
const { requestOptions }: any = this.options
|
||||
|
||||
if (requestOptions.throttle !== undefined && requestOptions.debounce !== undefined) {
|
||||
throw new Error('throttle and debounce cannot be set at the same time')
|
||||
}
|
||||
|
||||
if (requestOptions.throttle && requestOptions.throttle.delay !== 0) {
|
||||
return new Promise((resolve) => {
|
||||
throttle(
|
||||
() => resolve(this.synthesisRequest(config, options)),
|
||||
requestOptions.throttle.delay
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (requestOptions.debounce && requestOptions.debounce.delay !== 0) {
|
||||
return new Promise((resolve) => {
|
||||
debounce(
|
||||
() => resolve(this.synthesisRequest(config, options)),
|
||||
requestOptions.debounce.delay
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return this.synthesisRequest(config, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求方法
|
||||
* @private
|
||||
*/
|
||||
private async synthesisRequest<T = any>(
|
||||
config: AxiosRequestConfigRetry,
|
||||
options?: RequestOptions
|
||||
): Promise<T> {
|
||||
let conf: CreateAxiosOptions = cloneDeep(config)
|
||||
const transform = this.getTransform()
|
||||
|
||||
const { requestOptions } = this.options
|
||||
|
||||
const opt: RequestOptions = { ...requestOptions, ...options }
|
||||
|
||||
const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {}
|
||||
if (beforeRequestHook && isFunction(beforeRequestHook)) {
|
||||
conf = beforeRequestHook(conf, opt)
|
||||
}
|
||||
conf.requestOptions = opt
|
||||
|
||||
conf = this.supportFormData(conf)
|
||||
// 支持params数组参数格式化,因axios默认的toFormData即为brackets方式,无需配置paramsSerializer为qs,有需要可解除注释,参数参考qs文档
|
||||
// conf = this.supportParamsStringify(conf);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.instance
|
||||
.request<any, AxiosResponse<Result>>(!config.retryCount ? conf : config)
|
||||
.then((res: AxiosResponse<Result>) => {
|
||||
if (transformRequestHook && isFunction(transformRequestHook)) {
|
||||
try {
|
||||
const ret = transformRequestHook(res, opt)
|
||||
resolve(ret)
|
||||
} catch (err) {
|
||||
reject(err || new Error('请求错误!'))
|
||||
}
|
||||
return
|
||||
}
|
||||
resolve(res as unknown as Promise<T>)
|
||||
})
|
||||
.catch((e: Error | AxiosError) => {
|
||||
if (requestCatchHook && isFunction(requestCatchHook)) {
|
||||
reject(requestCatchHook(e, opt))
|
||||
return
|
||||
}
|
||||
if (axios.isAxiosError(e)) {
|
||||
// 在这里重写Axios的错误信息
|
||||
}
|
||||
reject(e)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
67
frontend/src/utils/request/AxiosCancel.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { AxiosRequestConfig, Canceler } from 'axios';
|
||||
import axios from 'axios';
|
||||
import isFunction from 'lodash/isFunction';
|
||||
|
||||
// 存储请求与取消令牌的键值对列表
|
||||
let pendingMap = new Map<string, Canceler>();
|
||||
|
||||
/**
|
||||
* 获取请求Url
|
||||
* @param config
|
||||
*/
|
||||
export const getPendingUrl = (config: AxiosRequestConfig) => [config.method, config.url].join('&');
|
||||
|
||||
/**
|
||||
* @description 请求管理器
|
||||
*/
|
||||
export class AxiosCanceler {
|
||||
/**
|
||||
* 添加请求到列表中
|
||||
* @param config
|
||||
*/
|
||||
addPending(config: AxiosRequestConfig) {
|
||||
this.removePending(config);
|
||||
const url = getPendingUrl(config);
|
||||
config.cancelToken =
|
||||
config.cancelToken ||
|
||||
new axios.CancelToken((cancel) => {
|
||||
if (!pendingMap.has(url)) {
|
||||
// 如果当前没有相同请求就添加
|
||||
pendingMap.set(url, cancel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除现有的所有请求
|
||||
*/
|
||||
removeAllPending() {
|
||||
pendingMap.forEach((cancel) => {
|
||||
if (cancel && isFunction(cancel)) cancel();
|
||||
});
|
||||
pendingMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定请求
|
||||
* @param config
|
||||
*/
|
||||
removePending(config: AxiosRequestConfig) {
|
||||
const url = getPendingUrl(config);
|
||||
|
||||
if (pendingMap.has(url)) {
|
||||
// If there is a current request identifier in pending,
|
||||
// the current request needs to be cancelled and removed
|
||||
const cancel = pendingMap.get(url);
|
||||
if (cancel) cancel(url);
|
||||
pendingMap.delete(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
reset() {
|
||||
pendingMap = new Map<string, Canceler>();
|
||||
}
|
||||
}
|
||||
67
frontend/src/utils/request/AxiosTransform.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { AxiosError } from 'axios'
|
||||
|
||||
import type { RequestOptions, Result } from '../../types/axios'
|
||||
|
||||
/**
|
||||
* @description 创建Axios实例配置
|
||||
*/
|
||||
export interface CreateAxiosOptions extends AxiosRequestConfig {
|
||||
/**
|
||||
* 请求验证方案
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
|
||||
*/
|
||||
authenticationScheme?: string
|
||||
/**
|
||||
* 请求数据处理
|
||||
*/
|
||||
transform?: AxiosTransform
|
||||
/**
|
||||
* 请求配置
|
||||
*/
|
||||
requestOptions?: RequestOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Axios请求数据处理 抽象类
|
||||
*/
|
||||
export abstract class AxiosTransform {
|
||||
/**
|
||||
* 请求前钩子
|
||||
*/
|
||||
beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig
|
||||
|
||||
/**
|
||||
* 数据处理前钩子
|
||||
*/
|
||||
transformRequestHook?: <T = any>(res: AxiosResponse<Result>, options: RequestOptions) => T
|
||||
|
||||
/**
|
||||
* 请求失败钩子
|
||||
*/
|
||||
requestCatchHook?: <T = any>(e: Error | AxiosError, options: RequestOptions) => Promise<T>
|
||||
|
||||
/**
|
||||
* 请求拦截器
|
||||
*/
|
||||
requestInterceptors?: (
|
||||
config: AxiosRequestConfig,
|
||||
options: CreateAxiosOptions
|
||||
) => AxiosRequestConfig
|
||||
|
||||
/**
|
||||
* 响应拦截器
|
||||
*/
|
||||
responseInterceptors?: (res: AxiosResponse) => AxiosResponse
|
||||
|
||||
/**
|
||||
* 请求拦截器错误处理
|
||||
*/
|
||||
requestInterceptorsCatch?: (error: AxiosError) => void
|
||||
|
||||
/**
|
||||
* 响应拦截器错误处理
|
||||
*/
|
||||
responseInterceptorsCatch?: (error: AxiosError, instance: AxiosInstance) => void
|
||||
}
|
||||
222
frontend/src/utils/request/index.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
|
||||
// import type { AxiosInstance } from 'axios'
|
||||
import isString from 'lodash/isString'
|
||||
import merge from 'lodash/merge'
|
||||
|
||||
// import { useRouter } from 'vue-router';
|
||||
// import { UserRefresh } from '@api/userCenter';
|
||||
// import { useUserStore } from '../../store'
|
||||
|
||||
import { VAxios } from './Axios'
|
||||
import type { AxiosTransform, CreateAxiosOptions } from './AxiosTransform'
|
||||
import { formatRequestDate, joinTimestamp, setObjToUrlParams } from './utils'
|
||||
|
||||
// 通用请求头
|
||||
export enum ContentTypeEnum {
|
||||
Json = 'application/json;charset=UTF-8',
|
||||
FormURLEncoded = 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
FormData = 'multipart/form-data;charset=UTF-8'
|
||||
}
|
||||
|
||||
const env = import.meta.env.MODE || 'development'
|
||||
|
||||
// 如果是mock模式 或 没启用直连代理 就不配置host 会走本地Mock拦截 或 Vite 代理
|
||||
const host =
|
||||
env === 'mock' || import.meta.env.VITE_IS_REQUEST_PROXY === 'true'
|
||||
? ''
|
||||
: import.meta.env.VITE_API_URL
|
||||
// 数据处理,方便区分多种处理方式
|
||||
const transform: AxiosTransform = {
|
||||
// 处理请求数据。如果数据不是预期格式,可直接抛出错误
|
||||
transformRequestHook: (res, options) => {
|
||||
const { isTransformResponse, isReturnNativeResponse } = options
|
||||
|
||||
// 如果204无内容直接返回
|
||||
const method:any = res.config.method?.toLowerCase()
|
||||
if (res.status === 204 && ['put', 'patch', 'delete'].includes(method)) {
|
||||
return res
|
||||
}
|
||||
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
if (isReturnNativeResponse) {
|
||||
return res
|
||||
}
|
||||
// 不进行任何处理,直接返回
|
||||
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
|
||||
if (!isTransformResponse) {
|
||||
return res.data
|
||||
}
|
||||
|
||||
// 错误的时候返回
|
||||
const { data } = res
|
||||
if (!data) {
|
||||
throw new Error('请求接口错误')
|
||||
}
|
||||
|
||||
// 这里 code为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
|
||||
const { code } = data
|
||||
// 这里逻辑可以根据项目进行修改
|
||||
const hasSuccess = data && code === 200
|
||||
if (hasSuccess) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
throw new Error(`请求接口错误, 错误码: ${code}`)
|
||||
},
|
||||
|
||||
// 请求前处理配置
|
||||
beforeRequestHook: (config, options) => {
|
||||
const {
|
||||
apiUrl,
|
||||
isJoinPrefix,
|
||||
urlPrefix,
|
||||
joinParamsToUrl,
|
||||
formatDate,
|
||||
joinTime = true
|
||||
} = options
|
||||
// 添加接口前缀
|
||||
if (isJoinPrefix && urlPrefix && isString(urlPrefix)) {
|
||||
let u = urlPrefix
|
||||
config.url = `${u}${config.url}`
|
||||
}
|
||||
// 将baseUrl拼接
|
||||
if (apiUrl && isString(apiUrl)) {
|
||||
config.url = `${apiUrl}${config.url}`
|
||||
}
|
||||
const params = config.params || {}
|
||||
const data = config.data || false
|
||||
|
||||
if (formatDate && data && !isString(data)) {
|
||||
formatRequestDate(data)
|
||||
}
|
||||
if (config.method?.toUpperCase() === 'GET') {
|
||||
if (!isString(params)) {
|
||||
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。
|
||||
config.params = Object.assign(params || {}, joinTimestamp(joinTime, false))
|
||||
} else {
|
||||
// 兼容restful风格
|
||||
config.url = `${config.url + params}${joinTimestamp(joinTime, true)}`
|
||||
config.params = undefined
|
||||
}
|
||||
} else if (!isString(params)) {
|
||||
if (formatDate) {
|
||||
formatRequestDate(params)
|
||||
}
|
||||
if (
|
||||
Reflect.has(config, 'data') &&
|
||||
config.data &&
|
||||
(Object.keys(config.data).length > 0 || data instanceof FormData)
|
||||
) {
|
||||
config.data = data
|
||||
config.params = params
|
||||
} else {
|
||||
// 非GET请求如果没有提供data,则将params视为data
|
||||
config.data = params
|
||||
config.params = undefined
|
||||
}
|
||||
if (joinParamsToUrl) {
|
||||
config.url = setObjToUrlParams(config.url as string, { ...config.params, ...config.data })
|
||||
}
|
||||
} else {
|
||||
// 兼容restful风格
|
||||
config.url += params
|
||||
config.params = undefined
|
||||
}
|
||||
return config
|
||||
},
|
||||
|
||||
// 请求拦截器处理
|
||||
requestInterceptors: (config) => {
|
||||
// 请求之前处理config
|
||||
// const userStore = useUserStore()
|
||||
// const { token } = userStore
|
||||
const token = localStorage.getItem('token')
|
||||
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
|
||||
;(config as Recordable).headers['x-rtt-login-token'] = token
|
||||
}
|
||||
return config
|
||||
},
|
||||
|
||||
// 响应拦截器处理
|
||||
responseInterceptors: (res) => {
|
||||
return res
|
||||
},
|
||||
|
||||
// 响应错误处理
|
||||
responseInterceptorsCatch: (error: any, instance: any) => {
|
||||
const { config } = error
|
||||
|
||||
if (!config || !config.requestOptions.retry) return Promise.reject(error)
|
||||
|
||||
config.retryCount = config.retryCount || 0
|
||||
|
||||
if (config.retryCount >= config.requestOptions.retry.count) return Promise.reject(error)
|
||||
|
||||
config.retryCount += 1
|
||||
|
||||
const backoff = new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(config)
|
||||
}, config.requestOptions.retry.delay || 1)
|
||||
})
|
||||
config.headers = { ...config.headers, 'Content-Type': ContentTypeEnum.Json }
|
||||
return backoff.then((config) => instance.request(config))
|
||||
}
|
||||
}
|
||||
|
||||
function createAxios(opt?: Partial<CreateAxiosOptions>) {
|
||||
return new VAxios(
|
||||
merge(
|
||||
<CreateAxiosOptions>{
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
|
||||
// 例如: authenticationScheme: 'Bearer'
|
||||
authenticationScheme: '',
|
||||
// 超时
|
||||
timeout: 10 * 1000,
|
||||
// 携带Cookie
|
||||
withCredentials: true,
|
||||
// 头信息
|
||||
headers: {
|
||||
'Content-Type': ContentTypeEnum.Json,
|
||||
},
|
||||
// 数据处理方式
|
||||
transform,
|
||||
// 配置项,下面的选项都可以在独立的接口请求中覆盖
|
||||
requestOptions: {
|
||||
// 接口地址
|
||||
apiUrl: host,
|
||||
// 是否自动添加接口前缀
|
||||
isJoinPrefix: true,
|
||||
// 接口前缀
|
||||
// 例如: https://www.baidu.com/api
|
||||
// urlPrefix: '/api'
|
||||
urlPrefix: import.meta.env.VITE_API_URL_PREFIX,
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
isReturnNativeResponse: false,
|
||||
// 需要对返回数据进行处理
|
||||
isTransformResponse: false,
|
||||
// post请求的时候添加参数到url
|
||||
joinParamsToUrl: false,
|
||||
// 格式化提交参数时间
|
||||
formatDate: true,
|
||||
// 是否加入时间戳
|
||||
joinTime: true,
|
||||
// 是否忽略请求取消令牌
|
||||
// 如果启用,则重复请求时不进行处理
|
||||
// 如果禁用,则重复请求时会取消当前请求
|
||||
ignoreCancelToken: true,
|
||||
// 是否携带token
|
||||
withToken: true,
|
||||
externalUrlStatus: 0,
|
||||
// 重试
|
||||
retry: {
|
||||
count: 3,
|
||||
delay: 1000
|
||||
}
|
||||
}
|
||||
},
|
||||
opt || {}
|
||||
)
|
||||
)
|
||||
}
|
||||
export const request = createAxios()
|
||||
57
frontend/src/utils/request/utils.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import isObject from 'lodash/isObject'
|
||||
import isString from 'lodash/isString'
|
||||
|
||||
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
|
||||
|
||||
export function joinTimestamp<T extends boolean>(
|
||||
join: boolean,
|
||||
restful: T
|
||||
): T extends true ? string : object
|
||||
|
||||
export function joinTimestamp(join: boolean, restful = false): string | object {
|
||||
if (!join) {
|
||||
return restful ? '' : {}
|
||||
}
|
||||
const now = new Date().getTime()
|
||||
if (restful) {
|
||||
return `?_t=${now}`
|
||||
}
|
||||
return { _t: now }
|
||||
}
|
||||
|
||||
// 格式化提交参数时间
|
||||
export function formatRequestDate(params: Recordable) {
|
||||
if (Object.prototype.toString.call(params) !== '[object Object]') {
|
||||
return
|
||||
}
|
||||
|
||||
for (const key in params) {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
if (params[key] && params[key]._isAMomentObject) {
|
||||
params[key] = params[key].format(DATE_TIME_FORMAT)
|
||||
}
|
||||
if (isString(key)) {
|
||||
const value = params[key]
|
||||
if (value) {
|
||||
try {
|
||||
params[key] = isString(value) ? value.trim() : value
|
||||
} catch (error: any) {
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isObject(params[key])) {
|
||||
formatRequestDate(params[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将对象转为Url参数
|
||||
export function setObjToUrlParams(baseUrl: string, obj: { [index: string]: any }): string {
|
||||
let parameters = ''
|
||||
for (const key in obj) {
|
||||
parameters += `${key}=${encodeURIComponent(obj[key])}&`
|
||||
}
|
||||
parameters = parameters.replace(/&$/, '')
|
||||
return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters
|
||||
}
|
||||
25
frontend/src/utils/websocket.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
function useWebsocket(handleMessage: any) {
|
||||
const ws = new WebSocket(import.meta.env.VITE_WB_BASE_URL)
|
||||
const init = () => {
|
||||
bindEvent();
|
||||
}
|
||||
const bindEvent = () => {
|
||||
ws.addEventListener('open',handleOpen,false)
|
||||
ws.addEventListener('close',handleClose,false)
|
||||
ws.addEventListener('error',handleError,false)
|
||||
ws.addEventListener('message',handleMessage,false)
|
||||
}
|
||||
const handleOpen = (e: any) => {
|
||||
console.log('websocket open',e);
|
||||
}
|
||||
const handleClose = (e: any) => {
|
||||
console.log('websocket close',e);
|
||||
}
|
||||
const handleError = (e: any) => {
|
||||
console.log('webscoket error',e)
|
||||
}
|
||||
init();
|
||||
return ws
|
||||
}
|
||||
|
||||
export default useWebsocket
|
||||
84
frontend/src/view/home/index.less
Normal file
@@ -0,0 +1,84 @@
|
||||
.content {
|
||||
max-width: 1196px;
|
||||
// width: 100%;
|
||||
margin: 0 auto;
|
||||
padding-top: 98px;
|
||||
|
||||
.title-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 30px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.recommend-box {
|
||||
width: 100%;
|
||||
margin-top: 72px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.recommend-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
img {
|
||||
width: 22px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.recommend-bottm {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
column-gap: 24px;
|
||||
row-gap: 24px;
|
||||
|
||||
.recommend-list {
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 24px;
|
||||
row-gap: 12px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.item {
|
||||
// max-width: 438px;
|
||||
width: calc(50% - 12px);
|
||||
padding: 8px 16px;
|
||||
border: 1px solid rgba(37, 79, 127, 1);
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
55
frontend/src/view/home/index.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div class="content">
|
||||
<div class="title-box">
|
||||
<img src="../../assets/logo.png" alt="" />
|
||||
<span>{{ generateTitle('您好,多智能体MARS为您服务') }}</span>
|
||||
</div>
|
||||
<TextareaView @submitFun="reasoningFun" :minRows="6"></TextareaView>
|
||||
<div class="recommend-box">
|
||||
<div class="recommend-title">
|
||||
<img src="../../assets/home/recommend.png" alt="" />
|
||||
<span>{{ generateTitle('推荐问题') }}</span>
|
||||
</div>
|
||||
<div class="recommend-bottm">
|
||||
<div class="recommend-list">
|
||||
<div
|
||||
class="item"
|
||||
@click="submitFun(item)"
|
||||
v-for="(item, index) in recommendList"
|
||||
:key="index"
|
||||
>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import TextareaView from '../../components/TextareaView.vue'
|
||||
import { generateTitle } from '../../utils/i18n'
|
||||
|
||||
const recommendList = ref<Array<any>>([
|
||||
'Structure: How can the self-assembly of materials be achieved for nanoparticles by controlling intermolecular interactions?',
|
||||
'Property: For perovskite nanocrystals, how can blue-shifting of the fluorescence emission wavelength be controlled through structural design?',
|
||||
'Synthesis: How to synthesize CsPbBr₃ nanocubes at room temperature?',
|
||||
'Application: How can the photoluminescence quantum efficiency of perovskites in LED devices be improved?'
|
||||
])
|
||||
const router = useRouter()
|
||||
const submitFun = (e: any) => {
|
||||
let data = {
|
||||
chat_id: new Date().getTime(),
|
||||
message: e
|
||||
}
|
||||
router.push('/reasoning?message=' + JSON.stringify(data))
|
||||
}
|
||||
const reasoningFun = (e: any) => {
|
||||
router.push('/reasoning?message=' + e)
|
||||
}
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
<style scoped>
|
||||
@import './index.less';
|
||||
</style>
|
||||
62
frontend/src/view/login/index.less
Normal file
@@ -0,0 +1,62 @@
|
||||
.login-box {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: url('../../assets/login/background.png') no-repeat;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.content {
|
||||
width: 450px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
box-sizing: border-box;
|
||||
padding: 40px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0px 15px 15px 0px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 32px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.code-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-form) {
|
||||
margin-bottom: 34px;
|
||||
|
||||
.el-input__inner {
|
||||
height: 56px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-box {
|
||||
width: 100%;
|
||||
|
||||
.el-button {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
109
frontend/src/view/login/index.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="login-box">
|
||||
<div v-loading="loading" class="content">
|
||||
<div class="title">
|
||||
<img src="/logo.png" alt="" />
|
||||
<span>{{generateTitle('MARS模型创制系统')}}</span>
|
||||
</div>
|
||||
<el-form :model="loginData">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
:placeholder="generateTitle('账号')"
|
||||
v-model="loginData.user_name"
|
||||
:prefix-icon="UserFilled"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input
|
||||
:placeholder="generateTitle('密码')"
|
||||
type="password"
|
||||
v-model="loginData.pass_word"
|
||||
:prefix-icon="Lock"
|
||||
show-password
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div class="code-box">
|
||||
<el-input
|
||||
:placeholder="generateTitle('验证码')"
|
||||
v-model="loginData.code"
|
||||
:prefix-icon="Lock"
|
||||
></el-input>
|
||||
<ValidCode ref="validCodeRef" :height="56"></ValidCode>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="btn-box">
|
||||
<el-button
|
||||
@click="submitFun"
|
||||
type="primary"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
element-loading-background="rgba(0, 0, 0, 0.3)"
|
||||
v-loading.fullscreen.lock="loading"
|
||||
>{{generateTitle("登录")}}</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { UserFilled, Lock } from '@element-plus/icons-vue'
|
||||
import ValidCode from '../../components/ValidCode.vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { authLogin } from '../../api/user'
|
||||
import {generateTitle} from '../../utils/i18n'
|
||||
const router = useRouter()
|
||||
const loginData = ref<any>({
|
||||
user_name: '',
|
||||
pass_word: '',
|
||||
code: ''
|
||||
})
|
||||
const loading = ref(false)
|
||||
const handleKeyPress = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
submitFun()
|
||||
}
|
||||
}
|
||||
const validCodeRef = ref()
|
||||
const submitFun = async () => {
|
||||
if (!loginData.value.user_name) {
|
||||
ElMessage.error(generateTitle('请输入账号'))
|
||||
return
|
||||
}
|
||||
if (!loginData.value.pass_word) {
|
||||
ElMessage.error(generateTitle('请输入密码'))
|
||||
return
|
||||
}
|
||||
if (!loginData.value.user_name) {
|
||||
ElMessage.error(generateTitle('请输入验证码'))
|
||||
return
|
||||
}
|
||||
let code = validCodeRef.value.validate(loginData.value.code)
|
||||
if (!code) {
|
||||
ElMessage.error(generateTitle('验证码错误'))
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const { token }: any = await authLogin(loginData.value)
|
||||
localStorage.setItem('token', token)
|
||||
loading.value = false
|
||||
router.push('/home')
|
||||
} catch (error: any) {
|
||||
loading.value = false
|
||||
ElMessage.error(error.response.data.error)
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
window.addEventListener('keypress', handleKeyPress)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keypress', handleKeyPress)
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
@import './index.less';
|
||||
</style>
|
||||
74
frontend/src/view/reasoning/index.less
Normal file
@@ -0,0 +1,74 @@
|
||||
.reasoning-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.content-left {
|
||||
width: 100%;
|
||||
|
||||
.body-box {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
height: calc(90vh - 175px);
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #1A87CA;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: #1A87CA;
|
||||
}
|
||||
}
|
||||
|
||||
.message-box {
|
||||
width: calc(100% - 48px);
|
||||
padding-top: 30px;
|
||||
|
||||
.tip-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 12px;
|
||||
|
||||
.tip-text {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.active_item {
|
||||
// width: 104px;
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px;
|
||||
font-size: 14px;
|
||||
height: 32px;
|
||||
margin: 0;
|
||||
color: rgba(73, 252, 255, 0.80);
|
||||
border-radius: 32px;
|
||||
border: 1px solid rgba(24, 91, 197, 1);
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 213, 250, 1) inset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 4px;
|
||||
background: #0C3870;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.el-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(180deg, #62FAF8 0%, #2DC9FE 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
213
frontend/src/view/reasoning/index.vue
Normal file
@@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<div class="reasoning-content">
|
||||
<div
|
||||
class="content-left"
|
||||
:style="{
|
||||
width: isCollapse ? 'calc(100% - 200px)' : 'calc(100% - 7px)'
|
||||
}"
|
||||
>
|
||||
<div class="body-box" ref="container">
|
||||
<reasoning-view
|
||||
ref="reasoningRef"
|
||||
@completeFun="completeFun"
|
||||
:reasoningList="reasoningList"
|
||||
/>
|
||||
</div>
|
||||
<div class="message-box">
|
||||
<div class="tip-box">
|
||||
<p class="active_item">
|
||||
<el-icon size="6" color="#fff"><Plus /></el-icon>
|
||||
{{ generateTitle('新建对话') }}
|
||||
</p>
|
||||
<div class="tip-text" v-show="disableStatus">
|
||||
{{ generateTitle('回答输出中,暂不能再次提问') }}
|
||||
</div>
|
||||
</div>
|
||||
<TextareaView ref="textareaRef" :minRows="1" @submitFun="submitFun" />
|
||||
</div>
|
||||
</div>
|
||||
<collapseView
|
||||
ref="collapseRef"
|
||||
:reasoningList="reasoningList"
|
||||
@changeStatusFun="changeStatusFun"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import ReasoningView from '../../components/ReasoningView/index.vue'
|
||||
import TextareaView from '../../components/TextareaView.vue'
|
||||
import collapseView from '../../components/collapseView/index.vue'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
// import { getModelList } from '../../api/user'
|
||||
import { useRoute } from 'vue-router'
|
||||
import useWebSocket from '../../utils/websocket'
|
||||
import { getAgent } from '../../utils/agent'
|
||||
import { generateTitle } from '../../utils/i18n'
|
||||
const route: any = useRoute()
|
||||
const reasoningRef = ref<any>(null)
|
||||
const textareaRef = ref<any>(null)
|
||||
const collapseRef = ref<any>(null)
|
||||
const isCollapse = ref<boolean>(true)
|
||||
const changeStatusFun = (val: boolean) => {
|
||||
isCollapse.value = val
|
||||
}
|
||||
const reasoningList = ref<Array<any>>([])
|
||||
// const getModelListFun = async () => {
|
||||
// try {
|
||||
// const { data, code } = await getModelList()
|
||||
// if (code) {
|
||||
// reasoningList.value = data
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.log(error)
|
||||
// }
|
||||
// }
|
||||
const submitFun = (val: any) => {
|
||||
completeFun()
|
||||
addMode(JSON.parse(val).message)
|
||||
ws.send(val)
|
||||
}
|
||||
const addMode = (val: any) => {
|
||||
endStatus.value = false
|
||||
reasoningList.value.push({
|
||||
title: val,
|
||||
children: []
|
||||
})
|
||||
reasoningRef.value.completeList.push({
|
||||
show: false,
|
||||
index: reasoningRef.value.completeList.length
|
||||
})
|
||||
}
|
||||
const disableStatus = ref(false)
|
||||
const completeFun = () => {
|
||||
disableStatus.value = true
|
||||
textareaRef.value.disableStatus = true
|
||||
}
|
||||
const handleMessage = (e: any) => {
|
||||
getMessage(e.data)
|
||||
getHeight()
|
||||
}
|
||||
const startStatus = ref(false)
|
||||
const contentStatus = ref(false)
|
||||
const endStatus = ref(false)
|
||||
const getMessage = async (e: any) => {
|
||||
// if (e.split('TERMINATE')[1]) {
|
||||
// talkList.value += e.split('TERMINATE')[0]
|
||||
// reasonStatus.value.show = false
|
||||
// endShow.value = true
|
||||
// extractSynthesisProcess()
|
||||
// } else {
|
||||
// talkList.value += e
|
||||
// }
|
||||
reasoningRef.value.reasonStatus.show = true
|
||||
// talkList.value += e
|
||||
// console.log(talkList.value);
|
||||
let list = reasoningList.value
|
||||
let children = list[list.length - 1].children
|
||||
// if (
|
||||
// e.includes(
|
||||
// '--------------------------------------------------------------------------------'
|
||||
// )
|
||||
// ) {
|
||||
// startStatus.value = true
|
||||
// } else if (startStatus.value) {
|
||||
if (parseChatData(e)) {
|
||||
if (parseChatData(e)?.type) {
|
||||
children.push({
|
||||
title: parseChatData(e)?.title,
|
||||
content: ''
|
||||
})
|
||||
reasoningRef.value.reasonStatus.index = children.length - 1
|
||||
} else {
|
||||
if (children[children.length - 1].content) {
|
||||
children[children.length - 1].content += parseChatData(e)?.current
|
||||
} else {
|
||||
children[children.length - 1].content = parseChatData(e)?.current
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// }
|
||||
}
|
||||
function parseChatData(e: any) {
|
||||
let condition =
|
||||
!e.includes(
|
||||
'--------------------------------------------------------------------------------'
|
||||
) &&
|
||||
!e.includes(
|
||||
`********************************************************************************`
|
||||
) &&
|
||||
!e.includes('Starting a new chat....') &&
|
||||
!e.includes('>>>>>>>>')
|
||||
let title = ''
|
||||
let current = ''
|
||||
let regex = /Next speaker: (.*)/
|
||||
if (condition) {
|
||||
if (e.includes('Planer') || e.includes('Planner')) {
|
||||
title = getAgent('Planer')
|
||||
return {
|
||||
title,
|
||||
type: true
|
||||
}
|
||||
}
|
||||
if (e.match(regex) && e.match(regex)[1]) {
|
||||
// if (!getAgent(e.match(regex)[1])) {
|
||||
// contentStatus.value = true
|
||||
// return
|
||||
// }
|
||||
// contentStatus.value = false
|
||||
// title = getAgent(e.match(regex)[1])
|
||||
title = getAgent(e.match(regex)[1]) || e.match(regex)[1]
|
||||
return {
|
||||
title,
|
||||
type: true
|
||||
}
|
||||
}
|
||||
if (contentStatus.value) {
|
||||
return
|
||||
}
|
||||
if (e.includes('TERMINATE')) {
|
||||
endStatus.value = true
|
||||
reasoningRef.value.reasonStatus.show = false
|
||||
reasoningRef.value.completeList[
|
||||
reasoningRef.value.completeList.length - 1
|
||||
].show = true
|
||||
disableStatus.value = false
|
||||
textareaRef.value.disableStatus = false
|
||||
startStatus.value = false
|
||||
}
|
||||
current = e.replace(
|
||||
/(?<!`)TERMINATE(?<!\*\*)|TERMINATE(?!\*\*)|`TERMINATE`/g,
|
||||
''
|
||||
)
|
||||
return {
|
||||
current,
|
||||
type: false
|
||||
}
|
||||
}
|
||||
}
|
||||
const ws = useWebSocket(handleMessage)
|
||||
const sendFun = () => {
|
||||
setTimeout(() => {
|
||||
addMode(JSON.parse(route.query.message).message)
|
||||
completeFun()
|
||||
ws.send(JSON.stringify(route.query.message))
|
||||
}, 300)
|
||||
}
|
||||
const container = ref<any>(null)
|
||||
const getHeight = () => {
|
||||
container.value.scrollTop = container.value.scrollHeight + 150
|
||||
collapseRef.value.scrollTop = collapseRef.value.scrollHeight + 50
|
||||
}
|
||||
onMounted(() => {
|
||||
// getModelListFun();
|
||||
nextTick(() => {
|
||||
getHeight()
|
||||
sendFun()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
@import './index.less';
|
||||
</style>
|
||||
1
frontend/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||