初始化

This commit is contained in:
2026-02-22 18:56:10 +08:00
commit 26677972a6
3112 changed files with 255972 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { cloneDeep } from '@easyflow/utils';
import { ArrowLeft, Minus, Plus } from '@element-plus/icons-vue';
import {
ElAside,
ElAvatar,
ElButton,
ElContainer,
ElIcon,
ElMain,
ElMessage,
ElSpace,
} from 'element-plus';
import { api } from '#/api/request';
import defaultBotAvatar from '#/assets/defaultBotAvatar.png';
import { Card, CardDescription, CardTitle } from '#/components/card';
import { ChatBubbleList, ChatSender } from '#/components/chat';
import { $t } from '#/locales';
onMounted(async () => {
getUserUsed();
getBotDetail();
});
const router = useRouter();
const route = useRoute();
const usedList = ref<any[]>([]);
const botInfo = ref<any>({});
const btnLoading = ref(false);
const conversationId = ref('');
function getUserUsed() {
api.get('/userCenter/botRecentlyUsed/list').then((res) => {
usedList.value = res.data.map((item: any) => item.botId);
});
}
function getBotDetail() {
api
.get('/userCenter/bot/getDetail', {
params: {
id: route.params.id,
},
})
.then((res) => {
botInfo.value = res.data;
});
api.get('/userCenter/bot/generateConversationId').then((res) => {
conversationId.value = res.data;
});
}
function addBotToRecentlyUsed(botId: any) {
btnLoading.value = true;
api
.post('/userCenter/botRecentlyUsed/save', {
botId,
})
.then((res) => {
btnLoading.value = false;
if (res.errorCode === 0) {
ElMessage.success($t('message.success'));
getUserUsed();
}
});
}
function removeBotFromRecentlyUsed(botId: any) {
btnLoading.value = true;
api
.get('/userCenter/botRecentlyUsed/removeByBotId', {
params: {
botId,
},
})
.then((res) => {
btnLoading.value = false;
if (res.errorCode === 0) {
ElMessage.success($t('message.success'));
getUserUsed();
}
});
}
const messageList = ref<any>([]);
function addMessage(message: any) {
messageList.value.push(message);
}
function updateLastMessage(item: any) {
const lastIndex = messageList.value.length - 1;
let message = item;
if (typeof item === 'function') {
message = item(messageList.value[lastIndex]);
}
if (lastIndex >= 0) {
messageList.value[lastIndex] = {
...messageList.value[lastIndex],
...message,
};
}
}
const stopThinking = () => {
const lastIndex = messageList.value.length - 1;
if (lastIndex >= 0 && messageList.value[lastIndex]?.chains) {
const chains = cloneDeep(messageList.value[lastIndex].chains);
for (const chain of chains) {
if (!('id' in chain) && chain.thinkingStatus === 'thinking') {
chain.thinkingStatus = 'end';
}
}
messageList.value[lastIndex].chains = chains;
}
};
</script>
<template>
<ElContainer class="bg-background-deep h-full p-6 pr-0">
<ElMain
class="border-border bg-background !flex flex-col rounded-xl border !p-6"
>
<ElSpace :size="16" class="cursor-pointer" @click="router.back()">
<ElIcon :size="24"><ArrowLeft /></ElIcon>
<ElSpace :size="12">
<ElAvatar :size="36" :src="botInfo.icon || defaultBotAvatar" />
<h1 class="text-base font-semibold">
{{ botInfo.title }}
</h1>
</ElSpace>
</ElSpace>
<div class="relative mx-auto w-full max-w-[884px] flex-1">
<Card
v-if="messageList.length === 0"
class="absolute left-1/2 top-1/2 max-w-[340px] -translate-x-1/2 -translate-y-1/2 flex-col items-center gap-0"
>
<ElAvatar :size="64" :src="botInfo.icon || defaultBotAvatar" />
<CardTitle class="mt-4">{{ botInfo.title }}</CardTitle>
<CardDescription class="mt-2.5 text-center text-[#566882]">
{{ botInfo.description }}
</CardDescription>
</Card>
<ChatBubbleList v-else :bot="botInfo" :messages="messageList" />
<ChatSender
class="absolute bottom-5 left-0 w-full"
:add-message="addMessage"
:update-last-message="updateLastMessage"
:stop-thinking="stopThinking"
:bot="botInfo"
:conversation-id="conversationId"
/>
</div>
</ElMain>
<ElAside width="407px" class="px-3 pt-10">
<Card class="mx-auto max-w-[340px] flex-col items-center gap-0">
<ElAvatar :size="64" :src="botInfo.icon || defaultBotAvatar" />
<CardTitle class="mt-4">{{ botInfo.title }}</CardTitle>
<CardDescription class="mt-2.5 text-center text-[#566882]">
{{ botInfo.description }}
</CardDescription>
<ElButton
v-if="!usedList.includes(botInfo.id)"
:loading="btnLoading"
class="mt-8 !h-9 w-full"
type="primary"
:icon="Plus"
@click="addBotToRecentlyUsed(botInfo.id)"
>
添加到聊天助理
</ElButton>
<ElButton
v-else
:loading="btnLoading"
class="mt-8 !h-9 w-full"
type="primary"
:icon="Minus"
@click="removeBotFromRecentlyUsed(botInfo.id)"
>
从聊天助理中移除
</ElButton>
</Card>
</ElAside>
</ElContainer>
</template>

View File

