可信代码库-可信开源代码库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

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',
},
];