可信代码库-可信开源代码库AI助手首页开发完成

This commit is contained in:
付民康
2025-05-06 18:07:15 +08:00
parent 3697fcee93
commit 3d7edb71d7
29 changed files with 5187 additions and 2896 deletions

1097
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
}, },
"dependencies": { "dependencies": {
"@devui-design/icons": "^1.3.0", "@devui-design/icons": "^1.3.0",
"@matechat/core": "^1.4.0",
"@micro-zoe/micro-app": "1.0.0-rc.3", "@micro-zoe/micro-app": "1.0.0-rc.3",
"@sentry/vue": "^7.101.1", "@sentry/vue": "^7.101.1",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
@@ -45,8 +46,8 @@
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-devui": "^1.6.31", "vue-devui": "^1.6.31",
"vue-router": "^4.2.2", "vue-router": "^4.2.2",
"yaml": "^2.4.1", "xlsx": "^0.18.5",
"xlsx": "^0.18.5" "yaml": "^2.4.1"
}, },
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.2.0", "@rushstack/eslint-patch": "^1.2.0",
@@ -62,7 +63,6 @@
"@vue/eslint-config-typescript": "^11.0.3", "@vue/eslint-config-typescript": "^11.0.3",
"@vue/tsconfig": "^0.4.0", "@vue/tsconfig": "^0.4.0",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"eslint-plugin-vue": "^9.11.0", "eslint-plugin-vue": "^9.11.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "^8.4.27", "postcss": "^8.4.27",

View File

