feat: ai
parent
d09b993bc8
commit
014785a1ad
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { Item } from './ui/typeing';
|
||||
import type { Item } from './ui/typing';
|
||||
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export { default as Tinyflow } from './tinyflow.vue';
|
||||
export * from './ui/typing';
|
|
@ -1,21 +1,21 @@
|
|||
export declare type Item = {
|
||||
export interface Item {
|
||||
children?: Item[];
|
||||
label: string;
|
||||
value: number | string;
|
||||
};
|
||||
}
|
||||
|
||||
export type Position = {
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type Viewport = {
|
||||
export interface Viewport {
|
||||
x: number;
|
||||
y: number;
|
||||
zoom: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type Node = {
|
||||
export interface Node {
|
||||
data?: Record<string, any>;
|
||||
draggable?: boolean;
|
||||
height?: number;
|
||||
|
@ -24,23 +24,23 @@ export type Node = {
|
|||
selected?: boolean;
|
||||
type?: string;
|
||||
width?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type Edge = {
|
||||
export interface Edge {
|
||||
animated?: boolean;
|
||||
id: string;
|
||||
label?: string;
|
||||
source: string;
|
||||
target: string;
|
||||
type?: string;
|
||||
};
|
||||
}
|
||||
export type TinyflowData = Partial<{
|
||||
edges: Edge[];
|
||||
nodes: Node[];
|
||||
viewport: Viewport;
|
||||
}>;
|
||||
|
||||
export declare type TinyflowOptions = {
|
||||
export interface TinyflowOptions {
|
||||
data?: TinyflowData;
|
||||
element: Element | string;
|
||||
provider?: {
|
||||
|
@ -48,7 +48,7 @@ export declare type TinyflowOptions = {
|
|||
knowledge?: () => Item[] | Promise<Item[]>;
|
||||
llm?: () => Item[] | Promise<Item[]>;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export declare class Tinyflow {
|
||||
private _init;
|
||||
|
@ -66,5 +66,3 @@ export declare class Tinyflow {
|
|||
getOptions(): TinyflowOptions;
|
||||
setData(data: TinyflowData): void;
|
||||
}
|
||||
|
||||
export {};
|
|
@ -0,0 +1,3 @@
|
|||
export { default as MarkdownView } from './markdown-view.vue';
|
||||
|
||||
export * from './typing';
|
|
@ -1,4 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { MarkdownViewProps } from './typing';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { MarkdownIt } from '@vben/plugins/markmap';
|
||||
|
@ -10,15 +12,10 @@ import hljs from 'highlight.js';
|
|||
import 'highlight.js/styles/vs2015.min.css';
|
||||
|
||||
// 定义组件属性
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const props = defineProps<MarkdownViewProps>();
|
||||
|
||||
const { copy } = useClipboard(); // 初始化 copy 到粘贴板
|
||||
const contentRef = ref();
|
||||
const contentRef = ref<HTMLElement | null>(null);
|
||||
|
||||
const md = new MarkdownIt({
|
||||
highlight(str, lang) {
|
||||
|
@ -40,7 +37,7 @@ const renderedMarkdown = computed(() => {
|
|||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
// 添加 copy 监听
|
||||
contentRef.value.addEventListener('click', (e: any) => {
|
||||
contentRef.value?.addEventListener('click', (e: any) => {
|
||||
if (e.target.id === 'copy') {
|
||||
copy(e.target?.dataset?.copy);
|
||||
message.success('复制成功!');
|
|
@ -0,0 +1,3 @@
|
|||
export type MarkdownViewProps = {
|
||||
content: string;
|
||||
};
|
|
@ -28,7 +28,6 @@ const props = defineProps({
|
|||
default: null,
|
||||
},
|
||||
});
|
||||
/** 新建对话 */
|
||||
|
||||
// 定义钩子
|
||||
const emits = defineEmits([
|
||||
|
@ -37,9 +36,11 @@ const emits = defineEmits([
|
|||
'onConversationClear',
|
||||
'onConversationDelete',
|
||||
]);
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
connectedComponent: RoleRepository,
|
||||
});
|
||||
|
||||
// 定义属性
|
||||
const searchName = ref<string>(''); // 对话搜索
|
||||
const activeConversationId = ref<null | number>(null); // 选中的对话,默认为 null
|
||||
|
@ -50,7 +51,7 @@ const loading = ref<boolean>(false); // 加载中
|
|||
const loadingTime = ref<any>();
|
||||
|
||||
/** 搜索对话 */
|
||||
const searchConversation = async () => {
|
||||
async function searchConversation() {
|
||||
// 恢复数据
|
||||
if (searchName.value.trim().length === 0) {
|
||||
conversationMap.value = await getConversationGroupByCreateTime(
|
||||
|
@ -64,25 +65,25 @@ const searchConversation = async () => {
|
|||
conversationMap.value =
|
||||
await getConversationGroupByCreateTime(filterValues);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 点击对话 */
|
||||
const handleConversationClick = async (id: number) => {
|
||||
async function handleConversationClick(id: number) {
|
||||
// 过滤出选中的对话
|
||||
const filterConversation = conversationList.value.find((item) => {
|
||||
return item.id === id;
|
||||
});
|
||||
// 回调 onConversationClick
|
||||
// noinspection JSVoidFunctionReturnValueUsed
|
||||
const success = emits('onConversationClick', filterConversation);
|
||||
const success = emits('onConversationClick', filterConversation) as any;
|
||||
// 切换对话
|
||||
if (success) {
|
||||
activeConversationId.value = id;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取对话列表 */
|
||||
const getChatConversationList = async () => {
|
||||
async function getChatConversationList() {
|
||||
try {
|
||||
// 加载中
|
||||
loadingTime.value = setTimeout(() => {
|
||||
|
@ -114,12 +115,12 @@ const getChatConversationList = async () => {
|
|||
// 加载完成
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 按照 creteTime 创建时间,进行分组 */
|
||||
const getConversationGroupByCreateTime = async (
|
||||
async function getConversationGroupByCreateTime(
|
||||
list: AiChatConversationApi.ChatConversationVO[],
|
||||
) => {
|
||||
) {
|
||||
// 排序、指定、时间分组(今天、一天前、三天前、七天前、30天前)
|
||||
// noinspection NonAsciiCharacters
|
||||
const groupMap: any = {
|
||||
|
@ -159,8 +160,9 @@ const getConversationGroupByCreateTime = async (
|
|||
}
|
||||
}
|
||||
return groupMap;
|
||||
};
|
||||
const createConversation = async () => {
|
||||
}
|
||||
|
||||
async function createConversation() {
|
||||
// 1. 新建对话
|
||||
const conversationId = await createChatConversationMy(
|
||||
{} as unknown as AiChatConversationApi.ChatConversationVO,
|
||||
|
@ -171,12 +173,12 @@ const createConversation = async () => {
|
|||
await handleConversationClick(conversationId);
|
||||
// 4. 回调
|
||||
emits('onConversationCreate');
|
||||
};
|
||||
}
|
||||
|
||||
/** 修改对话的标题 */
|
||||
const updateConversationTitle = async (
|
||||
async function updateConversationTitle(
|
||||
conversation: AiChatConversationApi.ChatConversationVO,
|
||||
) => {
|
||||
) {
|
||||
// 1. 二次确认
|
||||
prompt({
|
||||
async beforeClose(scope) {
|
||||
|
@ -225,12 +227,12 @@ const updateConversationTitle = async (
|
|||
title: '修改标题',
|
||||
modelPropName: 'value',
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** 删除聊天对话 */
|
||||
const deleteChatConversation = async (
|
||||
async function deleteChatConversation(
|
||||
conversation: AiChatConversationApi.ChatConversationVO,
|
||||
) => {
|
||||
) {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await confirm(`是否确认删除对话 - ${conversation.title}?`);
|
||||
|
@ -242,8 +244,9 @@ const deleteChatConversation = async (
|
|||
// 回调
|
||||
emits('onConversationDelete', conversation);
|
||||
} catch {}
|
||||
};
|
||||
const handleClearConversation = async () => {
|
||||
}
|
||||
|
||||
async function handleClearConversation() {
|
||||
try {
|
||||
await confirm('确认后对话会全部清空,置顶的对话除外。');
|
||||
await deleteChatConversationMyByUnpinned();
|
||||
|
@ -255,18 +258,18 @@ const handleClearConversation = async () => {
|
|||
// 回调 方法
|
||||
emits('onConversationClear');
|
||||
} catch {}
|
||||
};
|
||||
}
|
||||
|
||||
/** 对话置顶 */
|
||||
const handleTop = async (
|
||||
async function handleTop(
|
||||
conversation: AiChatConversationApi.ChatConversationVO,
|
||||
) => {
|
||||
) {
|
||||
// 更新对话置顶
|
||||
conversation.pinned = !conversation.pinned;
|
||||
await updateChatConversationMy(conversation);
|
||||
// 刷新对话
|
||||
await getChatConversationList();
|
||||
};
|
||||
}
|
||||
|
||||
// ============ 角色仓库 ============
|
||||
|
||||
|
|
|
@ -47,10 +47,10 @@ const documentList = computed(() => {
|
|||
});
|
||||
|
||||
/** 点击 document 处理 */
|
||||
const handleClick = (doc: any) => {
|
||||
function handleClick(doc: any) {
|
||||
document.value = doc;
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -14,7 +14,7 @@ import { useClipboard } from '@vueuse/core';
|
|||
import { Avatar, Button, message } from 'ant-design-vue';
|
||||
|
||||
import { deleteChatMessage } from '#/api/ai/chat/message';
|
||||
import MarkdownView from '#/components/MarkdownView/index.vue';
|
||||
import { MarkdownView } from '#/components/markdown-view';
|
||||
|
||||
import MessageKnowledge from './MessageKnowledge.vue';
|
||||
// 定义 props
|
||||
|
@ -67,44 +67,44 @@ function handleScroll() {
|
|||
}
|
||||
|
||||
/** 回到底部 */
|
||||
const handleGoBottom = async () => {
|
||||
async function handleGoBottom() {
|
||||
const scrollContainer = messageContainer.value;
|
||||
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
||||
};
|
||||
}
|
||||
|
||||
/** 回到顶部 */
|
||||
const handlerGoTop = async () => {
|
||||
async function handlerGoTop() {
|
||||
const scrollContainer = messageContainer.value;
|
||||
scrollContainer.scrollTop = 0;
|
||||
};
|
||||
}
|
||||
|
||||
defineExpose({ scrollToBottom, handlerGoTop }); // 提供方法给 parent 调用
|
||||
|
||||
// ============ 处理消息操作 ==============
|
||||
|
||||
/** 复制 */
|
||||
const copyContent = async (content: string) => {
|
||||
async function copyContent(content: string) {
|
||||
await copy(content);
|
||||
message.success('复制成功!');
|
||||
};
|
||||
}
|
||||
/** 删除 */
|
||||
const onDelete = async (id: number) => {
|
||||
async function onDelete(id: number) {
|
||||
// 删除 message
|
||||
await deleteChatMessage(id);
|
||||
message.success('删除成功!');
|
||||
// 回调
|
||||
emits('onDeleteSuccess');
|
||||
};
|
||||
}
|
||||
|
||||
/** 刷新 */
|
||||
const onRefresh = async (message: AiChatMessageApi.ChatMessageVO) => {
|
||||
async function onRefresh(message: AiChatMessageApi.ChatMessageVO) {
|
||||
emits('onRefresh', message);
|
||||
};
|
||||
}
|
||||
|
||||
/** 编辑 */
|
||||
const onEdit = async (message: AiChatMessageApi.ChatMessageVO) => {
|
||||
async function onEdit(message: AiChatMessageApi.ChatMessageVO) {
|
||||
emits('onEdit', message);
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
|
|
|
@ -11,9 +11,9 @@ const promptList = [
|
|||
prompt: '写一首好听的诗歌?',
|
||||
},
|
||||
]; /** 选中 prompt 点击 */
|
||||
const handlerPromptClick = async (prompt: any) => {
|
||||
async function handlerPromptClick(prompt: any) {
|
||||
emits('onPrompt', prompt.prompt);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="relative flex h-full w-full flex-row justify-center">
|
||||
|
|
|
@ -4,9 +4,9 @@ import { Button } from 'ant-design-vue';
|
|||
const emits = defineEmits(['onNewConversation']);
|
||||
|
||||
/** 新建 conversation 聊天对话 */
|
||||
const handlerNewChat = () => {
|
||||
function handlerNewChat() {
|
||||
emits('onNewConversation');
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -19,9 +19,9 @@ defineProps({
|
|||
const emits = defineEmits(['onCategoryClick']);
|
||||
|
||||
/** 处理分类点击事件 */
|
||||
const handleCategoryClick = async (category: string) => {
|
||||
async function handleCategoryClick(category: string) {
|
||||
emits('onCategoryClick', category);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -33,7 +33,7 @@ const emits = defineEmits(['onDelete', 'onEdit', 'onUse', 'onPage']);
|
|||
const tabsRef = ref<any>();
|
||||
|
||||
/** 操作:编辑、删除 */
|
||||
const handleMoreClick = async (data: any) => {
|
||||
async function handleMoreClick(data: any) {
|
||||
const type = data[0];
|
||||
const role = data[1];
|
||||
if (type === 'delete') {
|
||||
|
@ -41,22 +41,22 @@ const handleMoreClick = async (data: any) => {
|
|||
} else {
|
||||
emits('onEdit', role);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 选中 */
|
||||
const handleUseClick = (role: any) => {
|
||||
function handleUseClick(role: any) {
|
||||
emits('onUse', role);
|
||||
};
|
||||
}
|
||||
|
||||
/** 滚动 */
|
||||
const handleTabsScroll = async () => {
|
||||
async function handleTabsScroll() {
|
||||
if (tabsRef.value) {
|
||||
const { scrollTop, scrollHeight, clientHeight } = tabsRef.value;
|
||||
if (scrollTop + clientHeight >= scrollHeight - 20 && !props.loading) {
|
||||
await emits('onPage');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -46,15 +46,15 @@ const activeCategory = ref<string>('全部'); // 选择中的分类
|
|||
const categoryList = ref<string[]>([]); // 角色分类类别
|
||||
|
||||
/** tabs 点击 */
|
||||
const handleTabsClick = async (tab: any) => {
|
||||
async function handleTabsClick(tab: any) {
|
||||
// 设置切换状态
|
||||
activeTab.value = tab;
|
||||
// 切换的时候重新加载数据
|
||||
await getActiveTabsRole();
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取 my role 我的角色 */
|
||||
const getMyRole = async (append?: boolean) => {
|
||||
async function getMyRole(append?: boolean) {
|
||||
const params: AiModelChatRoleApi.ChatRolePageReqVO = {
|
||||
...myRoleParams,
|
||||
name: search.value,
|
||||
|
@ -66,10 +66,10 @@ const getMyRole = async (append?: boolean) => {
|
|||
} else {
|
||||
myRoleList.value = list;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取 public role 公共角色 */
|
||||
const getPublicRole = async (append?: boolean) => {
|
||||
async function getPublicRole(append?: boolean) {
|
||||
const params: AiModelChatRoleApi.ChatRolePageReqVO = {
|
||||
...publicRoleParams,
|
||||
category: activeCategory.value === '全部' ? '' : activeCategory.value,
|
||||
|
@ -82,10 +82,10 @@ const getPublicRole = async (append?: boolean) => {
|
|||
} else {
|
||||
publicRoleList.value = list;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取选中的 tabs 角色 */
|
||||
const getActiveTabsRole = async () => {
|
||||
async function getActiveTabsRole() {
|
||||
if (activeTab.value === 'my-role') {
|
||||
myRoleParams.pageNo = 1;
|
||||
await getMyRole();
|
||||
|
@ -93,43 +93,44 @@ const getActiveTabsRole = async () => {
|
|||
publicRoleParams.pageNo = 1;
|
||||
await getPublicRole();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取角色分类列表 */
|
||||
const getRoleCategoryList = async () => {
|
||||
async function getRoleCategoryList() {
|
||||
categoryList.value = ['全部', ...(await getCategoryList())];
|
||||
};
|
||||
}
|
||||
|
||||
/** 处理分类点击 */
|
||||
const handlerCategoryClick = async (category: string) => {
|
||||
async function handlerCategoryClick(category: string) {
|
||||
// 切换选择的分类
|
||||
activeCategory.value = category;
|
||||
// 筛选
|
||||
await getActiveTabsRole();
|
||||
};
|
||||
}
|
||||
|
||||
const handlerAddRole = async () => {
|
||||
async function handlerAddRole() {
|
||||
formModalApi.setData({ formType: 'my-create' }).open();
|
||||
};
|
||||
}
|
||||
/** 编辑角色 */
|
||||
const handlerCardEdit = async (role: any) => {
|
||||
async function handlerCardEdit(role: any) {
|
||||
formModalApi.setData({ formType: 'my-update', id: role.id }).open();
|
||||
};
|
||||
}
|
||||
|
||||
/** 添加角色成功 */
|
||||
const handlerAddRoleSuccess = async () => {
|
||||
async function handlerAddRoleSuccess() {
|
||||
// 刷新数据
|
||||
await getActiveTabsRole();
|
||||
};
|
||||
}
|
||||
|
||||
/** 删除角色 */
|
||||
const handlerCardDelete = async (role: any) => {
|
||||
async function handlerCardDelete(role: any) {
|
||||
await deleteMy(role.id);
|
||||
// 刷新数据
|
||||
await getActiveTabsRole();
|
||||
};
|
||||
}
|
||||
|
||||
/** 角色分页:获取下一页 */
|
||||
const handlerCardPage = async (type: string) => {
|
||||
async function handlerCardPage(type: string) {
|
||||
try {
|
||||
loading.value = true;
|
||||
if (type === 'public') {
|
||||
|
@ -142,10 +143,10 @@ const handlerCardPage = async (type: string) => {
|
|||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 选择 card 角色:新建聊天对话 */
|
||||
const handlerCardUse = async (role: any) => {
|
||||
async function handlerCardUse(role: any) {
|
||||
// 1. 创建对话
|
||||
const data: AiChatConversationApi.ChatConversationVO = {
|
||||
roleId: role.id,
|
||||
|
@ -159,7 +160,8 @@ const handlerCardUse = async (role: any) => {
|
|||
conversationId,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
// 获取分类
|
||||
|
|
|
@ -61,7 +61,7 @@ const receiveMessageDisplayedText = ref('');
|
|||
// =========== 【聊天对话】相关 ===========
|
||||
|
||||
/** 获取对话信息 */
|
||||
const getConversation = async (id: null | number) => {
|
||||
async function getConversation(id: null | number) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ const getConversation = async (id: null | number) => {
|
|||
}
|
||||
activeConversation.value = conversation;
|
||||
activeConversationId.value = conversation.id;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击某个对话
|
||||
|
@ -80,9 +80,9 @@ const getConversation = async (id: null | number) => {
|
|||
* @param conversation 选中的对话
|
||||
* @return 是否切换成功
|
||||
*/
|
||||
const handleConversationClick = async (
|
||||
async function handleConversationClick(
|
||||
conversation: AiChatConversationApi.ChatConversationVO,
|
||||
) => {
|
||||
) {
|
||||
// 对话进行中,不允许切换
|
||||
if (conversationInProgress.value) {
|
||||
alert('对话中,不允许切换!');
|
||||
|
@ -99,20 +99,20 @@ const handleConversationClick = async (
|
|||
// 清空输入框
|
||||
prompt.value = '';
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** 删除某个对话*/
|
||||
const handlerConversationDelete = async (
|
||||
async function handlerConversationDelete(
|
||||
delConversation: AiChatConversationApi.ChatConversationVO,
|
||||
) => {
|
||||
) {
|
||||
// 删除的对话如果是当前选中的,那么就重置
|
||||
if (activeConversationId.value === delConversation.id) {
|
||||
await handleConversationClear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 清空选中的对话 */
|
||||
const handleConversationClear = async () => {
|
||||
async function handleConversationClear() {
|
||||
// 对话进行中,不允许切换
|
||||
if (conversationInProgress.value) {
|
||||
alert('对话中,不允许切换!');
|
||||
|
@ -121,31 +121,31 @@ const handleConversationClear = async () => {
|
|||
activeConversationId.value = null;
|
||||
activeConversation.value = null;
|
||||
activeMessageList.value = [];
|
||||
};
|
||||
}
|
||||
|
||||
const openChatConversationUpdateForm = async () => {
|
||||
async function openChatConversationUpdateForm() {
|
||||
formModalApi.setData({ id: activeConversationId.value }).open();
|
||||
};
|
||||
const handleConversationUpdateSuccess = async () => {
|
||||
}
|
||||
async function handleConversationUpdateSuccess() {
|
||||
// 对话更新成功,刷新最新信息
|
||||
await getConversation(activeConversationId.value);
|
||||
};
|
||||
}
|
||||
|
||||
/** 处理聊天对话的创建成功 */
|
||||
const handleConversationCreate = async () => {
|
||||
async function handleConversationCreate() {
|
||||
// 创建对话
|
||||
await conversationListRef.value.createConversation();
|
||||
};
|
||||
}
|
||||
/** 处理聊天对话的创建成功 */
|
||||
const handleConversationCreateSuccess = async () => {
|
||||
async function handleConversationCreateSuccess() {
|
||||
// 创建新的对话,清空输入框
|
||||
prompt.value = '';
|
||||
};
|
||||
}
|
||||
|
||||
// =========== 【消息列表】相关 ===========
|
||||
|
||||
/** 获取消息 message 列表 */
|
||||
const getMessageList = async () => {
|
||||
async function getMessageList() {
|
||||
try {
|
||||
if (activeConversationId.value === null) {
|
||||
return;
|
||||
|
@ -171,7 +171,7 @@ const getMessageList = async () => {
|
|||
// 加载结束
|
||||
activeMessageListLoading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息列表
|
||||
|
@ -196,17 +196,17 @@ const messageList = computed(() => {
|
|||
});
|
||||
|
||||
/** 处理删除 message 消息 */
|
||||
const handleMessageDelete = () => {
|
||||
function handleMessageDelete() {
|
||||
if (conversationInProgress.value) {
|
||||
alert('回答中,不能删除!');
|
||||
return;
|
||||
}
|
||||
// 刷新 message 列表
|
||||
getMessageList();
|
||||
};
|
||||
}
|
||||
|
||||
/** 处理 message 清空 */
|
||||
const handlerMessageClear = async () => {
|
||||
async function handlerMessageClear() {
|
||||
if (!activeConversationId.value) {
|
||||
return;
|
||||
}
|
||||
|
@ -218,16 +218,16 @@ const handlerMessageClear = async () => {
|
|||
// 刷新 message 列表
|
||||
activeMessageList.value = [];
|
||||
} catch {}
|
||||
};
|
||||
}
|
||||
|
||||
/** 回到 message 列表的顶部 */
|
||||
const handleGoTopMessage = () => {
|
||||
function handleGoTopMessage() {
|
||||
messageRef.value.handlerGoTop();
|
||||
};
|
||||
}
|
||||
|
||||
// =========== 【发送消息】相关 ===========
|
||||
/** 处理来自 keydown 的发送消息 */
|
||||
const handleSendByKeydown = async (event: any) => {
|
||||
async function handleSendByKeydown(event: any) {
|
||||
// 判断用户是否在输入
|
||||
if (isComposing.value) {
|
||||
return;
|
||||
|
@ -248,15 +248,15 @@ const handleSendByKeydown = async (event: any) => {
|
|||
event.preventDefault(); // 防止默认的提交行为
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 处理来自【发送】按钮的发送消息 */
|
||||
const handleSendByButton = () => {
|
||||
function handleSendByButton() {
|
||||
doSendMessage(prompt.value?.trim() as string);
|
||||
};
|
||||
}
|
||||
|
||||
/** 处理 prompt 输入变化 */
|
||||
const handlePromptInput = (event) => {
|
||||
function handlePromptInput(event: any) {
|
||||
// 非输入法 输入设置为 true
|
||||
if (!isComposing.value) {
|
||||
// 回车 event data 是 null
|
||||
|
@ -273,27 +273,27 @@ const handlePromptInput = (event) => {
|
|||
inputTimeout.value = setTimeout(() => {
|
||||
isComposing.value = false;
|
||||
}, 400);
|
||||
};
|
||||
}
|
||||
|
||||
const onCompositionstart = () => {
|
||||
function onCompositionstart() {
|
||||
isComposing.value = true;
|
||||
};
|
||||
}
|
||||
|
||||
const onCompositionend = () => {
|
||||
function onCompositionend() {
|
||||
// console.log('输入结束...')
|
||||
setTimeout(() => {
|
||||
isComposing.value = false;
|
||||
}, 200);
|
||||
};
|
||||
}
|
||||
|
||||
/** 真正执行【发送】消息操作 */
|
||||
const doSendMessage = async (content: string) => {
|
||||
async function doSendMessage(content: string) {
|
||||
// 校验
|
||||
if (content.length === 0) {
|
||||
message.error('发送失败,原因:内容为空!');
|
||||
return;
|
||||
}
|
||||
if (activeConversationId.value == null) {
|
||||
if (activeConversationId.value === null) {
|
||||
message.error('还没创建对话,不能发送!');
|
||||
return;
|
||||
}
|
||||
|
@ -304,12 +304,12 @@ const doSendMessage = async (content: string) => {
|
|||
conversationId: activeConversationId.value,
|
||||
content,
|
||||
} as AiChatMessageApi.ChatMessageVO);
|
||||
};
|
||||
}
|
||||
|
||||
/** 真正执行【发送】消息操作 */
|
||||
const doSendMessageStream = async (
|
||||
async function doSendMessageStream(
|
||||
userMessage: AiChatMessageApi.ChatMessageVO,
|
||||
) => {
|
||||
) {
|
||||
// 创建 AbortController 实例,以便中止请求
|
||||
conversationInAbortController.value = new AbortController();
|
||||
// 标记对话进行中
|
||||
|
@ -385,40 +385,40 @@ const doSendMessageStream = async (
|
|||
},
|
||||
);
|
||||
} catch {}
|
||||
};
|
||||
}
|
||||
|
||||
/** 停止 stream 流式调用 */
|
||||
const stopStream = async () => {
|
||||
async function stopStream() {
|
||||
// tip:如果 stream 进行中的 message,就需要调用 controller 结束
|
||||
if (conversationInAbortController.value) {
|
||||
conversationInAbortController.value.abort();
|
||||
}
|
||||
// 设置为 false
|
||||
conversationInProgress.value = false;
|
||||
};
|
||||
}
|
||||
|
||||
/** 编辑 message:设置为 prompt,可以再次编辑 */
|
||||
const handleMessageEdit = (message: AiChatMessageApi.ChatMessageVO) => {
|
||||
function handleMessageEdit(message: AiChatMessageApi.ChatMessageVO) {
|
||||
prompt.value = message.content;
|
||||
};
|
||||
}
|
||||
|
||||
/** 刷新 message:基于指定消息,再次发起对话 */
|
||||
const handleMessageRefresh = (message: AiChatMessageApi.ChatMessageVO) => {
|
||||
function handleMessageRefresh(message: AiChatMessageApi.ChatMessageVO) {
|
||||
doSendMessage(message.content);
|
||||
};
|
||||
}
|
||||
|
||||
// ============== 【消息滚动】相关 =============
|
||||
|
||||
/** 滚动到 message 底部 */
|
||||
const scrollToBottom = async (isIgnore?: boolean) => {
|
||||
async function scrollToBottom(isIgnore?: boolean) {
|
||||
await nextTick();
|
||||
if (messageRef.value) {
|
||||
messageRef.value.scrollToBottom(isIgnore);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 自提滚动效果 */
|
||||
const textRoll = async () => {
|
||||
async function textRoll() {
|
||||
let index = 0;
|
||||
try {
|
||||
// 只能执行一次
|
||||
|
@ -475,7 +475,7 @@ const textRoll = async () => {
|
|||
};
|
||||
let timer = setTimeout(task, textSpeed.value);
|
||||
} catch {}
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
|
@ -569,8 +569,8 @@ onMounted(async () => {
|
|||
<MessageList
|
||||
v-if="!activeMessageListLoading && messageList.length > 0"
|
||||
ref="messageRef"
|
||||
:conversation="activeConversation"
|
||||
:list="messageList"
|
||||
:conversation="activeConversation as any"
|
||||
:list="messageList as any"
|
||||
@on-delete-success="handleMessageDelete"
|
||||
@on-edit="handleMessageEdit"
|
||||
@on-refresh="handleMessageRefresh"
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { DICT_TYPE } from '#/utils';
|
||||
import { DICT_TYPE, getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchemaConversation(): VbenFormSchema[] {
|
||||
|
@ -22,8 +22,7 @@ export function useGridFormSchemaConversation(): VbenFormSchema[] {
|
|||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
@ -118,8 +117,7 @@ export function useGridFormSchemaMessage(): VbenFormSchema[] {
|
|||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@ import { confirm } from '@vben/common-ui';
|
|||
|
||||
import { Button, Card, Image, message } from 'ant-design-vue';
|
||||
|
||||
import { AiImageStatusEnum } from '#/utils/constants';
|
||||
import { AiImageStatusEnum } from '#/utils';
|
||||
|
||||
// 消息
|
||||
|
||||
|
@ -24,20 +24,18 @@ const emits = defineEmits(['onBtnClick', 'onMjBtnClick']);
|
|||
const cardImageRef = ref<any>(); // 卡片 image ref
|
||||
|
||||
/** 处理点击事件 */
|
||||
const handleButtonClick = async (type: string, detail: AiImageApi.ImageVO) => {
|
||||
async function handleButtonClick(type: string, detail: AiImageApi.ImageVO) {
|
||||
emits('onBtnClick', type, detail);
|
||||
};
|
||||
}
|
||||
|
||||
/** 处理 Midjourney 按钮点击事件 */
|
||||
const handleMidjourneyBtnClick = async (
|
||||
async function handleMidjourneyBtnClick(
|
||||
button: AiImageApi.ImageMidjourneyButtonsVO,
|
||||
) => {
|
||||
) {
|
||||
// 确认窗体
|
||||
await confirm(`确认操作 "${button.label} ${button.emoji}" ?`);
|
||||
emits('onMjBtnClick', button, props.detail);
|
||||
};
|
||||
|
||||
// emits
|
||||
}
|
||||
|
||||
/** 监听详情 */
|
||||
const { detail } = toRefs(props);
|
||||
|
@ -46,7 +44,7 @@ watch(detail, async (newVal) => {
|
|||
});
|
||||
const loading = ref();
|
||||
/** 处理加载状态 */
|
||||
const handleLoading = async (status: number) => {
|
||||
async function handleLoading(status: number) {
|
||||
// 情况一:如果是生成中,则设置加载中的 loading
|
||||
if (status === AiImageStatusEnum.IN_PROGRESS) {
|
||||
loading.value = message.loading({
|
||||
|
@ -57,7 +55,7 @@ const handleLoading = async (status: number) => {
|
|||
} else {
|
||||
if (loading.value) setTimeout(loading.value, 100);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
|
|
|
@ -3,6 +3,8 @@ import type { AiImageApi } from '#/api/ai/image';
|
|||
|
||||
import { ref, toRefs, watch } from 'vue';
|
||||
|
||||
import { formatDate } from '@vben/utils';
|
||||
|
||||
import { Image } from 'ant-design-vue';
|
||||
|
||||
import { getImageMy } from '#/api/ai/image';
|
||||
|
@ -12,8 +14,7 @@ import {
|
|||
StableDiffusionClipGuidancePresets,
|
||||
StableDiffusionSamplers,
|
||||
StableDiffusionStylePresets,
|
||||
} from '#/utils/constants';
|
||||
import { formatTime } from '#/utils/formatTime';
|
||||
} from '#/utils';
|
||||
|
||||
// 图片详细信息
|
||||
const props = defineProps({
|
||||
|
@ -25,9 +26,9 @@ const props = defineProps({
|
|||
const detail = ref<AiImageApi.ImageVO>({} as AiImageApi.ImageVO);
|
||||
|
||||
/** 获取图片详情 */
|
||||
const getImageDetail = async (id: number) => {
|
||||
async function getImageDetail(id: number) {
|
||||
detail.value = await getImageMy(id);
|
||||
};
|
||||
}
|
||||
|
||||
const { id } = toRefs(props);
|
||||
watch(
|
||||
|
@ -53,10 +54,10 @@ watch(
|
|||
<div class="tip text-lg font-bold">时间</div>
|
||||
<div class="body mt-2 text-gray-600">
|
||||
<div>
|
||||
提交时间:{{ formatTime(detail.createTime, 'yyyy-MM-dd HH:mm:ss') }}
|
||||
提交时间:{{ formatDate(detail.createTime, 'yyyy-MM-dd HH:mm:ss') }}
|
||||
</div>
|
||||
<div>
|
||||
生成时间:{{ formatTime(detail.finishTime, 'yyyy-MM-dd HH:mm:ss') }}
|
||||
生成时间:{{ formatDate(detail.finishTime, 'yyyy-MM-dd HH:mm:ss') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,7 @@ import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
|||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { confirm, useVbenDrawer } from '@vben/common-ui';
|
||||
import { downloadFileFromImageUrl } from '@vben/utils';
|
||||
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { Button, Card, message, Pagination } from 'ant-design-vue';
|
||||
|
@ -15,8 +16,7 @@ import {
|
|||
getImagePageMy,
|
||||
midjourneyAction,
|
||||
} from '#/api/ai/image';
|
||||
import { AiImageStatusEnum } from '#/utils/constants';
|
||||
import { download } from '#/utils/download';
|
||||
import { AiImageStatusEnum } from '#/utils';
|
||||
|
||||
import ImageCard from './ImageCard.vue';
|
||||
import ImageDetail from './ImageDetail.vue';
|
||||
|
@ -43,18 +43,18 @@ const inProgressTimer = ref<any>(); // 生成中的 image 定时器,轮询生
|
|||
const showImageDetailId = ref<number>(0); // 图片详情的图片编号
|
||||
|
||||
/** 处理查看绘图作品 */
|
||||
const handleViewPublic = () => {
|
||||
function handleViewPublic() {
|
||||
router.push({
|
||||
name: 'AiImageSquare',
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** 查看图片的详情 */
|
||||
const handleDetailOpen = async () => {
|
||||
async function handleDetailOpen() {
|
||||
drawerApi.open();
|
||||
};
|
||||
}
|
||||
/** 获得 image 图片列表 */
|
||||
const getImageList = async () => {
|
||||
async function getImageList() {
|
||||
const loading = message.loading({
|
||||
content: `加载中...`,
|
||||
});
|
||||
|
@ -77,10 +77,10 @@ const getImageList = async () => {
|
|||
// 关闭正在“加载中”的 Loading
|
||||
loading();
|
||||
}
|
||||
};
|
||||
}
|
||||
const debounceGetImageList = useDebounceFn(getImageList, 80);
|
||||
/** 轮询生成中的 image 列表 */
|
||||
const refreshWatchImages = async () => {
|
||||
async function refreshWatchImages() {
|
||||
const imageIds = Object.keys(inProgressImageMap.value).map(Number);
|
||||
if (imageIds.length === 0) {
|
||||
return;
|
||||
|
@ -101,13 +101,13 @@ const refreshWatchImages = async () => {
|
|||
}
|
||||
});
|
||||
inProgressImageMap.value = newWatchImages;
|
||||
};
|
||||
}
|
||||
|
||||
/** 图片的点击事件 */
|
||||
const handleImageButtonClick = async (
|
||||
async function handleImageButtonClick(
|
||||
type: string,
|
||||
imageDetail: AiImageApi.ImageVO,
|
||||
) => {
|
||||
) {
|
||||
// 详情
|
||||
if (type === 'more') {
|
||||
showImageDetailId.value = imageDetail.id;
|
||||
|
@ -124,20 +124,23 @@ const handleImageButtonClick = async (
|
|||
}
|
||||
// 下载
|
||||
if (type === 'download') {
|
||||
await download.image({ url: imageDetail.picUrl });
|
||||
await downloadFileFromImageUrl({
|
||||
fileName: imageDetail.model,
|
||||
source: imageDetail.picUrl,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 重新生成
|
||||
if (type === 'regeneration') {
|
||||
await emits('onRegeneration', imageDetail);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 处理 Midjourney 按钮点击事件 */
|
||||
const handleImageMidjourneyButtonClick = async (
|
||||
async function handleImageMidjourneyButtonClick(
|
||||
button: AiImageApi.ImageMidjourneyButtonsVO,
|
||||
imageDetail: AiImageApi.ImageVO,
|
||||
) => {
|
||||
) {
|
||||
// 1. 构建 params 参数
|
||||
const data = {
|
||||
id: imageDetail.id,
|
||||
|
@ -147,7 +150,7 @@ const handleImageMidjourneyButtonClick = async (
|
|||
await midjourneyAction(data);
|
||||
// 3. 刷新列表
|
||||
await getImageList();
|
||||
};
|
||||
}
|
||||
|
||||
defineExpose({ getImageList }); /** 组件挂在的时候 */
|
||||
onMounted(async () => {
|
||||
|
|
|
@ -10,11 +10,7 @@ import { confirm } from '@vben/common-ui';
|
|||
import { Button, InputNumber, Select, Space, Textarea } from 'ant-design-vue';
|
||||
|
||||
import { drawImage } from '#/api/ai/image';
|
||||
import {
|
||||
AiPlatformEnum,
|
||||
ImageHotWords,
|
||||
OtherPlatformEnum,
|
||||
} from '#/utils/constants';
|
||||
import { AiPlatformEnum, ImageHotWords, OtherPlatformEnum } from '#/utils';
|
||||
|
||||
// 消息弹窗
|
||||
|
||||
|
@ -39,7 +35,7 @@ const platformModels = ref<AiModelModelApi.ModelVO[]>([]); // 模型列表
|
|||
const modelId = ref<number>(); // 选中的模型
|
||||
|
||||
/** 选择热词 */
|
||||
const handleHotWordClick = async (hotWord: string) => {
|
||||
async function handleHotWordClick(hotWord: string) {
|
||||
// 情况一:取消选中
|
||||
if (selectHotWord.value === hotWord) {
|
||||
selectHotWord.value = '';
|
||||
|
@ -49,10 +45,10 @@ const handleHotWordClick = async (hotWord: string) => {
|
|||
// 情况二:选中
|
||||
selectHotWord.value = hotWord; // 选中
|
||||
prompt.value = hotWord; // 替换提示词
|
||||
};
|
||||
}
|
||||
|
||||
/** 图片生成 */
|
||||
const handleGenerateImage = async () => {
|
||||
async function handleGenerateImage() {
|
||||
// 二次确认
|
||||
await confirm(`确认生成内容?`);
|
||||
try {
|
||||
|
@ -76,17 +72,17 @@ const handleGenerateImage = async () => {
|
|||
// 加载结束
|
||||
drawIn.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 填充值 */
|
||||
const settingValues = async (detail: AiImageApi.ImageVO) => {
|
||||
async function settingValues(detail: AiImageApi.ImageVO) {
|
||||
prompt.value = detail.prompt;
|
||||
width.value = detail.width;
|
||||
height.value = detail.height;
|
||||
};
|
||||
}
|
||||
|
||||
/** 平台切换 */
|
||||
const handlerPlatformChange = async (platform: any) => {
|
||||
async function handlerPlatformChange(platform: any) {
|
||||
// 根据选择的平台筛选模型
|
||||
platformModels.value = props.models.filter(
|
||||
(item: AiModelModelApi.ModelVO) => item.platform === platform,
|
||||
|
@ -96,7 +92,7 @@ const handlerPlatformChange = async (platform: any) => {
|
|||
? platformModels.value[0].id
|
||||
: undefined;
|
||||
// 切换平台,默认选择一个模型
|
||||
};
|
||||
}
|
||||
|
||||
/** 监听 models 变化 */
|
||||
watch(
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { AiImageApi } from '#/api/ai/image';
|
||||
import type { AiModelModelApi } from '#/api/ai/model/model';
|
||||
import type { ImageModelVO, ImageSizeVO } from '#/utils/constants';
|
||||
import type { ImageModelVO, ImageSizeVO } from '#/utils';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
|
@ -17,7 +17,7 @@ import {
|
|||
Dall3SizeList,
|
||||
Dall3StyleList,
|
||||
ImageHotWords,
|
||||
} from '#/utils/constants';
|
||||
} from '#/utils';
|
||||
|
||||
// 接收父组件传入的模型列表
|
||||
const props = defineProps({
|
||||
|
@ -37,7 +37,7 @@ const selectSize = ref<string>('1024x1024'); // 选中 size
|
|||
const style = ref<string>('vivid'); // style 样式
|
||||
|
||||
/** 选择热词 */
|
||||
const handleHotWordClick = async (hotWord: string) => {
|
||||
async function handleHotWordClick(hotWord: string) {
|
||||
// 情况一:取消选中
|
||||
if (selectHotWord.value === hotWord) {
|
||||
selectHotWord.value = '';
|
||||
|
@ -47,10 +47,10 @@ const handleHotWordClick = async (hotWord: string) => {
|
|||
// 情况二:选中
|
||||
selectHotWord.value = hotWord;
|
||||
prompt.value = hotWord;
|
||||
};
|
||||
}
|
||||
|
||||
/** 选择 model 模型 */
|
||||
const handleModelClick = async (model: ImageModelVO) => {
|
||||
async function handleModelClick(model: ImageModelVO) {
|
||||
selectModel.value = model.key;
|
||||
// 可以在这里添加模型特定的处理逻辑
|
||||
// 例如,如果未来需要根据不同模型设置不同参数
|
||||
|
@ -73,20 +73,20 @@ const handleModelClick = async (model: ImageModelVO) => {
|
|||
if (recommendedSize) {
|
||||
selectSize.value = recommendedSize.key;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 选择 style 样式 */
|
||||
const handleStyleClick = async (imageStyle: ImageModelVO) => {
|
||||
async function handleStyleClick(imageStyle: ImageModelVO) {
|
||||
style.value = imageStyle.key;
|
||||
};
|
||||
}
|
||||
|
||||
/** 选择 size 大小 */
|
||||
const handleSizeClick = async (imageSize: ImageSizeVO) => {
|
||||
async function handleSizeClick(imageSize: ImageSizeVO) {
|
||||
selectSize.value = imageSize.key;
|
||||
};
|
||||
}
|
||||
|
||||
/** 图片生产 */
|
||||
const handleGenerateImage = async () => {
|
||||
async function handleGenerateImage() {
|
||||
// 从 models 中查找匹配的模型
|
||||
const matchedModel = props.models.find(
|
||||
(item) =>
|
||||
|
@ -127,10 +127,10 @@ const handleGenerateImage = async () => {
|
|||
// 加载结束
|
||||
drawIn.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 填充值 */
|
||||
const settingValues = async (detail: AiImageApi.ImageVO) => {
|
||||
async function settingValues(detail: AiImageApi.ImageVO) {
|
||||
prompt.value = detail.prompt;
|
||||
selectModel.value = detail.model;
|
||||
style.value = detail.options?.style;
|
||||
|
@ -138,7 +138,7 @@ const settingValues = async (detail: AiImageApi.ImageVO) => {
|
|||
(item) => item.key === `${detail.width}x${detail.height}`,
|
||||
) as ImageSizeVO;
|
||||
await handleSizeClick(imageSize);
|
||||
};
|
||||
}
|
||||
|
||||
/** 暴露组件方法 */
|
||||
defineExpose({ settingValues });
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { AiImageApi } from '#/api/ai/image';
|
||||
import type { AiModelModelApi } from '#/api/ai/model/model';
|
||||
import type { ImageModelVO, ImageSizeVO } from '#/utils/constants';
|
||||
import type { ImageModelVO, ImageSizeVO } from '#/utils';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
|
@ -26,7 +26,7 @@ import {
|
|||
MidjourneySizeList,
|
||||
MidjourneyVersions,
|
||||
NijiVersionList,
|
||||
} from '#/utils/constants';
|
||||
} from '#/utils';
|
||||
|
||||
// 消息弹窗
|
||||
|
||||
|
@ -51,7 +51,7 @@ const selectVersion = ref<any>('6.0'); // 选中的 version
|
|||
const versionList = ref<any>(MidjourneyVersions); // version 列表
|
||||
|
||||
/** 选择热词 */
|
||||
const handleHotWordClick = async (hotWord: string) => {
|
||||
async function handleHotWordClick(hotWord: string) {
|
||||
// 情况一:取消选中
|
||||
if (selectHotWord.value === hotWord) {
|
||||
selectHotWord.value = '';
|
||||
|
@ -61,23 +61,23 @@ const handleHotWordClick = async (hotWord: string) => {
|
|||
// 情况二:选中
|
||||
selectHotWord.value = hotWord; // 选中
|
||||
prompt.value = hotWord; // 设置提示次
|
||||
};
|
||||
}
|
||||
|
||||
/** 点击 size 尺寸 */
|
||||
const handleSizeClick = async (imageSize: ImageSizeVO) => {
|
||||
async function handleSizeClick(imageSize: ImageSizeVO) {
|
||||
selectSize.value = imageSize.key;
|
||||
};
|
||||
}
|
||||
|
||||
/** 点击 model 模型 */
|
||||
const handleModelClick = async (model: ImageModelVO) => {
|
||||
async function handleModelClick(model: ImageModelVO) {
|
||||
selectModel.value = model.key;
|
||||
versionList.value =
|
||||
model.key === 'niji' ? NijiVersionList : MidjourneyVersions;
|
||||
selectVersion.value = versionList.value[0].value;
|
||||
};
|
||||
}
|
||||
|
||||
/** 图片生成 */
|
||||
const handleGenerateImage = async () => {
|
||||
async function handleGenerateImage() {
|
||||
// 从 models 中查找匹配的模型
|
||||
const matchedModel = props.models.find(
|
||||
(item) =>
|
||||
|
@ -115,10 +115,10 @@ const handleGenerateImage = async () => {
|
|||
// 加载结束
|
||||
drawIn.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 填充值 */
|
||||
const settingValues = async (detail: AiImageApi.ImageVO) => {
|
||||
async function settingValues(detail: AiImageApi.ImageVO) {
|
||||
// 提示词
|
||||
prompt.value = detail.prompt;
|
||||
// image size
|
||||
|
@ -137,7 +137,7 @@ const settingValues = async (detail: AiImageApi.ImageVO) => {
|
|||
).value;
|
||||
// image
|
||||
referImageUrl.value = detail.options.referImageUrl;
|
||||
};
|
||||
}
|
||||
|
||||
/** 暴露组件方法 */
|
||||
defineExpose({ settingValues });
|
||||
|
|
|
@ -23,10 +23,7 @@ import {
|
|||
StableDiffusionClipGuidancePresets,
|
||||
StableDiffusionSamplers,
|
||||
StableDiffusionStylePresets,
|
||||
} from '#/utils/constants';
|
||||
import { hasChinese } from '#/utils/utils';
|
||||
|
||||
// 消息弹窗
|
||||
} from '#/utils';
|
||||
|
||||
// 接收父组件传入的模型列表
|
||||
const props = defineProps({
|
||||
|
@ -35,8 +32,13 @@ const props = defineProps({
|
|||
default: () => [] as AiModelModelApi.ModelVO[],
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['onDrawStart', 'onDrawComplete']);
|
||||
|
||||
function hasChinese(str: string) {
|
||||
return /[\u4E00-\u9FA5]/.test(str);
|
||||
}
|
||||
|
||||
// 定义属性
|
||||
const drawIn = ref<boolean>(false); // 生成中
|
||||
const selectHotWord = ref<string>(''); // 选中的热词
|
||||
|
@ -52,7 +54,7 @@ const clipGuidancePreset = ref<string>('NONE'); // 文本提示相匹配的图
|
|||
const stylePreset = ref<string>('3d-model'); // 风格
|
||||
|
||||
/** 选择热词 */
|
||||
const handleHotWordClick = async (hotWord: string) => {
|
||||
async function handleHotWordClick(hotWord: string) {
|
||||
// 情况一:取消选中
|
||||
if (selectHotWord.value === hotWord) {
|
||||
selectHotWord.value = '';
|
||||
|
@ -62,10 +64,10 @@ const handleHotWordClick = async (hotWord: string) => {
|
|||
// 情况二:选中
|
||||
selectHotWord.value = hotWord; // 选中
|
||||
prompt.value = hotWord; // 替换提示词
|
||||
};
|
||||
}
|
||||
|
||||
/** 图片生成 */
|
||||
const handleGenerateImage = async () => {
|
||||
async function handleGenerateImage() {
|
||||
// 从 models 中查找匹配的模型
|
||||
const selectModel = 'stable-diffusion-v1-6';
|
||||
const matchedModel = props.models.find(
|
||||
|
@ -112,10 +114,10 @@ const handleGenerateImage = async () => {
|
|||
// 加载结束
|
||||
drawIn.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 填充值 */
|
||||
const settingValues = async (detail: AiImageApi.ImageVO) => {
|
||||
async function settingValues(detail: AiImageApi.ImageVO) {
|
||||
prompt.value = detail.prompt;
|
||||
width.value = detail.width;
|
||||
height.value = detail.height;
|
||||
|
@ -125,7 +127,7 @@ const settingValues = async (detail: AiImageApi.ImageVO) => {
|
|||
sampler.value = detail.options?.sampler;
|
||||
clipGuidancePreset.value = detail.options?.clipGuidancePreset;
|
||||
stylePreset.value = detail.options?.stylePreset;
|
||||
};
|
||||
}
|
||||
|
||||
/** 暴露组件方法 */
|
||||
defineExpose({ settingValues });
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Page } from '@vben/common-ui';
|
|||
import { Segmented } from 'ant-design-vue';
|
||||
|
||||
import { getModelSimpleList } from '#/api/ai/model/model';
|
||||
import { AiModelTypeEnum, AiPlatformEnum } from '#/utils/constants';
|
||||
import { AiModelTypeEnum, AiPlatformEnum } from '#/utils';
|
||||
|
||||
import Common from './components/common/index.vue';
|
||||
import Dall3 from './components/dall3/index.vue';
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { DICT_TYPE, getDictOptions } from '#/utils';
|
||||
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
|
@ -49,8 +49,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -13,7 +13,7 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|||
import { deleteImage, getImagePage, updateImage } from '#/api/ai/image';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { $t } from '#/locales';
|
||||
import { AiImageStatusEnum } from '#/utils/constants';
|
||||
import { AiImageStatusEnum } from '#/utils';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { useDebounceFn } from '@vueuse/core';
|
|||
import { Input, Pagination } from 'ant-design-vue';
|
||||
|
||||
import { getImagePageMy } from '#/api/ai/image';
|
||||
// TODO @fan:加个 loading 加载中的状态
|
||||
|
||||
const loading = ref(true); // 列表的加载中
|
||||
const list = ref<AiImageApi.ImageVO[]>([]); // 列表的数据
|
||||
const total = ref(0); // 列表的总页数
|
||||
|
@ -21,7 +21,7 @@ const queryParams = reactive({
|
|||
});
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
async function getList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getImagePageMy(queryParams);
|
||||
|
@ -30,7 +30,7 @@ const getList = async () => {
|
|||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
const debounceGetList = useDebounceFn(getList, 80);
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
|
|
|
@ -19,9 +19,9 @@ const parent = inject('parent') as any;
|
|||
const pollingTimer = ref<null | number>(null); // 轮询定时器 ID,用于跟踪和清除轮询进程
|
||||
|
||||
/** 判断文件处理是否完成 */
|
||||
const isProcessComplete = (file: any) => {
|
||||
function isProcessComplete(file: any) {
|
||||
return file.progress === 100;
|
||||
};
|
||||
}
|
||||
|
||||
/** 判断所有文件是否都处理完成 */
|
||||
const allProcessComplete = computed(() => {
|
||||
|
@ -29,14 +29,14 @@ const allProcessComplete = computed(() => {
|
|||
});
|
||||
|
||||
/** 完成按钮点击事件处理 */
|
||||
const handleComplete = () => {
|
||||
function handleComplete() {
|
||||
if (parent?.exposed?.handleBack) {
|
||||
parent.exposed.handleBack();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取文件处理进度 */
|
||||
const getProcessList = async () => {
|
||||
async function getProcessList() {
|
||||
try {
|
||||
// 1. 调用 API 获取处理进度
|
||||
const documentIds = props.modelValue.list
|
||||
|
@ -82,7 +82,7 @@ const getProcessList = async () => {
|
|||
console.error('获取处理进度失败:', error);
|
||||
pollingTimer.value = window.setTimeout(getProcessList, 5000);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 组件挂载时开始轮询 */
|
||||
onMounted(() => {
|
||||
|
|
|
@ -42,12 +42,13 @@ const currentFile = ref<any>(null); // 当前选中的文件
|
|||
const submitLoading = ref(false); // 提交按钮加载状态
|
||||
|
||||
/** 选择文件 */
|
||||
const selectFile = async (index: number) => {
|
||||
async function selectFile(index: number) {
|
||||
currentFile.value = modelData.value.list[index];
|
||||
await splitContentFile(currentFile.value);
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取文件分段内容 */
|
||||
const splitContentFile = async (file: any) => {
|
||||
async function splitContentFile(file: any) {
|
||||
if (!file || !file.url) {
|
||||
message.warning('文件 URL 不存在');
|
||||
return;
|
||||
|
@ -65,9 +66,9 @@ const splitContentFile = async (file: any) => {
|
|||
} finally {
|
||||
splitLoading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
/** 处理预览分段 */
|
||||
const handleAutoSegment = async () => {
|
||||
async function handleAutoSegment() {
|
||||
// 如果没有选中文件,默认选中第一个
|
||||
if (
|
||||
!currentFile.value &&
|
||||
|
@ -84,18 +85,18 @@ const handleAutoSegment = async () => {
|
|||
|
||||
// 获取分段内容
|
||||
await splitContentFile(currentFile.value);
|
||||
};
|
||||
}
|
||||
|
||||
/** 上一步按钮处理 */
|
||||
const handlePrevStep = () => {
|
||||
function handlePrevStep() {
|
||||
const parentEl = parent || getCurrentInstance()?.parent;
|
||||
if (parentEl && typeof parentEl.exposed?.goToPrevStep === 'function') {
|
||||
parentEl.exposed.goToPrevStep();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 保存操作 */
|
||||
const handleSave = async () => {
|
||||
async function handleSave() {
|
||||
// 保存前验证
|
||||
if (
|
||||
!currentFile?.value?.segments ||
|
||||
|
@ -140,7 +141,7 @@ const handleSave = async () => {
|
|||
// 关闭按钮加载状态
|
||||
submitLoading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
|
|
|
@ -10,11 +10,11 @@ import { computed, getCurrentInstance, inject, onMounted, ref } from 'vue';
|
|||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import { generateAcceptedFileTypes } from '@vben/utils';
|
||||
|
||||
import { Button, Form, message, UploadDragger } from 'ant-design-vue';
|
||||
|
||||
import { useUpload } from '#/components/upload/use-upload';
|
||||
import { generateAcceptedFileTypes } from '#/utils/upload';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
|
@ -70,14 +70,14 @@ const modelData = computed({
|
|||
set: (val) => emit('update:modelValue', val),
|
||||
});
|
||||
/** 确保 list 属性存在 */
|
||||
const ensureListExists = () => {
|
||||
function ensureListExists() {
|
||||
if (!props.modelValue.list) {
|
||||
emit('update:modelValue', {
|
||||
...props.modelValue,
|
||||
list: [],
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
/** 是否所有文件都已上传完成 */
|
||||
const isAllUploaded = computed(() => {
|
||||
return (
|
||||
|
@ -93,7 +93,7 @@ const isAllUploaded = computed(() => {
|
|||
* @param file 待上传的文件
|
||||
* @returns 是否允许上传
|
||||
*/
|
||||
const beforeUpload = (file: any) => {
|
||||
function beforeUpload(file: any) {
|
||||
// 1.1 检查文件扩展名
|
||||
const fileName = file.name.toLowerCase();
|
||||
const fileExtension = fileName.slice(
|
||||
|
@ -112,7 +112,7 @@ const beforeUpload = (file: any) => {
|
|||
// 2. 增加上传中的文件计数
|
||||
uploadingCount.value++;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
async function customRequest(info: UploadRequestOption<any>) {
|
||||
const file = info.file as File;
|
||||
const name = file?.name;
|
||||
|
@ -148,7 +148,7 @@ async function customRequest(info: UploadRequestOption<any>) {
|
|||
*
|
||||
* @param index 要移除的文件索引
|
||||
*/
|
||||
const removeFile = (index: number) => {
|
||||
function removeFile(index: number) {
|
||||
// 从列表中移除文件
|
||||
const newList = [...props.modelValue.list];
|
||||
newList.splice(index, 1);
|
||||
|
@ -157,10 +157,10 @@ const removeFile = (index: number) => {
|
|||
...props.modelValue,
|
||||
list: newList,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** 下一步按钮处理 */
|
||||
const handleNextStep = () => {
|
||||
function handleNextStep() {
|
||||
// 1.1 检查是否有文件上传
|
||||
if (!modelData.value.list || modelData.value.list.length === 0) {
|
||||
message.warning('请上传至少一个文件');
|
||||
|
@ -177,7 +177,7 @@ const handleNextStep = () => {
|
|||
if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') {
|
||||
parentEl.exposed.goToNextStep();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
|
@ -210,9 +210,9 @@ onMounted(() => {
|
|||
/>
|
||||
<div class="ant-upload-text text-[16px] text-[#606266]">
|
||||
拖拽文件至此,或者
|
||||
<em class="cursor-pointer not-italic text-[#409eff]"
|
||||
>选择文件</em
|
||||
>
|
||||
<em class="cursor-pointer not-italic text-[#409eff]">
|
||||
选择文件
|
||||
</em>
|
||||
</div>
|
||||
<div class="ant-upload-tip mt-10px text-[12px] text-[#909399]">
|
||||
已支持 {{ supportedFileTypes.join('、') }},每个文件不超过
|
||||
|
|
|
@ -56,7 +56,7 @@ provide('parent', getCurrentInstance()); // 提供 parent 给子组件使用
|
|||
const tabs = useTabs();
|
||||
|
||||
/** 返回列表页 */
|
||||
const handleBack = () => {
|
||||
function handleBack() {
|
||||
// 关闭当前页签
|
||||
tabs.closeCurrentTab();
|
||||
// 跳转到列表页,使用路径, 目前后端的路由 name: 'name'+ menuId
|
||||
|
@ -66,10 +66,10 @@ const handleBack = () => {
|
|||
knowledgeId: route.query.knowledgeId,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化数据 */
|
||||
const initData = async () => {
|
||||
async function initData() {
|
||||
if (route.query.knowledgeId) {
|
||||
formData.value.knowledgeId = route.query.knowledgeId as any;
|
||||
}
|
||||
|
@ -91,20 +91,20 @@ const initData = async () => {
|
|||
// 进入下一步
|
||||
goToNextStep();
|
||||
}
|
||||
};
|
||||
}
|
||||
/** 切换到下一步 */
|
||||
const goToNextStep = () => {
|
||||
function goToNextStep() {
|
||||
if (currentStep.value < steps.length - 1) {
|
||||
currentStep.value++;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 切换到上一步 */
|
||||
const goToPrevStep = () => {
|
||||
function goToPrevStep() {
|
||||
if (currentStep.value > 0) {
|
||||
currentStep.value--;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
updateKnowledgeDocumentStatus,
|
||||
} from '#/api/ai/knowledge/document';
|
||||
import { $t } from '#/locales';
|
||||
import { CommonStatusEnum } from '#/utils/constants';
|
||||
import { CommonStatusEnum } from '#/utils';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
CommonStatusEnum,
|
||||
DICT_TYPE,
|
||||
getDictOptions,
|
||||
getRangePickerDefaultProps,
|
||||
} from '#/utils';
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
|
@ -112,8 +113,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -57,20 +57,20 @@ async function handleDelete(row: AiKnowledgeKnowledgeApi.KnowledgeVO) {
|
|||
}
|
||||
/** 文档按钮操作 */
|
||||
const router = useRouter();
|
||||
const handleDocument = (id: number) => {
|
||||
function handleDocument(id: number) {
|
||||
router.push({
|
||||
name: 'AiKnowledgeDocument',
|
||||
query: { knowledgeId: id },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** 跳转到文档召回测试页面 */
|
||||
const handleRetrieval = (id: number) => {
|
||||
function handleRetrieval(id: number) {
|
||||
router.push({
|
||||
name: 'AiKnowledgeRetrieval',
|
||||
query: { id },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
|
|
|
@ -32,7 +32,7 @@ const queryParams = reactive({
|
|||
});
|
||||
|
||||
/** 调用文档召回测试接口 */
|
||||
const getRetrievalResult = async () => {
|
||||
async function getRetrievalResult() {
|
||||
if (!queryParams.content) {
|
||||
message.warning('请输入查询文本');
|
||||
return;
|
||||
|
@ -54,15 +54,15 @@ const getRetrievalResult = async () => {
|
|||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 展开/收起段落内容 */
|
||||
const toggleExpand = (segment: any) => {
|
||||
function toggleExpand(segment: any) {
|
||||
segment.expanded = !segment.expanded;
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取知识库信息 */
|
||||
const getKnowledgeInfo = async (id: number) => {
|
||||
async function getKnowledgeInfo(id: number) {
|
||||
try {
|
||||
const knowledge = await getKnowledge(id);
|
||||
if (knowledge) {
|
||||
|
@ -71,7 +71,7 @@ const getKnowledgeInfo = async (id: number) => {
|
|||
knowledge.similarityThreshold || queryParams.similarityThreshold;
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
updateKnowledgeSegmentStatus,
|
||||
} from '#/api/ai/knowledge/segment';
|
||||
import { $t } from '#/locales';
|
||||
import { CommonStatusEnum } from '#/utils/constants';
|
||||
import { CommonStatusEnum } from '#/utils';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
|
@ -93,9 +93,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
});
|
||||
|
||||
/** 修改是否发布 */
|
||||
const handleStatusChange = async (
|
||||
async function handleStatusChange(
|
||||
row: AiKnowledgeSegmentApi.KnowledgeSegmentVO,
|
||||
) => {
|
||||
) {
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const text = row.status ? '启用' : '禁用';
|
||||
|
@ -112,7 +112,8 @@ const handleStatusChange = async (
|
|||
? CommonStatusEnum.DISABLE
|
||||
: CommonStatusEnum.ENABLE;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gridApi.formApi.setFieldValue('documentId', route.query.documentId);
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ import { nextTick, onMounted, ref } from 'vue';
|
|||
import { alert, Page } from '@vben/common-ui';
|
||||
|
||||
import { generateMindMap } from '#/api/ai/mindmap';
|
||||
import { MindMapContentExample } from '#/utils/constants';
|
||||
import { MindMapContentExample } from '#/utils';
|
||||
|
||||
import Left from './modules/Left.vue';
|
||||
import Right from './modules/Right.vue';
|
||||
|
@ -21,13 +21,13 @@ const leftRef = ref<InstanceType<typeof Left>>(); // 左边组件
|
|||
const rightRef = ref(); // 右边组件
|
||||
|
||||
/** 使用已有内容直接生成 */
|
||||
const directGenerate = (existPrompt: string) => {
|
||||
function directGenerate(existPrompt: string) {
|
||||
isEnd.value = false; // 先设置为 false 再设置为 true,让子组建的 watch 能够监听到
|
||||
generatedContent.value = existPrompt;
|
||||
isEnd.value = true;
|
||||
};
|
||||
}
|
||||
/** 提交生成 */
|
||||
const submit = (data: AiMindmapApi.AiMindMapGenerateReqVO) => {
|
||||
function submit(data: AiMindmapApi.AiMindMapGenerateReqVO) {
|
||||
isGenerating.value = true;
|
||||
isStart.value = true;
|
||||
isEnd.value = false;
|
||||
|
@ -59,13 +59,13 @@ const submit = (data: AiMindmapApi.AiMindMapGenerateReqVO) => {
|
|||
},
|
||||
ctrl: ctrl.value,
|
||||
});
|
||||
};
|
||||
}
|
||||
/** 停止 stream 生成 */
|
||||
const stopStream = () => {
|
||||
function stopStream() {
|
||||
isGenerating.value = false;
|
||||
isStart.value = false;
|
||||
ctrl.value?.abort();
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { reactive, ref } from 'vue';
|
|||
|
||||
import { Button, Textarea } from 'ant-design-vue';
|
||||
|
||||
import { MindMapContentExample } from '#/utils/constants';
|
||||
import { MindMapContentExample } from '#/utils';
|
||||
|
||||
defineProps<{
|
||||
isGenerating: boolean;
|
||||
|
|
|
@ -7,11 +7,10 @@ import {
|
|||
Toolbar,
|
||||
Transformer,
|
||||
} from '@vben/plugins/markmap';
|
||||
import { downloadImageByCanvas } from '@vben/utils';
|
||||
|
||||
import { Button, Card, message } from 'ant-design-vue';
|
||||
|
||||
import { download } from '#/utils/download';
|
||||
|
||||
const props = defineProps<{
|
||||
generatedContent: string; // 生成结果
|
||||
isEnd: boolean; // 是否结束
|
||||
|
@ -85,7 +84,7 @@ const update = () => {
|
|||
}
|
||||
};
|
||||
/** 处理内容 */
|
||||
const processContent = (text: string) => {
|
||||
function processContent(text: string) {
|
||||
const arr: string[] = [];
|
||||
const lines = text.split('\n');
|
||||
for (let line of lines) {
|
||||
|
@ -97,21 +96,21 @@ const processContent = (text: string) => {
|
|||
arr.push(line);
|
||||
}
|
||||
return arr.join('\n');
|
||||
};
|
||||
}
|
||||
/** 下载图片:download SVG to png file */
|
||||
const downloadImage = () => {
|
||||
function downloadImage() {
|
||||
const svgElement = mindMapRef.value;
|
||||
// 将 SVG 渲染到图片对象
|
||||
const serializer = new XMLSerializer();
|
||||
const source = `<?xml version="1.0" standalone="no"?>\r\n${serializer.serializeToString(svgRef.value!)}`;
|
||||
const base64Url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`;
|
||||
download.image({
|
||||
downloadImageByCanvas({
|
||||
url: base64Url,
|
||||
canvasWidth: svgElement?.offsetWidth,
|
||||
canvasHeight: svgElement?.offsetHeight,
|
||||
drawWithImageSize: false,
|
||||
});
|
||||
};
|
||||
}
|
||||
defineExpose({
|
||||
scrollBottom() {
|
||||
mdContainerRef.value?.scrollTo(0, mdContainerRef.value?.scrollHeight);
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
|
@ -26,8 +27,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -75,13 +75,13 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||
},
|
||||
} as VxeTableGridOptions<AiMindmapApi.MindMapVO>,
|
||||
});
|
||||
const openPreview = async (row: AiMindmapApi.MindMapVO) => {
|
||||
async function openPreview(row: AiMindmapApi.MindMapVO) {
|
||||
previewVisible.value = false;
|
||||
drawerApi.open();
|
||||
await nextTick();
|
||||
previewVisible.value = true;
|
||||
previewContent.value = row.generatedContent;
|
||||
};
|
||||
}
|
||||
onMounted(async () => {
|
||||
// 获得下拉数据
|
||||
userList.value = await getSimpleUserList();
|
||||
|
|
|
@ -109,9 +109,9 @@ onMounted(async () => {
|
|||
/>
|
||||
</template>
|
||||
<template #keyId="{ row }">
|
||||
<span>{{
|
||||
apiKeyList.find((item) => item.id === row.keyId)?.name
|
||||
}}</span>
|
||||
<span>
|
||||
{{ apiKeyList.find((item) => item.id === row.keyId)?.name }}
|
||||
</span>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
||||
import {
|
||||
CommonStatusEnum,
|
||||
DICT_TYPE,
|
||||
getDictOptions,
|
||||
getRangePickerDefaultProps,
|
||||
} from '#/utils';
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
|
@ -64,8 +69,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Page } from '@vben/common-ui';
|
|||
import List from './list/index.vue';
|
||||
import Mode from './mode/index.vue';
|
||||
|
||||
defineOptions({ name: 'Index' });
|
||||
defineOptions({ name: 'AiMusicIndex' });
|
||||
|
||||
const listRef = ref<Nullable<{ generateMusic: (...args: any) => void }>>(null);
|
||||
|
||||
|
|
|
@ -4,12 +4,11 @@ import type { Nullable } from '@vben/types';
|
|||
import { inject, reactive, ref } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { formatPast } from '@vben/utils';
|
||||
|
||||
import { Image, Slider } from 'ant-design-vue';
|
||||
|
||||
import { formatPast } from '#/utils/formatTime';
|
||||
|
||||
defineOptions({ name: 'Index' });
|
||||
defineOptions({ name: 'AiMusicAudioBarIndex' });
|
||||
|
||||
const currentSong = inject('currentSong', {});
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import audioBar from './audioBar/index.vue';
|
|||
import songCard from './songCard/index.vue';
|
||||
import songInfo from './songInfo/index.vue';
|
||||
|
||||
defineOptions({ name: 'Index' });
|
||||
defineOptions({ name: 'AiMusicListIndex' });
|
||||
|
||||
const currentType = ref('mine');
|
||||
// loading 状态
|
||||
|
|
|
@ -5,7 +5,7 @@ import { IconifyIcon } from '@vben/icons';
|
|||
|
||||
import { Image } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'Index' });
|
||||
defineOptions({ name: 'AiMusicSongCardIndex' });
|
||||
|
||||
defineProps({
|
||||
songInfo: {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { inject } from 'vue';
|
|||
|
||||
import { Button, Card, Image } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'Index' });
|
||||
defineOptions({ name: 'AiMusicSongInfoIndex' });
|
||||
|
||||
const currentSong = inject('currentSong', {});
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Select, Switch, Textarea } from 'ant-design-vue';
|
|||
|
||||
import Title from '../title/index.vue';
|
||||
|
||||
defineOptions({ name: 'Desc' });
|
||||
defineOptions({ name: 'AiMusicModeDesc' });
|
||||
|
||||
const formData = reactive({
|
||||
desc: '',
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Button, Card, Radio } from 'ant-design-vue';
|
|||
import desc from './desc.vue';
|
||||
import lyric from './lyric.vue';
|
||||
|
||||
defineOptions({ name: 'Index' });
|
||||
defineOptions({ name: 'AiMusicModeIndex' });
|
||||
|
||||
const emits = defineEmits(['generateMusic']);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Button, Input, Select, Space, Tag, Textarea } from 'ant-design-vue';
|
|||
|
||||
import Title from '../title/index.vue';
|
||||
|
||||
defineOptions({ name: 'Lyric' });
|
||||
defineOptions({ name: 'AiMusicModeLyric' });
|
||||
|
||||
const tags = ['rock', 'punk', 'jazz', 'soul', 'country', 'kidsmusic', 'pop'];
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'Index' });
|
||||
defineOptions({ name: 'AiMusicTitleIndex' });
|
||||
|
||||
defineProps({
|
||||
title: {
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { DICT_TYPE, getDictOptions } from '#/utils';
|
||||
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
|
@ -45,8 +45,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -13,7 +13,7 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|||
import { deleteMusic, getMusicPage, updateMusic } from '#/api/ai/music';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { $t } from '#/locales';
|
||||
import { AiMusicStatusEnum } from '#/utils/constants';
|
||||
import { AiMusicStatusEnum } from '#/utils';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { DICT_TYPE, getDictOptions } from '#/utils';
|
||||
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
|
@ -30,8 +30,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -28,14 +28,14 @@ const basicInfoRef = ref<InstanceType<typeof BasicInfo>>();
|
|||
const workflowDesignRef = ref<InstanceType<typeof WorkflowDesign>>();
|
||||
|
||||
/** 步骤校验函数 */
|
||||
const validateBasic = async () => {
|
||||
async function validateBasic() {
|
||||
await basicInfoRef.value?.validate();
|
||||
};
|
||||
}
|
||||
|
||||
/** 工作流设计校验 */
|
||||
const validateWorkflow = async () => {
|
||||
async function validateWorkflow() {
|
||||
await workflowDesignRef.value?.validate();
|
||||
};
|
||||
}
|
||||
|
||||
const currentStep = ref(-1); // 步骤控制。-1 用于,一开始全部不展示等当前页面数据初始化完成
|
||||
|
||||
|
@ -60,7 +60,8 @@ provide('workflowData', workflowData);
|
|||
|
||||
/** 初始化数据 */
|
||||
const actionType = route.params.type as string;
|
||||
const initData = async () => {
|
||||
|
||||
async function initData() {
|
||||
if (actionType === 'update') {
|
||||
const workflowId = route.params.id as string;
|
||||
formData.value = await getWorkflow(workflowId);
|
||||
|
@ -79,10 +80,10 @@ const initData = async () => {
|
|||
|
||||
// 设置当前步骤
|
||||
currentStep.value = 0;
|
||||
};
|
||||
}
|
||||
|
||||
/** 校验所有步骤数据是否完整 */
|
||||
const validateAllSteps = async () => {
|
||||
async function validateAllSteps() {
|
||||
// 基本信息校验
|
||||
try {
|
||||
await validateBasic();
|
||||
|
@ -99,10 +100,10 @@ const validateAllSteps = async () => {
|
|||
throw new Error('请完善工作流信息');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/** 保存操作 */
|
||||
const handleSave = async () => {
|
||||
async function handleSave() {
|
||||
try {
|
||||
// 保存前校验所有步骤的数据
|
||||
await validateAllSteps();
|
||||
|
@ -124,10 +125,10 @@ const handleSave = async () => {
|
|||
console.error('保存失败:', error);
|
||||
message.warning(error.message || '请完善所有步骤的必填信息');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 发布操作 */
|
||||
const handleDeploy = async () => {
|
||||
async function handleDeploy() {
|
||||
try {
|
||||
// 修改场景下直接发布,新增场景下需要先确认
|
||||
if (!formData.value.id) {
|
||||
|
@ -158,10 +159,10 @@ const handleDeploy = async () => {
|
|||
console.error('发布失败:', error);
|
||||
message.warning(error.message || '发布失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 步骤切换处理 */
|
||||
const handleStepClick = async (index: number) => {
|
||||
async function handleStepClick(index: number) {
|
||||
try {
|
||||
if (index !== 0) {
|
||||
await validateBasic();
|
||||
|
@ -176,17 +177,17 @@ const handleStepClick = async (index: number) => {
|
|||
console.error('步骤切换失败:', error);
|
||||
message.warning('请先完善当前步骤必填信息');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const tabs = useTabs();
|
||||
|
||||
/** 返回列表页 */
|
||||
const handleBack = () => {
|
||||
function handleBack() {
|
||||
// 关闭当前页签
|
||||
tabs.closeCurrentTab();
|
||||
// 跳转到列表页,使用路径, 目前后端的路由 name: 'name'+ menuId
|
||||
router.push({ path: '/ai/workflow' });
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
|
|
|
@ -18,9 +18,9 @@ const rules: Record<string, Rule[]> = {
|
|||
};
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = async () => {
|
||||
async function validate() {
|
||||
await formRef.value?.validate();
|
||||
};
|
||||
}
|
||||
|
||||
defineExpose({ validate });
|
||||
</script>
|
||||
|
|
|
@ -4,11 +4,12 @@ import type { Ref } from 'vue';
|
|||
import { inject, ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { isNumber } from '@vben/utils';
|
||||
|
||||
import { Button, Input, Select } from 'ant-design-vue';
|
||||
|
||||
import { testWorkflow } from '#/api/ai/workflow';
|
||||
import Tinyflow from '#/components/tinyflow/tinyflow.vue';
|
||||
import { Tinyflow } from '#/components/tinyflow';
|
||||
|
||||
defineProps<{
|
||||
provider: any;
|
||||
|
@ -28,26 +29,26 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||
modal: false,
|
||||
});
|
||||
/** 展示工作流测试抽屉 */
|
||||
const testWorkflowModel = () => {
|
||||
function testWorkflowModel() {
|
||||
drawerApi.open();
|
||||
const startNode = getStartNode();
|
||||
|
||||
// 获取参数定义
|
||||
const parameters = startNode.data?.parameters || [];
|
||||
const paramDefinitions = {};
|
||||
const paramDefinitions: Record<string, any> = {};
|
||||
|
||||
// 加入参数选项方便用户添加非必须参数
|
||||
parameters.forEach((param) => {
|
||||
parameters.forEach((param: any) => {
|
||||
paramDefinitions[param.name] = param;
|
||||
});
|
||||
|
||||
function mergeIfRequiredButNotSet(target) {
|
||||
function mergeIfRequiredButNotSet(target: any) {
|
||||
const needPushList = [];
|
||||
for (const key in paramDefinitions) {
|
||||
const param = paramDefinitions[key];
|
||||
|
||||
if (param.required) {
|
||||
const item = target.find((item) => item.key === key);
|
||||
const item = target.find((item: any) => item.key === key);
|
||||
|
||||
if (!item) {
|
||||
needPushList.push({
|
||||
|
@ -63,10 +64,10 @@ const testWorkflowModel = () => {
|
|||
mergeIfRequiredButNotSet(params4Test.value);
|
||||
|
||||
paramsOfStartNode.value = paramDefinitions;
|
||||
};
|
||||
}
|
||||
|
||||
/** 运行流程 */
|
||||
const goRun = async () => {
|
||||
async function goRun() {
|
||||
try {
|
||||
const val = tinyflowRef.value.getData();
|
||||
loading.value = true;
|
||||
|
@ -77,13 +78,13 @@ const goRun = async () => {
|
|||
|
||||
// 获取参数定义
|
||||
const parameters = startNode.data?.parameters || [];
|
||||
const paramDefinitions = {};
|
||||
parameters.forEach((param) => {
|
||||
const paramDefinitions: Record<string, any> = {};
|
||||
parameters.forEach((param: any) => {
|
||||
paramDefinitions[param.name] = param.dataType;
|
||||
});
|
||||
|
||||
// 参数类型转换
|
||||
const convertedParams = {};
|
||||
const convertedParams: Record<string, any> = {};
|
||||
for (const { key, value } of params4Test.value) {
|
||||
const paramKey = key.trim();
|
||||
if (!paramKey) continue;
|
||||
|
@ -95,8 +96,8 @@ const goRun = async () => {
|
|||
|
||||
try {
|
||||
convertedParams[paramKey] = convertParamValue(value, dataType);
|
||||
} catch (error_) {
|
||||
throw new Error(`参数 ${paramKey} 转换失败: ${error_.message}`);
|
||||
} catch (error: any) {
|
||||
throw new Error(`参数 ${paramKey} 转换失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,42 +108,42 @@ const goRun = async () => {
|
|||
|
||||
const response = await testWorkflow(data);
|
||||
testResult.value = response;
|
||||
} catch (error_) {
|
||||
} catch (error: any) {
|
||||
error.value =
|
||||
error_.response?.data?.message || '运行失败,请检查参数和网络连接';
|
||||
error.response?.data?.message || '运行失败,请检查参数和网络连接';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取开始节点 */
|
||||
const getStartNode = () => {
|
||||
function getStartNode() {
|
||||
const val = tinyflowRef.value.getData();
|
||||
const startNode = val.nodes.find((node) => node.type === 'startNode');
|
||||
const startNode = val.nodes.find((node: any) => node.type === 'startNode');
|
||||
if (!startNode) {
|
||||
throw new Error('流程缺少开始节点');
|
||||
}
|
||||
return startNode;
|
||||
};
|
||||
}
|
||||
|
||||
/** 添加参数项 */
|
||||
const addParam = () => {
|
||||
function addParam() {
|
||||
params4Test.value.push({ key: '', value: '' });
|
||||
};
|
||||
}
|
||||
|
||||
/** 删除参数项 */
|
||||
const removeParam = (index) => {
|
||||
function removeParam(index: number) {
|
||||
params4Test.value.splice(index, 1);
|
||||
};
|
||||
}
|
||||
|
||||
/** 类型转换函数 */
|
||||
const convertParamValue = (value, dataType) => {
|
||||
function convertParamValue(value: string, dataType: string) {
|
||||
if (value === '') return null; // 空值处理
|
||||
|
||||
switch (dataType) {
|
||||
case 'Number': {
|
||||
const num = Number(value);
|
||||
if (isNaN(num)) throw new Error('非数字格式');
|
||||
if (!isNumber(num)) throw new Error('非数字格式');
|
||||
return num;
|
||||
}
|
||||
case 'String': {
|
||||
|
@ -157,24 +158,24 @@ const convertParamValue = (value, dataType) => {
|
|||
case 'Object': {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (error_) {
|
||||
throw new Error(`JSON格式错误: ${error_.message}`);
|
||||
} catch (error: any) {
|
||||
throw new Error(`JSON格式错误: ${error.message}`);
|
||||
}
|
||||
}
|
||||
default: {
|
||||
throw new Error(`不支持的类型: ${dataType}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
/** 表单校验 */
|
||||
const validate = async () => {
|
||||
async function validate() {
|
||||
// 获取最新的流程数据
|
||||
if (!workflowData.value) {
|
||||
throw new Error('请设计流程');
|
||||
}
|
||||
workflowData.value = tinyflowRef.value.getData();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
defineExpose({ validate });
|
||||
</script>
|
||||
|
|
|
@ -6,8 +6,12 @@ import { ref } from 'vue';
|
|||
import { createReusableTemplate } from '@vueuse/core';
|
||||
import { Button, message, Textarea } from 'ant-design-vue';
|
||||
|
||||
import { DICT_TYPE, getIntDictOptions } from '#/utils';
|
||||
import { AiWriteTypeEnum, WriteExample } from '#/utils/constants';
|
||||
import {
|
||||
AiWriteTypeEnum,
|
||||
DICT_TYPE,
|
||||
getIntDictOptions,
|
||||
WriteExample,
|
||||
} from '#/utils';
|
||||
|
||||
import Tag from './Tag.vue';
|
||||
|
||||
|
@ -33,19 +37,19 @@ function omit(obj: Record<string, any>, keysToOmit: string[]) {
|
|||
return result;
|
||||
}
|
||||
/** 点击示例的时候,将定义好的文章作为示例展示出来 */
|
||||
const example = (type: 'reply' | 'write') => {
|
||||
function example(type: 'reply' | 'write') {
|
||||
formData.value = {
|
||||
...initData,
|
||||
...omit(WriteExample[type], ['data']),
|
||||
};
|
||||
emit('example', type);
|
||||
};
|
||||
}
|
||||
|
||||
/** 重置,将表单值作为初选值 */
|
||||
const reset = () => {
|
||||
function reset() {
|
||||
formData.value = { ...initData };
|
||||
emit('reset');
|
||||
};
|
||||
}
|
||||
|
||||
const selectedTab = ref<TabType>(AiWriteTypeEnum.WRITING);
|
||||
const tabs: {
|
||||
|
@ -83,7 +87,7 @@ const formData = ref<AiWriteApi.WriteVO>({ ...initData });
|
|||
/** 用来记录切换之前所填写的数据,切换的时候给赋值回来 */
|
||||
const recordFormData = {} as Record<AiWriteTypeEnum, AiWriteApi.WriteVO>;
|
||||
/** 切换tab */
|
||||
const switchTab = (value: TabType) => {
|
||||
function switchTab(value: TabType) {
|
||||
if (value !== selectedTab.value) {
|
||||
// 保存之前的久数据
|
||||
recordFormData[selectedTab.value] = formData.value;
|
||||
|
@ -91,10 +95,10 @@ const switchTab = (value: TabType) => {
|
|||
// 将之前的旧数据赋值回来
|
||||
formData.value = { ...initData, ...recordFormData[value] };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 提交写作 */
|
||||
const submit = () => {
|
||||
function submit() {
|
||||
if (selectedTab.value === 2 && !formData.value.originalContent) {
|
||||
message.warning('请输入原文');
|
||||
return;
|
||||
|
@ -111,7 +115,7 @@ const submit = () => {
|
|||
/** 使用选中 tab 值覆盖当前的 type 类型 */
|
||||
type: selectedTab.value,
|
||||
});
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -40,9 +40,9 @@ defineExpose({
|
|||
|
||||
/** 点击复制的时候复制内容 */
|
||||
const showCopy = computed(() => props.content && !props.isWriting); // 是否展示复制按钮,在生成内容完成的时候展示
|
||||
const copyContent = () => {
|
||||
function copyContent() {
|
||||
copy(props.content);
|
||||
};
|
||||
}
|
||||
|
||||
/** 复制成功的时候 copied.value 为 true */
|
||||
watch(copied, (val) => {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { nextTick, ref } from 'vue';
|
|||
import { alert, Page } from '@vben/common-ui';
|
||||
|
||||
import { writeStream } from '#/api/ai/write';
|
||||
import { WriteExample } from '#/utils/constants';
|
||||
import { WriteExample } from '#/utils';
|
||||
|
||||
import Left from './components/Left.vue';
|
||||
import Right from './components/Right.vue';
|
||||
|
@ -16,15 +16,15 @@ const isWriting = ref(false); // 是否正在写作中
|
|||
const abortController = ref<AbortController>(); // // 写作进行中 abort 控制器(控制 stream 写作)
|
||||
|
||||
/** 停止 stream 生成 */
|
||||
const stopStream = () => {
|
||||
function stopStream() {
|
||||
abortController.value?.abort();
|
||||
isWriting.value = false;
|
||||
};
|
||||
}
|
||||
|
||||
/** 执行写作 */
|
||||
const rightRef = ref<InstanceType<typeof Right>>();
|
||||
|
||||
const submit = (data: Partial<AiWriteApi.WriteVO>) => {
|
||||
function submit(data: Partial<AiWriteApi.WriteVO>) {
|
||||
abortController.value = new AbortController();
|
||||
writeResult.value = '';
|
||||
isWriting.value = true;
|
||||
|
@ -51,17 +51,17 @@ const submit = (data: Partial<AiWriteApi.WriteVO>) => {
|
|||
throw error;
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** 点击示例触发 */
|
||||
const handleExampleClick = (type: keyof typeof WriteExample) => {
|
||||
function handleExampleClick(type: keyof typeof WriteExample) {
|
||||
writeResult.value = WriteExample[type].data;
|
||||
};
|
||||
}
|
||||
|
||||
/** 点击重置的时候清空写作的结果*/
|
||||
const reset = () => {
|
||||
function reset() {
|
||||
writeResult.value = '';
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { DICT_TYPE, getDictOptions } from '#/utils';
|
||||
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
|
@ -40,8 +40,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -3,7 +3,12 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
||||
import {
|
||||
CommonStatusEnum,
|
||||
DICT_TYPE,
|
||||
getDictOptions,
|
||||
getRangePickerDefaultProps,
|
||||
} from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
|
@ -87,7 +92,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -2,7 +2,12 @@ import type { VbenFormSchema } from '#/adapter/form';
|
|||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
||||
import {
|
||||
CommonStatusEnum,
|
||||
DICT_TYPE,
|
||||
getDictOptions,
|
||||
getRangePickerDefaultProps,
|
||||
} from '#/utils';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
|
@ -74,8 +79,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -71,6 +71,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -77,6 +77,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -55,7 +55,6 @@ export function useGridFormSchema(
|
|||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
placeholder: ['开始日期', '结束日期'],
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
@ -65,7 +64,6 @@ export function useGridFormSchema(
|
|||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
placeholder: ['开始日期', '结束日期'],
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -21,6 +21,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -50,13 +50,13 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
allowClear: true,
|
||||
},
|
||||
},
|
||||
// 发起时间
|
||||
{
|
||||
fieldName: 'createTime',
|
||||
label: '发起时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -21,6 +21,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -56,6 +56,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
Loading…
Reference in New Issue