'frontendadd'
This commit is contained in:
84
frontend/src/view/home/index.less
Normal file
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
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
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
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
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
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>
|
||||
Reference in New Issue
Block a user