@@ -0,0 +1,233 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { IconifyIcon } from '@easyflow/icons';
import { cn } from '@easyflow/utils';
import { Minus, Plus, Search } from '@element-plus/icons-vue';
import {
ElButton,
ElContainer,
ElHeader,
ElInput,
ElMain,
ElMessage,
ElSpace,
} from 'element-plus';
import { api } from '#/api/request';
import defaultBotAvatar from '#/assets/defaultBotAvatar.png';
import {
Card,
CardAvatar,
CardContent,
CardDescription,
CardTitle,
} from '#/components/card';
import { $t } from '#/locales';
const router = useRouter();
const categories = ref<any[]>([]);
const botList = ref<any[]>([]);
const queryParams = ref<any>({});
const pageLoading = ref(false);
const activeTag = ref('');
const usedList = ref<any[]>([]);
const btnLoading = ref(false);
onMounted(async () => {
getBotList();
getCategoryList();
getUserUsed();
});
function getCategoryList() {
api.get('/userCenter/botCategory/list').then((res) => {
categories.value = [
{
id: '',
categoryName: '全部',
},
...res.data,
];
});
}
function getBotList() {
pageLoading.value = true;
api
.get('/userCenter/bot/list', {
params: { ...queryParams.value, status: 1 },
})
.then((res) => {
pageLoading.value = false;
botList.value = res.data;
});
}
function handleTagClick(tag: any) {
activeTag.value = tag;
queryParams.value.categoryId = tag;
getBotList();
}
function getUserUsed() {
api.get('/userCenter/botRecentlyUsed/list').then((res) => {
usedList.value = res.data.map((item: any) => item.botId);
});
}
function addBotToRecentlyUsed(botId: any) {
btnLoading.value = true;
api
.post('/userCenter/botRecentlyUsed/save', {
botId,
})
.then((res) => {
btnLoading.value = false;
if (res.errorCode === 0) {
ElMessage.success($t('message.success'));
getUserUsed();
getBotList();
}
});
}
function removeBotFromRecentlyUsed(botId: any) {
btnLoading.value = true;
api
.get('/userCenter/botRecentlyUsed/removeByBotId', {
params: {
botId,
},
})
.then((res) => {
btnLoading.value = false;
if (res.errorCode === 0) {
ElMessage.success($t('message.success'));
getUserUsed();
getBotList();
}
});
}
</script>
<template>
<ElContainer class="bg-background-deep h-full">
<ElHeader class="!h-auto !p-8 !pb-0">
<ElSpace direction="vertical" :size="24" alignment="flex-start">
<h1 class="text-2xl font-medium">助理市场</h1>
<ElSpace :size="20">
<ElInput
placeholder="搜索"
v-model="queryParams.title"
@keyup.enter="getBotList"
:prefix-icon="Search"
/>
<ElSpace :size="12">
<button
type="button"
:class="
cn(
'border-border text-foreground bg-background h-[35px] w-[94px] rounded-3xl border text-sm',
activeTag === category.id
? 'border-primary text-primary bg-primary/10'
: 'hover:bg-accent',
)
"
v-for="category in categories"
:key="category.id"
@click="handleTagClick(category.id)"
>
{{ category.categoryName }}
</button>
</ElSpace>
</ElSpace>
</ElSpace>
</ElHeader>
<ElMain class="!px-8">
<div
class="grid grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-5"
v-loading="pageLoading"
>
<Card
class="border-border bg-background h-[168px] w-full max-w-none flex-col justify-between rounded-xl border p-6 pb-5 transition hover:-translate-y-2 hover:shadow-[0px_2px_16px_0px_rgba(6,27,57,0.07)]"
v-for="assistant in botList"
:key="assistant.id"
>
<CardContent class="gap-3">
<CardContent class="flex-row items-center gap-3">
<CardAvatar
:src="assistant.icon"
:default-avatar="defaultBotAvatar"
/>
<CardTitle :title="assistant.title">
{{ assistant.title }}
</CardTitle>
</CardContent>
<CardDescription
class="text-foreground/50 line-clamp-2 text-wrap text-sm"
:title="assistant.description"
>
{{ assistant.description }}
</CardDescription>
</CardContent>
<div class="flex w-full items-center">
<ElButton
v-if="!usedList.includes(assistant.id)"
:loading="btnLoading"
class="w-full"
type="primary"
style="--el-border: none"
:icon="Plus"
plain
@click="addBotToRecentlyUsed(assistant.id)"
>
添加到聊天助理
</ElButton>
<ElButton
v-else
:loading="btnLoading"
class="w-full"
type="primary"
style="--el-border: none"
:icon="Minus"
plain
@click="removeBotFromRecentlyUsed(assistant.id)"
>
从聊天助理中移除
</ElButton>
<ElButton
class="w-full"
type="primary"
style="--el-border: none"
plain
@click="router.push(`/assistantMarket/${assistant.id}`)"
>
<template #icon>
<IconifyIcon icon="mdi:play-outline" />
</template>
立即体验
</ElButton>
</div>
<!-- <ElRow class="w-full" :gutter="16">
<ElCol :span="12">
</ElCol>
<ElCol :span="12">
</ElCol>
</ElRow> -->
</Card>
</div>
</ElMain>
</ElContainer>
</template>
<style lang="css" scoped>
.el-input :deep(.el-input__wrapper) {
--el-input-border-radius: 18px;
--el-input-border-color: #e6e9ee;
}
:deep(.el-button) {
--el-font-size-base: 12px;
--el-button-font-weight: 400;
}
</style>