图生视频
This commit is contained in:
parent
a3c0fba87a
commit
c513814c0a
@ -1 +1,2 @@
|
||||
VITE_API_BASE_URL = 'https://opencomputing.ai'
|
||||
VITE_API_BASE_URL = 'https://opencomputing.ai'
|
||||
VITE_IMG_BASE_URL = 'https://opencomputing.ai'
|
||||
@ -2,6 +2,10 @@ import {get, post} from '../utlis/request'
|
||||
|
||||
// 文生视频
|
||||
export const textvideo = <T,D>(data:D):Promise<T> =>post('/api/llmage/vidu_inference.dspy',data)
|
||||
|
||||
// 图生视频
|
||||
export const imagetovideo = <T,D>(data:D):Promise<T> =>post('/api/llmage/vidu_inference.dspy',data)
|
||||
|
||||
// 获取所有模型
|
||||
export const AllModel = <T,D>(data:D):Promise<T> =>get('/api/llmage/get_type_llms.dspy',data)
|
||||
|
||||
|
||||
@ -142,3 +142,7 @@
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #fff;
|
||||
}
|
||||
:deep(.el-upload--picture-card) {
|
||||
width: 140px !important;
|
||||
height: 140px !important;
|
||||
}
|
||||
|
||||
@ -175,3 +175,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.el-upload--picture-card) {
|
||||
width: 140px !important;
|
||||
height: 140px !important;
|
||||
}
|
||||
206
src/assets/less/Video/img/img.css
Normal file
206
src/assets/less/Video/img/img.css
Normal file
@ -0,0 +1,206 @@
|
||||
:deep(.el-select__wrapper) {
|
||||
width: 100% !important;
|
||||
border: none !important;
|
||||
background-color: rgba(0, 0, 0, 0.32) !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 10px !important;
|
||||
font-size: 14px;
|
||||
color: #fff !important;
|
||||
resize: vertical;
|
||||
outline: none !important;
|
||||
}
|
||||
:deep(.el-upload--picture-card) {
|
||||
width: 140px !important;
|
||||
height: 140px !important;
|
||||
}
|
||||
:deep(.el-select__selected-item) {
|
||||
color: #fff !important;
|
||||
}
|
||||
:deep(.el-textarea) .el-textarea__inner {
|
||||
border: none !important;
|
||||
background-color: rgba(0, 0, 0, 0.32) !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 10px !important;
|
||||
padding: 14px 16px !important;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #fff !important;
|
||||
resize: none;
|
||||
outline: none !important;
|
||||
}
|
||||
:deep(.el-textarea) .el-textarea__inner:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
}
|
||||
:deep(.el-textarea) .el-textarea__inner::placeholder {
|
||||
color: rgba(255, 255, 255, 0.36) !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
:deep(.el-textarea) .el-input__count {
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #1a1c20;
|
||||
border: none;
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.card :deep(.el-card__body) {
|
||||
padding: 12px 16px;
|
||||
height: 100%;
|
||||
}
|
||||
.reference-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
}
|
||||
.upload-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.upload-item {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
.upload-item .upload-label {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
.upload-item :deep(.el-upload--picture-card) {
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border: 1px dashed rgba(255, 255, 255, 0.3);
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
}
|
||||
.upload-item :deep(.el-upload--picture-card):hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.upload-item :deep(.el-upload-list--picture-card) .el-upload-list__item {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.required-star {
|
||||
color: #ff4d4f;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.input {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.btmcard {
|
||||
width: 100%;
|
||||
background-color: #1a1c20;
|
||||
border: none;
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.btmcard :deep(.el-card__body) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
.select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.select-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.select-title {
|
||||
width: 50%;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.select-content {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.select-content :deep(.el-input-number) {
|
||||
width: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.select-content :deep(.el-input-number) .el-input-number__decrease,
|
||||
.select-content :deep(.el-input-number) .el-input-number__increase {
|
||||
background-color: transparent;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
.select-content :deep(.el-input-number) .el-input-number__decrease:hover,
|
||||
.select-content :deep(.el-input-number) .el-input-number__increase:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.select-content :deep(.el-input-number) .el-input__inner {
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
.select-content :deep(.el-input) .el-input__inner {
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
color: #fff;
|
||||
}
|
||||
.select-content :deep(.el-input) .el-input__inner::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.select-content :deep(.el-textarea) .el-textarea__inner {
|
||||
border: none !important;
|
||||
background-color: rgba(0, 0, 0, 0.32) !important;
|
||||
border-radius: 10px !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
.audio-wrapper {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
.upload-btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.custom-select-popup {
|
||||
background-color: #2A2D34 !important;
|
||||
border-radius: 12px !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;
|
||||
border: none !important;
|
||||
}
|
||||
.custom-select-popup .el-select-dropdown__item {
|
||||
color: #ffffff !important;
|
||||
background-color: transparent !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.custom-select-popup .el-select-dropdown__item.selected,
|
||||
.custom-select-popup .el-select-dropdown__item:hover {
|
||||
background-color: #3a3d46 !important;
|
||||
}
|
||||
.el-select .el-input__inner {
|
||||
background-color: #35383F !important;
|
||||
border: none !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
.el-select .el-input__suffix-inner {
|
||||
color: #fff !important;
|
||||
}
|
||||
:deep(.el-upload-list__item) {
|
||||
transition: none !important;
|
||||
}
|
||||
253
src/assets/less/Video/img/img.less
Normal file
253
src/assets/less/Video/img/img.less
Normal file
@ -0,0 +1,253 @@
|
||||
:deep(.el-select__wrapper) {
|
||||
width: 100% !important;
|
||||
border: none !important;
|
||||
background-color: rgba(0, 0, 0, 0.32) !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 10px !important;
|
||||
font-size: 14px;
|
||||
color: #fff !important;
|
||||
resize: vertical;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-upload--picture-card) {
|
||||
width: 140px !important;
|
||||
height: 140px !important;
|
||||
}
|
||||
|
||||
:deep(.el-select__selected-item) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
:deep(.el-textarea) {
|
||||
.el-textarea__inner {
|
||||
border: none !important;
|
||||
background-color: rgba(0, 0, 0, 0.32) !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 10px !important;
|
||||
padding: 14px 16px !important;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #fff !important;
|
||||
resize: none;
|
||||
outline: none !important;
|
||||
|
||||
&:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.36) !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-input__count {
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #1a1c20;
|
||||
border: none;
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 12px 16px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.reference-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.upload-item {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
|
||||
.upload-label {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
:deep(.el-upload--picture-card) {
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border: 1px dashed rgba(255, 255, 255, 0.3);
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-upload-list--picture-card) {
|
||||
.el-upload-list__item {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.required-star {
|
||||
color: #ff4d4f;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.btmcard {
|
||||
width: 100%;
|
||||
background-color: #1a1c20;
|
||||
border: none;
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
flex-shrink: 0;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.select-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.select-title {
|
||||
width: 50%;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.select-content {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
:deep(.el-input-number) {
|
||||
width: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border-radius: 10px;
|
||||
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase {
|
||||
background-color: transparent;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-input) {
|
||||
.el-input__inner {
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
color: #fff;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-textarea) {
|
||||
.el-textarea__inner {
|
||||
border: none !important;
|
||||
background-color: rgba(0, 0, 0, 0.32) !important;
|
||||
border-radius: 10px !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.audio-wrapper {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.custom-select-popup {
|
||||
background-color: #2A2D34 !important;
|
||||
border-radius: 12px !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;
|
||||
border: none !important;
|
||||
|
||||
.el-select-dropdown__item {
|
||||
color: #ffffff !important;
|
||||
background-color: transparent !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.selected,
|
||||
.el-select-dropdown__item:hover {
|
||||
background-color: #3a3d46 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-select {
|
||||
.el-input__inner {
|
||||
background-color: #35383F !important;
|
||||
border: none !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.el-input__suffix-inner {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-upload-list__item) {
|
||||
transition: none !important;
|
||||
}
|
||||
@ -184,3 +184,39 @@
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
}
|
||||
:deep(.el-upload--picture-card) {
|
||||
width: 140px !important;
|
||||
height: 140px !important;
|
||||
}
|
||||
.upload-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.upload-item {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
.upload-item .upload-label {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
.upload-item :deep(.el-upload--picture-card) {
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border: 1px dashed rgba(255, 255, 255, 0.3);
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
}
|
||||
.upload-item :deep(.el-upload--picture-card):hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.upload-item :deep(.el-upload-list--picture-card) .el-upload-list__item {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border-radius: 10px;
|
||||
}
|
||||
@ -211,3 +211,46 @@
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
}
|
||||
:deep(.el-upload--picture-card) {
|
||||
width: 140px !important;
|
||||
height: 140px !important;
|
||||
}
|
||||
.upload-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.upload-item {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
|
||||
.upload-label {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
:deep(.el-upload--picture-card) {
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border: 1px dashed rgba(255, 255, 255, 0.3);
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-upload-list--picture-card) {
|
||||
.el-upload-list__item {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,333 +1,593 @@
|
||||
<template>
|
||||
<div class="reference-to-video">
|
||||
<!-- 顶部卡片 -->
|
||||
<!-- 顶部卡片:图片上传和提示词输入区 -->
|
||||
<el-card class="card">
|
||||
<div class="reference-container">
|
||||
<!-- 上传模块 -->
|
||||
<!-- 动态上传区域:根据模型生成图片上传项 -->
|
||||
<div v-if="currentModel" class="upload-section">
|
||||
<div v-for="field in imageFields" :key="field.name" class="upload-item">
|
||||
<div class="upload-label">
|
||||
{{ field.label }}
|
||||
<span v-if="field.required" class="required-star">*</span>
|
||||
</div>
|
||||
<el-upload action="#" list-type="picture-card" :auto-upload="false"
|
||||
:on-change="(file: UploadFile) => handleFileChange(file, field.name)"
|
||||
:on-remove="() => handleRemove(field.name)" :file-list="getFileList(field.name)" :limit="1"
|
||||
:on-exceed="() => ElMessage.warning('每个字段只能上传一张图片')"
|
||||
:disabled="generating" class="upload">
|
||||
<el-icon>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
<template #file="{ file }">
|
||||
<div>
|
||||
<img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
|
||||
<span class="el-upload-list__item-actions">
|
||||
<span class="el-upload-list__item-preview"
|
||||
@click="handlePictureCardPreview(file)">
|
||||
<el-icon>
|
||||
<ZoomIn />
|
||||
</el-icon>
|
||||
</span>
|
||||
<span v-if="!disabled" class="el-upload-list__item-delete"
|
||||
@click="handleDownload(field.name)">
|
||||
<el-icon>
|
||||
<Download />
|
||||
</el-icon>
|
||||
</span>
|
||||
<span v-if="!disabled" class="el-upload-list__item-delete"
|
||||
@click="handleRemove(field.name)">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 文本输入框 -->
|
||||
<div class="input">
|
||||
<el-input type="textarea" :rows="10" maxlength="400"
|
||||
placeholder="支持上传图片、视频或主体作为参考。可参考视频中的动作、表情、运镜、特效、风格与构图等,并支持对元素进行增删和修改" />
|
||||
<!-- 文本输入框(绑定 prompt 字段) -->
|
||||
<div v-if="hasPromptField" class="input">
|
||||
<el-input type="textarea" :rows="6" maxlength="400" :placeholder="promptPlaceholder"
|
||||
v-model="prompt" show-word-limit :disabled="generating" />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 图片预览对话框 -->
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<img w-full :src="dialogImageUrl" alt="Preview Image" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 底部配置卡片 -->
|
||||
<el-card class="btmcard">
|
||||
<div class="select">
|
||||
<!-- 模型 -->
|
||||
<!-- 模型选择(动态从接口加载) -->
|
||||
<div class="select-item">
|
||||
<div class="select-title">模型</div>
|
||||
<div class="select-content">
|
||||
<el-select v-model="selectModel" style="width: 100%" @change="handleChange"
|
||||
popper-class="custom-select-popup">
|
||||
<el-option label="Jack" value="jack" />
|
||||
<el-option label="Lucy" value="lucy" />
|
||||
<el-option label="yiminghe" value="Yiminghe" />
|
||||
<el-option label="Disabled" value="disabled" disabled />
|
||||
<el-select v-model="currentModelId" style="width: 100%" popper-class="custom-select-popup"
|
||||
@change="handleModelChange" :disabled="generating">
|
||||
<el-option v-for="model in models" :key="model.id" :label="model.name" :value="model.id" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 动态生成其他配置项(排除 prompt 和图片字段) -->
|
||||
<template v-for="field in visibleFields" :key="field.name">
|
||||
<div class="select-item">
|
||||
<div class="select-title">
|
||||
{{ field.label }}
|
||||
<span v-if="field.required" class="required-star">*</span>
|
||||
</div>
|
||||
<div class="select-content">
|
||||
<!-- 下拉选择类型(code) -->
|
||||
<el-select v-if="field.uitype === 'code'" v-model="configData[field.name]"
|
||||
style="width: 100%" popper-class="custom-select-popup"
|
||||
:placeholder="`请选择${field.label}`" :disabled="generating">
|
||||
<el-option v-for="option in field.data" :key="option.value"
|
||||
:label="option.text || option.value" :value="option.value" />
|
||||
</el-select>
|
||||
|
||||
<!-- 数字输入类型(int) -->
|
||||
<el-input-number v-else-if="field.uitype === 'int'" v-model="configData[field.name]"
|
||||
:min="1" :max="field.max" :step="1" controls-position="right" style="width: 100%"
|
||||
:disabled="generating" />
|
||||
<el-input-number v-else-if="field.uitype === 'int'" v-model="configData[field.name]"
|
||||
:min="1" :max="field.max || 15" :step="1" controls-position="right" style="width: 100%"
|
||||
:disabled="generating" />
|
||||
<!-- 文本输入(text 类型,但非 prompt) -->
|
||||
<el-input v-else-if="field.uitype === 'text'" v-model="configData[field.name]"
|
||||
:placeholder="`请输入${field.label}`"
|
||||
:type="field.name === 'negative_prompt' ? 'textarea' : 'text'"
|
||||
:rows="field.name === 'negative_prompt' ? 3 : 1" clearable :disabled="generating" />
|
||||
|
||||
|
||||
<!-- 清晰度 & 编码 -->
|
||||
<div class="select-item">
|
||||
<div class="select-title">
|
||||
清晰度<span class="and">&</span>编码格式
|
||||
<!-- 音频上传 -->
|
||||
<div v-else-if="field.uitype === 'audio'" class="audio-wrapper">
|
||||
<el-input v-model="configData[field.name]" placeholder="请输入音频文件URL或点击上传" clearable
|
||||
:disabled="generating" />
|
||||
<el-button size="small" @click="simulateUpload" :disabled="generating"
|
||||
class="upload-btn">
|
||||
上传
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="select-content">
|
||||
<el-select v-model="selectQuality" style="width: 50%" @change="handleChange"
|
||||
popper-class="custom-select-popup">
|
||||
<el-option label="1080P" value="1080P" />
|
||||
</el-select>
|
||||
<el-select v-model="selectCodec" style="width: 50%" @change="handleChange"
|
||||
popper-class="custom-select-popup">
|
||||
<el-option label="H265" value="H265" />
|
||||
<el-option label="H264" value="H264" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 生成模式 -->
|
||||
<div class="select-item">
|
||||
<div class="select-title">生成模式</div>
|
||||
<div class="select-content">
|
||||
<el-select v-model="selectModelviedo" style="width: 100%" @change="handleChange"
|
||||
popper-class="custom-select-popup">
|
||||
<el-option label="电影大片" value="电影大片" />
|
||||
</el-select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数量 -->
|
||||
<div class="select-item">
|
||||
<div class="select-title">数量</div>
|
||||
<div class="radio-group">
|
||||
<button v-for="num in [1, 2, 3, 4]" :key="num" class="radio-item"
|
||||
:class="{ selected: selectedCount === num }" @click="selectedCount = num" type="button">
|
||||
{{ num }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, defineEmits } from 'vue'
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Delete, Download, Plus, ZoomIn } from '@element-plus/icons-vue';
|
||||
import type { UploadFile } from 'element-plus';
|
||||
import { imagetovideo, AllModel, queryModelPrice } from '../../apis/Model';
|
||||
import type {
|
||||
Model,
|
||||
AllModelResponse,
|
||||
PriceQueryParams,
|
||||
PriceQueryResponse,
|
||||
TextVideoResponse,
|
||||
ModelParams,
|
||||
} from '../../types/model';
|
||||
|
||||
// 定义 emit 事件
|
||||
// ==================== 环境变量 ====================
|
||||
const IMG_BASE_URL = import.meta.env.VITE_IMG_BASE_URL || '';
|
||||
|
||||
// ==================== 事件定义 ====================
|
||||
const emit = defineEmits<{
|
||||
(e: 'price-update', price: number): void;
|
||||
(e: 'prompt-change', isEmpty: boolean): void;
|
||||
(e: 'price-update', price: number): void;
|
||||
(e: 'prompt-change', isEmpty: boolean): void;
|
||||
(e: 'task-created', taskid: string): void;
|
||||
}>();
|
||||
|
||||
// 下拉选中项
|
||||
const selectModel = ref('lucy')
|
||||
const selectQuality = ref('1080P')
|
||||
const selectCodec = ref('H265')
|
||||
const selectModelviedo = ref('电影大片')
|
||||
// ==================== 类型定义 ====================
|
||||
type ConfigValue = string | number | boolean;
|
||||
|
||||
// 单选按钮
|
||||
const selectedDuration = ref(5)
|
||||
const selectedCount = ref(1)
|
||||
// ==================== 数据定义 ====================
|
||||
const models = ref<Model[]>([]);
|
||||
const currentModelId = ref('');
|
||||
const currentModel = ref<Model | null>(null);
|
||||
|
||||
// 价格计算
|
||||
const price = computed(() => {
|
||||
const basePrice = selectedDuration.value > 5 ? 2 : 1
|
||||
return basePrice * selectedCount.value
|
||||
})
|
||||
const prompt = ref('');
|
||||
const uploadedFiles = ref<Record<string, string>>({});
|
||||
const uploadedFileNames = ref<Record<string, string>>({});
|
||||
const configData = ref<Record<string, ConfigValue>>({});
|
||||
|
||||
// 监听价格变化
|
||||
watch(price, (newPrice) => {
|
||||
emit('price-update', newPrice)
|
||||
})
|
||||
const generating = ref(false);
|
||||
const price = ref(0);
|
||||
|
||||
// 统一 change 事件
|
||||
const handleChange = (value: string) => {
|
||||
console.log('selected', value)
|
||||
}
|
||||
const dialogImageUrl = ref('');
|
||||
const dialogVisible = ref(false);
|
||||
const disabled = ref(false);
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
const imageFields = computed(() => {
|
||||
if (!currentModel.value) return [];
|
||||
return currentModel.value.input_fields.filter(
|
||||
(field: any) => field.uitype === 'image'
|
||||
);
|
||||
});
|
||||
|
||||
const hasPromptField = computed(() => {
|
||||
if (!currentModel.value) return false;
|
||||
return currentModel.value.input_fields.some(f => f.name === 'prompt');
|
||||
});
|
||||
|
||||
const visibleFields = computed(() => {
|
||||
if (!currentModel.value) return [];
|
||||
return currentModel.value.input_fields.filter(
|
||||
(field: any) => field.uitype !== 'image' && field.name !== 'prompt'
|
||||
);
|
||||
});
|
||||
|
||||
const promptPlaceholder = computed(() => {
|
||||
if (!currentModel.value) return '请输入提示词';
|
||||
const promptField = currentModel.value.input_fields.find(f => f.name === 'prompt');
|
||||
if (promptField?.required) return '请输入提示词(必填)';
|
||||
return '请输入提示词';
|
||||
});
|
||||
|
||||
// ==================== 表单初始化 ====================
|
||||
const initFormData = () => {
|
||||
if (!currentModel.value) return;
|
||||
const newConfigData: Record<string, ConfigValue> = {};
|
||||
|
||||
currentModel.value.input_fields.forEach(field => {
|
||||
if (field.uitype === 'image' || field.name === 'prompt') {
|
||||
return;
|
||||
}
|
||||
|
||||
let defaultValue: ConfigValue = field.defaultvalue ?? '';
|
||||
|
||||
if (defaultValue === undefined || defaultValue === null) {
|
||||
if (field.uitype === 'code' && field.data && field.data.length > 0) {
|
||||
defaultValue = field.data[0].value;
|
||||
} else if (field.uitype === 'int') {
|
||||
defaultValue = 1;
|
||||
} else {
|
||||
defaultValue = '';
|
||||
}
|
||||
}
|
||||
newConfigData[field.name] = defaultValue;
|
||||
});
|
||||
|
||||
configData.value = newConfigData;
|
||||
};
|
||||
|
||||
// ==================== 价格查询 ====================
|
||||
const buildPriceParams = (): PriceQueryParams | null => {
|
||||
if (!currentModel.value) return null;
|
||||
|
||||
const configDataForPrice: PriceQueryParams['config_data'] = { action: '' };
|
||||
|
||||
currentModel.value.input_fields.forEach(field => {
|
||||
if (field.uitype === 'image' || field.name === 'prompt') {
|
||||
return;
|
||||
}
|
||||
const value = configData.value[field.name];
|
||||
if (value !== undefined) {
|
||||
if (field.uitype === 'audio') {
|
||||
configDataForPrice.audio = Boolean(value);
|
||||
} else {
|
||||
configDataForPrice[field.name] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
configDataForPrice.action = currentModel.value.apiname;
|
||||
|
||||
const priceParams = {
|
||||
product_type: 'llm',
|
||||
product_id: currentModel.value.id,
|
||||
config_data: configDataForPrice,
|
||||
};
|
||||
|
||||
console.log('=== 构建价格查询参数 ===');
|
||||
console.log('价格查询参数:', JSON.stringify(priceParams, null, 2));
|
||||
|
||||
return priceParams;
|
||||
};
|
||||
|
||||
let priceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const fetchPrice = () => {
|
||||
if (priceTimer) clearTimeout(priceTimer);
|
||||
|
||||
priceTimer = setTimeout(async () => {
|
||||
const hasRequiredContent = checkRequiredFields();
|
||||
if (!hasRequiredContent) {
|
||||
price.value = 0;
|
||||
emit('price-update', 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentModel.value) return;
|
||||
|
||||
const priceParams = buildPriceParams();
|
||||
if (!priceParams) return;
|
||||
|
||||
try {
|
||||
const res = (await queryModelPrice(priceParams)) as PriceQueryResponse;
|
||||
if (res.status === 'ok' && res.data) {
|
||||
price.value = res.data.amount;
|
||||
emit('price-update', price.value);
|
||||
} else {
|
||||
price.value = 0;
|
||||
emit('price-update', 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('价格查询失败:', error);
|
||||
price.value = 0;
|
||||
emit('price-update', 0);
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const checkRequiredFields = (): boolean => {
|
||||
if (!currentModel.value) return false;
|
||||
|
||||
const hasPrompt = hasPromptField.value && prompt.value?.trim();
|
||||
const hasUploads = imageFields.value.length > 0 &&
|
||||
imageFields.value.some(f => uploadedFileNames.value[f.name]?.trim());
|
||||
|
||||
return hasPrompt || hasUploads;
|
||||
};
|
||||
|
||||
// ==================== 模型加载 ====================
|
||||
const loadModels = async () => {
|
||||
try {
|
||||
const params: ModelParams = { type: '图生视频' };
|
||||
const res = await AllModel<AllModelResponse, ModelParams>(params);
|
||||
if (res.status === 'ok' && Array.isArray(res.data)) {
|
||||
models.value = res.data;
|
||||
if (models.value.length > 0) {
|
||||
currentModelId.value = models.value[0].id;
|
||||
currentModel.value = models.value[0];
|
||||
initFormData();
|
||||
fetchPrice();
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('获取模型列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载模型失败:', error);
|
||||
ElMessage.error('加载模型列表失败,请刷新重试');
|
||||
}
|
||||
};
|
||||
|
||||
const handleModelChange = (modelId: string) => {
|
||||
const selected = models.value.find(m => m.id === modelId);
|
||||
if (selected) {
|
||||
console.log('=== 切换模型 ===');
|
||||
console.log('新模型:', selected.name);
|
||||
console.log('清空的文件:', uploadedFileNames.value);
|
||||
|
||||
currentModel.value = selected;
|
||||
uploadedFiles.value = {};
|
||||
uploadedFileNames.value = {};
|
||||
initFormData();
|
||||
fetchPrice();
|
||||
}
|
||||
};
|
||||
// ==================== 监听器 ====================
|
||||
watch(
|
||||
() => [configData.value, currentModelId.value],
|
||||
() => {
|
||||
if (currentModel.value) fetchPrice();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [prompt.value, uploadedFileNames.value],
|
||||
() => {
|
||||
const isEmpty = !checkRequiredFields();
|
||||
emit('prompt-change', isEmpty);
|
||||
if (isEmpty) {
|
||||
price.value = 0;
|
||||
emit('price-update', 0);
|
||||
} else {
|
||||
fetchPrice();
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
// ==================== 上传处理 ====================
|
||||
const getFileList = (fieldName: string) => {
|
||||
const url = uploadedFiles.value[fieldName];
|
||||
if (url) {
|
||||
return [{ name: fieldName, url }];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const handleFileChange = (file: UploadFile, fieldName: string) => {
|
||||
// 检查是否已存在该字段的文件
|
||||
if (uploadedFiles.value[fieldName]) {
|
||||
ElMessage.warning('每个字段只能上传一张图片,请先删除已有图片再上传');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = URL.createObjectURL(file.raw as File);
|
||||
const fileName = file.name;
|
||||
|
||||
uploadedFiles.value[fieldName] = url;
|
||||
uploadedFileNames.value[fieldName] = fileName;
|
||||
|
||||
console.log('=== 上传图片 ===');
|
||||
console.log('字段名:', fieldName);
|
||||
console.log('文件名:', fileName);
|
||||
console.log('预览 URL:', url);
|
||||
console.log('提交 URL:', `${IMG_BASE_URL}/${fileName}`);
|
||||
console.log('当前所有上传文件:', uploadedFileNames.value);
|
||||
|
||||
ElMessage.success('上传成功');
|
||||
};
|
||||
|
||||
const handleRemove = (fieldName: string) => {
|
||||
uploadedFiles.value[fieldName] = '';
|
||||
uploadedFileNames.value[fieldName] = '';
|
||||
|
||||
console.log('=== 删除图片 ===');
|
||||
console.log('字段名:', fieldName);
|
||||
console.log('剩余上传文件:', uploadedFileNames.value);
|
||||
|
||||
ElMessage.info('已移除图片');
|
||||
};
|
||||
|
||||
const handlePictureCardPreview = (file: UploadFile) => {
|
||||
dialogImageUrl.value = file.url!;
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
const handleDownload = (fieldName: string) => {
|
||||
const url = uploadedFiles.value[fieldName];
|
||||
if (url) {
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = fieldName + '.png';
|
||||
a.click();
|
||||
}
|
||||
};
|
||||
|
||||
const simulateUpload = () => {
|
||||
ElMessage.info('此处可对接实际音频上传服务');
|
||||
configData.value.audio_file = 'https://example.com/demo_audio.mp3';
|
||||
};
|
||||
|
||||
// ==================== 生成视频 ====================
|
||||
const buildSubmitParams = (): Record<string, unknown> => {
|
||||
const baseParams: Record<string, unknown> = {
|
||||
llmid: currentModel.value!.id,
|
||||
};
|
||||
|
||||
// 添加提示词(如果存在)
|
||||
if (hasPromptField.value && prompt.value) {
|
||||
baseParams.prompt = prompt.value;
|
||||
}
|
||||
|
||||
// 添加上传的文件
|
||||
Object.keys(uploadedFileNames.value).forEach(key => {
|
||||
if (uploadedFileNames.value[key]) {
|
||||
// 使用环境变量中的图片前缀 + 文件名
|
||||
baseParams[key] = `${IMG_BASE_URL}/${uploadedFileNames.value[key]}`;
|
||||
console.log('123',baseParams[key]);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// 添加其他配置字段
|
||||
Object.keys(configData.value).forEach(key => {
|
||||
if (configData.value[key] !== undefined) {
|
||||
const value = configData.value[key];
|
||||
// 确保 audio 字段是布尔值
|
||||
if (key === 'audio_file') {
|
||||
baseParams.audio = Boolean(value);
|
||||
} else {
|
||||
baseParams[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('=== 构建提交参数 ===');
|
||||
console.log('完整参数:', JSON.stringify(baseParams, null, 2));
|
||||
console.log('上传的图片字段:', uploadedFileNames.value);
|
||||
console.log('图片基础 URL:', IMG_BASE_URL);
|
||||
|
||||
return baseParams;
|
||||
};
|
||||
|
||||
const generate = async (): Promise<boolean> => {
|
||||
if (!currentModel.value) {
|
||||
ElMessage.error('请选择模型');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('=== 开始验证必填字段 ===');
|
||||
console.log('当前模型:', currentModel.value.name);
|
||||
console.log('图片文件:', uploadedFileNames.value);
|
||||
console.log('提示词:', prompt.value);
|
||||
console.log('配置数据:', configData.value);
|
||||
|
||||
const missingFields: string[] = [];
|
||||
currentModel.value.input_fields.forEach(field => {
|
||||
if (field.required) {
|
||||
if (field.name === 'prompt') {
|
||||
if (!prompt.value?.trim()) missingFields.push(field.label);
|
||||
} else if (field.uitype === 'image') {
|
||||
if (!uploadedFileNames.value[field.name]?.trim()) missingFields.push(field.label);
|
||||
} else {
|
||||
const val = configData.value[field.name];
|
||||
if (!val) missingFields.push(field.label);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
console.error('=== 必填字段验证失败 ===');
|
||||
console.error('缺失的字段:', missingFields);
|
||||
ElMessage.error(`请填写必填项:${missingFields.join('、')}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('=== 必填字段验证通过 ===');
|
||||
|
||||
generating.value = true;
|
||||
|
||||
try {
|
||||
const params = buildSubmitParams();
|
||||
|
||||
console.log('=== 准备提交接口 ===');
|
||||
console.log('接口路径: /api/llmage/vidu_inference.dspy');
|
||||
console.log('请求参数:', JSON.stringify(params, null, 2));
|
||||
|
||||
const res = (await imagetovideo(params)) as TextVideoResponse;
|
||||
|
||||
console.log('=== 图生视频接口响应 ===');
|
||||
console.log('响应状态:', res.status);
|
||||
console.log('响应数据:', res.data);
|
||||
console.log('完整响应:', JSON.stringify(res, null, 2));
|
||||
|
||||
let taskid = res.data?.taskid || res.taskid;
|
||||
console.log('提取的 taskid:', taskid);
|
||||
|
||||
if (res.status === 'created' || res.status === 'processing' || res.status === 'queueing') {
|
||||
const statusMsg = res.status === 'created' ? '视频生成任务已提交' : (res.status === 'processing' ? '进行中' : '排队中');
|
||||
console.log('任务状态:', statusMsg);
|
||||
console.log('任务创建成功,等待处理...');
|
||||
ElMessage.success(`${statusMsg},请稍后在任务列表中查看`);
|
||||
|
||||
if (taskid) {
|
||||
console.log('向上传递 task-created 事件,taskid:', taskid);
|
||||
emit('task-created', taskid);
|
||||
}
|
||||
resetForm();
|
||||
return true;
|
||||
} else {
|
||||
console.error('任务创建失败,状态:', res.status);
|
||||
ElMessage.error(res.message || '生成失败,请重试');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('=== 生成失败 ===');
|
||||
console.error('错误详情:', error);
|
||||
ElMessage.error(error instanceof Error ? error.message : '生成失败,请重试');
|
||||
return false;
|
||||
} finally {
|
||||
generating.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
console.log('=== 重置表单 ===');
|
||||
console.log('清空的图片文件:', uploadedFileNames.value);
|
||||
|
||||
prompt.value = '';
|
||||
uploadedFiles.value = {};
|
||||
uploadedFileNames.value = {};
|
||||
initFormData();
|
||||
price.value = 0;
|
||||
emit('price-update', 0);
|
||||
emit('prompt-change', true);
|
||||
};
|
||||
// ==================== 暴露给父组件 ====================
|
||||
defineExpose({
|
||||
generate,
|
||||
resetForm,
|
||||
});
|
||||
|
||||
// ==================== 生命周期 ====================
|
||||
onMounted(() => {
|
||||
loadModels();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.el-select__wrapper) {
|
||||
|
||||
width: 100% !important;
|
||||
border: none !important;
|
||||
background-color: rgba(0, 0, 0, 0.32) !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 0 !important;
|
||||
font-size: 14px;
|
||||
color: #fff !important;
|
||||
resize: vertical;
|
||||
outline: none !important;
|
||||
border-radius: 10px !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
:deep(.el-select__selected-item) {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
:deep(.el-textarea) {
|
||||
|
||||
.el-textarea__inner {
|
||||
|
||||
border: none !important;
|
||||
background-color: rgba(0, 0, 0, 0.32) !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 0 !important;
|
||||
padding: 12px 0 !important;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #fff !important;
|
||||
resize: vertical;
|
||||
outline: none !important;
|
||||
border-radius: 10px !important;
|
||||
color: #fff;
|
||||
backdrop-filter: blur(30px);
|
||||
padding: 14px 16px !important;
|
||||
|
||||
&:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #ffffff7a !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保没有任何附加的边框或装饰出现
|
||||
.el-input__count {
|
||||
background: transparent;
|
||||
color: #ffffff7a;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #1a1c20;
|
||||
border: none;
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 12px 16px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.reference-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
color: #fff;
|
||||
backdrop-filter: blur(30px);
|
||||
padding: 14px 16px;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.36);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btmcard {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #1a1c20;
|
||||
border: none;
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.select-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.select-title {
|
||||
width: 50%;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.select-content {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.and {
|
||||
color: #ffffff66;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.radio-item {
|
||||
flex: 1;
|
||||
height: 36px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.radio-item.selected {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.radio-item.premium {
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
background-image: linear-gradient(to bottom, #7280FF, #DADFFF);
|
||||
font-weight: 600;
|
||||
}
|
||||
@import url('../../assets/less/Video/text/text.less');
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.custom-select-popup {
|
||||
background-color: #2A2D34 !important;
|
||||
border-radius: 12px !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;
|
||||
border: none !important;
|
||||
|
||||
|
||||
.el-select-dropdown__item {
|
||||
color: #ffffff !important;
|
||||
background-color: transparent !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.selected,
|
||||
.el-select-dropdown__item:hover {
|
||||
background-color: #3a3d46 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-select {
|
||||
.el-input__inner {
|
||||
background-color: #35383F !important;
|
||||
border: none !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
.custom-select-popup .el-select-dropdown__item {
|
||||
color: #ffffff !important;
|
||||
background-color: transparent !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.el-input__suffix-inner {
|
||||
color: #fff !important;
|
||||
}
|
||||
.custom-select-popup .el-select-dropdown__item.selected,
|
||||
.custom-select-popup .el-select-dropdown__item:hover {
|
||||
background-color: #3a3d46 !important;
|
||||
}
|
||||
</style>
|
||||
@ -128,6 +128,7 @@ import type {
|
||||
PriceQueryResponse,
|
||||
TextVideoParams,
|
||||
TextVideoResponse,
|
||||
ModelParams,
|
||||
} from '../../types/model';
|
||||
|
||||
// ==================== 事件定义 ====================
|
||||
@ -264,8 +265,11 @@ const fetchPrice = () => {
|
||||
|
||||
// ==================== 模型加载 ====================
|
||||
const loadModels = async () => {
|
||||
try {
|
||||
const res = (await AllModel({})) as AllModelResponse;
|
||||
const Props = {
|
||||
type:'文生视频'
|
||||
}
|
||||
|
||||
const res = await AllModel<AllModelResponse,ModelParams>(Props);
|
||||
if (res.status === 'ok' && Array.isArray(res.data)) {
|
||||
models.value = res.data;
|
||||
if (models.value.length > 0) {
|
||||
@ -277,10 +281,7 @@ const loadModels = async () => {
|
||||
} else {
|
||||
ElMessage.error('获取模型列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载模型失败:', error);
|
||||
ElMessage.error('加载模型列表失败,请刷新重试');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const handleModelChange = (modelId: string) => {
|
||||
@ -341,7 +342,7 @@ const buildSubmitParams = (): TextVideoParams => {
|
||||
if (configData.value.size !== undefined && !baseParams.resolution) {
|
||||
baseParams.resolution = String(configData.value.size);
|
||||
}
|
||||
baseParams.audio = !!configData.value.audio_file;
|
||||
baseParams.audio = Boolean(configData.value.audio_file);
|
||||
|
||||
return baseParams;
|
||||
};
|
||||
@ -389,7 +390,7 @@ const generate = async (): Promise<boolean> => {
|
||||
ElMessage.success(`${statusMsg},请稍后在任务列表中查看`);
|
||||
|
||||
if (taskid) {
|
||||
console.log('触发 task-created 事件, taskid:', taskid);
|
||||
console.log('触发 task-created 事件, taskid:', taskid);
|
||||
emit('task-created', taskid);
|
||||
console.log('task-created 事件已发送');
|
||||
} else {
|
||||
@ -400,7 +401,7 @@ const generate = async (): Promise<boolean> => {
|
||||
resetForm();
|
||||
return true;
|
||||
} else {
|
||||
ElMessage.error(res.data?.message || '生成失败,请重试');
|
||||
ElMessage.error(res.message || '生成失败,请重试');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
@ -433,7 +434,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import url('../../assets/less/Video/text.less');
|
||||
@import url('../../assets/less/Video/text/text.less');
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
v-else-if="activeTab === 'image'"
|
||||
@price-update="handleImagePriceUpdate"
|
||||
@prompt-change="handleImagePromptChange"
|
||||
@task-created="handleTaskCreated"
|
||||
/>
|
||||
<TextVideo
|
||||
ref="textVideoRef"
|
||||
@ -59,13 +60,15 @@ interface TextVideoExposed {
|
||||
resetForm: () => void;
|
||||
}
|
||||
|
||||
/** 图生视频组件暴露的方法(示例,根据实际情况补充) */
|
||||
/** 图生视频组件暴露的方法 */
|
||||
interface ImgVideoExposed {
|
||||
generate: () => Promise<boolean>;
|
||||
resetForm: () => void;
|
||||
}
|
||||
|
||||
/** 参考生视频组件暴露的方法(示例) */
|
||||
/** 参考生视频组件暴露的方法 */
|
||||
interface ReferenceVideoExposed {
|
||||
generate?: () => Promise<boolean>;
|
||||
resetForm: () => void;
|
||||
}
|
||||
|
||||
@ -123,7 +126,11 @@ const handleTabChange = (tab: 'reference' | 'image' | 'text') => {
|
||||
|
||||
// 创作按钮点击
|
||||
const handleCreate = async () => {
|
||||
if (isCreateButtonDisabled.value) return;
|
||||
if (isCreateButtonDisabled.value) {
|
||||
ElMessage.warning('请先填写必要信息');
|
||||
return;
|
||||
}
|
||||
|
||||
let result = false;
|
||||
switch (activeTab.value) {
|
||||
case 'text':
|
||||
@ -131,6 +138,18 @@ const handleCreate = async () => {
|
||||
result = await textVideoRef.value.generate();
|
||||
}
|
||||
break;
|
||||
case 'image':
|
||||
if (imgVideoRef.value?.generate) {
|
||||
result = await imgVideoRef.value.generate();
|
||||
}
|
||||
break;
|
||||
case 'reference':
|
||||
if (referenceVideoRef.value?.generate) {
|
||||
result = await referenceVideoRef.value.generate();
|
||||
} else {
|
||||
ElMessage.info('参考生视频功能暂未开放');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ElMessage.info('该功能暂未开放');
|
||||
break;
|
||||
@ -200,7 +219,7 @@ onMounted(() => updateActiveTabFromRoute());
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: linear-gradient(180deg, rgba(2, 11, 19, 0) 0%, #0f0f19 100%);
|
||||
// background: linear-gradient(180deg, rgba(2, 11, 19, 0) 0%, #0f0f19 100%);
|
||||
// position: relative;
|
||||
}
|
||||
.create-button {
|
||||
|
||||
@ -227,4 +227,9 @@ export interface TaskItem {
|
||||
model?: string;
|
||||
use_time?: string;
|
||||
action?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface ModelParams {
|
||||
type: string;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user