MateChat
MateChat
Hi,欢迎使用 MateChat
MateChat 可以辅助研发人员编码、查询知识和相关作业信息、编写文档等。
作为AI模型,MateChat 提供的答案可能不总是确定或准确的,但您的反馈可以帮助 MateChat 做的更好。
帮我写一个快速排序
使用 js 实现一个快速排序
你可以帮我做些什么?
了解当前大模型可以帮你做的事
怎么绑定项目空间
如何绑定云空间中的项目
猜你想问
换一批
最近执行流水线列表帮我写一个快速排序使用 js 格式化时间
添加知识
智能体 词库 附件0/2000
vue
<template>
<div class="demo-test">
<McHeader :logoImg="'/logo.svg'" :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="/logo.svg" />
<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>
显示代码
复制代码片段