Merge remote-tracking branch 'yudao-ui-admin-vue3/dev' into dev

# Conflicts:
#	src/views/ai/utils/constants.ts
This commit is contained in:
hhhero
2024-07-10 00:15:07 +08:00
30 changed files with 1813 additions and 1237 deletions

View File

@@ -1,5 +1,5 @@
<template>
<div ref="messageContainer" class="h-100% overflow-y relative">
<div ref="messageContainer" class="h-100% overflow-y-auto relative">
<div class="chat-list" v-for="(item, index) in list" :key="index">
<!-- 靠左 messagesystemassistant 类型 -->
<div class="left-message message-item" v-if="item.type !== 'user'">
@@ -101,13 +101,12 @@ const emits = defineEmits(['onDeleteSuccess', 'onRefresh', 'onEdit']) // 定义
/** 滚动到底部 */
const scrollToBottom = async (isIgnore?: boolean) => {
// 注意要使用 nextTick 以免获取不到dom
await nextTick(() => {
if (isIgnore || !isScrolling.value) {
messageContainer.value.scrollTop =
messageContainer.value.scrollHeight - messageContainer.value.offsetHeight
}
})
// 注意要使用 nextTick 以免获取不到 dom
await nextTick()
if (isIgnore || !isScrolling.value) {
messageContainer.value.scrollTop =
messageContainer.value.scrollHeight - messageContainer.value.offsetHeight
}
}
function handleScroll() {

View File

@@ -10,15 +10,13 @@
<el-icon><More /></el-icon>
</el-button>
</span>
<!-- TODO @fan下面两个 icon可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="['edit', role]">
<el-icon><EditPen /></el-icon>编辑
<Icon icon="ep:edit" color="#787878" />编辑
</el-dropdown-item>
<el-dropdown-item :command="['delete', role]" style="color: red">
<el-icon><Delete /></el-icon>
<span>删除</span>
<Icon icon="ep:delete" color="red" />删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
@@ -43,9 +41,9 @@
</template>
<script setup lang="ts">
import { ChatRoleVO } from '@/api/ai/model/chatRole'
import { PropType, ref } from 'vue'
import { Delete, EditPen, More } from '@element-plus/icons-vue'
import {ChatRoleVO} from '@/api/ai/model/chatRole'
import {PropType, ref} from 'vue'
import {More} from '@element-plus/icons-vue'
const tabsRef = ref<any>() // tabs ref

View File

@@ -23,10 +23,7 @@
@click="handlerAddRole"
class="ml-20px"
>
<!-- TODO @fan下面两个 icon可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
<el-icon>
<User />
</el-icon>
<Icon icon="ep:user" style="margin-right: 5px;" />
添加角色
</el-button>
</div>
@@ -67,15 +64,15 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import {ref} from 'vue'
import RoleHeader from './RoleHeader.vue'
import RoleList from './RoleList.vue'
import ChatRoleForm from '@/views/ai/model/chatRole/ChatRoleForm.vue'
import RoleCategoryList from './RoleCategoryList.vue'
import { ChatRoleApi, ChatRolePageReqVO, ChatRoleVO } from '@/api/ai/model/chatRole'
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
import { Search, User } from '@element-plus/icons-vue'
import { TabsPaneContext } from 'element-plus'
import {ChatRoleApi, ChatRolePageReqVO, ChatRoleVO} from '@/api/ai/model/chatRole'
import {ChatConversationApi, ChatConversationVO} from '@/api/ai/chat/conversation'
import {Search} from '@element-plus/icons-vue'
import {TabsPaneContext} from 'element-plus'
const router = useRouter() // 路由对象
@@ -222,15 +219,14 @@ onMounted(async () => {
// 获取 role 数据
await getActiveTabsRole()
})
// TODO @fancss 是不是可以融合到 scss 里面呀?
</script>
<style lang="css">
<!-- 覆盖 element ui css -->
<style lang="scss">
.el-tabs__content {
position: relative;
height: 100%;
overflow: hidden;
}
.el-tabs__nav-scroll {
margin: 10px 20px;
}

View File

@@ -22,11 +22,14 @@
<Icon icon="ep:setting" class="ml-10px" />
</el-button>
<el-button size="small" class="btn" @click="handlerMessageClear">
<img src="@/assets/ai/clear.svg" class="h-14px" />
<Icon icon="heroicons-outline:archive-box-x-mark" color="#787878" />
</el-button>
<el-button size="small" class="btn">
<Icon icon="ep:download" color="#787878" />
</el-button>
<el-button size="small" class="btn" @click="handleGoTopMessage" >
<Icon icon="ep:top" color="#787878" />
</el-button>
<!-- TODO @fan下面两个 icon可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
<el-button size="small" :icon="Download" class="btn" />
<el-button size="small" :icon="Top" class="btn" @click="handleGoTopMessage" />
</div>
</el-header>
@@ -180,11 +183,6 @@ const handleConversationClick = async (conversation: ChatConversationVO) => {
// 更新选中的对话 id
activeConversationId.value = conversation.id
activeConversation.value = conversation
// 处理进行中的对话
// TODO @fan这里和上面的 “对话进行中,不允许切换” 是不是重叠了?
if (conversationInProgress.value) {
await stopStream()
}
// 刷新 message 列表
await getMessageList()
// 滚动底部
@@ -203,7 +201,11 @@ const handlerConversationDelete = async (delConversation: ChatConversationVO) =>
}
/** 清空选中的对话 */
const handleConversationClear = async () => {
// TODO @fan需要加一个 对话进行中,不允许切换
// 对话进行中,不允许切换
if (conversationInProgress.value) {
message.alert('对话中,不允许切换!')
return false
}
activeConversationId.value = null
activeConversation.value = null
activeMessageList.value = []
@@ -363,7 +365,7 @@ const handlePromptInput = (event) => {
isComposing.value = false
}, 400)
}
// TODO @fan:是不是可以通过 @keydown.enter、@keydown.shift.enter 来实现回车发送、shift+回车换行;主要看看,是不是可以简化 isComposing 相关的逻辑
// TODO @芋艿:是不是可以通过 @keydown.enter、@keydown.shift.enter 来实现回车发送、shift+回车换行;主要看看,是不是可以简化 isComposing 相关的逻辑
const onCompositionstart = () => {
isComposing.value = true
}
@@ -394,7 +396,6 @@ const doSendMessage = async (content: string) => {
} as ChatMessageVO)
}
// TODO @fan= = 不知道哪里被改动了。点击【发送】后,不会跳转到消息最底部了。。
/** 真正执行【发送】消息操作 */
const doSendMessageStream = async (userMessage: ChatMessageVO) => {
// 创建 AbortController 实例,以便中止请求
@@ -421,9 +422,8 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
createTime: new Date()
} as ChatMessageVO)
// 1.2 滚动到最下面
nextTick(async () => {
await scrollToBottom() // 底部
})
await nextTick()
await scrollToBottom() // 底部
// 1.3 开始滚动
textRoll()
@@ -573,7 +573,6 @@ onMounted(async () => {
<style lang="scss" scoped>
.ai-layout {
// TODO @范 这里height不能 100% 先这样临时处理 TODO @fan这个目前要搞处理么
position: absolute;
flex: 1;
top: 0;

View File

@@ -1,140 +0,0 @@
<template>
<el-drawer
v-model="showDrawer"
title="图片详细"
@close="handleDrawerClose"
custom-class="drawer-class"
>
<!-- 图片 -->
<div class="item">
<!-- <div class="header">-->
<!-- <div>图片</div>-->
<!-- <div>-->
<!-- </div>-->
<!-- </div>-->
<div class="body">
<!-- TODO @fan: 要不这里只展示图片不用 ImageTaskCard -->
<ImageTaskCard :image-detail="imageDetail" />
</div>
</div>
<!-- 时间 -->
<div class="item">
<div class="tip">时间</div>
<div class="body">
<div>提交时间{{ imageDetail.createTime }}</div>
<div>生成时间{{ imageDetail.finishTime }}</div>
</div>
</div>
<!-- 模型 -->
<div class="item">
<div class="tip">模型</div>
<div class="body">
{{ imageDetail.model }}({{ imageDetail.height }}x{{ imageDetail.width }})
</div>
</div>
<!-- 提示词 -->
<div class="item">
<div class="tip">提示词</div>
<div class="body">
{{ imageDetail.prompt }}
</div>
</div>
<!-- 地址 -->
<div class="item">
<div class="tip">图片地址</div>
<div class="body">
{{ imageDetail.picUrl }}
</div>
</div>
<!-- 风格 -->
<div class="item" v-if="imageDetail?.options?.style">
<div class="tip">风格</div>
<div class="body">
<!-- TODO @fan貌似需要把 imageStyleList 搞到 api/image/index.ts 枚举起来 -->
<!-- TODO @fan这里的展示可能需要按照平台做区分 -->
{{ imageDetail?.options?.style }}
</div>
</div>
</el-drawer>
</template>
<script setup lang="ts">
import { ImageApi, ImageVO } from '@/api/ai/image'
import ImageTaskCard from './ImageTaskCard.vue'
const showDrawer = ref<boolean>(false) // 是否显示
const imageDetail = ref<ImageVO>({} as ImageVO) // 图片详细信息
const props = defineProps({
show: {
type: Boolean,
require: true,
default: false
},
id: {
type: Number,
required: true
}
})
/** 抽屉 - close */
const handleDrawerClose = async () => {
emits('handleDrawerClose')
}
/** 获取 - 图片 detail */
const getImageDetail = async (id) => {
// 获取图片详细
imageDetail.value = await ImageApi.getImageMy(id)
}
/** 任务 - detail */
const handleTaskDetail = async () => {
showDrawer.value = true
}
// watch show
const { show } = toRefs(props)
watch(show, async (newValue, oldValue) => {
showDrawer.value = newValue as boolean
})
// watch id
const { id } = toRefs(props)
watch(id, async (newVal, oldVal) => {
if (newVal) {
await getImageDetail(newVal)
}
})
//
const emits = defineEmits(['handleDrawerClose'])
//
onMounted(async () => {})
</script>
<style scoped lang="scss">
.item {
margin-bottom: 20px;
width: 100%;
overflow: hidden;
word-wrap: break-word;
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.tip {
font-weight: bold;
font-size: 16px;
}
.body {
margin-top: 10px;
color: #616161;
.taskImage {
border-radius: 10px;
}
}
}
</style>

View File

@@ -2,58 +2,53 @@
<el-card body-class="" class="image-card">
<div class="image-operation">
<div>
<el-button
type="primary"
text
bg
v-if="imageDetail?.status === AiImageStatusEnum.IN_PROGRESS"
>
<el-button type="primary" text bg v-if="detail?.status === AiImageStatusEnum.IN_PROGRESS">
生成中
</el-button>
<el-button text bg v-else-if="imageDetail?.status === AiImageStatusEnum.SUCCESS">
<el-button text bg v-else-if="detail?.status === AiImageStatusEnum.SUCCESS">
已完成
</el-button>
<el-button type="danger" text bg v-else-if="imageDetail?.status === AiImageStatusEnum.FAIL">
<el-button type="danger" text bg v-else-if="detail?.status === AiImageStatusEnum.FAIL">
异常
</el-button>
</div>
<!-- 操作区 -->
<div>
<el-button
class="btn"
text
:icon="Download"
@click="handleBtnClick('download', imageDetail)"
@click="handleButtonClick('download', detail)"
/>
<el-button
class="btn"
text
:icon="RefreshRight"
@click="handleBtnClick('regeneration', imageDetail)"
@click="handleButtonClick('regeneration', detail)"
/>
<el-button
class="btn"
text
:icon="Delete"
@click="handleBtnClick('delete', imageDetail)"
/>
<el-button class="btn" text :icon="More" @click="handleBtnClick('more', imageDetail)" />
<el-button class="btn" text :icon="Delete" @click="handleButtonClick('delete', detail)" />
<el-button class="btn" text :icon="More" @click="handleButtonClick('more', detail)" />
</div>
</div>
<div class="image-wrapper" ref="cardImageRef">
<!-- TODO @fan要不加个点击大图预览 -->
<img class="image" :src="imageDetail?.picUrl" />
<div v-if="imageDetail?.status === AiImageStatusEnum.FAIL">
{{ imageDetail?.errorMessage }}
<el-image
class="image"
:src="detail?.picUrl"
:preview-src-list="[detail.picUrl]"
preview-teleported
/>
<div v-if="detail?.status === AiImageStatusEnum.FAIL">
{{ detail?.errorMessage }}
</div>
</div>
<!-- TODO @fanstyle 使用 unocss 替代下 -->
<!-- Midjourney 专属操作 -->
<div class="image-mj-btns">
<el-button
size="small"
v-for="button in imageDetail?.buttons"
v-for="button in detail?.buttons"
:key="button"
style="min-width: 40px; margin-left: 0; margin-right: 10px; margin-top: 5px"
@click="handleMjBtnClick(button)"
class="min-w-40px ml-0 mr-10px mt-5px"
@click="handleMidjourneyBtnClick(button)"
>
{{ button.label }}{{ button.emoji }}
</el-button>
@@ -61,34 +56,53 @@
</el-card>
</template>
<script setup lang="ts">
import {Delete, Download, More, RefreshRight} from '@element-plus/icons-vue'
import { ImageVO, ImageMjButtonsVO } from '@/api/ai/image'
import { Delete, Download, More, RefreshRight } from '@element-plus/icons-vue'
import { ImageVO, ImageMidjourneyButtonsVO } from '@/api/ai/image'
import { PropType } from 'vue'
import {ElLoading, LoadingOptionsResolved} from 'element-plus'
import { ElLoading, LoadingOptionsResolved } from 'element-plus'
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
const cardImageRef = ref<any>() // image ref
const cardImageLoadingInstance = ref<any>() // image ref
const message = useMessage()
const message = useMessage() //
const props = defineProps({
imageDetail: {
detail: {
type: Object as PropType<ImageVO>,
require: true
}
})
/** 按钮 - 点击事件 */
const handleBtnClick = async (type, imageDetail: ImageVO) => {
emits('onBtnClick', type, imageDetail)
const cardImageRef = ref<any>() // image ref
const cardImageLoadingInstance = ref<any>() // image ref
/** 处理点击事件 */
const handleButtonClick = async (type, detail: ImageVO) => {
emits('onBtnClick', type, detail)
}
/** 处理 Midjourney 按钮点击事件 */
const handleMidjourneyBtnClick = async (button: ImageMidjourneyButtonsVO) => {
//
await message.confirm(`确认操作 "${button.label} ${button.emoji}" ?`)
emits('onMjBtnClick', button, props.detail)
}
const emits = defineEmits(['onBtnClick', 'onMjBtnClick']) // emits
/** 监听详情 */
const { detail } = toRefs(props)
watch(detail, async (newVal, oldVal) => {
await handleLoading(newVal.status as string)
})
/** 处理加载状态 */
const handleLoading = async (status: number) => {
// TODO @ Loading
// loading
if (status === AiImageStatusEnum.IN_PROGRESS) {
cardImageLoadingInstance.value = ElLoading.service({
target: cardImageRef.value,
text: '生成中...'
} as LoadingOptionsResolved)
// loading
} else {
if (cardImageLoadingInstance.value) {
cardImageLoadingInstance.value.close()
@@ -97,25 +111,9 @@ const handleLoading = async (status: number) => {
}
}
/** mj 按钮 click */
const handleMjBtnClick = async (button: ImageMjButtonsVO) => {
//
await message.confirm(`确认操作 "${button.label} ${button.emoji}" ?`)
emits('onMjBtnClick', button, props.imageDetail)
}
// watch
const { imageDetail } = toRefs(props)
watch(imageDetail, async (newVal, oldVal) => {
await handleLoading(newVal.status as string)
})
// emits
const emits = defineEmits(['onBtnClick', 'onMjBtnClick'])
//
/** 初始化 */
onMounted(async () => {
await handleLoading(props.imageDetail.status as string)
await handleLoading(props.detail.status as string)
})
</script>

View File

@@ -0,0 +1,224 @@
<template>
<el-drawer
v-model="showDrawer"
title="图片详细"
@close="handleDrawerClose"
custom-class="drawer-class"
>
<!-- 图片 -->
<div class="item">
<div class="body">
<el-image
class="image"
:src="detail?.picUrl"
:preview-src-list="[detail.picUrl]"
preview-teleported
/>
</div>
</div>
<!-- 时间 -->
<div class="item">
<div class="tip">时间</div>
<div class="body">
<div>提交时间{{ formatTime(detail.createTime, 'yyyy-MM-dd HH:mm:ss') }}</div>
<div>生成时间{{ formatTime(detail.finishTime, 'yyyy-MM-dd HH:mm:ss') }}</div>
</div>
</div>
<!-- 模型 -->
<div class="item">
<div class="tip">模型</div>
<div class="body"> {{ detail.model }}({{ detail.height }}x{{ detail.width }}) </div>
</div>
<!-- 提示词 -->
<div class="item">
<div class="tip">提示词</div>
<div class="body">
{{ detail.prompt }}
</div>
</div>
<!-- 地址 -->
<div class="item">
<div class="tip">图片地址</div>
<div class="body">
{{ detail.picUrl }}
</div>
</div>
<!-- StableDiffusion 专属区域 -->
<div
class="item"
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.sampler"
>
<div class="tip">采样方法</div>
<div class="body">
{{
StableDiffusionSamplers.find(
(item: ImageModelVO) => item.key === detail?.options?.sampler
)?.name
}}
</div>
</div>
<div
class="item"
v-if="
detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.clipGuidancePreset
"
>
<div class="tip">CLIP</div>
<div class="body">
{{
StableDiffusionClipGuidancePresets.find(
(item: ImageModelVO) => item.key === detail?.options?.clipGuidancePreset
)?.name
}}
</div>
</div>
<div
class="item"
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.stylePreset"
>
<div class="tip">风格</div>
<div class="body">
{{
StableDiffusionStylePresets.find(
(item: ImageModelVO) => item.key === detail?.options?.stylePreset
)?.name
}}
</div>
</div>
<div
class="item"
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.steps"
>
<div class="tip">迭代步数</div>
<div class="body">
{{ detail?.options?.steps }}
</div>
</div>
<div
class="item"
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.scale"
>
<div class="tip">引导系数</div>
<div class="body">
{{ detail?.options?.scale }}
</div>
</div>
<div
class="item"
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.seed"
>
<div class="tip">随机因子</div>
<div class="body">
{{ detail?.options?.seed }}
</div>
</div>
<!-- Dall3 专属区域 -->
<div class="item" v-if="detail.platform === AiPlatformEnum.OPENAI && detail?.options?.style">
<div class="tip">风格选择</div>
<div class="body">
{{ Dall3StyleList.find((item: ImageModelVO) => item.key === detail?.options?.style)?.name }}
</div>
</div>
<!-- Midjourney 专属区域 -->
<div
class="item"
v-if="detail.platform === AiPlatformEnum.MIDJOURNEY && detail?.options?.version"
>
<div class="tip">模型版本</div>
<div class="body">
{{ detail?.options?.version }}
</div>
</div>
<div
class="item"
v-if="detail.platform === AiPlatformEnum.MIDJOURNEY && detail?.options?.referImageUrl"
>
<div class="tip">参考图</div>
<div class="body">
<el-image :src="detail.options.referImageUrl" />
</div>
</div>
</el-drawer>
</template>
<script setup lang="ts">
import { ImageApi, ImageVO } from '@/api/ai/image'
import {
AiPlatformEnum,
Dall3StyleList,
ImageModelVO,
StableDiffusionClipGuidancePresets,
StableDiffusionSamplers,
StableDiffusionStylePresets
} from '@/views/ai/utils/constants'
import { formatTime } from '@/utils'
const showDrawer = ref<boolean>(false) // 是否显示
const detail = ref<ImageVO>({} as ImageVO) // 图片详细信息
const props = defineProps({
show: {
type: Boolean,
require: true,
default: false
},
id: {
type: Number,
required: true
}
})
/** 关闭抽屉 */
const handleDrawerClose = async () => {
emits('handleDrawerClose')
}
/** 监听 drawer 是否打开 */
const { show } = toRefs(props)
watch(show, async (newValue, oldValue) => {
showDrawer.value = newValue as boolean
})
/** 获取图片详情 */
const getImageDetail = async (id: number) => {
detail.value = await ImageApi.getImageMy(id)
}
/** 监听 id 变化,加载最新图片详情 */
const { id } = toRefs(props)
watch(id, async (newVal, oldVal) => {
if (newVal) {
await getImageDetail(newVal)
}
})
const emits = defineEmits(['handleDrawerClose'])
</script>
<style scoped lang="scss">
.item {
margin-bottom: 20px;
width: 100%;
overflow: hidden;
word-wrap: break-word;
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.tip {
font-weight: bold;
font-size: 16px;
}
.body {
margin-top: 10px;
color: #616161;
.taskImage {
border-radius: 10px;
}
}
}
</style>

View File

@@ -1,85 +1,87 @@
<template>
<el-card class="dr-task" body-class="task-card" shadow="never">
<template #header>绘画任务</template>
<div class="task-image-list" ref="imageTaskRef">
<ImageTaskCard
<!-- 图片列表 -->
<div class="task-image-list" ref="imageListRef">
<ImageCard
v-for="image in imageList"
:key="image"
:image-detail="image"
@on-btn-click="handleImageBtnClick"
@on-mj-btn-click="handleImageMjBtnClick"
:key="image.id"
:detail="image"
@on-btn-click="handleImageButtonClick"
@on-mj-btn-click="handleImageMidjourneyButtonClick"
/>
</div>
<div class="task-image-pagination">
<el-pagination
background
layout="prev, pager, next"
:default-page-size="pageSize"
<Pagination
:total="pageTotal"
@change="handlePageChange"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getImageList"
/>
</div>
</el-card>
<!-- 图片 detail 抽屉 -->
<ImageDetailDrawer
<!-- 图片详情 -->
<ImageDetail
:show="isShowImageDetail"
:id="showImageDetailId"
@handle-drawer-close="handleDrawerClose"
@handle-drawer-close="handleDetailClose"
/>
</template>
<script setup lang="ts">
import { ImageApi, ImageVO, ImageMjActionVO, ImageMjButtonsVO } from '@/api/ai/image'
import ImageDetailDrawer from './ImageDetailDrawer.vue'
import ImageTaskCard from './ImageTaskCard.vue'
import {
ImageApi,
ImageVO,
ImageMidjourneyActionVO,
ImageMidjourneyButtonsVO
} from '@/api/ai/image'
import ImageDetail from './ImageDetail.vue'
import ImageCard from './ImageCard.vue'
import { ElLoading, LoadingOptionsResolved } from 'element-plus'
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
import { downloadImage } from '@/utils/download'
import download from '@/utils/download'
const message = useMessage() //
const imageList = ref<ImageVO[]>([]) // image
const inProgressImageMap = ref<{}>({}) // image key image value image
const imageListInterval = ref<any>() // image
const isShowImageDetail = ref<boolean>(false) // task
const showImageDetailId = ref<number>(0) // task
const imageTaskRef = ref<any>() // ref
const imageTaskLoadingInstance = ref<any>() // loading
const imageTaskLoading = ref<boolean>(false) // loading
const pageNo = ref<number>(1) // page no
const pageSize = ref<number>(10) // page size
//
const queryParams = reactive({
pageNo: 1,
pageSize: 10
})
const pageTotal = ref<number>(0) // page size
const imageList = ref<ImageVO[]>([]) // image
const imageListLoadingInstance = ref<any>() // image
const imageListRef = ref<any>() // ref
//
const inProgressImageMap = ref<{}>({}) // image key image value image
const inProgressTimer = ref<any>() // image
//
const isShowImageDetail = ref<boolean>(false) //
const showImageDetailId = ref<number>(0) //
/** 抽屉 - close */
const handleDrawerClose = async () => {
isShowImageDetail.value = false
}
/** 任务 - detail */
const handleDrawerOpen = async () => {
/** 查看图片的详情 */
const handleDetailOpen = async () => {
isShowImageDetail.value = true
}
/**
* 获取 - image 列表
*/
const getImageList = async (apply: boolean = false) => {
imageTaskLoading.value = true
/** 关闭图片的详情 */
const handleDetailClose = async () => {
isShowImageDetail.value = false
}
/** 获得 image 图片列表 */
const getImageList = async () => {
try {
imageTaskLoadingInstance.value = ElLoading.service({
target: imageTaskRef.value,
// 1.
imageListLoadingInstance.value = ElLoading.service({
target: imageListRef.value,
text: '加载中...'
} as LoadingOptionsResolved)
const { list, total } = await ImageApi.getImagePageMy({
pageNo: pageNo.value,
pageSize: pageSize.value
})
if (apply) {
imageList.value = [...imageList.value, ...list]
} else {
imageList.value = list
}
const { list, total } = await ImageApi.getImagePageMy(queryParams)
imageList.value = list
pageTotal.value = total
// watch
// 2.
const newWatImages = {}
imageList.value.forEach((item) => {
if (item.status === AiImageStatusEnum.IN_PROGRESS) {
@@ -88,9 +90,10 @@ const getImageList = async (apply: boolean = false) => {
})
inProgressImageMap.value = newWatImages
} finally {
if (imageTaskLoadingInstance.value) {
imageTaskLoadingInstance.value.close()
imageTaskLoadingInstance.value = null
// Loading
if (imageListLoadingInstance.value) {
imageListLoadingInstance.value.close()
imageListLoadingInstance.value = null
}
}
}
@@ -117,50 +120,52 @@ const refreshWatchImages = async () => {
inProgressImageMap.value = newWatchImages
}
/** 图片 - btn click */
const handleImageBtnClick = async (type: string, imageDetail: ImageVO) => {
// image detail id
showImageDetailId.value = imageDetail.id
// btn
/** 图片的点击事件 */
const handleImageButtonClick = async (type: string, imageDetail: ImageVO) => {
//
if (type === 'more') {
await handleDrawerOpen()
} else if (type === 'delete') {
showImageDetailId.value = imageDetail.id
await handleDetailOpen()
return
}
//
if (type === 'delete') {
await message.confirm(`是否删除照片?`)
await ImageApi.deleteImageMy(imageDetail.id)
await getImageList()
message.success('删除成功!')
} else if (type === 'download') {
await downloadImage(imageDetail.picUrl)
} else if (type === 'regeneration') {
// Midjourney
console.log('regeneration', imageDetail.id)
return
}
//
if (type === 'download') {
await download.image(imageDetail.picUrl)
return
}
//
if (type === 'regeneration') {
await emits('onRegeneration', imageDetail)
return
}
}
/** 图片 - mj btn click */
const handleImageMjBtnClick = async (button: ImageMjButtonsVO, imageDetail: ImageVO) => {
// 1 params
/** 处理 Midjourney 按钮点击事件 */
const handleImageMidjourneyButtonClick = async (
button: ImageMidjourneyButtonsVO,
imageDetail: ImageVO
) => {
// 1. params
const data = {
id: imageDetail.id,
customId: button.customId
} as ImageMjActionVO
// 2 action
} as ImageMidjourneyActionVO
// 2. action
await ImageApi.midjourneyAction(data)
// 3
// 3.
await getImageList()
}
// page change
const handlePageChange = async (page) => {
pageNo.value = page
await getImageList(false)
}
defineExpose({ getImageList }) //
/** 暴露组件方法 */
defineExpose({ getImageList })
// emits
const emits = defineEmits(['onRegeneration'])
/** 组件挂在的时候 */
@@ -168,19 +173,20 @@ onMounted(async () => {
// image
await getImageList()
// image
imageListInterval.value = setInterval(async () => {
inProgressTimer.value = setInterval(async () => {
await refreshWatchImages()
}, 1000 * 3)
})
/** 组件取消挂在的时候 */
onUnmounted(async () => {
if (imageListInterval.value) {
clearInterval(imageListInterval.value)
if (inProgressTimer.value) {
clearInterval(inProgressTimer.value)
}
})
</script>
<!-- TODO fan 2 scss 可以合并么 -->
<style lang="scss">
.task-card {
margin: 0;
@@ -197,8 +203,7 @@ onUnmounted(async () => {
align-content: flex-start;
height: 100%;
overflow: auto;
padding: 20px;
padding-bottom: 140px;
padding: 20px 20px 140px;
box-sizing: border-box; /* 确保内边距不会增加高度 */
> div {
@@ -224,7 +229,6 @@ onUnmounted(async () => {
align-items: center;
}
</style>
<style scoped lang="scss">
.dr-task {
width: 100%;

View File

@@ -3,12 +3,11 @@
<div class="prompt">
<el-text tag="b">画面描述</el-text>
<el-text tag="p">建议使用形容词+动词+风格的格式使用隔开</el-text>
<!-- TODO @fanstyle 看看能不能哟 unocss 替代 -->
<el-input
v-model="prompt"
maxlength="1024"
rows="5"
style="width: 100%; margin-top: 15px;"
class="w-100% mt-15px"
input-style="border-radius: 7px;"
placeholder="例如:童话里的小屋应该是什么样子?"
show-word-limit
@@ -20,12 +19,13 @@
<el-text tag="b">随机热词</el-text>
</div>
<el-space wrap class="word-list">
<el-button round
class="btn"
:type="(selectHotWord === hotWord ? 'primary' : 'default')"
v-for="hotWord in hotWords"
:key="hotWord"
@click="handleHotWordClick(hotWord)"
<el-button
round
class="btn"
:type="selectHotWord === hotWord ? 'primary' : 'default'"
v-for="hotWord in ImageHotWords"
:key="hotWord"
@click="handleHotWordClick(hotWord)"
>
{{ hotWord }}
</el-button>
@@ -38,16 +38,11 @@
<el-space wrap class="model-list">
<div
:class="selectModel === model.key ? 'modal-item selectModel' : 'modal-item'"
v-for="model in models"
v-for="model in Dall3Models"
:key="model.key"
>
<el-image
:src="model.image"
fit="contain"
@click="handleModelClick(model)"
/>
<div class="model-font">{{model.name}}</div>
<el-image :src="model.image" fit="contain" @click="handleModelClick(model)" />
<div class="model-font">{{ model.name }}</div>
</div>
</el-space>
</div>
@@ -57,16 +52,12 @@
</div>
<el-space wrap class="image-style-list">
<div
:class="selectImageStyle === imageStyle.key ? 'image-style-item selectImageStyle' : 'image-style-item'"
v-for="imageStyle in imageStyleList"
:class="style === imageStyle.key ? 'image-style-item selectImageStyle' : 'image-style-item'"
v-for="imageStyle in Dall3StyleList"
:key="imageStyle.key"
>
<el-image
:src="imageStyle.image"
fit="contain"
@click="handleStyleClick(imageStyle)"
/>
<div class="style-font">{{imageStyle.name}}</div>
<el-image :src="imageStyle.image" fit="contain" @click="handleStyleClick(imageStyle)" />
<div class="style-font">{{ imageStyle.name }}</div>
</div>
</el-space>
</div>
@@ -75,11 +66,15 @@
<el-text tag="b">画面比例</el-text>
</div>
<el-space wrap class="size-list">
<div class="size-item"
v-for="imageSize in imageSizeList"
:key="imageSize.key"
@click="handleSizeClick(imageSize)">
<div :class="selectImageSize === imageSize.key ? 'size-wrapper selectImageSize' : 'size-wrapper'">
<div
class="size-item"
v-for="imageSize in Dall3SizeList"
:key="imageSize.key"
@click="handleSizeClick(imageSize)"
>
<div
:class="selectSize === imageSize.key ? 'size-wrapper selectImageSize' : 'size-wrapper'"
>
<div :style="imageSize.style"></div>
</div>
<div class="size-font">{{ imageSize.name }}</div>
@@ -87,126 +82,60 @@
</el-space>
</div>
<div class="btns">
<el-button type="primary"
size="large"
round
:loading="drawIn"
@click="handleGenerateImage">
{{drawIn ? '生成中' : '生成内容'}}
<el-button type="primary" size="large" round :loading="drawIn" @click="handleGenerateImage">
{{ drawIn ? '生成中' : '生成内容' }}
</el-button>
</div>
</template>
<script setup lang="ts">
import {ImageApi, ImageDrawReqVO, ImageVO} from '@/api/ai/image';
import { ImageApi, ImageDrawReqVO, ImageVO } from '@/api/ai/image'
import {
Dall3Models,
Dall3StyleList,
ImageHotWords,
Dall3SizeList,
ImageModelVO,
AiPlatformEnum
} from '@/views/ai/utils/constants'
// image
interface ImageModelVO {
key: string
name: string
image: string
}
// image
interface ImageSizeVO {
key: string
name: string,
style: string,
width: string,
height: string,
}
const message = useMessage() //
//
const prompt = ref<string>('') //
const drawIn = ref<boolean>(false) //
const prompt = ref<string>('') //
const drawIn = ref<boolean>(false) //
const selectHotWord = ref<string>('') //
const hotWords = ref<string[]>(['中国旗袍', '古装美女', '卡通头像', '机甲战士', '童话小屋', '中国长城']) //
const selectModel = ref<string>('dall-e-3') //
// message
const message = useMessage()
const models = ref<ImageModelVO[]>([
{
key: 'dall-e-3',
name: 'DALL·E 3',
image: `/src/assets/ai/dall2.jpg`,
},
{
key: 'dall-e-2',
name: 'DALL·E 2',
image: `/src/assets/ai/dall3.jpg`,
},
]) //
const selectSize = ref<string>('1024x1024') // size
const style = ref<string>('vivid') // style
const selectImageStyle = ref<string>('vivid') // style
const emits = defineEmits(['onDrawStart', 'onDrawComplete']) // emits
const imageStyleList = ref<ImageModelVO[]>([
{
key: 'vivid',
name: '清晰',
image: `/src/assets/ai/qingxi.jpg`,
},
{
key: 'natural',
name: '自然',
image: `/src/assets/ai/ziran.jpg`,
},
]) // style
const selectImageSize = ref<string>('1024x1024') // size
const imageSizeList = ref<ImageSizeVO[]>([
{
key: '1024x1024',
name: '1:1',
width: '1024',
height: '1024',
style: 'width: 30px; height: 30px;background-color: #dcdcdc;',
},
{
key: '1024x1792',
name: '3:5',
width: '1024',
height: '1792',
style: 'width: 30px; height: 50px;background-color: #dcdcdc;',
},
{
key: '1792x1024',
name: '5:3',
width: '1792',
height: '1024',
style: 'width: 50px; height: 30px;background-color: #dcdcdc;',
}
]) // size
// Props
const props = defineProps({})
// emits
const emits = defineEmits(['onDrawStart', 'onDrawComplete'])
/** 热词 - click */
/** 选择热词 */
const handleHotWordClick = async (hotWord: string) => {
//
//
if (selectHotWord.value == hotWord) {
selectHotWord.value = ''
return
}
//
//
selectHotWord.value = hotWord
//
prompt.value = hotWord
}
/** 模型 - click */
/** 选择 model 模型 */
const handleModelClick = async (model: ImageModelVO) => {
selectModel.value = model.key
}
/** 样式 - click */
/** 选择 style 样式 */
const handleStyleClick = async (imageStyle: ImageModelVO) => {
selectImageStyle.value = imageStyle.key
style.value = imageStyle.key
}
/** size - click */
/** 选择 size 大小 */
const handleSizeClick = async (imageSize: ImageSizeVO) => {
selectImageSize.value = imageSize.key
selectSize.value = imageSize.key
}
/** 图片生产 */
@@ -217,42 +146,41 @@ const handleGenerateImage = async () => {
//
drawIn.value = true
//
emits('onDrawStart', selectModel.value)
const imageSize = imageSizeList.value.find(item => item.key === selectImageSize.value) as ImageSizeVO
emits('onDrawStart', AiPlatformEnum.OPENAI)
const imageSize = Dall3SizeList.find((item) => item.key === selectSize.value) as ImageSizeVO
const form = {
platform: 'OpenAI',
platform: AiPlatformEnum.OPENAI,
prompt: prompt.value, //
model: selectModel.value, //
width: imageSize.width, // size
height: imageSize.height, // size
options: {
style: selectImageStyle.value, //
style: style.value //
}
} as ImageDrawReqVO
//
await ImageApi.drawImage(form)
} finally {
//
emits('onDrawComplete', selectModel.value)
emits('onDrawComplete', AiPlatformEnum.OPENAI)
//
drawIn.value = false
}
}
/** 填充值 */
const settingValues = async (imageDetail: ImageVO) => {
prompt.value = imageDetail.prompt
selectModel.value = imageDetail.model
//
selectImageStyle.value = imageDetail.options?.style
//
const imageSize = imageSizeList.value.find(item => item.key === `${imageDetail.width}x${imageDetail.height}`) as ImageSizeVO
const settingValues = async (detail: ImageVO) => {
prompt.value = detail.prompt
selectModel.value = detail.model
style.value = detail.options?.style
const imageSize = Dall3SizeList.find(
(item) => item.key === `${detail.width}x${detail.height}`
) as ImageSizeVO
await handleSizeClick(imageSize)
}
/** 暴露组件方法 */
defineExpose({ settingValues })
</script>
<style scoped lang="scss">
//
@@ -309,7 +237,6 @@ defineExpose({ settingValues })
}
}
// style
.image-style {
margin-top: 30px;

View File

@@ -7,7 +7,7 @@
v-model="prompt"
maxlength="1024"
rows="5"
style="width: 100%; margin-top: 15px;"
class="w-100% mt-15px"
input-style="border-radius: 7px;"
placeholder="例如:童话里的小屋应该是什么样子?"
show-word-limit
@@ -19,12 +19,13 @@
<el-text tag="b">随机热词</el-text>
</div>
<el-space wrap class="word-list">
<el-button round
class="btn"
:type="(selectHotWord === hotWord ? 'primary' : 'default')"
v-for="hotWord in hotWords"
:key="hotWord"
@click="handleHotWordClick(hotWord)"
<el-button
round
class="btn"
:type="selectHotWord === hotWord ? 'primary' : 'default'"
v-for="hotWord in ImageHotWords"
:key="hotWord"
@click="handleHotWordClick(hotWord)"
>
{{ hotWord }}
</el-button>
@@ -35,17 +36,36 @@
<el-text tag="b">尺寸</el-text>
</div>
<el-space wrap class="size-list">
<div class="size-item"
v-for="imageSize in imageSizeList"
:key="imageSize.key"
@click="handleSizeClick(imageSize)">
<div :class="selectImageSize === imageSize.key ? 'size-wrapper selectImageSize' : 'size-wrapper'">
<div
class="size-item"
v-for="imageSize in MidjourneySizeList"
:key="imageSize.key"
@click="handleSizeClick(imageSize)"
>
<div
:class="selectSize === imageSize.key ? 'size-wrapper selectImageSize' : 'size-wrapper'"
>
<div :style="imageSize.style"></div>
</div>
<div class="size-font">{{ imageSize.key }}</div>
</div>
</el-space>
</div>
<div class="model">
<div>
<el-text tag="b">模型</el-text>
</div>
<el-space wrap class="model-list">
<div
:class="selectModel === model.key ? 'modal-item selectModel' : 'modal-item'"
v-for="model in MidjourneyModels"
:key="model.key"
>
<el-image :src="model.image" fit="contain" @click="handleModelClick(model)" />
<div class="model-font">{{ model.name }}</div>
</div>
</el-space>
</div>
<div class="version">
<div>
<el-text tag="b">版本</el-text>
@@ -53,11 +73,9 @@
<el-space wrap class="version-list">
<el-select
v-model="selectVersion"
class="version-select"
class="version-select !w-350px"
clearable
placeholder="请选择版本"
style="width: 350px"
@change="handleChangeVersion"
>
<el-option
v-for="item in versionList"
@@ -68,228 +86,130 @@
</el-select>
</el-space>
</div>
<div class="model">
<div>
<el-text tag="b">模型</el-text>
</div>
<el-space wrap class="model-list">
<div
:class="selectModel === model.key ? 'modal-item selectModel' : 'modal-item'"
v-for="model in models"
:key="model.key"
>
<el-image
:src="model.image"
fit="contain"
@click="handleModelClick(model)"
/>
<div class="model-font">{{model.name}}</div>
</div>
</el-space>
</div>
<div class="model">
<div>
<el-text tag="b">参考图</el-text>
</div>
<el-space wrap class="model-list">
<UploadImg v-model="referImage" height="80px" width="80px" />
<UploadImg v-model="referImageUrl" height="120px" width="120px" />
</el-space>
</div>
<div class="btns">
<!-- <el-button size="large" round>重置内容</el-button>-->
<el-button type="primary" size="large" round @click="handleGenerateImage">生成内容</el-button>
<el-button type="primary" size="large" round @click="handleGenerateImage">
{{ drawIn ? '生成中' : '生成内容' }}
</el-button>
</div>
</template>
<script setup lang="ts">
import { ImageApi, ImageMidjourneyImagineReqVO, ImageVO } from '@/api/ai/image'
import {
AiPlatformEnum,
ImageHotWords,
ImageSizeVO,
ImageModelVO,
MidjourneyModels,
MidjourneySizeList,
MidjourneyVersions,
NijiVersionList
} from '@/views/ai/utils/constants'
// image
import {ImageApi, ImageMidjourneyImagineReqVO, ImageVO} from "@/api/ai/image";
// message
const message = useMessage()
// emits
const emits = defineEmits(['onDrawStart', 'onDrawComplete'])
interface ImageModelVO {
key: string
name: string
image: string
}
// image
interface ImageSizeVO {
key: string
style: string,
width: string,
height: string,
}
const message = useMessage() //
//
const prompt = ref<string>('') //
const referImage = ref<any>() //
const drawIn = ref<boolean>(false) //
const selectHotWord = ref<string>('') //
const hotWords = ref<string[]>(['中国旗袍', '古装美女', '卡通头像', '机甲战士', '童话小屋', '中国长城']) //
const selectModel = ref<string>('midjourney') //
const models = ref<ImageModelVO[]>([
{
key: 'midjourney',
name: 'MJ',
image: 'https://bigpt8.com/pc/_nuxt/mj.34a61377.png',
},
{
key: 'niji',
name: 'NIJI',
image: 'https://bigpt8.com/pc/_nuxt/nj.ca79b143.png',
},
]) //
const selectImageSize = ref<string>('1:1') // size
const imageSizeList = ref<ImageSizeVO[]>([
{
key: '1:1',
width: "1",
height: "1",
style: 'width: 30px; height: 30px;background-color: #dcdcdc;',
},
{
key: '3:4',
width: "3",
height: "4",
style: 'width: 30px; height: 40px;background-color: #dcdcdc;',
},
{
key: '4:3',
width: "4",
height: "3",
style: 'width: 40px; height: 30px;background-color: #dcdcdc;',
},
{
key: '9:16',
width: "9",
height: "16",
style: 'width: 30px; height: 50px;background-color: #dcdcdc;',
},
{
key: '16:9',
width: "16",
height: "9",
style: 'width: 50px; height: 30px;background-color: #dcdcdc;',
},
]) // size
// version
const midjourneyVersionList = ref<any>([
{
value: '6.0',
label: 'v6.0',
},
{
value: '5.2',
label: 'v5.2',
},
{
value: '5.1',
label: 'v5.1',
},
{
value: '5.0',
label: 'v5.0',
},
{
value: '4.0',
label: 'v4.0',
},
])
const nijiVersionList = ref<any>([
{
value: '5',
label: 'v5',
},
])
//
const prompt = ref<string>('') //
const referImageUrl = ref<any>() //
const selectModel = ref<string>('midjourney') //
const selectSize = ref<string>('1:1') // size
const selectVersion = ref<any>('6.0') // version
let versionList = ref<any>([]) // version
versionList.value = midjourneyVersionList.value // midjourney
const versionList = ref<any>(MidjourneyVersions) // version
const emits = defineEmits(['onDrawStart', 'onDrawComplete']) // emits
/** 热词 - click */
/** 选择热词 */
const handleHotWordClick = async (hotWord: string) => {
//
//
if (selectHotWord.value == hotWord) {
selectHotWord.value = ''
return
}
//
selectHotWord.value = hotWord
//
prompt.value = hotWord
//
selectHotWord.value = hotWord //
prompt.value = hotWord //
}
/** size - click */
/** 点击 size 尺寸 */
const handleSizeClick = async (imageSize: ImageSizeVO) => {
selectImageSize.value = imageSize.key
selectSize.value = imageSize.key
}
/** 模型 - click */
/** 点击 model 模型 */
const handleModelClick = async (model: ImageModelVO) => {
selectModel.value = model.key
if (model.key === 'niji') {
versionList.value = nijiVersionList.value // niji
versionList.value = NijiVersionList // niji
} else {
versionList.value = midjourneyVersionList.value // midjourney
versionList.value = MidjourneyVersions // midjourney
}
selectVersion.value = versionList.value[0].value
}
/** version - click */
const handleChangeVersion = async (version) => {
console.log('version', version)
}
/** 图片生产 */
/** 图片生成 */
const handleGenerateImage = async () => {
//
await message.confirm(`确认生成内容?`)
// todo @
try {
//
drawIn.value = true
//
emits('onDrawStart', selectModel.value)
emits('onDrawStart', AiPlatformEnum.MIDJOURNEY)
//
const imageSize = imageSizeList.value.find(item => selectImageSize.value === item.key) as ImageSizeVO
const imageSize = MidjourneySizeList.find(
(item) => selectSize.value === item.key
) as ImageSizeVO
const req = {
prompt: prompt.value,
model: selectModel.value,
width: imageSize.width,
height: imageSize.height,
version: selectVersion.value,
referImageUrl: referImage.value,
referImageUrl: referImageUrl.value
} as ImageMidjourneyImagineReqVO
await ImageApi.midjourneyImagine(req)
} finally {
//
emits('onDrawComplete', selectModel.value)
emits('onDrawComplete', AiPlatformEnum.MIDJOURNEY)
//
drawIn.value = false
}
}
/** 填充值 */
const settingValues = async (imageDetail: ImageVO) => {
const settingValues = async (detail: ImageVO) => {
//
prompt.value = imageDetail.prompt
prompt.value = detail.prompt
// image size
const imageSize = imageSizeList.value.find(item => item.key === `${imageDetail.width}:${imageDetail.height}`) as ImageSizeVO
selectImageSize.value = imageSize.key
const imageSize = MidjourneySizeList.find(
(item) => item.key === `${detail.width}:${detail.height}`
) as ImageSizeVO
selectSize.value = imageSize.key
//
const model = models.value.find(item => item.key === imageDetail.options?.model) as ImageModelVO
const model = MidjourneyModels.find((item) => item.key === detail.options?.model) as ImageModelVO
await handleModelClick(model)
//
selectVersion.value = versionList.value.find(item => item.value === imageDetail.options?.version).value
selectVersion.value = versionList.value.find(
(item) => item.value === detail.options?.version
).value
// image
referImage.value = imageDetail.options.referImageUrl
referImageUrl.value = detail.options.referImageUrl
}
/** 暴露组件方法 */
defineExpose({ settingValues })
</script>
<style scoped lang="scss">
//
.prompt {
}
@@ -354,7 +274,6 @@ defineExpose({ settingValues })
}
}
//
.image-size {
width: 100%;

View File

@@ -0,0 +1,272 @@
<!-- dall3 -->
<template>
<div class="prompt">
<el-text tag="b">画面描述</el-text>
<el-text tag="p">建议使用形容词+动词+风格的格式使用隔开</el-text>
<el-input
v-model="prompt"
maxlength="1024"
rows="5"
class="w-100% mt-15px"
input-style="border-radius: 7px;"
placeholder="例如:童话里的小屋应该是什么样子?"
show-word-limit
type="textarea"
/>
</div>
<div class="hot-words">
<div>
<el-text tag="b">随机热词</el-text>
</div>
<el-space wrap class="word-list">
<el-button
round
class="btn"
:type="selectHotWord === hotWord ? 'primary' : 'default'"
v-for="hotWord in ImageHotEnglishWords"
:key="hotWord"
@click="handleHotWordClick(hotWord)"
>
{{ hotWord }}
</el-button>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">采样方法</el-text>
</div>
<el-space wrap class="group-item-body">
<el-select v-model="sampler" placeholder="Select" size="large" class="!w-350px">
<el-option
v-for="item in StableDiffusionSamplers"
:key="item.key"
:label="item.name"
:value="item.key"
/>
</el-select>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">CLIP</el-text>
</div>
<el-space wrap class="group-item-body">
<el-select v-model="clipGuidancePreset" placeholder="Select" size="large" class="!w-350px">
<el-option
v-for="item in StableDiffusionClipGuidancePresets"
:key="item.key"
:label="item.name"
:value="item.key"
/>
</el-select>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">风格</el-text>
</div>
<el-space wrap class="group-item-body">
<el-select v-model="stylePreset" placeholder="Select" size="large" class="!w-350px">
<el-option
v-for="item in StableDiffusionStylePresets"
:key="item.key"
:label="item.name"
:value="item.key"
/>
</el-select>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">图片尺寸</el-text>
</div>
<el-space wrap class="group-item-body">
<el-input v-model="width" class="w-170px" placeholder="图片宽度" />
<el-input v-model="height" class="w-170px" placeholder="图片高度" />
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">迭代步数</el-text>
</div>
<el-space wrap class="group-item-body">
<el-input
v-model="steps"
type="number"
size="large"
class="!w-350px"
placeholder="Please input"
/>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">引导系数</el-text>
</div>
<el-space wrap class="group-item-body">
<el-input
v-model="scale"
type="number"
size="large"
class="!w-350px"
placeholder="Please input"
/>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">随机因子</el-text>
</div>
<el-space wrap class="group-item-body">
<el-input
v-model="seed"
type="number"
size="large"
class="!w-350px"
placeholder="Please input"
/>
</el-space>
</div>
<div class="btns">
<el-button type="primary" size="large" round :loading="drawIn" @click="handleGenerateImage">
{{ drawIn ? '生成中' : '生成内容' }}
</el-button>
</div>
</template>
<script setup lang="ts">
import { ImageApi, ImageDrawReqVO, ImageVO } from '@/api/ai/image'
import { hasChinese } from '@/views/ai/utils/utils'
import {
AiPlatformEnum,
ImageHotEnglishWords,
StableDiffusionClipGuidancePresets,
StableDiffusionSamplers,
StableDiffusionStylePresets
} from '@/views/ai/utils/constants'
const message = useMessage() // 消息弹窗
// 定义属性
const drawIn = ref<boolean>(false) // 生成中
const selectHotWord = ref<string>('') // 选中的热词
// 表单
const prompt = ref<string>('') // 提示词
const width = ref<number>(512) // 图片宽度
const height = ref<number>(512) // 图片高度
const sampler = ref<string>('DDIM') // 采样方法
const steps = ref<number>(20) // 迭代步数
const seed = ref<number>(42) // 控制生成图像的随机性
const scale = ref<number>(7.5) // 引导系数
const clipGuidancePreset = ref<string>('NONE') // 文本提示相匹配的图像(clip_guidance_preset) 简称 CLIP
const stylePreset = ref<string>('3d-model') // 风格
const emits = defineEmits(['onDrawStart', 'onDrawComplete']) // 定义 emits
/** 选择热词 */
const handleHotWordClick = async (hotWord: string) => {
// 情况一:取消选中
if (selectHotWord.value == hotWord) {
selectHotWord.value = ''
return
}
// 情况二:选中
selectHotWord.value = hotWord // 选中
prompt.value = hotWord // 替换提示词
}
/** 图片生成 */
const handleGenerateImage = async () => {
// 二次确认
if (hasChinese(prompt.value)) {
message.alert('暂不支持中文!')
return
}
await message.confirm(`确认生成内容?`)
try {
// 加载中
drawIn.value = true
// 回调
emits('onDrawStart', AiPlatformEnum.STABLE_DIFFUSION)
// 发送请求
const form = {
platform: AiPlatformEnum.STABLE_DIFFUSION,
model: 'stable-diffusion-v1-6',
prompt: prompt.value, // 提示词
width: width.value, // 图片宽度
height: height.value, // 图片高度
options: {
seed: seed.value, // 随机种子
steps: steps.value, // 图片生成步数
scale: scale.value, // 引导系数
sampler: sampler.value, // 采样算法
clipGuidancePreset: clipGuidancePreset.value, // 文本提示相匹配的图像 CLIP
stylePreset: stylePreset.value // 风格
}
} as ImageDrawReqVO
await ImageApi.drawImage(form)
} finally {
// 回调
emits('onDrawComplete', AiPlatformEnum.STABLE_DIFFUSION)
// 加载结束
drawIn.value = false
}
}
/** 填充值 */
const settingValues = async (detail: ImageVO) => {
prompt.value = detail.prompt
width.value = detail.width
height.value = detail.height
seed.value = detail.options?.seed
steps.value = detail.options?.steps
scale.value = detail.options?.scale
sampler.value = detail.options?.sampler
clipGuidancePreset.value = detail.options?.clipGuidancePreset
stylePreset.value = detail.options?.stylePreset
}
/** 暴露组件方法 */
defineExpose({ settingValues })
</script>
<style scoped lang="scss">
// 提示词
.prompt {
}
// 热词
.hot-words {
display: flex;
flex-direction: column;
margin-top: 30px;
.word-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: start;
margin-top: 15px;
.btn {
margin: 0;
}
}
}
// 模型
.group-item {
margin-top: 30px;
.group-item-body {
margin-top: 15px;
width: 100%;
}
}
.btns {
display: flex;
justify-content: center;
margin-top: 50px;
}
</style>

View File

@@ -12,10 +12,7 @@
@on-draw-start="handleDrawStart"
@on-draw-complete="handleDrawComplete"
/>
<Midjourney
v-if="selectPlatform === AiPlatformEnum.MIDJOURNEY"
ref="midjourneyRef"
/>
<Midjourney v-if="selectPlatform === AiPlatformEnum.MIDJOURNEY" ref="midjourneyRef" />
<StableDiffusion
v-if="selectPlatform === AiPlatformEnum.STABLE_DIFFUSION"
ref="stableDiffusionRef"
@@ -24,28 +21,26 @@
</div>
</div>
<div class="main">
<ImageTask ref="imageTaskRef" @on-regeneration="handleRegeneration" />
<ImageList ref="imageListRef" @on-regeneration="handleRegeneration" />
</div>
</div>
</template>
<script setup lang="ts">
// TODO @fan在整个挪到 /views/ai/image/index 目录。因为我想在 /views/ai/image/manager 做管理的功能,进行下区分!
import Dall3 from './dall3/index.vue'
import Midjourney from './midjourney/index.vue'
import StableDiffusion from './stable-diffusion/index.vue'
import ImageTask from './ImageTask.vue'
import ImageList from './components/ImageList.vue'
import { AiPlatformEnum } from '@/views/ai/utils/constants'
import {ImageVO} from "@/api/ai/image";
import { ImageVO } from '@/api/ai/image'
import Dall3 from './components/dall3/index.vue'
import Midjourney from './components/midjourney/index.vue'
import StableDiffusion from './components/stableDiffusion/index.vue'
const imageTaskRef = ref<any>() // image task ref
const dall3Ref = ref<any>() // openai ref
const imageListRef = ref<any>() // image 列表 ref
const dall3Ref = ref<any>() // dall3(openai) ref
const midjourneyRef = ref<any>() // midjourney ref
const stableDiffusionRef = ref<any>() // stable diffusion ref
// 定义属性
const selectPlatform = ref('StableDiffusion')
const selectPlatform = ref(AiPlatformEnum.MIDJOURNEY)
const platformOptions = [
{
label: 'DALL3 绘画',
@@ -61,35 +56,27 @@ const platformOptions = [
}
]
/** 绘画 - start */
const handleDrawStart = async (type) => {
/** 绘画 start */
const handleDrawStart = async (platform: string) => {}
/** 绘画 complete */
const handleDrawComplete = async (platform: string) => {
await imageListRef.value.getImageList()
}
/** 绘画 - complete */
const handleDrawComplete = async (type) => {
await imageTaskRef.value.getImageList()
}
/** 绘画 - 重新生成 */
const handleRegeneration = async (imageDetail: ImageVO) => {
/** 重新生成:将画图详情填充到对应平台 */
const handleRegeneration = async (image: ImageVO) => {
// 切换平台
selectPlatform.value = imageDetail.platform
console.log('切换平台', imageDetail.platform)
// 根据不同平台填充 imageDetail
if (imageDetail.platform === AiPlatformEnum.MIDJOURNEY) {
await nextTick(async () => {
midjourneyRef.value.settingValues(imageDetail)
})
} else if (imageDetail.platform === AiPlatformEnum.OPENAI) {
await nextTick(async () => {
dall3Ref.value.settingValues(imageDetail)
})
} else if (imageDetail.platform === AiPlatformEnum.STABLE_DIFFUSION) {
await nextTick(async () => {
stableDiffusionRef.value.settingValues(imageDetail)
})
selectPlatform.value = image.platform
// 根据不同平台填充 image
await nextTick()
if (image.platform === AiPlatformEnum.MIDJOURNEY) {
midjourneyRef.value.settingValues(image)
} else if (image.platform === AiPlatformEnum.OPENAI) {
dall3Ref.value.settingValues(image)
} else if (image.platform === AiPlatformEnum.STABLE_DIFFUSION) {
stableDiffusionRef.value.settingValues(image)
}
}
</script>

View File

@@ -1,437 +0,0 @@
<!-- dall3 -->
<template>
<div class="prompt">
<el-text tag="b">画面描述</el-text>
<el-text tag="p">建议使用形容词+动词+风格的格式使用隔开</el-text>
<!-- TODO @fanstyle 看看能不能哟 unocss 替代 -->
<el-input
v-model="prompt"
maxlength="1024"
rows="5"
style="width: 100%; margin-top: 15px"
input-style="border-radius: 7px;"
placeholder="例如:童话里的小屋应该是什么样子?"
show-word-limit
type="textarea"
/>
</div>
<div class="hot-words">
<div>
<el-text tag="b">随机热词</el-text>
</div>
<el-space wrap class="word-list">
<el-button
round
class="btn"
:type="selectHotWord === hotWord ? 'primary' : 'default'"
v-for="hotWord in hotWords"
:key="hotWord"
@click="handleHotWordClick(hotWord)"
>
{{ hotWord }}
</el-button>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">采样方法</el-text>
</div>
<el-space wrap class="group-item-body">
<el-select v-model="selectSampler" placeholder="Select" size="large" style="width: 350px">
<el-option v-for="item in sampler" :key="item.key" :label="item.name" :value="item.key" />
</el-select>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">CLIP</el-text>
</div>
<el-space wrap class="group-item-body">
<el-select
v-model="selectClipGuidancePreset"
placeholder="Select"
size="large"
style="width: 350px"
>
<el-option
v-for="item in clipGuidancePresets"
:key="item.key"
:label="item.name"
:value="item.key"
/>
</el-select>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">风格</el-text>
</div>
<el-space wrap class="group-item-body">
<el-select v-model="selectStylePreset" placeholder="Select" size="large" style="width: 350px">
<el-option
v-for="item in stylePresets"
:key="item.key"
:label="item.name"
:value="item.key"
/>
</el-select>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">图片尺寸</el-text>
</div>
<el-space wrap class="group-item-body">
<el-input v-model="imageWidth" style="width: 170px" placeholder="图片宽度" />
<el-input v-model="imageHeight" style="width: 170px" placeholder="图片高度" />
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">迭代步数</el-text>
</div>
<el-space wrap class="group-item-body">
<el-input
v-model="steps"
type="number"
size="large"
style="width: 350px"
placeholder="Please input"
/>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">引导系数</el-text>
</div>
<el-space wrap class="group-item-body">
<el-input
v-model="scale"
type="number"
size="large"
style="width: 350px"
placeholder="Please input"
/>
</el-space>
</div>
<div class="group-item">
<div>
<el-text tag="b">随机因子</el-text>
</div>
<el-space wrap class="group-item-body">
<el-input
v-model="seed"
type="number"
size="large"
style="width: 350px"
placeholder="Please input"
/>
</el-space>
</div>
<div class="btns">
<el-button type="primary" size="large" round :loading="drawIn" @click="handleGenerateImage">
{{ drawIn ? '生成中' : '生成内容' }}
</el-button>
</div>
</template>
<script setup lang="ts">
import { ImageApi, ImageDrawReqVO, ImageVO } from '@/api/ai/image'
import { hasChinese } from '@/views/ai/utils/utils'
// image 模型
interface ImageModelVO {
key: string
name: string
}
// 定义属性
const prompt = ref<string>('') // 提示词
const drawIn = ref<boolean>(false) // 生成中
const selectHotWord = ref<string>('') // 选中的热词
const imageWidth = ref<number>(512) // 图片宽度
const imageHeight = ref<number>(512) // 图片高度
const hotWords = ref<string[]>([
'中国旗袍',
'古装美女',
'卡通头像',
'机甲战士',
'童话小屋',
'中国长城'
]) // 热词
// message
const message = useMessage()
// 采样方法
const selectSampler = ref<string>('DDIM') // 模型
// DDIM DDPM K_DPMPP_2M K_DPMPP_2S_ANCESTRAL K_DPM_2 K_DPM_2_ANCESTRAL K_EULER K_EULER_ANCESTRAL K_HEUN K_LMS
const sampler = ref<ImageModelVO[]>([
{
key: 'DDIM',
name: 'DDIM'
},
{
key: 'DDPM',
name: 'DDPM'
},
{
key: 'K_DPMPP_2M',
name: 'K_DPMPP_2M'
},
{
key: 'K_DPMPP_2S_ANCESTRAL',
name: 'K_DPMPP_2S_ANCESTRAL'
},
{
key: 'K_DPM_2',
name: 'K_DPM_2'
},
{
key: 'K_DPM_2_ANCESTRAL',
name: 'K_DPM_2_ANCESTRAL'
},
{
key: 'K_EULER',
name: 'K_EULER'
},
{
key: 'K_EULER_ANCESTRAL',
name: 'K_EULER_ANCESTRAL'
},
{
key: 'K_HEUN',
name: 'K_HEUN'
},
{
key: 'K_LMS',
name: 'K_LMS'
}
])
// 风格
// 3d-model analog-film anime cinematic comic-book digital-art enhance fantasy-art isometric
// line-art low-poly modeling-compound neon-punk origami photographic pixel-art tile-texture
const selectStylePreset = ref<string>('3d-model') // 模型
const stylePresets = ref<ImageModelVO[]>([
{
key: '3d-model',
name: '3d-model'
},
{
key: 'analog-film',
name: 'analog-film'
},
{
key: 'anime',
name: 'anime'
},
{
key: 'cinematic',
name: 'cinematic'
},
{
key: 'comic-book',
name: 'comic-book'
},
{
key: 'digital-art',
name: 'digital-art'
},
{
key: 'enhance',
name: 'enhance'
},
{
key: 'fantasy-art',
name: 'fantasy-art'
},
{
key: 'isometric',
name: 'isometric'
},
{
key: 'line-art',
name: 'line-art'
},
{
key: 'low-poly',
name: 'low-poly'
},
{
key: 'modeling-compound',
name: 'modeling-compound'
},
// neon-punk origami photographic pixel-art tile-texture
{
key: 'neon-punk',
name: 'neon-punk'
},
{
key: 'origami',
name: 'origami'
},
{
key: 'photographic',
name: 'photographic'
},
{
key: 'pixel-art',
name: 'pixel-art'
},
{
key: 'tile-texture',
name: 'tile-texture'
}
])
// 文本提示相匹配的图像(clip_guidance_preset) 简称 CLIP
// https://platform.stability.ai/docs/api-reference#tag/SDXL-and-SD1.6/operation/textToImage
// FAST_BLUE FAST_GREEN NONE SIMPLE SLOW SLOWER SLOWEST
const selectClipGuidancePreset = ref<string>('NONE') // 模型
const clipGuidancePresets = ref<ImageModelVO[]>([
{
key: 'NONE',
name: 'NONE'
},
{
key: 'FAST_BLUE',
name: 'FAST_BLUE'
},
{
key: 'FAST_GREEN',
name: 'FAST_GREEN'
},
{
key: 'SIMPLE',
name: 'SIMPLE'
},
{
key: 'SLOW',
name: 'SLOW'
},
{
key: 'SLOWER',
name: 'SLOWER'
},
{
key: 'SLOWEST',
name: 'SLOWEST'
}
])
const steps = ref<number>(20) // 迭代步数
const seed = ref<number>(42) // 控制生成图像的随机性
const scale = ref<number>(7.5) // 引导系数
// 定义 Props
const props = defineProps({})
// 定义 emits
const emits = defineEmits(['onDrawStart', 'onDrawComplete'])
/** 热词 - click */
const handleHotWordClick = async (hotWord: string) => {
// 取消选中
if (selectHotWord.value == hotWord) {
selectHotWord.value = ''
return
}
// 选中
selectHotWord.value = hotWord
// 替换提示词
prompt.value = hotWord
}
/** 图片生产 */
const handleGenerateImage = async () => {
// 二次确认
await message.confirm(`确认生成内容?`)
if (await hasChinese(prompt.value)) {
message.alert('暂不支持中文!')
return
}
try {
// 加载中
drawIn.value = true
// 回调
emits('onDrawStart', 'StableDiffusion')
// 发送请求
const form = {
platform: 'StableDiffusion',
model: 'stable-diffusion-v1-6',
prompt: prompt.value, // 提示词
width: imageWidth.value, // 图片宽度
height: imageHeight.value, // 图片高度
options: {
seed: seed.value, // 随机种子
steps: steps.value, // 图片生成步数
scale: scale.value, // 引导系数
sampler: selectSampler.value, // 采样算法
clipGuidancePreset: selectClipGuidancePreset.value, // 文本提示相匹配的图像 CLIP
stylePreset: selectStylePreset.value // 风格
}
} as ImageDrawReqVO
await ImageApi.drawImage(form)
} finally {
// 回调
emits('onDrawComplete', 'StableDiffusion')
// 加载结束
drawIn.value = false
}
}
/** 填充值 */
const settingValues = async (imageDetail: ImageVO) => {
prompt.value = imageDetail.prompt
imageWidth.value = imageDetail.width
imageHeight.value = imageDetail.height
seed.value = imageDetail.options?.seed
steps.value = imageDetail.options?.steps
scale.value = imageDetail.options?.scale
selectSampler.value = imageDetail.options?.sampler
selectClipGuidancePreset.value = imageDetail.options?.clipGuidancePreset
selectStylePreset.value = imageDetail.options?.stylePreset
}
/** 暴露组件方法 */
defineExpose({ settingValues })
</script>
<style scoped lang="scss">
// 提示词
.prompt {
}
// 热词
.hot-words {
display: flex;
flex-direction: column;
margin-top: 30px;
.word-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: start;
margin-top: 15px;
.btn {
margin: 0;
}
}
}
// 模型
.group-item {
margin-top: 30px;
.group-item-body {
margin-top: 15px;
width: 100%;
}
}
.btns {
display: flex;
justify-content: center;
margin-top: 50px;
}
</style>

View File

@@ -13,7 +13,7 @@
<el-form-item label="角色头像" prop="avatar">
<UploadImg v-model="formData.avatar" height="60px" width="60px" />
</el-form-item>
<el-form-item label="绑定模型" prop="modelId" v-if="!isUser(formType)">
<el-form-item label="绑定模型" prop="modelId" v-if="!isUser">
<el-select v-model="formData.modelId" placeholder="请选择模型" clearable>
<el-option
v-for="chatModel in chatModelList"
@@ -23,7 +23,7 @@
/>
</el-select>
</el-form-item>
<el-form-item label="角色类别" prop="category" v-if="!isUser(formType)">
<el-form-item label="角色类别" prop="category" v-if="!isUser">
<el-input v-model="formData.category" placeholder="请输入角色类别" />
</el-form-item>
<el-form-item label="角色描述" prop="description">
@@ -32,7 +32,7 @@
<el-form-item label="角色设定" prop="systemMessage">
<el-input type="textarea" v-model="formData.systemMessage" placeholder="请输入角色设定" />
</el-form-item>
<el-form-item label="是否公开" prop="publicStatus" v-if="!isUser(formType)">
<el-form-item label="是否公开" prop="publicStatus" v-if="!isUser">
<el-radio-group v-model="formData.publicStatus">
<el-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
@@ -43,10 +43,10 @@
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="角色排序" prop="sort" v-if="!isUser(formType)">
<el-form-item label="角色排序" prop="sort" v-if="!isUser">
<el-input-number v-model="formData.sort" placeholder="请输入角色排序" class="!w-1/1" />
</el-form-item>
<el-form-item label="开启状态" prop="status" v-if="!isUser(formType)">
<el-form-item label="开启状态" prop="status" v-if="!isUser">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
@@ -97,10 +97,9 @@ const formRef = ref() // 表单 Ref
const chatModelList = ref([] as ChatModelVO[]) // 聊天模型列表
/** 是否【我】自己创建,私有角色 */
// TODO @fan建议改成计算函数 computed
const isUser = (type: string) => {
return type === 'my-create' || type === 'my-update'
}
const isUser = computed(() => {
return formType.value === 'my-create' || formType.value === 'my-update'
})
// TODO @fan直接使用 formRules只要隐藏掉的字段它是不会校验的哈
const getFormRules = async (type: string) => {

View File

@@ -48,3 +48,308 @@ export enum AiWriteTypeEnum {
WRITING = 1, // 撰写
REPLY // 回复
}
// ========== 【图片 UI】相关的枚举 ==========
export const ImageHotWords = [
'中国旗袍',
'古装美女',
'卡通头像',
'机甲战士',
'童话小屋',
'中国长城'
] // 图片热词
export const ImageHotEnglishWords = [
'Chinese Cheongsam',
'Ancient Beauty',
'Cartoon Avatar',
'Mech Warrior',
'Fairy Tale Cottage',
'The Great Wall of China'
] // 图片热词(英文)
export interface ImageModelVO {
key: string
name: string
image?: string
}
export const StableDiffusionSamplers: ImageModelVO[] = [
{
key: 'DDIM',
name: 'DDIM'
},
{
key: 'DDPM',
name: 'DDPM'
},
{
key: 'K_DPMPP_2M',
name: 'K_DPMPP_2M'
},
{
key: 'K_DPMPP_2S_ANCESTRAL',
name: 'K_DPMPP_2S_ANCESTRAL'
},
{
key: 'K_DPM_2',
name: 'K_DPM_2'
},
{
key: 'K_DPM_2_ANCESTRAL',
name: 'K_DPM_2_ANCESTRAL'
},
{
key: 'K_EULER',
name: 'K_EULER'
},
{
key: 'K_EULER_ANCESTRAL',
name: 'K_EULER_ANCESTRAL'
},
{
key: 'K_HEUN',
name: 'K_HEUN'
},
{
key: 'K_LMS',
name: 'K_LMS'
}
]
export const StableDiffusionStylePresets: ImageModelVO[] = [
{
key: '3d-model',
name: '3d-model'
},
{
key: 'analog-film',
name: 'analog-film'
},
{
key: 'anime',
name: 'anime'
},
{
key: 'cinematic',
name: 'cinematic'
},
{
key: 'comic-book',
name: 'comic-book'
},
{
key: 'digital-art',
name: 'digital-art'
},
{
key: 'enhance',
name: 'enhance'
},
{
key: 'fantasy-art',
name: 'fantasy-art'
},
{
key: 'isometric',
name: 'isometric'
},
{
key: 'line-art',
name: 'line-art'
},
{
key: 'low-poly',
name: 'low-poly'
},
{
key: 'modeling-compound',
name: 'modeling-compound'
},
// neon-punk origami photographic pixel-art tile-texture
{
key: 'neon-punk',
name: 'neon-punk'
},
{
key: 'origami',
name: 'origami'
},
{
key: 'photographic',
name: 'photographic'
},
{
key: 'pixel-art',
name: 'pixel-art'
},
{
key: 'tile-texture',
name: 'tile-texture'
}
]
export const StableDiffusionClipGuidancePresets: ImageModelVO[] = [
{
key: 'NONE',
name: 'NONE'
},
{
key: 'FAST_BLUE',
name: 'FAST_BLUE'
},
{
key: 'FAST_GREEN',
name: 'FAST_GREEN'
},
{
key: 'SIMPLE',
name: 'SIMPLE'
},
{
key: 'SLOW',
name: 'SLOW'
},
{
key: 'SLOWER',
name: 'SLOWER'
},
{
key: 'SLOWEST',
name: 'SLOWEST'
}
]
export const Dall3Models: ImageModelVO[] = [
{
key: 'dall-e-3',
name: 'DALL·E 3',
image: `/src/assets/ai/dall2.jpg`
},
{
key: 'dall-e-2',
name: 'DALL·E 2',
image: `/src/assets/ai/dall3.jpg`
}
]
export const Dall3StyleList: ImageModelVO[] = [
{
key: 'vivid',
name: '清晰',
image: `/src/assets/ai/qingxi.jpg`
},
{
key: 'natural',
name: '自然',
image: `/src/assets/ai/ziran.jpg`
}
]
export interface ImageSizeVO {
key: string
name: string
style: string
width: string
height: string
}
export const Dall3SizeList: ImageSizeVO[] = [
{
key: '1024x1024',
name: '1:1',
width: '1024',
height: '1024',
style: 'width: 30px; height: 30px;background-color: #dcdcdc;'
},
{
key: '1024x1792',
name: '3:5',
width: '1024',
height: '1792',
style: 'width: 30px; height: 50px;background-color: #dcdcdc;'
},
{
key: '1792x1024',
name: '5:3',
width: '1792',
height: '1024',
style: 'width: 50px; height: 30px;background-color: #dcdcdc;'
}
]
export const MidjourneyModels: ImageModelVO[] = [
{
key: 'midjourney',
name: 'MJ',
image: 'https://bigpt8.com/pc/_nuxt/mj.34a61377.png'
},
{
key: 'niji',
name: 'NIJI',
image: 'https://bigpt8.com/pc/_nuxt/nj.ca79b143.png'
}
]
export const MidjourneySizeList: ImageSizeVO[] = [
{
key: '1:1',
width: '1',
height: '1',
style: 'width: 30px; height: 30px;background-color: #dcdcdc;'
},
{
key: '3:4',
width: '3',
height: '4',
style: 'width: 30px; height: 40px;background-color: #dcdcdc;'
},
{
key: '4:3',
width: '4',
height: '3',
style: 'width: 40px; height: 30px;background-color: #dcdcdc;'
},
{
key: '9:16',
width: '9',
height: '16',
style: 'width: 30px; height: 50px;background-color: #dcdcdc;'
},
{
key: '16:9',
width: '16',
height: '9',
style: 'width: 50px; height: 30px;background-color: #dcdcdc;'
}
]
export const MidjourneyVersions = [
{
value: '6.0',
label: 'v6.0'
},
{
value: '5.2',
label: 'v5.2'
},
{
value: '5.1',
label: 'v5.1'
},
{
value: '5.0',
label: 'v5.0'
},
{
value: '4.0',
label: 'v4.0'
}
]
export const NijiVersionList = [
{
value: '5',
label: 'v5'
}
]

View File

@@ -8,7 +8,7 @@
*/
/** 判断字符串是否包含中文 */
export const hasChinese = async (str) => {
export const hasChinese = (str: string) => {
return /[\u4e00-\u9fa5]/.test(str)
}