336 lines
8.6 KiB
Vue
336 lines
8.6 KiB
Vue
<template>
|
||
<Transition name="slide-up">
|
||
<div class="input-container" :class="{ generate : props.isGenerate}">
|
||
<div v-if="props.isGenerate" class="title">AI绘画2026</div>
|
||
<Sender :key="useDisplay.Sender_variant" v-model="prompt" :variant="useDisplay.Sender_variant" :placeholder="promptPlaceholder" :submit-btn-disabled="isgerenate.value" :auto-size="autoSizeConfig">
|
||
<template #prefix>
|
||
<div v-show="useDisplay.Sender_variant !== 'default'" class="prefix-self-wrap">
|
||
<div class="upload-btn">
|
||
<img src="@/assets/dialog/originalImage.svg" alt="" style="width: 16px;">
|
||
<span>上传原图</span>
|
||
</div>
|
||
<div class="upload-btn">
|
||
<img src="@/assets/dialog/referenceDiagram.svg" alt="" style="width: 16px;">
|
||
<span>上传参考图</span>
|
||
</div>
|
||
|
||
<Model v-model="model" />
|
||
<Proportion v-model="proportion" />
|
||
<Quantity v-model="quantity" />
|
||
</div>
|
||
</template>
|
||
|
||
<template #action-list>
|
||
<div style="display: flex; align-items: center; gap: 8px; height: 100%;">
|
||
<el-button v-if="isgerenate" round color="#626aef" style="animation: spin 1s linear infinite;">
|
||
<!-- <i-ep-loading /> -->
|
||
</el-button>
|
||
<div v-else class="gerenate" :class="{ isprompt: prompt }" @click="generate">
|
||
<img v-if="!prompt" src="@/assets/dialog/darkArrow.svg" alt="" />
|
||
<img v-else src="@/assets/dialog/writerArrow.svg" alt="" />
|
||
<div v-show="useDisplay.Sender_variant !== 'default'">生成</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</Sender>
|
||
|
||
<el-upload
|
||
v-show="false"
|
||
ref="uploadRef"
|
||
class="uploader"
|
||
:action="uploadurl"
|
||
multiple
|
||
:limit="1"
|
||
list-type="picture-card"
|
||
:before-upload="beforeUpload"
|
||
:on-success="SuccessSet"
|
||
:on-error="Errors"
|
||
:class="{ exceed: imageurl }"
|
||
/>
|
||
</div>
|
||
</Transition>
|
||
</template>
|
||
|
||
<script setup>
|
||
import Proportion from './proportion/index.vue'
|
||
import Quantity from './quantity/index.vue'
|
||
import Model from './model/index.vue'
|
||
import { Sender } from 'vue-element-plus-x'
|
||
import { useDisplayStore, useParamStore } from '@/stores'
|
||
import { generateSubImage } from '@/utils/websocket'
|
||
|
||
const props = defineProps({
|
||
isGenerate: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
})
|
||
console.log(props.isGenerate)
|
||
const useDisplay = useDisplayStore()
|
||
const useParams = useParamStore()
|
||
const uploadurl = import.meta.env.VITE_API_WORKFLOW_UPLOAD
|
||
const uploadRef = ref(null)
|
||
|
||
const model = ref('flux')
|
||
const proportion = ref('9:16')
|
||
const quantity = ref(1)
|
||
|
||
const promptPlaceholder = '结合图片,描述你想生成的画面和动作。例如:电影感剧照,氛围温聲。柔和色调,略带胶片颗粒感,连贯摆出棚拍的动作。'
|
||
const prompt = ref('')
|
||
const imageurl = ref('')
|
||
const imageurlShow = ref('')
|
||
const isgerenate = ref(false)
|
||
|
||
const autoSizeConfig = computed(() => {
|
||
if (useDisplay.Sender_variant !== 'default') {
|
||
return { minRows: 3, maxRows: 3 }
|
||
} else {
|
||
return { minRows: 1, maxRows: 1 }
|
||
}
|
||
})
|
||
|
||
// 处理图片选择
|
||
const handleSelect = async (url) => {
|
||
imageurlShow.value = url
|
||
// 从URL获取图片Blob对象
|
||
const initialFile = await fetch(url)
|
||
const blob = await initialFile.blob()
|
||
|
||
// 创建文件对象
|
||
const file = new File([blob], `selected_image_${Date.now()}.jpg`, { type: blob.type })
|
||
file.uid = genFileId()
|
||
console.log('file', file)
|
||
|
||
uploadRef.value.clearFiles() // 如果上传数量为1且已存在图片,则清除已存在的图
|
||
|
||
// 获取当前文件列表
|
||
uploadRef.value.handleStart(file)
|
||
uploadRef.value.submit()
|
||
}
|
||
|
||
// 检查文件类型和大小
|
||
const beforeUpload = (rawFile) => {
|
||
console.log('beforeUpload', rawFile)
|
||
const allowedTypes = ['image/jpeg', 'image/png']
|
||
|
||
// 检查文件类型
|
||
if (!allowedTypes.includes(rawFile.type)) {
|
||
// eslint-disable-next-line no-undef
|
||
ElMessage.error('不支持的文件类型,请上传 JPG、PNG 格式的图片')
|
||
return false
|
||
}
|
||
|
||
// 检查文件大小(限制为2MB)
|
||
if (rawFile.size / 1024 / 1024 > 10) {
|
||
// eslint-disable-next-line no-undef
|
||
ElMessage.error('图片大小不能超过 10MB')
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 成功上传后,将文件信息保存到 state 中
|
||
const SuccessSet = (response) => {
|
||
console.log('上传成功', response)
|
||
// eslint-disable-next-line no-undef
|
||
ElMessage.success('上传成功')
|
||
imageurl.value = response.url
|
||
}
|
||
|
||
// 错误处理
|
||
const Errors = (error) => {
|
||
console.log('上传失败', error)
|
||
// eslint-disable-next-line no-undef
|
||
ElMessage.error('上传失败,请重新选择图片')
|
||
imageurlShow.value = ''
|
||
}
|
||
|
||
const generate = async () => {
|
||
if (!imageurl.value && !prompt.value) {
|
||
// eslint-disable-next-line no-undef
|
||
ElMessage.error('请输入提示词')
|
||
return
|
||
}
|
||
isgerenate.value = true
|
||
useDisplay.isSubGerenate = true
|
||
console.log('生成开始', isgerenate.value)
|
||
await generateSubImage(3, { videoImg: imageurl.value, text: prompt.value, file_type: 'video', parentCreateTime: parentTime.value, parentIndex: parentIndex.value, parentTaskId: parentTaskId.value }, '生成视频')
|
||
console.log('生成中', isgerenate.value)
|
||
}
|
||
|
||
watch(() => useDisplay.isSubGerenate, (newValue) => {
|
||
console.log('生成状态', newValue)
|
||
if (!newValue) {
|
||
console.log('生成完成', isgerenate.value)
|
||
isgerenate.value = useDisplay.isSubGerenate
|
||
}
|
||
})
|
||
|
||
watch(() => useParams.AIvideoImage, (newValue) => {
|
||
if (newValue) {
|
||
if (newValue.url === imageurl.value) return
|
||
console.log('图片选择成功,打开视频页面', newValue)
|
||
parentTime.value = newValue.time
|
||
parentIndex.value = newValue.parentIndex
|
||
parentTaskId.value = newValue.parentTaskId
|
||
imageurl.value = newValue.url
|
||
handleSelect(newValue.url)
|
||
useParams.AIvideoImage = ''
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
/* 输入区域 */
|
||
.input-container {
|
||
width: 760px;
|
||
position: absolute;
|
||
bottom: 10px;
|
||
z-index: 100;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
border-radius: 10px;
|
||
|
||
}
|
||
.generate{
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
gap: 40px;
|
||
position: relative;
|
||
border: none;
|
||
box-shadow: none;
|
||
|
||
:deep(.el-sender){
|
||
background-color: #F8F9FA;
|
||
border: none;
|
||
box-shadow: none;
|
||
}
|
||
}
|
||
.prefix-self-wrap{
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 5px;
|
||
|
||
img{
|
||
height: 50px;
|
||
border-radius: 4px;
|
||
}
|
||
}
|
||
.title{
|
||
background-color: #FFF;
|
||
color: #333;
|
||
text-align: center;
|
||
font-family: "Alibaba PuHuiTi";
|
||
font-size: 24px;
|
||
font-style: normal;
|
||
font-weight: 500;
|
||
line-height: normal;
|
||
}
|
||
:deep(.el-sender){
|
||
background-color: #ffffff;
|
||
border: none;
|
||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.10);
|
||
}
|
||
|
||
:deep(.el-sender:focus-within){
|
||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.10);
|
||
}
|
||
|
||
// 时间选择器
|
||
.select{
|
||
background: #ffffff;
|
||
border-radius: 10px;
|
||
border: 1px solid rgba(0, 0, 0, 0.10);
|
||
}
|
||
// 视频效果选择器
|
||
.upload-btn{
|
||
display: flex;
|
||
height: 40px;
|
||
padding: 0 15px;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 5px;
|
||
border-radius: 10px;
|
||
border: 1px solid rgba(0, 0, 0, 0.10);
|
||
background: #ffffff;
|
||
cursor: pointer;
|
||
position: relative;
|
||
}
|
||
.upload-btn:hover{
|
||
background: #E5E7EB;
|
||
}
|
||
/* 圆形按钮 */
|
||
.circle-btn {
|
||
position: absolute;
|
||
right: 0px;
|
||
top: 0px;
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
z-index: 90;
|
||
transition: all 0.3s ease;
|
||
color: rgb(0, 0, 0);
|
||
font-size: 20px;
|
||
|
||
&:hover {
|
||
transform: scale(1.1);
|
||
// box-shadow: 0 6px 16px rgba(98, 106, 239, 0.6);
|
||
}
|
||
|
||
&:active {
|
||
transform: scale(0.95);
|
||
}
|
||
}
|
||
|
||
/* 过渡动画 */
|
||
.slide-up-enter-active,
|
||
.slide-up-leave-active {
|
||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.slide-up-enter-from {
|
||
opacity: 0;
|
||
transform: translate(-50%, 100%);
|
||
}
|
||
|
||
.slide-up-leave-to {
|
||
opacity: 0;
|
||
transform: translate(-50%, 100%);
|
||
}
|
||
|
||
.gerenate{
|
||
display: inline-flex;
|
||
height: 40px;
|
||
padding: 0 20px;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 5px;
|
||
border-radius: 10px;
|
||
background: rgba(0, 15, 51, 0.10);
|
||
cursor: pointer;
|
||
|
||
color: #000F33;
|
||
text-align: center;
|
||
font-family: "Microsoft YaHei";
|
||
font-size: 14px;
|
||
font-style: normal;
|
||
font-weight: 700;
|
||
line-height: normal;
|
||
}
|
||
.isprompt{
|
||
color: #ffffff;
|
||
background-color: #000F33;
|
||
}
|
||
// .gerenate:hover{
|
||
// background: rgba(0, 15, 51, 0.20);
|
||
// }
|
||
</style>
|