Commit d3c596dc by puhui999

Merge remote-tracking branch 'refs/remotes/yudao/dev' into dev-crm

parents 334c0241 5fd81777
......@@ -43,8 +43,8 @@ export const ChatConversationApi = {
},
// 删除【我的】所有对话,置顶除外
deleteMyAllExceptPinned: async () => {
return await request.delete({ url: `/ai/chat/conversation/delete-my-all-except-pinned` })
deleteChatConversationMyByUnpinned: async () => {
return await request.delete({ url: `/ai/chat/conversation/delete-by-unpinned` })
},
// 获得【我的】聊天对话列表
......
......@@ -16,6 +16,7 @@ export interface ImageVO {
taskId: number // 任务编号
buttons: ImageMjButtonsVO[] // mj 操作按钮
createTime: string // 创建时间
finishTime: string // 完成时间
}
export interface ImagePageReqVO {
......
......@@ -29,10 +29,29 @@ const download = {
html: (data: Blob, fileName: string) => {
download0(data, fileName, 'text/html')
},
// 下载 MarkdownView 方法
// 下载 Markdown 方法
markdown: (data: Blob, fileName: string) => {
download0(data, fileName, 'text/markdown')
}
}
export default download
/** 图片下载(通过浏览器图片下载) */
export const downloadImage = async (imageUrl) => {
const image = new Image()
image.setAttribute('crossOrigin', 'anonymous')
image.src = imageUrl
image.onload = () => {
const canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
const ctx = canvas.getContext('2d') as CanvasDrawImage
ctx.drawImage(image, 0, 0, image.width, image.height)
const url = canvas.toDataURL('image/png')
const a = document.createElement('a')
a.href = url
a.download = 'image.png'
a.click()
}
}
......@@ -2,7 +2,7 @@
<el-drawer
v-model="showDrawer"
title="图片详细"
@close="handlerDrawerClose"
@close="handleDrawerClose"
custom-class="drawer-class"
>
<!-- 图片 -->
......@@ -22,8 +22,7 @@
<div class="tip">时间</div>
<div class="body">
<div>提交时间:{{ imageDetail.createTime }}</div>
<!-- TODO @fan:要不加个完成时间的字段 finishTime?updateTime 不算特别合理哈 -->
<div>生成时间:{{ imageDetail.updateTime }}</div>
<div>生成时间:{{ imageDetail.finishTime }}</div>
</div>
</div>
<!-- 模型 -->
......@@ -79,8 +78,8 @@ const props = defineProps({
})
/** 抽屉 - close */
const handlerDrawerClose = async () => {
emits('handlerDrawerClose')
const handleDrawerClose = async () => {
emits('handleDrawerClose')
}
/** 获取 - 图片 detail */
......@@ -90,7 +89,7 @@ const getImageDetail = async (id) => {
}
/** 任务 - detail */
const handlerTaskDetail = async () => {
const handleTaskDetail = async () => {
showDrawer.value = true
}
......@@ -107,7 +106,7 @@ watch(id, async (newVal, oldVal) => {
}
})
//
const emits = defineEmits(['handlerDrawerClose'])
const emits = defineEmits(['handleDrawerClose'])
//
onMounted(async () => {})
</script>
......
......@@ -6,8 +6,8 @@
v-for="image in imageList"
:key="image"
:image-detail="image"
@on-btn-click="handlerImageBtnClick"
@on-mj-btn-click="handlerImageMjBtnClick"
@on-btn-click="handleImageBtnClick"
@on-mj-btn-click="handleImageMjBtnClick"
/>
</div>
<div class="task-image-pagination">
......@@ -16,7 +16,7 @@
layout="prev, pager, next"
:default-page-size="pageSize"
:total="pageTotal"
@change="handlerPageChange"
@change="handlePageChange"
/>
</div>
</el-card>
......@@ -24,7 +24,7 @@
<ImageDetailDrawer
:show="isShowImageDetail"
:id="showImageDetailId"
@handler-drawer-close="handlerDrawerClose"
@handle-drawer-close="handleDrawerClose"
/>
</template>
<script setup lang="ts">
......@@ -33,6 +33,7 @@ import ImageDetailDrawer from './ImageDetailDrawer.vue'
import ImageTaskCard from './ImageTaskCard.vue'
import { ElLoading, LoadingOptionsResolved } from 'element-plus'
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
import { downloadImage } from '@/utils/download'
const message = useMessage() // 消息弹窗
......@@ -49,12 +50,12 @@ const pageSize = ref<number>(10) // page size
const pageTotal = ref<number>(0) // page size
/** 抽屉 - close */
const handlerDrawerClose = async () => {
const handleDrawerClose = async () => {
isShowImageDetail.value = false
}
/** 任务 - detail */
const handlerDrawerOpen = async () => {
const handleDrawerOpen = async () => {
isShowImageDetail.value = true
}
......@@ -117,12 +118,12 @@ const refreshWatchImages = async () => {
}
/** 图片 - btn click */
const handlerImageBtnClick = async (type: string, imageDetail: ImageVO) => {
const handleImageBtnClick = async (type: string, imageDetail: ImageVO) => {
// 获取 image detail id
showImageDetailId.value = imageDetail.id
// 处理不用 btn
if (type === 'more') {
await handlerDrawerOpen()
await handleDrawerOpen()
} else if (type === 'delete') {
await message.confirm(`是否删除照片?`)
await ImageApi.deleteImageMy(imageDetail.id)
......@@ -130,11 +131,15 @@ const handlerImageBtnClick = async (type: string, imageDetail: ImageVO) => {
message.success('删除成功!')
} else if (type === 'download') {
await downloadImage(imageDetail.picUrl)
} else if (type === 'regeneration') {
// Midjourney 平台
console.log('regeneration', imageDetail.id)
await emits('onRegeneration', imageDetail)
}
}
/** 图片 - mj btn click */
const handlerImageMjBtnClick = async (button: ImageMjButtonsVO, imageDetail: ImageVO) => {
const handleImageMjBtnClick = async (button: ImageMjButtonsVO, imageDetail: ImageVO) => {
// 1、构建 params 参数
const data = {
id: imageDetail.id,
......@@ -146,28 +151,8 @@ const handlerImageMjBtnClick = async (button: ImageMjButtonsVO, imageDetail: Ima
await getImageList()
}
/** 下载 - image */
// TODO @fan:貌似可以考虑抽到 download 里面,作为一个方法
const downloadImage = async (imageUrl) => {
const image = new Image()
image.setAttribute('crossOrigin', 'anonymous')
image.src = imageUrl
image.onload = () => {
const canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
const ctx = canvas.getContext('2d') as CanvasDrawImage
ctx.drawImage(image, 0, 0, image.width, image.height)
const url = canvas.toDataURL('image/png')
const a = document.createElement('a')
a.href = url
a.download = 'image.png'
a.click()
}
}
// page change
const handlerPageChange = async (page) => {
const handlePageChange = async (page) => {
pageNo.value = page
await getImageList(false)
}
......@@ -175,6 +160,9 @@ const handlerPageChange = async (page) => {
/** 暴露组件方法 */
defineExpose({ getImageList })
// emits
const emits = defineEmits(['onRegeneration'])
/** 组件挂在的时候 */
onMounted(async () => {
// 获取 image 列表
......
......@@ -17,21 +17,26 @@
异常
</el-button>
</div>
<!-- TODO @fan:1)按钮要不调整成详情、下载、再次生成、删除?;2)如果是再次生成,就把当前的参数填写到左侧的框框里? -->
<div>
<el-button
class="btn"
text
:icon="Download"
@click="handlerBtnClick('download', imageDetail)"
@click="handleBtnClick('download', imageDetail)"
/>
<el-button
class="btn"
text
:icon="RefreshRight"
@click="handleBtnClick('regeneration', imageDetail)"
/>
<el-button
class="btn"
text
:icon="Delete"
@click="handlerBtnClick('delete', imageDetail)"
@click="handleBtnClick('delete', imageDetail)"
/>
<el-button class="btn" text :icon="More" @click="handlerBtnClick('more', imageDetail)" />
<el-button class="btn" text :icon="More" @click="handleBtnClick('more', imageDetail)" />
</div>
</div>
<div class="image-wrapper" ref="cardImageRef">
......@@ -48,7 +53,7 @@
v-for="button in imageDetail?.buttons"
:key="button"
style="min-width: 40px; margin-left: 0; margin-right: 10px; margin-top: 5px"
@click="handlerMjBtnClick(button)"
@click="handleMjBtnClick(button)"
>
{{ button.label }}{{ button.emoji }}
</el-button>
......@@ -56,10 +61,10 @@
</el-card>
</template>
<script setup lang="ts">
import { Delete, Download, More } from '@element-plus/icons-vue'
import {Delete, Download, More, RefreshRight} from '@element-plus/icons-vue'
import { ImageVO, ImageMjButtonsVO } from '@/api/ai/image'
import { PropType } from 'vue'
import { ElLoading } from 'element-plus'
import {ElLoading, LoadingOptionsResolved} from 'element-plus'
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
const cardImageRef = ref<any>() // 卡片 image ref
......@@ -73,17 +78,17 @@ const props = defineProps({
})
/** 按钮 - 点击事件 */
const handlerBtnClick = async (type, imageDetail: ImageVO) => {
const handleBtnClick = async (type, imageDetail: ImageVO) => {
emits('onBtnClick', type, imageDetail)
}
const handlerLoading = async (status: number) => {
// TODO @fan:这个搞成 Loading 组件,然后通过数据驱动,这样搞可以哇?
const handleLoading = async (status: number) => {
// TODO @芋艿:这个搞成 Loading 组件,然后通过数据驱动,这样搞可以哇?
if (status === AiImageStatusEnum.IN_PROGRESS) {
cardImageLoadingInstance.value = ElLoading.service({
target: cardImageRef.value,
text: '生成中...'
})
} as LoadingOptionsResolved)
} else {
if (cardImageLoadingInstance.value) {
cardImageLoadingInstance.value.close()
......@@ -93,7 +98,7 @@ const handlerLoading = async (status: number) => {
}
/** mj 按钮 click */
const handlerMjBtnClick = async (button: ImageMjButtonsVO) => {
const handleMjBtnClick = async (button: ImageMjButtonsVO) => {
// 确认窗体
await message.confirm(`确认操作 "${button.label} ${button.emoji}" ?`)
emits('onMjBtnClick', button, props.imageDetail)
......@@ -102,7 +107,7 @@ const handlerMjBtnClick = async (button: ImageMjButtonsVO) => {
// watch
const { imageDetail } = toRefs(props)
watch(imageDetail, async (newVal, oldVal) => {
await handlerLoading(newVal.status as string)
await handleLoading(newVal.status as string)
})
// emits
......@@ -110,7 +115,7 @@ const emits = defineEmits(['onBtnClick', 'onMjBtnClick'])
//
onMounted(async () => {
await handlerLoading(props.imageDetail.status as string)
await handleLoading(props.imageDetail.status as string)
})
</script>
......
......@@ -25,7 +25,7 @@
:type="(selectHotWord === hotWord ? 'primary' : 'default')"
v-for="hotWord in hotWords"
:key="hotWord"
@click="handlerHotWordClick(hotWord)"
@click="handleHotWordClick(hotWord)"
>
{{ hotWord }}
</el-button>
......@@ -37,7 +37,7 @@
</div>
<el-space wrap class="model-list">
<div
:class="selectModel === model ? 'modal-item selectModel' : 'modal-item'"
:class="selectModel === model.key ? 'modal-item selectModel' : 'modal-item'"
v-for="model in models"
:key="model.key"
......@@ -45,7 +45,7 @@
<el-image
:src="model.image"
fit="contain"
@click="handlerModelClick(model)"
@click="handleModelClick(model)"
/>
<div class="model-font">{{model.name}}</div>
</div>
......@@ -57,14 +57,14 @@
</div>
<el-space wrap class="image-style-list">
<div
:class="selectImageStyle === imageStyle ? 'image-style-item selectImageStyle' : 'image-style-item'"
:class="selectImageStyle === imageStyle.key ? 'image-style-item selectImageStyle' : 'image-style-item'"
v-for="imageStyle in imageStyleList"
:key="imageStyle.key"
>
<el-image
:src="imageStyle.image"
fit="contain"
@click="handlerStyleClick(imageStyle)"
@click="handleStyleClick(imageStyle)"
/>
<div class="style-font">{{imageStyle.name}}</div>
</div>
......@@ -78,8 +78,8 @@
<div class="size-item"
v-for="imageSize in imageSizeList"
:key="imageSize.key"
@click="handlerSizeClick(imageSize)">
<div :class="selectImageSize === imageSize ? 'size-wrapper selectImageSize' : 'size-wrapper'">
@click="handleSizeClick(imageSize)">
<div :class="selectImageSize === imageSize.key ? 'size-wrapper selectImageSize' : 'size-wrapper'">
<div :style="imageSize.style"></div>
</div>
<div class="size-font">{{ imageSize.name }}</div>
......@@ -91,13 +91,13 @@
size="large"
round
:loading="drawIn"
@click="handlerGenerateImage">
@click="handleGenerateImage">
{{drawIn ? '生成中' : '生成内容'}}
</el-button>
</div>
</template>
<script setup lang="ts">
import {ImageApi, ImageDrawReqVO} from '@/api/ai/image';
import {ImageApi, ImageDrawReqVO, ImageVO} from '@/api/ai/image';
// image 模型
interface ImageModelVO {
......@@ -120,42 +120,38 @@ const prompt = ref<string>('') // 提示词
const drawIn = ref<boolean>(false) // 生成中
const selectHotWord = ref<string>('') // 选中的热词
const hotWords = ref<string[]>(['中国旗袍', '古装美女', '卡通头像', '机甲战士', '童话小屋', '中国长城']) // 热词
const selectModel = ref<any>({}) // 模型
const selectModel = ref<string>('dall-e-3') // 模型
// message
const message = useMessage()
// TODO @fan:image 改成项目里自己的哈
// TODO @fan:这个 image,要不看看网上有没合适的图片,作为占位符,啊哈哈
const models = ref<ImageModelVO[]>([
{
key: 'dall-e-3',
name: 'DALL·E 3',
image: 'https://h5.cxyhub.com/images/model_2.png',
image: `/src/assets/ai/dall2.jpg`,
},
{
key: 'dall-e-2',
name: 'DALL·E 2',
image: 'https://h5.cxyhub.com/images/model_1.png',
image: `/src/assets/ai/dall3.jpg`,
},
]) // 模型
selectModel.value = models.value[0]
const selectImageStyle = ref<any>({}) // style 样式
// TODO @fan:image 改成项目里自己的哈
const selectImageStyle = ref<string>('vivid') // style 样式
const imageStyleList = ref<ImageModelVO[]>([
{
key: 'vivid',
name: '清晰',
image: 'https://h5.cxyhub.com/images/model_1.png',
image: `/src/assets/ai/qingxi.jpg`,
},
{
key: 'natural',
name: '自然',
image: 'https://h5.cxyhub.com/images/model_2.png',
image: `/src/assets/ai/ziran.jpg`,
},
]) // style
selectImageStyle.value = imageStyleList.value[0]
const selectImageSize = ref<ImageSizeVO>({} as ImageSizeVO) // 选中 size
const selectImageSize = ref<string>('1024x1024') // 选中 size
const imageSizeList = ref<ImageSizeVO[]>([
{
key: '1024x1024',
......@@ -179,17 +175,14 @@ const imageSizeList = ref<ImageSizeVO[]>([
style: 'width: 50px; height: 30px;background-color: #dcdcdc;',
}
]) // size
selectImageSize.value = imageSizeList.value[0]
// 定义 Props
const props = defineProps({})
// 定义 emits
const emits = defineEmits(['onDrawStart', 'onDrawComplete'])
// TODO @fan:如果是简单注释,建议用 /** */,主要是现在项目里是这种风格哈,保持一致好点~
// TODO @fan:handler 应该改成 handle 哈
/** 热词 - click */
const handlerHotWordClick = async (hotWord: string) => {
const handleHotWordClick = async (hotWord: string) => {
// 取消选中
if (selectHotWord.value == hotWord) {
selectHotWord.value = ''
......@@ -202,64 +195,66 @@ const handlerHotWordClick = async (hotWord: string) => {
}
/** 模型 - click */
const handlerModelClick = async (model: ImageModelVO) => {
if (selectModel.value === model) {
selectModel.value = {} as ImageModelVO
return
}
selectModel.value = model
const handleModelClick = async (model: ImageModelVO) => {
selectModel.value = model.key
}
/** 样式 - click */
const handlerStyleClick = async (imageStyle: ImageModelVO) => {
if (selectImageStyle.value === imageStyle) {
selectImageStyle.value = {} as ImageModelVO
return
}
selectImageStyle.value = imageStyle
const handleStyleClick = async (imageStyle: ImageModelVO) => {
selectImageStyle.value = imageStyle.key
}
/** size - click */
const handlerSizeClick = async (imageSize: ImageSizeVO) => {
if (selectImageSize.value === imageSize) {
selectImageSize.value = {} as ImageSizeVO
return
}
selectImageSize.value = imageSize
const handleSizeClick = async (imageSize: ImageSizeVO) => {
selectImageSize.value = imageSize.key
}
/** 图片生产 */
const handlerGenerateImage = async () => {
const handleGenerateImage = async () => {
// 二次确认
await message.confirm(`确认生成内容?`)
try {
// 加载中
drawIn.value = true
// 回调
emits('onDrawStart', selectModel.value.key)
emits('onDrawStart', selectModel.value)
const imageSize = imageSizeList.value.find(item => item.key === selectImageSize.value) as ImageSizeVO
const form = {
platform: 'OpenAI',
prompt: prompt.value, // 提示词
model: selectModel.value.key, // 模型
width: selectImageSize.value.width, // size 不能为空
height: selectImageSize.value.height, // size 不能为空
model: selectModel.value, // 模型
width: imageSize.width, // size 不能为空
height: imageSize.height, // size 不能为空
options: {
style: selectImageStyle.value.key, // 图像生成的风格
style: selectImageStyle.value, // 图像生成的风格
}
} as ImageDrawReqVO
// 发送请求
await ImageApi.drawImage(form)
} finally {
// 回调
emits('onDrawComplete', selectModel.value.key)
emits('onDrawComplete', selectModel.value)
// 加载结束
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
await handleSizeClick(imageSize)
}
/** 暴露组件方法 */
defineExpose({ settingValues })
</script>
<style scoped lang="scss">
// 提示词
.prompt {
}
......
......@@ -8,18 +8,23 @@
<div class="modal-switch-container">
<Dall3
v-if="selectPlatform === AiPlatformEnum.OPENAI"
@on-draw-start="handlerDrawStart"
@on-draw-complete="handlerDrawComplete"
ref="dall3Ref"
@on-draw-start="handleDrawStart"
@on-draw-complete="handleDrawComplete"
/>
<Midjourney
v-if="selectPlatform === AiPlatformEnum.MIDJOURNEY"
ref="midjourneyRef"
/>
<Midjourney v-if="selectPlatform === AiPlatformEnum.MIDJOURNEY" />
<StableDiffusion
v-if="selectPlatform === AiPlatformEnum.STABLE_DIFFUSION"
@on-draw-complete="handlerDrawComplete"
ref="stableDiffusionRef"
@on-draw-complete="handleDrawComplete"
/>
</div>
</div>
<div class="main">
<ImageTask ref="imageTaskRef" />
<ImageTask ref="imageTaskRef" @on-regeneration="handleRegeneration" />
</div>
</div>
</template>
......@@ -31,8 +36,13 @@ import Midjourney from './midjourney/index.vue'
import StableDiffusion from './stable-diffusion/index.vue'
import ImageTask from './ImageTask.vue'
import { AiPlatformEnum } from '@/views/ai/utils/constants'
import {ImageVO} from "@/api/ai/image";
const imageTaskRef = ref<any>() // image task ref
const dall3Ref = ref<any>() // openai ref
const midjourneyRef = ref<any>() // midjourney ref
const stableDiffusionRef = ref<any>() // stable diffusion ref
// 定义属性
const selectPlatform = ref('StableDiffusion')
......@@ -50,20 +60,37 @@ const platformOptions = [
value: AiPlatformEnum.STABLE_DIFFUSION
}
]
const drawIn = ref<boolean>(false) // 生成中
/** 绘画 - start */
const handlerDrawStart = async (type) => {
// todo @fan:这个是不是没用啦?
drawIn.value = true
const handleDrawStart = async (type) => {
}
/** 绘画 - complete */
const handlerDrawComplete = async (type) => {
drawIn.value = false
// todo
const handleDrawComplete = async (type) => {
await imageTaskRef.value.getImageList()
}
/** 绘画 - 重新生成 */
const handleRegeneration = async (imageDetail: 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)
})
}
}
</script>
<style scoped lang="scss">
......
......@@ -24,7 +24,7 @@
:type="(selectHotWord === hotWord ? 'primary' : 'default')"
v-for="hotWord in hotWords"
:key="hotWord"
@click="handlerHotWordClick(hotWord)"
@click="handleHotWordClick(hotWord)"
>
{{ hotWord }}
</el-button>
......@@ -38,8 +38,8 @@
<div class="size-item"
v-for="imageSize in imageSizeList"
:key="imageSize.key"
@click="handlerSizeClick(imageSize)">
<div :class="selectImageSize === imageSize ? 'size-wrapper selectImageSize' : 'size-wrapper'">
@click="handleSizeClick(imageSize)">
<div :class="selectImageSize === imageSize.key ? 'size-wrapper selectImageSize' : 'size-wrapper'">
<div :style="imageSize.style"></div>
</div>
<div class="size-font">{{ imageSize.key }}</div>
......@@ -57,7 +57,7 @@
clearable
placeholder="请选择版本"
style="width: 350px"
@change="handlerChangeVersion"
@change="handleChangeVersion"
>
<el-option
v-for="item in versionList"
......@@ -74,7 +74,7 @@
</div>
<el-space wrap class="model-list">
<div
:class="selectModel === model ? 'modal-item selectModel' : 'modal-item'"
:class="selectModel === model.key ? 'modal-item selectModel' : 'modal-item'"
v-for="model in models"
:key="model.key"
......@@ -82,21 +82,29 @@
<el-image
:src="model.image"
fit="contain"
@click="handlerModelClick(model)"
@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" />
</el-space>
</div>
<div class="btns">
<!-- <el-button size="large" round>重置内容</el-button>-->
<el-button type="primary" size="large" round @click="handlerGenerateImage">生成内容</el-button>
<el-button type="primary" size="large" round @click="handleGenerateImage">生成内容</el-button>
</div>
</template>
<script setup lang="ts">
// image 模型
import {ImageApi, ImageMidjourneyImagineReqVO} from "@/api/ai/image";
import {ImageApi, ImageMidjourneyImagineReqVO, ImageVO} from "@/api/ai/image";
// message
const message = useMessage()
// 定义 emits
......@@ -118,9 +126,10 @@ interface ImageSizeVO {
// 定义属性
const prompt = ref<string>('') // 提示词
const referImage = ref<any>() // 参考图
const selectHotWord = ref<string>('') // 选中的热词
const hotWords = ref<string[]>(['中国旗袍', '古装美女', '卡通头像', '机甲战士', '童话小屋', '中国长城']) // 热词
const selectModel = ref<any>() // 选中的热词
const selectModel = ref<string>('midjourney') // 选中的热词
const models = ref<ImageModelVO[]>([
{
key: 'midjourney',
......@@ -133,9 +142,8 @@ const models = ref<ImageModelVO[]>([
image: 'https://bigpt8.com/pc/_nuxt/nj.ca79b143.png',
},
]) // 模型
selectModel.value = models.value[0] // 默认选中
const selectImageSize = ref<ImageSizeVO>({} as ImageSizeVO) // 选中 size
const selectImageSize = ref<string>('1:1') // 选中 size
const imageSizeList = ref<ImageSizeVO[]>([
{
key: '1:1',
......@@ -168,10 +176,8 @@ const imageSizeList = ref<ImageSizeVO[]>([
style: 'width: 50px; height: 30px;background-color: #dcdcdc;',
},
]) // size
selectImageSize.value = imageSizeList.value[0]
// version
let versionList = ref<any>([]) // version 列表
const midjourneyVersionList = ref<any>([
{
value: '6.0',
......@@ -201,10 +207,11 @@ const nijiVersionList = ref<any>([
},
])
const selectVersion = ref<any>('6.0') // 选中的 version
let versionList = ref<any>([]) // version 列表
versionList.value = midjourneyVersionList.value // 默认选择 midjourney
/** 热词 - click */
const handlerHotWordClick = async (hotWord: string) => {
const handleHotWordClick = async (hotWord: string) => {
// 取消
if (selectHotWord.value == hotWord) {
selectHotWord.value = ''
......@@ -217,17 +224,13 @@ const handlerHotWordClick = async (hotWord: string) => {
}
/** size - click */
const handlerSizeClick = async (imageSize: ImageSizeVO) => {
if (selectImageSize.value === imageSize) {
selectImageSize.value = {} as ImageSizeVO
return
}
selectImageSize.value = imageSize
const handleSizeClick = async (imageSize: ImageSizeVO) => {
selectImageSize.value = imageSize.key
}
/** 模型 - click */
const handlerModelClick = async (model: ImageModelVO) => {
selectModel.value = model
const handleModelClick = async (model: ImageModelVO) => {
selectModel.value = model.key
if (model.key === 'niji') {
versionList.value = nijiVersionList.value // 默认选择 niji
} else {
......@@ -237,33 +240,53 @@ const handlerModelClick = async (model: ImageModelVO) => {
}
/** version - click */
const handlerChangeVersion = async (version) => {
const handleChangeVersion = async (version) => {
console.log('version', version)
}
/** 图片生产 */
const handlerGenerateImage = async () => {
const handleGenerateImage = async () => {
// 二次确认
await message.confirm(`确认生成内容?`)
// todo @ 图片生产逻辑
// todo @芋艿 图片生产逻辑
try {
// 回调
emits('onDrawStart', selectModel.value.key)
emits('onDrawStart', selectModel.value)
// 发送请求
const imageSize = imageSizeList.value.find(item => selectImageSize.value === item.key) as ImageSizeVO
const req = {
prompt: prompt.value,
model: selectModel.value.key,
width: selectImageSize.value.width,
height: selectImageSize.value.height,
model: selectModel.value,
width: imageSize.width,
height: imageSize.height,
version: selectVersion.value,
base64Array: [],
referImageUrl: referImage.value,
} as ImageMidjourneyImagineReqVO
await ImageApi.midjourneyImagine(req)
} finally {
// 回调
emits('onDrawComplete', selectModel.value.key)
emits('onDrawComplete', selectModel.value)
}
}
/** 填充值 */
const settingValues = async (imageDetail: ImageVO) => {
// 提示词
prompt.value = imageDetail.prompt
// image size
const imageSize = imageSizeList.value.find(item => item.key === `${imageDetail.width}:${imageDetail.height}`) as ImageSizeVO
selectImageSize.value = imageSize.key
// 选中模型
const model = models.value.find(item => item.key === imageDetail.options?.model) as ImageModelVO
await handleModelClick(model)
// 版本
selectVersion.value = versionList.value.find(item => item.value === imageDetail.options?.version).value
// image
referImage.value = imageDetail.options.referImageUrl
}
/** 暴露组件方法 */
defineExpose({ settingValues })
</script>
<style scoped lang="scss">
......
......@@ -120,7 +120,8 @@
</div>
</template>
<script setup lang="ts">
import {ImageApi, ImageDrawReqVO} from '@/api/ai/image'
import {ImageApi, ImageDrawReqVO, ImageVO} from '@/api/ai/image'
import {hasChinese} from '../../utils/common-utils'
// image 模型
interface ImageModelVO {
......@@ -146,8 +147,8 @@ const hotWords = ref<string[]>([
// message
const message = useMessage()
// 采样方法 TODO @fan:有 Euler a;DPM++ 2S a;DPM++ 2M;DPM++ SDE;DPM++ 2M SDE;UniPC;Restart;另外,要不这种枚举,我们都放到 image 里?写成 stableDiffusionSampler ?
const selectSampler = ref<any>({}) // 模型
// 采样方法
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[]>([
{
......@@ -191,12 +192,11 @@ const sampler = ref<ImageModelVO[]>([
name: 'K_LMS'
},
])
selectSampler.value = sampler.value[0]
// 风格
// 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<any>({}) // 模型
const selectStylePreset = ref<string>('3d-model') // 模型
const stylePresets = ref<ImageModelVO[]>([
{
key: '3d-model',
......@@ -268,13 +268,11 @@ const stylePresets = ref<ImageModelVO[]>([
name: 'tile-texture'
},
])
selectStylePreset.value = stylePresets.value[0]
// 文本提示相匹配的图像(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<any>({}) // 模型
const selectClipGuidancePreset = ref<string>('NONE') // 模型
const clipGuidancePresets = ref<ImageModelVO[]>([
{
key: 'NONE',
......@@ -305,7 +303,6 @@ const clipGuidancePresets = ref<ImageModelVO[]>([
name: 'SLOWEST'
},
])
selectClipGuidancePreset.value = clipGuidancePresets.value[0]
const steps = ref<number>(20) // 迭代步数
const seed = ref<number>(42) // 控制生成图像的随机性
......@@ -333,6 +330,10 @@ const handleHotWordClick = async (hotWord: string) => {
const handleGenerateImage = async () => {
// 二次确认
await message.confirm(`确认生成内容?`)
if (await hasChinese(prompt.value)) {
message.alert('暂不支持中文!')
return
}
try {
// 加载中
drawIn.value = true
......@@ -349,9 +350,9 @@ const handleGenerateImage = async () => {
seed: seed.value, // 随机种子
steps: steps.value, // 图片生成步数
scale: scale.value, // 引导系数
sampler: selectSampler.value.key, // 采样算法
clipGuidancePreset: selectClipGuidancePreset.value.key, // 文本提示相匹配的图像 CLIP
stylePreset: selectStylePreset.value.key, // 风格
sampler: selectSampler.value, // 采样算法
clipGuidancePreset: selectClipGuidancePreset.value, // 文本提示相匹配的图像 CLIP
stylePreset: selectStylePreset.value, // 风格
}
} as ImageDrawReqVO
await ImageApi.drawImage(form)
......@@ -362,6 +363,22 @@ const handleGenerateImage = async () => {
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">
// 提示词
......
/**
* Created by 芋道源码
*
* AI 枚举类
*
* 问题:为什么不放在 src/utils/common-utils.ts 呢?
* 回答:主要 AI 是可选模块,考虑到独立、解耦,所以放在了 /views/ai/utils/common-utils.ts
*/
/** 判断字符串是否包含中文 */
export const hasChinese = async (str) => {
return /[\u4e00-\u9fa5]/.test(str)
}
......@@ -19,7 +19,6 @@ interface ImportMetaEnv {
readonly VITE_UPLOAD_URL: string
readonly VITE_API_URL: string
readonly VITE_BASE_PATH: string
readonly VITE_STATIC_URL: string
readonly VITE_DROP_DEBUGGER: string
readonly VITE_DROP_CONSOLE: string
readonly VITE_SOURCEMAP: string
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment