feat(ai): 添加 AI 聊天功能

- 新增 AI 聊天对话和消息相关 API
- 实现聊天界面,包括对话列表、消息列表、发送消息等功能
- 添加音乐生成功能的初始框架
This commit is contained in:
gjd
2025-06-12 18:26:10 +08:00
parent d2fbb5a18b
commit 4596cd9fa5
25 changed files with 3109 additions and 94 deletions

View File

@@ -1,28 +1,28 @@
<script lang="ts" setup>
import type { Nullable, Recordable } from '@vben/types';
import { ref, unref } from 'vue';
import { Page } from '@vben/common-ui';
import { Button } from 'ant-design-vue';
import Mode from './mode/index.vue';
defineOptions({ name: 'Index' });
const listRef = ref<Nullable<{ generateMusic: (...args: any) => void }>>(null);
function generateMusic(args: { formData: Recordable<any> }) {
unref(listRef)?.generateMusic(args.formData);
}
</script>
<template>
<Page>
<Button
danger
type="link"
target="_blank"
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
>
该功能支持 Vue3 + element-plus 版本
</Button>
<br />
<Button
type="link"
target="_blank"
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/ai/music/index/index.vue"
>
可参考
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/ai/music/index/index.vue
代码pull request 贡献给我们
</Button>
<div class="flex h-full items-stretch">
<!-- 模式 -->
<Mode class="flex-none" @generate-music="generateMusic" />
<!-- 音频列表 -->
<List ref="listRef" class="flex-auto" />
</div>
</Page>
</template>

View File

@@ -0,0 +1,70 @@
<script lang="ts" setup>
import { reactive } from 'vue';
import { Select, Switch, Textarea } from 'ant-design-vue';
import Title from '../title/index.vue';
defineOptions({ name: 'Desc' });
const formData = reactive({
desc: '',
pure: false,
version: '3',
});
defineExpose({
formData,
});
</script>
<template>
<div>
<Title
title="音乐/歌词说明"
desc="描述您想要的音乐风格和主题,使用流派和氛围而不是特定的艺术家和歌曲"
>
<Textarea
v-model:value="formData.desc"
:autosize="{ minRows: 6, maxRows: 6 }"
:maxlength="1200"
:show-count="true"
placeholder="一首关于糟糕分手的欢快歌曲"
/>
</Title>
<Title title="纯音乐" class="mt-[20px]" desc="创建一首没有歌词的歌曲">
<template #extra>
<Switch v-model:checked="formData.pure" size="small" />
</template>
</Title>
<Title
title="版本"
desc="描述您想要的音乐风格和主题,使用流派和氛围而不是特定的艺术家和歌曲"
>
<Select
v-model:value="formData.version"
class="w-full"
placeholder="请选择"
>
<Select.Option
v-for="item in [
{
value: '3',
label: 'V3',
},
{
value: '2',
label: 'V2',
},
]"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</Select.Option>
</Select>
</Title>
</div>
</template>

View File

@@ -0,0 +1,43 @@
<script lang="ts" setup>
import type { Nullable, Recordable } from '@vben/types';
import { ref, unref } from 'vue';
import { Button, Card, Radio } from 'ant-design-vue';
import desc from './desc.vue';
import lyric from './lyric.vue';
defineOptions({ name: 'Index' });
const emits = defineEmits(['generateMusic']);
const generateMode = ref('lyric');
const modeRef = ref<Nullable<{ formData: Recordable<any> }>>(null);
/*
*@Description: 根据信息生成音乐
*@MethodAuthor: xiaohong
*@Date: 2024-06-27 16:40:16
*/
function generateMusic() {
emits('generateMusic', { formData: unref(modeRef)?.formData });
}
</script>
<template>
<Card class="mb-[0!important] h-full w-[300px]">
<Radio.Group v-model:value="generateMode" class="mb-[15px]">
<Radio.Button value="desc"> 描述模式 </Radio.Button>
<Radio.Button value="lyric"> 歌词模式 </Radio.Button>
</Radio.Group>
<!-- 描述模式/歌词模式 切换 -->
<component :is="generateMode === 'desc' ? desc : lyric" ref="modeRef" />
<Button type="primary" shape="round" class="w-full" @click="generateMusic">
创作音乐
</Button>
</Card>
</template>

View File

@@ -0,0 +1,103 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { Button, Input, Select, Space, Tag, Textarea } from 'ant-design-vue';
import Title from '../title/index.vue';
defineOptions({ name: 'Lyric' });
const tags = ['rock', 'punk', 'jazz', 'soul', 'country', 'kidsmusic', 'pop'];
const showCustom = ref(false);
const formData = reactive({
lyric: '',
style: '',
name: '',
version: '',
});
defineExpose({
formData,
});
</script>
<template>
<div class="">
<Title title="歌词" desc="自己编写歌词或使用Ai生成歌词两节/8行效果最佳">
<Textarea
v-model:value="formData.lyric"
:autosize="{ minRows: 6, maxRows: 6 }"
:maxlength="1200"
:show-count="true"
placeholder="请输入您自己的歌词"
/>
</Title>
<Title title="音乐风格">
<Space class="flex-wrap">
<Tag v-for="tag in tags" :key="tag" class="mb-[8px]">
{{ tag }}
</Tag>
</Space>
<Button
:type="showCustom ? 'primary' : 'default'"
shape="round"
size="small"
class="mb-[6px]"
@click="showCustom = !showCustom"
>
自定义风格
</Button>
</Title>
<Title
v-show="showCustom"
desc="描述您想要的音乐风格Suno无法识别艺术家的名字但可以理解流派和氛围"
class="mt-[12px]"
>
<Textarea
v-model="formData.style"
:autosize="{ minRows: 4, maxRows: 4 }"
:maxlength="256"
show-count
placeholder="输入音乐风格(英文)"
/>
</Title>
<Title title="音乐/歌曲名称">
<Input
class="w-full"
v-model="formData.name"
placeholder="请输入音乐/歌曲名称"
/>
</Title>
<Title title="版本">
<Select
v-model:value="formData.version"
class="w-full"
placeholder="请选择"
>
<Select.Option
v-for="item in [
{
value: '3',
label: 'V3',
},
{
value: '2',
label: 'V2',
},
]"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</Select.Option>
</Select>
</Title>
</div>
</template>

View File

@@ -0,0 +1,27 @@
<script lang="ts" setup>
defineOptions({ name: 'Index' });
defineProps({
title: {
type: String,
default: '',
},
desc: {
type: String,
default: '',
},
});
</script>
<template>
<div class="mb-[12px]">
<div class="flex items-center justify-between" style="color: #303133">
<span>{{ title }}</span>
<slot name="extra"></slot>
</div>
<div class="my-[8px] text-[12px]" style="color: #909399">
{{ desc }}
</div>
<slot></slot>
</div>
</template>