508 lines
12 KiB
Vue
508 lines
12 KiB
Vue
|
|
<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>
|