diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..a491a21 --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,6 @@ +# 打包路径 +VITE_BASE_URL = / +VITE_IS_REQUEST_PROXY = true +VITE_API_URL = http://159.75.91.126 +VITE_API_URL_PREFIX = /matagent +VITE_WB_BASE_URL = ws://159.75.91.126:8000/matagent/chat \ No newline at end of file diff --git a/frontend/.env.prod b/frontend/.env.prod new file mode 100644 index 0000000..a491a21 --- /dev/null +++ b/frontend/.env.prod @@ -0,0 +1,6 @@ +# 打包路径 +VITE_BASE_URL = / +VITE_IS_REQUEST_PROXY = true +VITE_API_URL = http://159.75.91.126 +VITE_API_URL_PREFIX = /matagent +VITE_WB_BASE_URL = ws://159.75.91.126:8000/matagent/chat \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..d68adc4 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,16 @@ +node_modules +.DS_Store +*.log* +package-lock.json +dist/ +tmp* +temp* +yarn-error.log +*.zip +.history +.stylelintcache +yarn.lock +# package-lock.json +pnpm-lock.yaml +.env.local +.env.*.local diff --git a/frontend/.vscode/extensions.json b/frontend/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..eee39be --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,11 @@ +### npm i 安装依赖 + +### npm run dev 运行项目 + +### 打包前确认: + +// .env 文件中的 VITE_API_URL 接口地址是否正确 +// .env 文件中的 VITE_API_URL_PREFIX 接口 api 是否正确 +// .env 文件中的 VITE_WB_BASE_URL websoket 链接获取答案接口地址是否正确 + +### npm run build 运行项目 diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..08410da --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + MARS模型创制系统 + + +
+ + + diff --git a/frontend/interface.txt b/frontend/interface.txt new file mode 100644 index 0000000..214ea13 --- /dev/null +++ b/frontend/interface.txt @@ -0,0 +1,34 @@ +1¼ӿ post +http://192.168.42.130:8000/matagent/login + + +{"user_name":"test","pass_word":"111111"} + + +{"token": "token_test"} + +2ȡģб get headerдtokentokenloginӿڷֵ +http://192.168.42.130:8000/matagent/model +header: + token: "token_test" + + +{ + "count":"1", + "data": + [ + {"model_name":"model1","model_des":"model1" + }, + {"model_name":"model1","model_des":"model2" + } + ] +} + +3 post headerдtokentokenloginӿڷֵ +http://192.168.42.130:8000/matagent/chat +header: + token: "token_test" + +{"chat_id":"test_chat_id","message":"ڳƱCsPbBr3"} + +{"data":"Ʊ"} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..aa6fdce --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,35 @@ +{ + "name": "chemical-model", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "build:prod": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "@vavt/cm-extension": "^1.6.0", + "@vavt/v3-extension": "^2.1.0", + "axios": "^1.7.9", + "element-plus": "^2.9.1", + "md-editor-v3": "^5.1.1", + "pinia": "^2.3.0", + "qs": "^6.13.1", + "vue": "^3.5.13", + "vue-i18n": "^11.0.1", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@types/qs": "^6.9.17", + "@types/vue-tel-input": "^2.1.7", + "@vitejs/plugin-vue": "^5.2.1", + "less": "^4.2.1", + "less-loader": "^12.2.0", + "typescript": "~5.6.2", + "vite": "^6.0.1", + "vue-tsc": "^2.1.10" + } +} diff --git a/frontend/public/logo.png b/frontend/public/logo.png new file mode 100644 index 0000000..2a0792c Binary files /dev/null and b/frontend/public/logo.png differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..1d79ff4 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/frontend/src/api/model/detailModel.ts b/frontend/src/api/model/detailModel.ts new file mode 100644 index 0000000..38fd018 --- /dev/null +++ b/frontend/src/api/model/detailModel.ts @@ -0,0 +1,27 @@ +export interface PurchaseListResult { + list: Array +} +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; +} +export interface ProjectInfo { + adminName: string + adminPhone: string + index: number + name: string + updateTime: Date +} diff --git a/frontend/src/api/model/listModel.ts b/frontend/src/api/model/listModel.ts new file mode 100644 index 0000000..453114d --- /dev/null +++ b/frontend/src/api/model/listModel.ts @@ -0,0 +1,26 @@ +export interface ListResult { + list: Array; +} +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; +} +export interface CardList { + banner: string; + description: string; + index: number; + isSetup: boolean; + name: string; + type: number; +} diff --git a/frontend/src/api/model/permissionModel.ts b/frontend/src/api/model/permissionModel.ts new file mode 100644 index 0000000..2c6ed58 --- /dev/null +++ b/frontend/src/api/model/permissionModel.ts @@ -0,0 +1,32 @@ +import { defineComponent } from 'vue'; + +export interface MenuListResult { + list: Array; +} + +export type Component = + | ReturnType + | (() => Promise) + | (() => Promise); + +export interface RouteItem { + path: string; + name: string; + component?: Component | string; + components?: Component; + redirect?: string; + meta: RouteMeta; + children?: Array; +} +export interface RouteMeta { + title: string; + icon?: string; + expanded?: boolean; + orderNo?: number; + hidden?: boolean; + hiddenBreadcrumb?: boolean; + single?: boolean; + keepAlive?: boolean; + frameSrc?: string; + frameBlank?: boolean; +} diff --git a/frontend/src/api/user.ts b/frontend/src/api/user.ts new file mode 100644 index 0000000..89807f5 --- /dev/null +++ b/frontend/src/api/user.ts @@ -0,0 +1,22 @@ +import type { ProjectListResult } from './model/detailModel' +import { request } from '../utils/request' +// 登录获取Token +export function authLogin(data: any) { + return request.post({ + url: `/login`, + data + }) +} +// 发送聊天请求 +export function chat(data: any) { + return request.post({ + url: `/chat`, + data + }) +} +// 获取模型列表 +export function getModelList() { + return request.get({ + url: `/model` + }) +} diff --git a/frontend/src/assets/background.png b/frontend/src/assets/background.png new file mode 100644 index 0000000..d0651e8 Binary files /dev/null and b/frontend/src/assets/background.png differ diff --git a/frontend/src/assets/chart/in-arrow.png b/frontend/src/assets/chart/in-arrow.png new file mode 100644 index 0000000..b052146 Binary files /dev/null and b/frontend/src/assets/chart/in-arrow.png differ diff --git a/frontend/src/assets/chart/model.png b/frontend/src/assets/chart/model.png new file mode 100644 index 0000000..8d92166 Binary files /dev/null and b/frontend/src/assets/chart/model.png differ diff --git a/frontend/src/assets/chart/out-arrow.png b/frontend/src/assets/chart/out-arrow.png new file mode 100644 index 0000000..9bdd06e Binary files /dev/null and b/frontend/src/assets/chart/out-arrow.png differ diff --git a/frontend/src/assets/chart/round-arrow.png b/frontend/src/assets/chart/round-arrow.png new file mode 100644 index 0000000..72fed7b Binary files /dev/null and b/frontend/src/assets/chart/round-arrow.png differ diff --git a/frontend/src/assets/chart/round-lage-arrow.png b/frontend/src/assets/chart/round-lage-arrow.png new file mode 100644 index 0000000..646eec6 Binary files /dev/null and b/frontend/src/assets/chart/round-lage-arrow.png differ diff --git a/frontend/src/assets/chart/sit.png b/frontend/src/assets/chart/sit.png new file mode 100644 index 0000000..c9b9c41 Binary files /dev/null and b/frontend/src/assets/chart/sit.png differ diff --git a/frontend/src/assets/chart/stand.png b/frontend/src/assets/chart/stand.png new file mode 100644 index 0000000..dd8f6ef Binary files /dev/null and b/frontend/src/assets/chart/stand.png differ diff --git a/frontend/src/assets/header-logo.png b/frontend/src/assets/header-logo.png new file mode 100644 index 0000000..4c36297 Binary files /dev/null and b/frontend/src/assets/header-logo.png differ diff --git a/frontend/src/assets/home/assistant.png b/frontend/src/assets/home/assistant.png new file mode 100644 index 0000000..775e010 Binary files /dev/null and b/frontend/src/assets/home/assistant.png differ diff --git a/frontend/src/assets/home/recommend.png b/frontend/src/assets/home/recommend.png new file mode 100644 index 0000000..83a9500 Binary files /dev/null and b/frontend/src/assets/home/recommend.png differ diff --git a/frontend/src/assets/layout/next.png b/frontend/src/assets/layout/next.png new file mode 100644 index 0000000..7eeb071 Binary files /dev/null and b/frontend/src/assets/layout/next.png differ diff --git a/frontend/src/assets/layout/vector.png b/frontend/src/assets/layout/vector.png new file mode 100644 index 0000000..483d1f3 Binary files /dev/null and b/frontend/src/assets/layout/vector.png differ diff --git a/frontend/src/assets/login/background.png b/frontend/src/assets/login/background.png new file mode 100644 index 0000000..84ce41d Binary files /dev/null and b/frontend/src/assets/login/background.png differ diff --git a/frontend/src/assets/login/password.png b/frontend/src/assets/login/password.png new file mode 100644 index 0000000..7d35f6e Binary files /dev/null and b/frontend/src/assets/login/password.png differ diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png new file mode 100644 index 0000000..cf333d7 Binary files /dev/null and b/frontend/src/assets/logo.png differ diff --git a/frontend/src/components/ReasoningView/index.less b/frontend/src/components/ReasoningView/index.less new file mode 100644 index 0000000..82e3f54 --- /dev/null +++ b/frontend/src/components/ReasoningView/index.less @@ -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; + } + } + +} \ No newline at end of file diff --git a/frontend/src/components/ReasoningView/index.vue b/frontend/src/components/ReasoningView/index.vue new file mode 100644 index 0000000..79ba48c --- /dev/null +++ b/frontend/src/components/ReasoningView/index.vue @@ -0,0 +1,153 @@ + + + diff --git a/frontend/src/components/StepsView.vue b/frontend/src/components/StepsView.vue new file mode 100644 index 0000000..4770a49 --- /dev/null +++ b/frontend/src/components/StepsView.vue @@ -0,0 +1,177 @@ + + + diff --git a/frontend/src/components/SwitchLanguage.vue b/frontend/src/components/SwitchLanguage.vue new file mode 100644 index 0000000..1b1fc81 --- /dev/null +++ b/frontend/src/components/SwitchLanguage.vue @@ -0,0 +1,55 @@ + + + diff --git a/frontend/src/components/TextareaView.vue b/frontend/src/components/TextareaView.vue new file mode 100644 index 0000000..0b41683 --- /dev/null +++ b/frontend/src/components/TextareaView.vue @@ -0,0 +1,118 @@ + + + diff --git a/frontend/src/components/ValidCode.vue b/frontend/src/components/ValidCode.vue new file mode 100644 index 0000000..b47b135 --- /dev/null +++ b/frontend/src/components/ValidCode.vue @@ -0,0 +1,136 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/collapseView/index.less b/frontend/src/components/collapseView/index.less new file mode 100644 index 0000000..f15d873 --- /dev/null +++ b/frontend/src/components/collapseView/index.less @@ -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); + } + } + + } + } +} \ No newline at end of file diff --git a/frontend/src/components/collapseView/index.vue b/frontend/src/components/collapseView/index.vue new file mode 100644 index 0000000..2b90cb4 --- /dev/null +++ b/frontend/src/components/collapseView/index.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/frontend/src/constants/index.ts b/frontend/src/constants/index.ts new file mode 100644 index 0000000..5cf7690 --- /dev/null +++ b/frontend/src/constants/index.ts @@ -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' +} diff --git a/frontend/src/lang/en.ts b/frontend/src/lang/en.ts new file mode 100644 index 0000000..d4ac9f9 --- /dev/null +++ b/frontend/src/lang/en.ts @@ -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', + } + +} diff --git a/frontend/src/lang/index.ts b/frontend/src/lang/index.ts new file mode 100644 index 0000000..8e9f27a --- /dev/null +++ b/frontend/src/lang/index.ts @@ -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 diff --git a/frontend/src/lang/zh.ts b/frontend/src/lang/zh.ts new file mode 100644 index 0000000..cbe3424 --- /dev/null +++ b/frontend/src/lang/zh.ts @@ -0,0 +1,30 @@ +export default { + routers:{ + logOut:'退出登录', + restore:'还原数据', + '智能协作无界,材料创新无限':'智能协作无界,材料创新无限', + '问题设定':'问题设定', + '模型名称':'模型名称', + '新建对话':'新建对话', + '推理完成,如果希望了解更多可继续在下面框中提问或新建对话。':'推理完成,如果希望了解更多可继续在下面框中提问或新建对话。', + '推理中':'推理中', + '角色':'角色', + '模型调用流程':'模型调用流程', + '发送':'发送', + '您好,多智能体MARS为您服务':'您好,多智能体MARS为您服务', + '推荐问题':'推荐问题', + '在此输入您的问题或需求,有问必答,Shift+Enter换行':'在此输入您的问题或需求,有问必答,Shift+Enter换行', + 'MARS模型创制系统':'MARS模型创制系统', + '账号':'账号', + '密码':'密码', + '验证码':'验证码', + '登录':'登录', + '请输入账号':'请输入账号', + '请输入密码':'请输入密码', + '请输入验证码':'请输入验证码', + '验证码错误':'验证码错误', + '请输入问题':'请输入问题', + '回答输出中,暂不能再次提问':'回答输出中,暂不能再次提问', + } + +} diff --git a/frontend/src/layout/index.less b/frontend/src/layout/index.less new file mode 100644 index 0000000..486a3b7 --- /dev/null +++ b/frontend/src/layout/index.less @@ -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); + } +} \ No newline at end of file diff --git a/frontend/src/layout/index.vue b/frontend/src/layout/index.vue new file mode 100644 index 0000000..a2ec744 --- /dev/null +++ b/frontend/src/layout/index.vue @@ -0,0 +1,18 @@ + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..9fe51bb --- /dev/null +++ b/frontend/src/main.ts @@ -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') \ No newline at end of file diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000..61e715f --- /dev/null +++ b/frontend/src/router/index.ts @@ -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; \ No newline at end of file diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts new file mode 100644 index 0000000..4e332b0 --- /dev/null +++ b/frontend/src/store/index.ts @@ -0,0 +1,13 @@ +// import { defineStore } from 'pinia' +// export const useUserStore = defineStore('user', { +// state: () => ({ +// token: '', // 登录token +// }), +// getters: {}, +// actions: { +// }, +// persist: { +// key: 'user', +// paths: ['token'] +// } +// }) diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..0e7d1a2 --- /dev/null +++ b/frontend/src/style.css @@ -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; + } +} \ No newline at end of file diff --git a/frontend/src/types/axios.d.ts b/frontend/src/types/axios.d.ts new file mode 100644 index 0000000..928f052 --- /dev/null +++ b/frontend/src/types/axios.d.ts @@ -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 { + code: number + data: T +} + +export interface AxiosRequestConfigRetry extends AxiosRequestConfig { + retryCount?: number +} diff --git a/frontend/src/types/env.d.ts b/frontend/src/types/env.d.ts new file mode 100644 index 0000000..6ca1ddb --- /dev/null +++ b/frontend/src/types/env.d.ts @@ -0,0 +1,5 @@ +export interface ImportMetaEnv { + readonly VITE_IS_REQUEST_PROXY: string; + readonly VITE_API_URL: string; + readonly VITE_API_URL_PREFIX: string; +} diff --git a/frontend/src/types/globals.d.ts b/frontend/src/types/globals.d.ts new file mode 100644 index 0000000..cd8136c --- /dev/null +++ b/frontend/src/types/globals.d.ts @@ -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 = Record diff --git a/frontend/src/types/interface.d.ts b/frontend/src/types/interface.d.ts new file mode 100644 index 0000000..c8a1a0a --- /dev/null +++ b/frontend/src/types/interface.d.ts @@ -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 diff --git a/frontend/src/utils/agent.ts b/frontend/src/utils/agent.ts new file mode 100644 index 0000000..b1bc687 --- /dev/null +++ b/frontend/src/utils/agent.ts @@ -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] || '' +} \ No newline at end of file diff --git a/frontend/src/utils/i18n.ts b/frontend/src/utils/i18n.ts new file mode 100644 index 0000000..3aa3488 --- /dev/null +++ b/frontend/src/utils/i18n.ts @@ -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 + } diff --git a/frontend/src/utils/request/Axios.ts b/frontend/src/utils/request/Axios.ts new file mode 100644 index 0000000..a5737f0 --- /dev/null +++ b/frontend/src/utils/request/Axios.ts @@ -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([]); +/** + * 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): 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(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'GET' }, options) + } + + post(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'POST' }, options) + } + + put(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'PUT' }, options) + } + + delete(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'DELETE' }, options) + } + + patch(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'PATCH' }, options) + } + + /** + * 上传文件封装 + * @param key 文件所属的key + * @param file 文件 + * @param config 请求配置 + * @param options + */ + upload( + key: string, + file: File, + config: AxiosRequestConfig, + options?: RequestOptions + ): Promise { + 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(config: AxiosRequestConfigRetry, options?: RequestOptions): Promise { + 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( + config: AxiosRequestConfigRetry, + options?: RequestOptions + ): Promise { + 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>(!config.retryCount ? conf : config) + .then((res: AxiosResponse) => { + 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) + }) + .catch((e: Error | AxiosError) => { + if (requestCatchHook && isFunction(requestCatchHook)) { + reject(requestCatchHook(e, opt)) + return + } + if (axios.isAxiosError(e)) { + // 在这里重写Axios的错误信息 + } + reject(e) + }) + }) + } +} diff --git a/frontend/src/utils/request/AxiosCancel.ts b/frontend/src/utils/request/AxiosCancel.ts new file mode 100644 index 0000000..010b0da --- /dev/null +++ b/frontend/src/utils/request/AxiosCancel.ts @@ -0,0 +1,67 @@ +import type { AxiosRequestConfig, Canceler } from 'axios'; +import axios from 'axios'; +import isFunction from 'lodash/isFunction'; + +// 存储请求与取消令牌的键值对列表 +let pendingMap = new Map(); + +/** + * 获取请求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(); + } +} diff --git a/frontend/src/utils/request/AxiosTransform.ts b/frontend/src/utils/request/AxiosTransform.ts new file mode 100644 index 0000000..add3805 --- /dev/null +++ b/frontend/src/utils/request/AxiosTransform.ts @@ -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?: (res: AxiosResponse, options: RequestOptions) => T + + /** + * 请求失败钩子 + */ + requestCatchHook?: (e: Error | AxiosError, options: RequestOptions) => Promise + + /** + * 请求拦截器 + */ + requestInterceptors?: ( + config: AxiosRequestConfig, + options: CreateAxiosOptions + ) => AxiosRequestConfig + + /** + * 响应拦截器 + */ + responseInterceptors?: (res: AxiosResponse) => AxiosResponse + + /** + * 请求拦截器错误处理 + */ + requestInterceptorsCatch?: (error: AxiosError) => void + + /** + * 响应拦截器错误处理 + */ + responseInterceptorsCatch?: (error: AxiosError, instance: AxiosInstance) => void +} diff --git a/frontend/src/utils/request/index.ts b/frontend/src/utils/request/index.ts new file mode 100644 index 0000000..24636a1 --- /dev/null +++ b/frontend/src/utils/request/index.ts @@ -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) { + return new VAxios( + merge( + { + // 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() diff --git a/frontend/src/utils/request/utils.ts b/frontend/src/utils/request/utils.ts new file mode 100644 index 0000000..8b6493a --- /dev/null +++ b/frontend/src/utils/request/utils.ts @@ -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( + 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 +} diff --git a/frontend/src/utils/websocket.ts b/frontend/src/utils/websocket.ts new file mode 100644 index 0000000..5ae2b84 --- /dev/null +++ b/frontend/src/utils/websocket.ts @@ -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 \ No newline at end of file diff --git a/frontend/src/view/home/index.less b/frontend/src/view/home/index.less new file mode 100644 index 0000000..3972d01 --- /dev/null +++ b/frontend/src/view/home/index.less @@ -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; + } + } + } + } + + +} \ No newline at end of file diff --git a/frontend/src/view/home/index.vue b/frontend/src/view/home/index.vue new file mode 100644 index 0000000..997cd11 --- /dev/null +++ b/frontend/src/view/home/index.vue @@ -0,0 +1,55 @@ + + + diff --git a/frontend/src/view/login/index.less b/frontend/src/view/login/index.less new file mode 100644 index 0000000..057782c --- /dev/null +++ b/frontend/src/view/login/index.less @@ -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; + } + } + } +} \ No newline at end of file diff --git a/frontend/src/view/login/index.vue b/frontend/src/view/login/index.vue new file mode 100644 index 0000000..8ee1987 --- /dev/null +++ b/frontend/src/view/login/index.vue @@ -0,0 +1,109 @@ + + + diff --git a/frontend/src/view/reasoning/index.less b/frontend/src/view/reasoning/index.less new file mode 100644 index 0000000..04fe772 --- /dev/null +++ b/frontend/src/view/reasoning/index.less @@ -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%); + } + } + } + } + +} \ No newline at end of file diff --git a/frontend/src/view/reasoning/index.vue b/frontend/src/view/reasoning/index.vue new file mode 100644 index 0000000..dac4b89 --- /dev/null +++ b/frontend/src/view/reasoning/index.vue @@ -0,0 +1,213 @@ + + + diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..0a1fffd --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..09d15c3 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.node.json" + } + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ] + } + }, +} \ No newline at end of file diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..db0becc --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..1666705 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + port: 3003, + host: '0.0.0.0', + proxy: { + '/matagent': { + target: 'http://159.75.91.126:8000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/matagent/, '/matagent') + } + } + }, +})