@@ -301,3 +301,22 @@
cursor: pointer; cursor: pointer;
color: rgb(246, 111, 106) color: rgb(246, 111, 106)
} }
.jyh-split {
background: #DFDFDF;
height: 20px;
width: 1px;
}
.jyh-button {
border-radius: 4px !important;
background: #E1E8FD !important;
color: black !important;
}
.jyh-button1 {
border-radius: 4px !important;
background: transparent !important;
color: black !important;
padding: 6px 6px !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,286 @@
<template>
<div class="full-w" :style="{ height: `${height}px` }">
<div class="g-header fixed z-20" ref="headerRef" :class="{ 'home-background': isHome }">
<div class="g-toolbar w-full overflow-hidden" :class="[searchClass, route?.meta?.headerClassName]">
<div class="g-toolbar-left flex-shrink items-center">
<div class="flex-center gap-[16px]" v-if="!isHome">
<!-- <g-icon v-if="!asideSetShow" @click="$emit('clickMenu')" name="gt-hamburger" size="20px" class="cursor-pointer hover:!text-black" color="#1D1F3A"></g-icon>-->
<GLink class="g-toolbar-left-logo" @click="gotoAIHome" />
<GLink class="g-toolbar-left-title" @click="gotoAIHome">AI助手</GLink>
<div class="jyh-split"></div>
<d-button class="jyh-button" variant="solid" @click="handleLogin('login')">
<div class="flex flex-center">
<img style="height: 15px;margin-right: 8px" width="16" :src="icon_dh" />
<span>AI对话</span>
</div>
</d-button>
<d-button class="jyh-button1" variant="solid" @click="handleLogin('login')">
<div class="flex flex-center">
<img style="height: 15px;margin-right: 8px" width="16" :src="icon_bbx" />
<span>百宝箱</span>
</div>
</d-button>
</div>
</div>
<div class="g-toolbar-right gap-[12px] flex-grow-0 flex-shrink-0">
<!--ai copilot-->
<a class="g-header-copilot" @click="gotoHome">
<img style="height: 15px;" width="16" :src="zh" />
<span class="ml-xs">返回平台搜索</span>
</a>
<d-button class="jyh-button1" variant="solid" @click="handleLogin('login')">
<div class="flex flex-center">
<img style="height: 15px;margin-right: 8px" width="16" :src="icon_sj" />
<span>升级产品</span>
</div>
</d-button>
<d-button class="jyh-button1" variant="solid" @click="handleLogin('login')">
<div class="flex flex-center">
<img style="height: 15px;margin-right: 8px" width="16" :src="icon_api" />
<span>API接口</span>
</div>
</d-button>
<d-button class="jyh-button1" variant="solid" @click="handleLogin('login')">
<div class="flex flex-center">
<img style="height: 15px;margin-right: 8px" width="16" :src="icon_helper" />
<span>帮助文档</span>
</div>
</d-button>
<UserAvatar />
</div>
</div>
<div v-if="$slots.info || $slots.menu || $slots.bodyHeader" class="g-header-body">
<slot name="bodyHeader">
<div class="g-header-body-left h-full">
<slot name="info" />
</div>
<div class="g-header-body-right h-full">
<slot name="menu"></slot>
</div>
</slot>
</div>
<div v-if="$slots.headerBottom">
<slot name="headerBottom"></slot>
</div>
</div>
</div>
</template>
<script setup>
import { onBeforeUnmount, ref, watchEffect, computed } from 'vue';
import { useElementSize } from '@vueuse/core';
import { storeToRefs } from 'pinia';
import { useRoute, useRouter } from 'vue-router';
import { useAccountStore } from '@/stores/user';
import { emitEvent } from '@/utils/eventBus';
import Create from './Create.vue';
import Entrance from './Entrance.vue';
import Notice from './Notice.vue';
import Search from './Search.vue';
import SearchAiHistory from './searchAiHistory.vue';
import UserAvatar from './UserAvatar.vue';
import { useWindowScroll } from '@vueuse/core';
import { usePageResize } from '@/utils/hooks/usePageResize';
import microApp from '@micro-zoe/micro-app';
import icon_dh from '@/assets/imgs/jyh/ai/icon_dh.png';
import icon_sj from '@/assets/imgs/jyh/ai/icon_sj.png';
import icon_api from '@/assets/imgs/jyh/ai/icon_api.png';
import icon_helper from '@/assets/imgs/jyh/ai/icon_helper.png';
import zh from '@/assets/imgs/jyh/ai/zh.png';
import icon_bbx from '@/assets/imgs/jyh/ai/icon_bbx.png';
const baseUrl = import.meta.env.VITE_BASE_URL;
const { y } = useWindowScroll();
const defaultLinkList = ref([]);
const route = useRoute();
const inSearch = ref(false);
const searchClass = computed(() => ({ 'border-b-transparent': inSearch.value && y.value === 0 }));
// header fixed布局高度自适应
const headerRef = ref(null);
const { height = 56 } = useElementSize(headerRef);
const userInfo = useAccountStore();
const { isLogin } = storeToRefs(userInfo);
const showSearch = computed(() => route.name !== 'home' && route.name !== 'search');
const isHome = computed(() => route.name === 'home');
const handleLogin = (type) => {
emitEvent('login', { type, triggerType: '主动注册' });
};
const { isMobile } = usePageResize();
const router = useRouter();
const gotoHome = (row) => {
// 后续请换成params暂时params传参没传过去
router.push({ name: 'home' });
};
const gotoAIHome = (row) => {
// 后续请换成params暂时params传参没传过去
router.push({ name: 'aihome' });
};
/* store 用户信息 */
/* @test 后面将清除 */
watchEffect(() => {});
microApp.router.beforeEach({
'micoro-app-homeweb-app': (to, from) => {
if (to.pathname === '/search') {
inSearch.value = true;
} else {
inSearch.value = false;
}
}
});
defineExpose({
height
});
</script>
<style lang="scss" scoped>
@import 'devui-theme/styles-var/devui-var.scss';
$radius: 4px;
$add-radius: 48px;
$header-bg: #fff;
.home-background {
background-color: #fff0 !important;
}
.g-header {
// overflow: hidden;
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: space-between;
background: #FFFFFF0C;
:deep(.g-link) {
color: $devui-text-weak;
}
.g-header-copilot {
background: black;
color: #fff;
//width: 120px;
padding: 0 12px;
border-radius: 48px;
height: 32px;
display: flex;
align-items: center;
cursor: pointer;
justify-content: center;
&:hover {
background: rgba(41, 81, 224, 0.8);
}
}
.g-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
flex: 1;
gap: 16px;
flex-basis: 56px;
overflow: hidden;
padding: 0 24px;
border-bottom: 1px solid #f1f1f8;
:deep(.devui-button) {
padding: 6px 16px;
border-radius: $add-radius;
}
&-left {
display: flex;
align-items: center;
gap: 25px;
&-logo {
display: inline-block;
width: 32px;
height: 32px;
background: url(../../assets/imgs/jyh/ai/aiLogo1.png) no-repeat;
background-size: contain;
vertical-align: middle;
background-position: 0 center;
}
&-title {
font-family: Inter;
font-weight: Semi Bold;
font-size: 20px;
line-height: 28px;
letter-spacing: 0px;
text-align: left;
background: linear-gradient(-81.02deg, #5EC9D4 0%, #83CA6F 24%, #D79808 49%, #F97C91 76%, #C87EDD 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
&-search {
max-width: 340px;
border-radius: $radius;
}
}
&-right {
display: flex;
align-items: center;
@media screen and (max-width: 576px) {
@apply gap-[16px];
}
}
&.border-b-transparent {
border-bottom-color: transparent !important;
}
&.border-b-show {
border-bottom-color: #f1f1f8 !important;
}
}
.g-header-gray {
@apply bg-[#F9F9FB];
}
&-body {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 32px;
background-color: $header-bg;
border-bottom: 1px solid $devui-dividing-line;
flex-basis: 48px;
}
}
@media screen and (max-width: 1000px) {
.g-header {
.g-toolbar {
padding: 20px;
border-color: transparent;
}
.g-toolbar-left-logo {
width: 28px;
background-size: cover;
}
&-body {
padding: 0;
}
}
}
.btn-add {
background-color: #000;
color: #fff !important;
width: 84px;
font-size: 14px;
line-height: 20px;
span {
margin-left: 8px;
}
&:hover {
color: #fff;
background-color: #333;
}
}
@media screen and (max-width: 576px) {
.hidden-m {
display: none;
}
.g-header-copilot {
display: none !important;
}
}
</style>

View File

@@ -2,7 +2,7 @@
<d-layout class="d-layout h-full" :class="{ 'home-background': isHome }"> <d-layout class="d-layout h-full" :class="{ 'home-background': isHome }">
<d-header class="d-header"> <d-header class="d-header">
<!-- header --> <!-- header -->
<Header @clickMenu="handleClickMenu" :asideSetShow="asideSetShow" ref="refHeader"> <AIHeader @clickMenu="handleClickMenu" :asideSetShow="asideSetShow" ref="refHeader">
<!-- 二级导航左侧内容 --> <!-- 二级导航左侧内容 -->
<template v-if="$slots.headerLeft && !route?.meta?.hideNav" #info> <template v-if="$slots.headerLeft && !route?.meta?.hideNav" #info>
<slot name="headerLeft"></slot> <slot name="headerLeft"></slot>
@@ -19,7 +19,7 @@
<template v-if="$slots.headerBottom && !route?.meta?.hideNav" #headerBottom> <template v-if="$slots.headerBottom && !route?.meta?.hideNav" #headerBottom>
<slot name="headerBottom"></slot> <slot name="headerBottom"></slot>
</template> </template>
</Header> </AIHeader>
</d-header> </d-header>
<!-- 顶栏信息 --> <!-- 顶栏信息 -->
<div v-if="$slots.contentBanner"> <div v-if="$slots.contentBanner">
@@ -70,7 +70,7 @@
defineOptions({ name: 'DefaultLayout' }); defineOptions({ name: 'DefaultLayout' });
import { RouterView, useRoute, useRouter } from 'vue-router'; import { RouterView, useRoute, useRouter } from 'vue-router';
import { computed, ref, watch, watchEffect, onMounted, onUnmounted } from 'vue'; import { computed, ref, watch, watchEffect, onMounted, onUnmounted } from 'vue';
import Header from '@/components/Header/index.vue'; import AIHeader from '@/components/Header/AIHeader.vue';
import Footer from '@/components/Footer/index.vue'; import Footer from '@/components/Footer/index.vue';
import Aside from './aside.vue'; import Aside from './aside.vue';
import { useGlobalInfoStore } from '@/stores/Global'; import { useGlobalInfoStore } from '@/stores/Global';

View File

@@ -28,6 +28,7 @@ import qs from 'qs';
import * as Sentry from '@sentry/vue'; import * as Sentry from '@sentry/vue';
import { storageApis } from './api/storage-apis'; import { storageApis } from './api/storage-apis';
import { storageService } from './utils/apiStorage'; import { storageService } from './utils/apiStorage';
import MateChat from '@matechat/core';
import { gitCodeTheme } from './utils/theme/gitcodeTheme'; import { gitCodeTheme } from './utils/theme/gitcodeTheme';
const themeService = ThemeServiceInit({ 'gitcode-theme': gitCodeTheme }, 'gitcode-theme'); const themeService = ThemeServiceInit({ 'gitcode-theme': gitCodeTheme }, 'gitcode-theme');
@@ -184,6 +185,7 @@ Sentry.init({
app.use(Icon); app.use(Icon);
app.use(dInput); app.use(dInput);
app.use(DevUI); app.use(DevUI);
app.use(MateChat);
app.component('gc-checkbox-group', DCheckboxGroup); app.component('gc-checkbox-group', DCheckboxGroup);
app.component('gc-checkbox', DCheckbox); app.component('gc-checkbox', DCheckbox);
app.component('gc-option', Option); app.component('gc-option', Option);

View File

@@ -117,12 +117,12 @@ export default [
{ {
path: '/ai/home', path: '/ai/home',
name: 'aihome', name: 'aihome',
component: () => import('@/views/Jyh/Home/index.vue'), component: () => import('@/views/Jyh/AI/Home/index.vue'),
meta: { meta: {
micorApp: ['micoro-app-homeweb-app'], micorApp: ['micoro-app-homeweb-app'],
hasMobile: true, hasMobile: true,
hiddenFooter: true, hiddenFooter: true,
reportTitle: '首页', reportTitle: 'AI首页',
className: 'w-full min-w-full', className: 'w-full min-w-full',
headerClassName: '', headerClassName: '',
asideMenu: 'drawer' // 左侧菜单抽屉模式 asideMenu: 'drawer' // 左侧菜单抽屉模式

View File

@@ -0,0 +1,64 @@
<template>
<McInput :value="inputValue" :loading="loading" @submit="onSubmit">
<template #head>
<div class="appendix-wrap">
<div class="appendix-item">
<span>README.md</span>
<i class="icon-code-editor-close"></i>
</div>
</div>
</template>
<template #extra>
<div class="input-foot-left">
<span><i class="icon-at"></i>智能体</span>
<span><i class="icon-appendix"></i>附件</span>
</div>
</template>
</McInput>
</template>
<script setup>
import { defineComponent, ref } from 'vue';
import { McInput } from '@matechat/core';
const inputValue = ref('');
const loading = ref(false);
const onSubmit = (e) => {
loading.value = true;
setTimeout(() => {
loading.value = false;
}, 1000);
console.log('input submit---', e);
};
</script>
<style scoped lang="scss">
.input-foot-left {
display: flex;
align-items: center;
gap: 8px;
font-size: var(--devui-font-size-sm);
color: var(--devui-text);
i {
margin-right: 4px;
}
}
.appendix-wrap {
display: flex;
padding: 4px 8px;
.appendix-item {
padding: 4px;
border-radius: var(--devui-border-radius);
background-color: var(--devui-area);
i {
margin-left: 4px;
cursor: pointer;
}
}
}
</style>

View File

@@ -0,0 +1,103 @@
<template>
<div class="guess-you-want-to-ask-card">
<div class="card-title">
<img src="@/assets/imgs/jyh/ai/icon_cnxw.png" style="width: 20px;height: 20px;margin-right: 8px"></img>
<span>猜你想问</span>
<d-button @click="changeQuestions" class="jyh-button1" variant="solid">
<div class="flex flex-center">
<img style="height: 15px;margin-right: 8px" width="16" src="@/assets/imgs/jyh/ai/icon_reflush.png" />
<span>换一批</span>
</div>
</d-button>
</div>
<div class="card-description">
<button v-for="(question, index) in questions" :key="index" class="question-button">
{{ question }}
</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
// 假设的图标 URL你可以根据实际情况修改
const iconUrl = 'https://example.com/icon.png';
// 初始化问题列表
const questions = ref([
'列出热门开源软件',
'开源软件',
'未通过校验列表',
'通过校验列表',
'可使用、未治理和有风险的软件有哪些',
'危险和严重的个人预警通知',
'我的订阅列表'
]);
// 换一批问题的函数
const changeQuestions = () => {
// 这里可以实现从后端获取新问题列表的逻辑
// 示例中简单地更新问题列表
questions.value = [
'热门数据库软件有哪些',
'数据库软件的选型',
'代码漏洞检测方法',
'性能优化策略'
];
};
</script>
<style scoped lang="scss">
.guess-you-want-to-ask-card {
border-radius: 24px;
background: #FFFFFF;
padding: 24px;
}
.card-title {
display: flex;
align-items: center;
color: #191919;
font-family: HarmonyOS Sans SC;
font-weight: bold;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.card-title i {
margin-right: 8px;
}
.title-icon {
width: 20px;
height: 20px;
margin-right: 8px;
}
.card-description {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 20px;
}
.question-button {
border: none;
border-radius: 35px;
background: #F3F3F3;
padding: 6px 12px;
cursor: pointer;
}
.question-button:hover {
background-color: #e0e0e0;
}
.jyh-button1 {
margin-left: auto;
}
</style>

View File

@@ -0,0 +1,139 @@
<template>
<div>
<div ref="mySwiper" class="swiper" id="swiper">
<!-- Slides -->
<div class="swiper-wrapper">
<div style="padding: 20px 6px" class="swiper-slide" v-for="item in slideData" :key="item.id">
<div class="ach-item bg-f" @click="onSelectItem(item)">
<div class="card">
<div class="card-title">
<img :src="item.icon" style="width: 20px;height: 20px;margin-right: 8px"></img>
<span >{{ item.title }}</span>
</div>
<div class="card-description">{{ item.description }}</div>
</div>
</div>
</div>
</div>
<!-- Navigation buttons -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import Swiper from 'swiper';
import { Navigation, Autoplay } from 'swiper/modules';
import 'swiper/css';
import 'swiper/css/navigation';
import icon_hot from '@/assets/imgs/jyh/ai/icon_hot.png';
import icon_yj from '@/assets/imgs/jyh/ai/icon_yj.png';
import icon_jh from '@/assets/imgs/jyh/ai/icon_jh.png';
const mySwiper = ref(null);
const slideData = [
{ id: 1, title: '热门开源软件', description: '列出热门开源软件',icon:icon_hot },
{ id: 2, title: '预警公告', description: '列出最近预警公告',icon:icon_yj },
{ id: 3, title: '你可以帮我做什么', description: '了解当前AI助手可以帮忙干什么',icon:icon_jh }
];
const onSelectItem = (item) => {
console.log('Selected item:', item);
};
onMounted(() => {
new Swiper(mySwiper.value, {
// Swiper选项
observer: true,
observeParents: true,
modules: [Navigation, Autoplay],
slidesPerView: 3,
spaceBetween: 20,
autoplay: {
delay: 2500,
disableOnInteraction: false
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev'
}
});
});
</script>
<style scoped>
.swiper {
width: 100%;
margin: 0 auto;
}
.card {
border-radius: 24px;
background: #FFFFFF;
padding: 24px;
}
.card-title {
display: flex;
align-items: center;
color: #191919;
font-family: HarmonyOS Sans SC;
font-weight: bold;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.card-title i {
margin-right: 8px;
}
.card-description {
margin-left: 28px;
margin-top: 4px;
color: #595959;
font-family: HarmonyOS Sans SC;
font-weight: regular;
font-size: 14px;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
.swiper-button-prev {
left: -18px;
}
.swiper-button-next {
right: -18px;
}
.swiper-button-prev, .swiper-button-next {
top: 55%;
width: 40px;
height: 40px;
display: inline-flex;
padding: 12px;
align-items: center;
gap: 10px;
border-radius: 100px;
border: 2px solid #FFF;
background: rgba(255, 255, 255, 0.50);
backdrop-filter: blur(5px);
}
.swiper-button-prev:after, .swiper-button-next:after {
color: #4E5969;
font-family: swiper-icons;
font-size: 16px;
text-transform: none !important;
letter-spacing: 0;
font-variant: initial;
line-height: 1;
}
</style>

View File

@@ -0,0 +1,507 @@
<template>
<div class="home-container">
<div class="home-content">
<div v-if="startChat" ref="conversationRef" class="conversation-area">
<template v-for="(msg, idx) in messages" :key="idx">
<McBubble v-if="msg.from === 'user'" :content="msg.content" :align="'right'" :avatarConfig="msg.avatarConfig"></McBubble>
<McBubble v-else :loading="msg.loading ?? false" :avatarConfig="msg.avatarConfig">
<McMarkdownCard :content="msg.content" :theme="theme"></McMarkdownCard>
<template #bottom>
<div class="bubble-bottom-operations">
<i class="icon-copy-new"></i>
<i class="icon-like"></i>
<i class="icon-dislike"></i>
</div>
</template>
</McBubble>
</template>
</div>
<div v-else class="welcome-page">
<div class="home-title">
<img src="@/assets/imgs/jyh/ai/aiLogo2.png" alt="logo" />
<h1>可信开源代码库AI助手</h1>
</div>
<div class="home-detail">
<div class="home-detail-1">Hi欢迎使用可信开源代码库AI助手</div>
<div class="home-detail-2">可信开源代码库AI助手 可以辅助研发人员进行软件查询批量校验查看软件详情等</div>
<div class="home-detail-2">作为AI模型可信开源代码库AI助手 提供的答案可能不总是确定或准确的但您的反馈可以帮助 可信开源代码库AI助手 做得更好</div>
</div>
<div style="width: 100%;margin-top: -20px">
<SwiperComponent />
</div>
<!--猜你想问-->
<div style="width: 100%;margin-top: -20px">
<GuessYouWantToAsk />
</div>
</div>
<!--操作按钮-->
<div class="new-convo-button">
<McPrompt
v-if="startChat"
class="simple-prompt"
style="flex: 1"
:list="simplePrompt"
:direction="'horizontal'"
@itemClick="onItemClick($event)"
></McPrompt>
<div v-else class="agent-knowledge">
<d-dropdown :position="['top']" @toggle="(val) => (isAgentOpen = val)">
<div class="agent-wrapper">
<img src="@/assets/imgs/jyh/ai/aiLogo2.png" />
<span>{{ selectedAgent.label }}</span>
<i class="icon-infrastructure"></i>
<i :class="['icon-chevron-down-2', { 'is-open': isAgentOpen }]"></i>
</div>
<template #menu>
<McList :data="agentList" @select="(val) => (selectedAgent = val)"></McList>
</template>
</d-dropdown>
<span class="agent-knowledge-dividing-line"></span>
<div class="knowledge-wrapper">
<i class="icon-operation-log"></i>
<span>添加知识</span>
</div>
</div>
<d-button icon="add" shape="circle" title="新建对话" size="sm" @click="onNewConvo" />
</div>
<!--输入框-->
<div style="padding: 0 12px 12px 12px;width: 100%">
<McInput :placeholder="placeholder" :value="inputValue" :maxLength="maxLength" @change="(e) => (inputValue = e)" @submit="onSubmit">
<template #extra>
<div class="input-foot-wrapper">
<div class="input-foot-left">
<span v-for="(item, index) in inputFootIcons" :key="index" @click="() => onInputIconClick(item)">
<i :class="item.icon"></i>
{{ item.text }}
</span>
<span class="input-foot-dividing-line"></span>
<span class="input-foot-maxlength">{{ inputValue.length }}/{{maxLength}}</span>
</div>
<div class="input-foot-right">
<d-button icon="op-clearup" shape="round" :disabled="!inputValue" @click="inputValue = ''">清空输入</d-button>
</div>
</div>
</template>
</McInput>
</div>
<!-- 添加使用条款部分 -->
<div class="usage-terms">
<p>内容由AI生成无法确保准确性和完整性仅供参考使用条款 隐私声明</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineComponent, nextTick, onMounted } from 'vue';
import { introPrompt, simplePrompt, mockAnswer, guessQuestions } from './mock.constants';
import SwiperComponent from './SwiperComponent.vue';
import GuessYouWantToAsk from './GuessYouWantToAsk.vue';
const inputValue = ref('');
const startChat = ref(false);
const conversationRef = ref();
const isAgentOpen = ref(false);
const theme = ref('light');
const maxLength = ref(8000);
const placeholder = ref('请输入问题描述,描述越详细 DevMate 回复越准确( Enter发送Shift+Enter 换行,↑ 查看最近发出的消息 )');
let themeService;
const agentList = ref([
{ label: 'MateChat', value: 'matechat', active: true },
{ label: 'InsCode', value: 'inscode' },
]);
const selectedAgent = ref(agentList.value[0]);
const aiModelAvatar = {
imgSrc: 'https://matechat.gitcode.com/logo.svg',
width: 32,
height: 32,
};
const customerAvatar = {
imgSrc: 'https://matechat.gitcode.com/png/demo/userAvatar.svg',
width: 32,
height: 32,
};
const inputFootIcons = [
{ icon: 'icon-at', text: '智能体' },
{ icon: 'icon-standard', text: '词库' },
{ icon: 'icon-add', text: '附件' },
];
const messages = ref([]);
const onInputIconClick = (e) => {
if (e.icon === 'icon-at') {
inputValue.value += `@${selectedAgent.value.label}`;
}
};
const onSubmit = (e, answer = undefined) => {
if(e === '') {
return;
}
inputValue.value = '';
if (!messages.value.length) {
startChat.value = true;
}
messages.value.push({
from: 'user',
content: e,
avatarPosition: 'side-right',
avatarConfig: { ...customerAvatar },
});
nextTick(() => {
conversationRef.value?.scrollTo({
top: conversationRef.value.scrollHeight,
behavior: 'smooth',
});
});
getAIAnswer(answer ?? e);
};
const getAIAnswer = (content) => {
messages.value.push({
from: 'ai-model',
content: '',
avatarPosition: 'side-left',
avatarConfig: { ...aiModelAvatar },
loading: true,
});
/* 模拟流式数据返回 */
setTimeout(async () => {
messages.value.at(-1).loading = false;
for (let i = 0; i < content.length;) {
await new Promise(r => setTimeout(r, 300 * Math.random()));
messages.value[messages.value.length - 1].content = content.slice(0, i += Math.random() * 10);
nextTick(() => {
conversationRef.value?.scrollTo({
top: conversationRef.value.scrollHeight
});
});
}
}, 1000);
};
const onNewConvo = () => {
startChat.value = false;
messages.value = [];
};
const onItemClick = (item) => {
if (mockAnswer[item.value]) {
// 使用 mock 数据
onSubmit(item.label, mockAnswer[item.value]);
}
};
const themeChange = () => {
if (themeService) {
theme.value = themeService.currentTheme.id === 'infinity-theme' ? 'light' : 'dark';
}
}
onMounted(() => {
if(typeof window !== 'undefined'){
themeService = window['devuiThemeService'];
}
themeChange();
if (themeService && themeService.eventBus) {
themeService.eventBus.add('themeChanged', themeChange);
}
});
</script>
<style scoped lang="scss">
.home-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
.home-content {
font-size: 14px;
padding-top: 3%;
padding-bottom: 1%;
height: 100%;
width: 50%;
display: flex;
flex-direction: column;
justify-self: center;
align-items: center;
gap: 20px;
}
.home-title {
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
gap: 20px;
h1 {
color: #191919;
font-family: Huawei Sans;
font-weight: bold;
font-size: 36px;
line-height: 48px;
letter-spacing: 0px;
text-align: center;
}
img {
width: 86px;
//filter: brightness(0);
}
}
.home-detail {
.home-detail-1 {
color: #191919;
font-family: HarmonyOS Sans SC;
font-weight: medium;
font-size: 24px;
line-height: 32px;
letter-spacing: 0px;
text-align: center;
margin-bottom: 24px;
}
.home-detail-2 {
color: #777777;
font-family: HarmonyOS Sans SC;
font-weight: regular;
font-size: 14px;
line-height: 22px;
letter-spacing: 0px;
text-align: center;
}
}
.conversation-area,
.welcome-page {
height: 90%;
width: 100%;
flex: 1;
display: flex;
flex-direction: column;
//overflow: auto;
padding: 0 12px;
}
.conversation-area {
gap: 8px;
}
.welcome-page {
gap: 24px;
justify-content: center;
}
.guess-question {
width: 100%;
padding: 16px 12px;
border-radius: var(--devui-border-radius-card);
background-color: var(--devui-gray-form-control-bg);
.guess-title {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--devui-text);
margin-bottom: 12px;
& > div:first-child {
font-weight: 700;
font-size: var(--devui-font-size);
}
& > div:last-child {
font-size: var(--devui-font-size-sm);
cursor: pointer;
span {
margin-left: 4px;
}
}
}
.guess-content {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
span {
font-size: var(--devui-font-size-sm);
color: var(--devui-text);
padding: 4px 12px;
border-radius: var(--devui-border-radius-full);
background-color: var(--devui-gray-form-control-hover-bg);
cursor: pointer;
}
}
}
.bubble-bottom-operations {
margin-top: 8px;
i {
padding: 4px;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: var(--devui-icon-hover-bg);
}
}
}
.new-convo-button {
width: 100%;
padding: 0 12px;
display: flex;
justify-content: flex-end;
align-items: center;
height: 39px;
gap: 4px;
}
:deep(.simple-prompt .mc-list) {
justify-content: unset;
}
:deep(.intro-prompt .mc-list) {
justify-content: center;
}
.operations {
i {
padding: 4px;
border-radius: 4px;
cursor: pointer;
&:hover {
background: var(--devui-global-bg);
}
}
}
.agent-knowledge {
flex: 1;
display: flex;
align-items: center;
.agent-wrapper {
display: flex;
align-items: center;
padding: 4px 8px;
border-radius: var(--devui-border-radius-full);
background-color: var(--devui-area);
cursor: pointer;
img {
width: 16px;
height: 16px;
margin-right: 4px;
}
span {
font-size: var(--devui-font-size);
color: var(--devui-text);
margin-right: 8px;
}
i {
font-size: var(--devui-font-size);
color: var(--devui-text);
transition: transform 0.3s ease-in-out;
&:last-child {
margin-left: 4px;
}
}
.is-open {
transform: rotate(180deg);
}
}
.agent-knowledge-dividing-line {
width: 1px;
height: 14px;
margin: 0 12px;
background-color: var(--devui-line);
}
.knowledge-wrapper {
font-size: var(--devui-font-size);
color: var(--devui-text);
cursor: pointer;
span {
margin-left: 4px;
}
}
}
.input-foot-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 100%;
margin-right: 8px;
.input-foot-left {
display: flex;
align-items: center;
gap: 8px;
span {
font-size: var(--devui-font-size-sm);
color: var(--devui-text);
cursor: pointer;
}
.input-foot-dividing-line {
width: 1px;
height: 14px;
background-color: var(--devui-line);
}
.input-foot-maxlength {
font-size: var(--devui-font-size-sm);
color: var(--devui-aide-text);
}
}
.input-foot-right {
& > *:not(:first-child) {
margin-left: 8px;
}
}
}
:deep(.mc-input) {
background: #ffffff;
.mc-textarea {
height: 100px;
}
}
}
.usage-terms {
position: absolute;
bottom: 0;
color: #AEAEAE;
font-family: HarmonyOS Sans SC;
font-weight: regular;
font-size: 12px;
line-height: 20px;
letter-spacing: 0px;
text-align: left;
}
</style>

View File

@@ -0,0 +1,422 @@
<template>
<div class="demo-test">
<McHeader :logoImg="'@/assets/imgs/jyh/ai/aiLogo2.png'" :title="'MateChat'">
<template #operationArea>
<div class="operations">
<i class="icon-helping"></i>
</div>
</template>
</McHeader>
<div v-if="startChat" ref="conversationRef" class="conversation-area">
<template v-for="(msg, idx) in messages" :key="idx">
<McBubble v-if="msg.from === 'user'" :content="msg.content" :align="'right'" :avatarConfig="msg.avatarConfig"></McBubble>
<McBubble v-else :loading="msg.loading ?? false" :avatarConfig="msg.avatarConfig">
<McMarkdownCard :content="msg.content" :theme="theme"></McMarkdownCard>
<template #bottom>
<div class="bubble-bottom-operations">
<i class="icon-copy-new"></i>
<i class="icon-like"></i>
<i class="icon-dislike"></i>
</div>
</template>
</McBubble>
</template>
</div>
<div v-else class="welcome-page">
<McIntroduction
logo-img="/logo2x.svg"
title="MateChat"
sub-title="Hi欢迎使用 MateChat"
:description="[
'MateChat 可以辅助研发人员编码、查询知识和相关作业信息、编写文档等。',
'作为AI模型MateChat 提供的答案可能不总是确定或准确的,但您的反馈可以帮助 MateChat 做的更好。',
]"
></McIntroduction>
<McPrompt :list="introPrompt.list" :direction="'horizontal'" class="intro-prompt" @itemClick="onItemClick($event)"></McPrompt>
<div class="guess-question">
<div class="guess-title">
<div>猜你想问</div>
<div>
<i class="icon-recover"></i>
<span>换一批</span>
</div>
</div>
<div class="guess-content">
<span v-for="(item, index) in guessQuestions" :key="index" @click="onItemClick(item)">{{ item.label }}</span>
</div>
</div>
</div>
<div class="new-convo-button">
<McPrompt
v-if="startChat"
class="simple-prompt"
style="flex: 1"
:list="simplePrompt"
:direction="'horizontal'"
@itemClick="onItemClick($event)"
></McPrompt>
<div v-else class="agent-knowledge">
<d-dropdown :position="['top']" @toggle="(val) => (isAgentOpen = val)">
<div class="agent-wrapper">
<img src="@/assets/imgs/jyh/ai/aiLogo2.png" />
<span>{{ selectedAgent.label }}</span>
<i class="icon-infrastructure"></i>
<i :class="['icon-chevron-down-2', { 'is-open': isAgentOpen }]"></i>
</div>
<template #menu>
<McList :data="agentList" @select="(val) => (selectedAgent = val)"></McList>
</template>
</d-dropdown>
<span class="agent-knowledge-dividing-line"></span>
<div class="knowledge-wrapper">
<i class="icon-operation-log"></i>
<span>添加知识</span>
</div>
</div>
<d-button icon="add" shape="circle" title="新建对话" size="sm" @click="onNewConvo" />
</div>
<div style="padding: 0 12px 12px 12px">
<McInput :value="inputValue" :maxLength="2000" @change="(e) => (inputValue = e)" @submit="onSubmit">
<template #extra>
<div class="input-foot-wrapper">
<div class="input-foot-left">
<span v-for="(item, index) in inputFootIcons" :key="index" @click="() => onInputIconClick(item)">
<i :class="item.icon"></i>
{{ item.text }}
</span>
<span class="input-foot-dividing-line"></span>
<span class="input-foot-maxlength">{{ inputValue.length }}/2000</span>
</div>
<div class="input-foot-right">
<d-button icon="op-clearup" shape="round" :disabled="!inputValue" @click="inputValue = ''">清空输入</d-button>
</div>
</div>
</template>
</McInput>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineComponent, nextTick, onMounted } from 'vue';
import { introPrompt, simplePrompt, mockAnswer, guessQuestions } from './mock.constants';
const inputValue = ref('');
const startChat = ref(false);
const conversationRef = ref();
const isAgentOpen = ref(false);
const theme = ref('light');
let themeService;
const agentList = ref([
{ label: 'MateChat', value: 'matechat', active: true },
{ label: 'InsCode', value: 'inscode' },
]);
const selectedAgent = ref(agentList.value[0]);
const aiModelAvatar = {
imgSrc: 'https://matechat.gitcode.com/logo.svg',
width: 32,
height: 32,
};
const customerAvatar = {
imgSrc: 'https://matechat.gitcode.com/png/demo/userAvatar.svg',
width: 32,
height: 32,
};
const inputFootIcons = [
{ icon: 'icon-at', text: '智能体' },
{ icon: 'icon-standard', text: '词库' },
{ icon: 'icon-add', text: '附件' },
];
const messages = ref([]);
const onInputIconClick = (e) => {
if (e.icon === 'icon-at') {
inputValue.value += `@${selectedAgent.value.label}`;
}
};
const onSubmit = (e, answer = undefined) => {
if(e === '') {
return;
}
inputValue.value = '';
if (!messages.value.length) {
startChat.value = true;
}
messages.value.push({
from: 'user',
content: e,
avatarPosition: 'side-right',
avatarConfig: { ...customerAvatar },
});
nextTick(() => {
conversationRef.value?.scrollTo({
top: conversationRef.value.scrollHeight,
behavior: 'smooth',
});
});
getAIAnswer(answer ?? e);
};
const getAIAnswer = (content) => {
messages.value.push({
from: 'ai-model',
content: '',
avatarPosition: 'side-left',
avatarConfig: { ...aiModelAvatar },
loading: true,
});
/* 模拟流式数据返回 */
setTimeout(async () => {
messages.value.at(-1).loading = false;
for (let i = 0; i < content.length;) {
await new Promise(r => setTimeout(r, 300 * Math.random()));
messages.value[messages.value.length - 1].content = content.slice(0, i += Math.random() * 10);
nextTick(() => {
conversationRef.value?.scrollTo({
top: conversationRef.value.scrollHeight
});
});
}
}, 1000);
};
const onNewConvo = () => {
startChat.value = false;
messages.value = [];
};
const onItemClick = (item) => {
if (mockAnswer[item.value]) {
// 使用 mock 数据
onSubmit(item.label, mockAnswer[item.value]);
}
};
const themeChange = () => {
if (themeService) {
theme.value = themeService.currentTheme.id === 'infinity-theme' ? 'light' : 'dark';
}
}
onMounted(() => {
if(typeof window !== 'undefined'){
themeService = window['devuiThemeService'];
}
themeChange();
if (themeService && themeService.eventBus) {
themeService.eventBus.add('themeChanged', themeChange);
}
});
</script>
<style scoped lang="scss">
.demo-test {
width: 100%;
min-width: 500px;
height: calc(100vh - 180px);
display: flex;
flex-direction: column;
gap: 8px;
.conversation-area,
.welcome-page {
flex: 1;
display: flex;
flex-direction: column;
overflow: auto;
padding: 0 12px;
}
.conversation-area {
gap: 8px;
}
.welcome-page {
gap: 24px;
justify-content: center;
}
.guess-question {
width: 100%;
padding: 16px 12px;
border-radius: var(--devui-border-radius-card);
background-color: var(--devui-gray-form-control-bg);
.guess-title {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--devui-text);
margin-bottom: 12px;
& > div:first-child {
font-weight: 700;
font-size: var(--devui-font-size);
}
& > div:last-child {
font-size: var(--devui-font-size-sm);
cursor: pointer;
span {
margin-left: 4px;
}
}
}
.guess-content {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
span {
font-size: var(--devui-font-size-sm);
color: var(--devui-text);
padding: 4px 12px;
border-radius: var(--devui-border-radius-full);
background-color: var(--devui-gray-form-control-hover-bg);
cursor: pointer;
}
}
}
.bubble-bottom-operations {
margin-top: 8px;
i {
padding: 4px;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: var(--devui-icon-hover-bg);
}
}
}
.new-convo-button {
padding: 0 12px;
display: flex;
justify-content: flex-end;
align-items: center;
height: 39px;
gap: 4px;
}
:deep(.simple-prompt .mc-list) {
justify-content: unset;
}
:deep(.intro-prompt .mc-list) {
justify-content: center;
}
.operations {
i {
padding: 4px;
border-radius: 4px;
cursor: pointer;
&:hover {
background: var(--devui-global-bg);
}
}
}
.agent-knowledge {
flex: 1;
display: flex;
align-items: center;
.agent-wrapper {
display: flex;
align-items: center;
padding: 4px 8px;
border-radius: var(--devui-border-radius-full);
background-color: var(--devui-area);
cursor: pointer;
img {
width: 16px;
height: 16px;
margin-right: 4px;
}
span {
font-size: var(--devui-font-size);
color: var(--devui-text);
margin-right: 8px;
}
i {
font-size: var(--devui-font-size);
color: var(--devui-text);
transition: transform 0.3s ease-in-out;
&:last-child {
margin-left: 4px;
}
}
.is-open {
transform: rotate(180deg);
}
}
.agent-knowledge-dividing-line {
width: 1px;
height: 14px;
margin: 0 12px;
background-color: var(--devui-line);
}
.knowledge-wrapper {
font-size: var(--devui-font-size);
color: var(--devui-text);
cursor: pointer;
span {
margin-left: 4px;
}
}
}
.input-foot-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 100%;
margin-right: 8px;
.input-foot-left {
display: flex;
align-items: center;
gap: 8px;
span {
font-size: var(--devui-font-size-sm);
color: var(--devui-text);
cursor: pointer;
}
.input-foot-dividing-line {
width: 1px;
height: 14px;
background-color: var(--devui-line);
}
.input-foot-maxlength {
font-size: var(--devui-font-size-sm);
color: var(--devui-aide-text);
}
}
.input-foot-right {
& > *:not(:first-child) {
margin-left: 8px;
}
}
}
}
</style>

View File

@@ -0,0 +1,473 @@
<template>
<div class="home-container">
<div class="home-content">
<div class="home-title">
<img src="@/assets/imgs/jyh/ai/aiLogo2.png" alt="logo" />
<h1>可信开源代码库AI助手</h1>
</div>
<div class="home-detail">
<div class="home-detail-1">Hi欢迎使用可信开源代码库AI助手</div>
<div class="home-detail-2">可信开源代码库AI助手 可以辅助研发人员进行软件查询批量校验查看软件详情等</div>
<div class="home-detail-2">作为AI模型可信开源代码库AI助手 提供的答案可能不总是确定或准确的但您的反馈可以帮助 可信开源代码库AI助手 做得更好</div>
</div>
<!--swiper-->
<div style="width: 100%;">
<SwiperComponent />
</div>
<!--猜你想问-->
<div style="width: 100%;margin-top: -20px">
<GuessYouWantToAsk />
</div>
<!--AI大模型输入框-->
<div style="width: 100%;">
<AIinout />
</div>
</div>
</div>
<d-modal class="warning-modal" v-model="warningModalVisable" title="" style="width: 600px">
<EarlyWarningDetail :warningData="currentWarning" :type="'user'"></EarlyWarningDetail>
</d-modal>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAccount } from '@/utils/hooks/useAccount';
import AdvanceSearch from '@/components/AdvanceSearch/index.vue';
import { getHomeNoticeList } from '@/api/jyh';
import EarlyWarningDetail from '@/views/Jyh/PersonalCenter/Detail/EarlyWarningDetail.vue';
import { useGlobalInfoStore } from '@/stores/Global';
const { updateHeaderSearch } = useGlobalInfoStore();
const { accountInfo } = useAccount();
import { useDiscussGetUserInfo } from '@/api/discussion/hook';
import SwiperComponent from './SwiperComponent.vue';
import GuessYouWantToAsk from './GuessYouWantToAsk.vue';
import AIinout from './AIinout.vue';
const route = useRoute();
const router = useRouter();
const inputGlow = ref(false);
const searchStr = ref('');
const position = ref(['right-start', 'right-end']);
const industryOptions = ref(['通信', '军工', '金融', '能源']);
const searchType = ref('软件');
const searchOptions = ref(['软件']);
const offset = ref({ mainAxis: 7, crossAxis: -10 });
const isKeepOpen = ref(true);
const isOpen = ref(false);
const cateRotate = ref(0);
const infoList = ref([]);
const warningModalVisable = ref(false);
const currentWarning = ref();
const { isLogin } = useDiscussGetUserInfo();
const homeSearch = ref(null);
const dropdownWidth = ref(0);
const tagMap = {
critical: {
text: '致命',
color: '#c7000b'
},
high: {
text: '高',
color: '#f66f6a'
},
medium: {
text: '中',
color: '#fac20a'
},
low: {
text: '低',
color: '#5e7ce0'
},
Critical: {
text: '致命',
color: '#c7000b'
},
High: {
text: '高',
color: '#f66f6a'
},
Medium: {
text: '中',
color: '#fac20a'
},
Low: {
text: '低',
color: '#5e7ce0'
}
};
const rotate = computed(() => {
return isOpen.value ? 180 : 0;
});
const searchInput = ref(null);
const openAdvancedSearch = () => {
isOpen.value = !isOpen.value;
inputGlow.value = isOpen.value;
};
const check = () => {
router.push({
name: 'Search',
query: { tab: 'BatchVerify' } // 通过查询参数指定 Tab
});
};
const closeOutside = () => {
return false;
};
const submit = (formData) => {
isOpen.value = false;
router.push({
name: 'Search',
query: {
softwareName: searchStr.value ? searchStr.value : '',
searchType: getIndustryValue(searchType.value),
softwareVersion: formData.softwareVersion || '',
programmingLanguage: formData.programmingLanguage || '',
interestTag: formData.interestTag || '',
industry: formData.industry || '',
licenseName: formData.licenseName || '',
dependentSoftware: formData.dependentSoftware || '',
releaseDateStart: formData.dateRange && formData.dateRange.length > 0 ? formData.dateRange[0] : '',
releaseDateEnd: formData.dateRange && formData.dateRange.length > 0 ? formData.dateRange[1] : ''
}
});
};
const inputFocus = () => {
inputGlow.value = true;
};
const inputBlur = () => {
inputGlow.value = false;
};
const updateDropdownWidth = () => {
if (homeSearch.value) {
dropdownWidth.value = homeSearch.value.offsetWidth * 0.85;
}
};
const globalInfo = useGlobalInfoStore();
const industryList = globalInfo.industryList;
const getIndustryValue = (name) => {
const industry = industryList.find((item) => item.name === name);
return industry ? industry.value : ''; // 如果找不到,返回默认值
};
const search = () => {
// updateHeaderSearch({ searchWord: searchStr.value, searchType: selectedCate.value });
router.push({
name: 'Search',
query: {
softwareName: searchStr.value ? searchStr.value : '',
searchType: getIndustryValue(searchType.value)
}
});
// window.open(`${import.meta.env.VITE_BASE_URL}repoList?softwareName=${searchStr.value ? searchStr.value : ''}&searchType=${getIndustryValue(searchType.value)}`, '_blank')
};
const onCategoryChange = (event) => {
searchType.value = event?.name || '软件';
globalInfo.updateHeaderSearch({ softwareName: softwareName.value, searchType: event.value });
};
const onCateToggle = (event) => {
cateRotate.value = event ? 180 : 0;
};
const checkMore = () => {
router.push({ name: 'PersonalCenter' });
};
const openWarningDetail = (event, index) => {
infoList.value.forEach((item, i) => {
if (i <= index) {
item.dotColor = 'var(--devui-info)';
} else {
item.dotColor = '';
}
});
warningModalVisable.value = true;
currentWarning.value = event;
};
let resizeObserver: ResizeObserver | null = null;
onMounted(() => {
resizeObserver = new ResizeObserver(updateDropdownWidth);
if (homeSearch.value) {
resizeObserver.observe(homeSearch.value);
}
updateDropdownWidth();
document.querySelector('body').onclick = (e) => {
const className = e.target.className;
if (
!className.includes('close-isopen') &&
!className.includes('devui-badge__content') &&
!className.includes('advanced-search')
) {
isOpen.value = false;
}
};
});
onBeforeUnmount(() => {
if (resizeObserver && homeSearch.value) {
resizeObserver.unobserve(homeSearch.value);
}
});
const getNoticeList = async () => {
const res: any = await getHomeNoticeList();
if (!res.error) {
infoList.value = res.data.data || [];
}
};
getNoticeList();
</script>
<style scoped lang="scss">
@import 'devui-theme/styles-var/devui-var.scss';
.home-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
.home-content {
font-size: 14px;
height: 80%;
width: 50%;
display: flex;
flex-direction: column;
justify-self: center;
align-items: center;
gap: 20px;
.home-title {
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
gap: 20px;
h1 {
color: #191919;
font-family: Huawei Sans;
font-weight: bold;
font-size: 48px;
line-height: 48px;
letter-spacing: 0px;
text-align: center;
}
img {
width: 90px;
//filter: brightness(0);
}
}
.home-search {
width: 574px;
height: 46px;
border: 1px solid #dfe1e5;
box-shadow: 0px 2px 8px 0px rgba(60, 64, 67, 0.25);
border-radius: 24px;
//width: 100%;
//height: 40px;
flex-shrink: 0;
background-color: white;
//border-radius: 10px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
//box-shadow: var(--devui-shadow, 0 2px 12px 0);
input {
border: none;
outline: none;
padding: 5px;
font-size: 14px;
}
.advanced-search {
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
&::before {
content: '';
position: absolute;
left: -10px;
top: 50%;
transform: translateY(-50%);
width: 1px;
height: 20px;
background-color: var(--devui-form-control-line, #d7d8da);
}
}
.advanced-search-menu {
height: 300px;
padding: 12px;
background-color: white;
}
.form-operation {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
}
.batch-btn {
color: $devui-aide-text;
width: 150px;
flex-shrink: 0;
background-color: #f0f0f0;
border-color: $devui-aide-text;
}
.home-information {
width: 100%;
background-color: #f0f0f0;
//overflow: auto;
margin-bottom: 20px;
border-radius: 10px;
padding: 12px;
.info-title {
img {
width: 20px;
}
display: flex;
justify-content: space-between;
.sub-title {
display: flex;
gap: 8px;
}
margin-bottom: 20px;
margin-left: 10px;
}
:deep(.devui-timeline-item-data-left) {
display: none;
}
:deep(.devui-timeline-item-dot) {
scale: 0.7;
}
}
}
}
.home-search {
:deep(.devui-input-slot__prepend),
:deep(.devui-input-slot__append),
:deep(.devui-input__wrapper) {
border: none;
}
:deep(.devui-select__selection--glow-style),
:deep(.devui-input--glow-style) {
box-shadow: none !important;
}
}
.glow-border {
border: 1px solid var(--devui-form-control-line, #d7d8da);
border-color: var(--devui-form-control-line-active, #5e7ce0) !important;
box-shadow: 0 0 0 4px var(--devui-form-control-interactive-outline, rgba(94, 124, 224, 0.08));
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.menu-item {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 4px;
cursor: pointer;
&:hover {
background-color: var(--devui-list-item-hover-bg, #f2f5fc);
color: var(--devui-list-item-hover-text, #526ecc);
}
}
.list-menu {
padding: 8px;
margin: 0;
width: 100px;
}
.title {
font-size: 14px;
font-weight: 500;
}
.notice-content {
flex: 1;
overflow: hidden;
margin-right: 8px;
span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
}
.time-container {
display: flex;
align-items: center;
gap: 8px;
width: 140px;
flex-shrink: 0;
}
:deep(.devui-timeline-item-data-right) {
width: 80%;
}
.home-detail {
.home-detail-1 {
color: #191919;
font-family: HarmonyOS Sans SC;
font-weight: medium;
font-size: 24px;
line-height: 32px;
letter-spacing: 0px;
text-align: center;
margin-bottom: 32px;
}
.home-detail-2 {
color: #777777;
font-family: HarmonyOS Sans SC;
font-weight: regular;
font-size: 14px;
line-height: 22px;
letter-spacing: 0px;
text-align: center;
}
}
</style>

View File

@@ -0,0 +1,57 @@
// mock.constants.js
export const introPrompt = {
list: [
{
label: '如何使用 MateChat 辅助编码?',
value: 'how_to_use_for_coding',
},
{
label: 'MateChat 能查询哪些知识?',
value: 'what_knowledge_can_query',
},
{
label: '如何添加知识到 MateChat',
value: 'how_to_add_knowledge',
},
],
};
export const simplePrompt = [
{
label: '快速查询代码示例',
value: 'quick_code_example',
},
{
label: '获取性能优化建议',
value: 'performance_optimization',
},
{
label: '辅助编写文档',
value: 'write_document_assistance',
},
];
export const mockAnswer = {
how_to_use_for_coding: 'MateChat 可以通过输入相关的代码需求比如函数实现、算法逻辑等它会为您提供代码示例和解释。您可以输入具体的代码功能描述例如“编写一个冒泡排序算法”MateChat 会给出相应的代码实现。',
what_knowledge_can_query: 'MateChat 可以查询编程知识、算法、数据结构、编程语言特性、框架使用方法等。还可以查询作业相关的信息,例如任务进度、资源分配等。',
how_to_add_knowledge: '要添加知识到 MateChat您可以点击界面上的“添加知识”按钮然后按照提示上传相关的文档或者输入具体的知识内容即可。',
quick_code_example: '以下是一个简单的 JavaScript 代码示例,用于计算数组的总和:\n```javascript\nconst arr = [1, 2, 3, 4, 5];\nconst sum = arr.reduce((acc, val) => acc + val, 0);\nconsole.log(sum);\n```',
performance_optimization: '性能优化可以从多个方面入手,例如减少循环嵌套、合理使用缓存、优化算法复杂度等。对于具体的代码,您可以提供给 MateChat 进行分析和建议。',
write_document_assistance: 'MateChat 可以辅助您编写文档,例如生成文档大纲、填充内容等。您可以输入文档的主题和一些关键信息,然后让 MateChat 帮助您生成相关内容。',
};
export const guessQuestions = [
{
label: 'MateChat 支持哪些编程语言?',
value: 'supported_programming_languages',
},
{
label: '如何查看 MateChat 的使用记录?',
value: 'how_to_view_usage_record',
},
{
label: 'MateChat 能处理复杂的业务逻辑吗?',
value: 'handle_complex_business_logic',
},
];

4898
yarn.lock

File diff suppressed because it is too large Load Diff