Commit 7bdfd5aa authored by 朱国瑞's avatar 朱国瑞

登录校验和裁剪视频功能

parent dd2526f1
VITE_APP_BASE_URL=http://se-test.wmdigit.com
\ No newline at end of file
......@@ -5,5 +5,5 @@
// Generated by unplugin-auto-import
export {}
declare global {
const ElMessage: typeof import('element-plus/es')['ElMessage']
}
......@@ -33,9 +33,11 @@ declare module 'vue' {
IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
LoginDialog: typeof import('./src/components/LoginDialog.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
VideoCrop: typeof import('./src/components/VideoCrop.vue')['default']
WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default']
}
export interface ComponentCustomProperties {
......
......@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>text2video</title>
</head>
<body>
......
......@@ -11,12 +11,14 @@
"@element-plus/icons-vue": "^2.1.0",
"axios": "^1.6.1",
"bootstrap": "^5.3.3",
"cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
"element-plus": "^2.4.2",
"file-saver": "^2.0.5",
"pinia": "^2.1.7",
"sass": "^1.69.5",
"vue": "^3.3.4",
"vue-drag-resize": "^2.0.3",
"vue-router": "^4.2.5"
},
"devDependencies": {
......@@ -1267,6 +1269,11 @@
"dev": true,
"license": "MIT"
},
"node_modules/cropperjs": {
"version": "1.6.2",
"resolved": "https://registry.npmmirror.com/cropperjs/-/cropperjs-1.6.2.tgz",
"integrity": "sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA=="
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
......@@ -4168,6 +4175,11 @@
}
}
},
"node_modules/vue-drag-resize": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/vue-drag-resize/-/vue-drag-resize-2.0.3.tgz",
"integrity": "sha512-5q03tZ/LyvQsg1iHRcqs+wI2OKNbNIWl9+7V8rVL6MxJhZLCIYSSgbAUaDE38LhD6dFd5aJhdgNmES61AxjXuw=="
},
"node_modules/vue-eslint-parser": {
"version": "9.3.2",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.2.tgz",
......
......@@ -15,12 +15,14 @@
"@element-plus/icons-vue": "^2.1.0",
"axios": "^1.6.1",
"bootstrap": "^5.3.3",
"cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
"element-plus": "^2.4.2",
"file-saver": "^2.0.5",
"pinia": "^2.1.7",
"sass": "^1.69.5",
"vue": "^3.3.4",
"vue-drag-resize": "^2.0.3",
"vue-router": "^4.2.5"
},
"devDependencies": {
......
<script setup lang="ts">
import { RouterLink, RouterView } from "vue-router";
import { ElConfigProvider } from "element-plus";
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
const locale = zhCn;
import { RouterLink, RouterView } from 'vue-router'
import { ElConfigProvider } from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
const locale = zhCn
</script>
<template>
......
import { getToken } from '@/utils/token'
import axios from 'axios'
import { ElLoading } from 'element-plus'
import { ElLoading, ElMessage } from 'element-plus'
const VERSION = '20210531'
......@@ -40,7 +41,18 @@ function isHideLoading() {
request.interceptors.request.use(
(config: any) => {
config.headers['tenant'] = '0'
// permission 处理
if (config.url === 'user/refresh') {
const refreshToken = getToken('refresh_token')
if (refreshToken) {
config.headers['Authorization'] = refreshToken
}
} else {
const accessToken = getToken('access_token')
if (accessToken) {
config.headers['Authorization'] = accessToken
}
}
if (!isHideLoading()) {
loading = ElLoading.service({ fullscreen: true })
}
......
......@@ -3,49 +3,61 @@
*/
import request from '@/api/request'
export default {
submitLLM(prompt: string, llm: string = "gpt", history: Wm.PromptHistory[] = [], task_id: string = "-", encrypt: string = "false"): Promise<string> {
submitLLM(
prompt: string,
llm: string = 'gpt',
history: Wm.PromptHistory[] = [],
task_id: string = '-',
encrypt: string = 'false'
): Promise<string> {
if (!prompt) {
return Promise.reject("输入不能为空");
return Promise.reject('输入不能为空')
}
const post_data = {
source_text: prompt,
history: history,
llm: llm,
task_id: task_id,
encrypt: encrypt
}
const post_data = { source_text: prompt, history: history, llm: llm, task_id: task_id, encrypt: encrypt }
return request.post('/text2video/text2llm', post_data)
return request
.post('/text2video/text2llm', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
return res.data.result.answer;
return res.data.result.answer
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`与LLM通讯失败`);
return Promise.reject(`与LLM通讯失败`)
}
});
})
},
submitSD(
task_id: string = "",
img_idx: string = "",
prompt: string = "",
negative_prompt: string = "",
width: string = "",
height: string = "",
sampler_index: string = "",
seed: string = "",
steps: string = "",
cfg_scale: string = "",
encrypt: string = "false",
model: string = ""
): Promise<{"domain_image_path": string, "local_image_path": string}> {
task_id: string = '',
img_idx: string = '',
prompt: string = '',
negative_prompt: string = '',
width: string = '',
height: string = '',
sampler_index: string = '',
seed: string = '',
steps: string = '',
cfg_scale: string = '',
encrypt: string = 'false',
model: string = ''
): Promise<{ domain_image_path: string; local_image_path: string }> {
if (!prompt) {
return Promise.reject("SD提示词不能为空");
return Promise.reject('SD提示词不能为空')
}
const post_data = {
task_id: task_id,
......@@ -59,232 +71,251 @@ export default {
height: height,
cfg_scale: cfg_scale,
encrypt: encrypt,
model: model,
model: model
}
return request.post('/text2video/text2img', post_data)
return request
.post('/text2video/text2img', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
return {"domain_image_path": res.data.result.domain_image_path, "local_image_path": res.data.result.local_image_path};
return {
domain_image_path: res.data.result.domain_image_path,
local_image_path: res.data.result.local_image_path
}
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`与 stable-diffusion-webui Api 通讯失败`);
return Promise.reject(`与 stable-diffusion-webui Api 通讯失败`)
}
});
})
},
submitGenVideo(gen_video_param: any): Promise<string> {
if (!gen_video_param) {
return Promise.reject("输入不能为空");
return Promise.reject('输入不能为空')
}
const post_data = gen_video_param
return request.post('/text2video/gen_video', post_data)
return request
.post('/text2video/gen_video', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
return res.data.result.domain_video_path;
return res.data.result.domain_video_path
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`gen_video接口通讯失败`);
return Promise.reject(`gen_video接口通讯失败`)
}
});
})
},
submitPwdCheck(pwd: string): Promise<string> {
if (!pwd) {
return Promise.reject("密码不能为空");
return Promise.reject('密码不能为空')
}
const post_data = { pwd: pwd }
return request.post('/text2video/pwd_check', post_data)
return request
.post('/text2video/pwd_check', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
return res.data.result;
return res.data.result
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`与pwd_check接口通讯失败`);
return Promise.reject(`与pwd_check接口通讯失败`)
}
});
})
},
submitTranslateToEn(input_string: string, task_id: string = "-", encrypt: string = "false"): Promise<string> {
submitTranslateToEn(
input_string: string,
task_id: string = '-',
encrypt: string = 'false'
): Promise<string> {
if (!input_string) {
return Promise.reject("输入不能为空");
return Promise.reject('输入不能为空')
}
const post_data = { input_string: input_string, task_id: task_id, encrypt: encrypt }
return request.post('/text2video/translate_to_en', post_data)
return request
.post('/text2video/translate_to_en', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
return res.data.result;
return res.data.result
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`与翻译接口通讯失败`);
return Promise.reject(`与翻译接口通讯失败`)
}
});
})
},
submitAddTextToImg(param: any): Promise<{"domain_image_path": string, "local_image_path": string}> {
submitAddTextToImg(param: any): Promise<{ domain_image_path: string; local_image_path: string }> {
const post_data = param
return request.post('/text2video/add_text_to_img', post_data)
return request
.post('/text2video/add_text_to_img', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
return {"domain_image_path": res.data.result.domain_image_path, "local_image_path": res.data.result.local_image_path};
return {
domain_image_path: res.data.result.domain_image_path,
local_image_path: res.data.result.local_image_path
}
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`add_text_to_img接口通讯失败`);
return Promise.reject(`add_text_to_img接口通讯失败`)
}
});
})
},
submitTest(prompt: string): Promise<string> {
const post_data = { source_text: prompt }
return request.post('/text2video/test', post_data)
return request
.post('/text2video/test', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
return res.data.result;
return res.data.result
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`与Test Api通讯失败`);
return Promise.reject(`与Test Api通讯失败`)
}
});
})
},
submitImgToText(param: any): Promise<string> {
const post_data = param;
return request.post('/text2video/img2text', post_data)
const post_data = param
return request
.post('/text2video/img2text', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
return res.data.result;
return res.data.result
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`与Img2Text Api通讯失败`);
return Promise.reject(`与Img2Text Api通讯失败`)
}
});
})
},
submitImgTextMatch(param: any): Promise<any[]> {
const post_data = param;
return request.post('/text2video/img_text_match', post_data)
const post_data = param
return request
.post('/text2video/img_text_match', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
return res.data.result;
return res.data.result
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`与ImgTextMatch Api通讯失败`);
return Promise.reject(`与ImgTextMatch Api通讯失败`)
}
});
})
},
submitCutOutImg(param: any): Promise<any> {
const post_data = param;
return request.post('/text2video/cutout_from_img', post_data)
const post_data = param
return request
.post('/text2video/cutout_from_img', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
return res.data.result;
return res.data.result
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`与 cutout_from_img Api 通讯失败`);
return Promise.reject(`与 cutout_from_img Api 通讯失败`)
}
});
})
},
submitSDInPaint(
task_id: string = "",
img_idx: string = "",
prompt: string = "",
negative_prompt: string = "",
width: string = "",
height: string = "",
sampler_index: string = "",
seed: string = "",
steps: string = "",
cfg_scale: string = "",
encrypt: string = "false",
model: string = "",
base_img: string = "",
mask: string = "",
img_data_type: string = ""
): Promise<{"domain_image_path": string, "local_image_path": string}> {
task_id: string = '',
img_idx: string = '',
prompt: string = '',
negative_prompt: string = '',
width: string = '',
height: string = '',
sampler_index: string = '',
seed: string = '',
steps: string = '',
cfg_scale: string = '',
encrypt: string = 'false',
model: string = '',
base_img: string = '',
mask: string = '',
img_data_type: string = ''
): Promise<{ domain_image_path: string; local_image_path: string }> {
if (!prompt || !base_img || !mask) {
return Promise.reject("SD提示词、基础图、mask均不能为空");
return Promise.reject('SD提示词、基础图、mask均不能为空')
}
const post_data = {
task_id: task_id,
......@@ -303,51 +334,56 @@ export default {
mask: mask,
img_data_type: img_data_type
}
return request.post('/text2video/img2img_inpaint', post_data)
return request
.post('/text2video/img2img_inpaint', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
return {"domain_image_path": res.data.result.domain_image_path, "local_image_path": res.data.result.local_image_path};
return {
domain_image_path: res.data.result.domain_image_path,
local_image_path: res.data.result.local_image_path
}
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`与 img2img_inpaint Api 通讯失败`);
return Promise.reject(`与 img2img_inpaint Api 通讯失败`)
}
});
})
},
submitGenDigitHumanVideo(post_data: any): Promise<string> {
if (!post_data) {
return Promise.reject("输入不能为空");
return Promise.reject('输入不能为空')
}
return request.post('/text2video/gen_video_digithuman', post_data)
return request
.post('/text2video/gen_video_digithuman', post_data)
.then((res: any) => {
// console.log(res);
if (res && res.code === 0) {
if (res.data.result.final_video_oss) {
return res.data.result.final_video_oss;
return res.data.result.final_video_oss
} else {
return Promise.reject("视频合成失败");
return Promise.reject('视频合成失败')
}
} else {
const errorMessage = res ? res.message : "未知错误";
return Promise.reject(errorMessage);
const errorMessage = res ? res.message : '未知错误'
return Promise.reject(errorMessage)
}
})
.catch((err: any) => {
console.log(`err = ${JSON.stringify(err)}`);
console.log(`err = ${JSON.stringify(err)}`)
try {
return Promise.reject(err.message);
return Promise.reject(err.message)
} catch (e) {
return Promise.reject(`与后端 gen_video_digithuman 接口通讯失败`);
return Promise.reject(`与后端 gen_video_digithuman 接口通讯失败`)
}
})
}
});
},
}
/**
* 用户相关接口
*/
import request from '@/api/request'
import { useAuthStore } from '@/stores/state'
import { saveTokens } from '@/utils/token'
export default {
// 登录
login(postData: Wm.LoginParams) {
return new Promise((resolve, reject) => {
request
.post('/user/login', postData)
.then((res: any) => {
const authStore = useAuthStore()
authStore.login()
saveTokens(res.access_token, res.refresh_token)
resolve(res)
})
.catch((err: any) => {
reject(err)
})
})
},
// 注册
register(postData: Wm.RegisterParams) {
return request.post('/user/login', postData)
}
}
<script lang="ts" setup>
import userService from '@/api/service/userService'
import utils from '@/utils/utils'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { onMounted, reactive, ref, watch, watchEffect } from 'vue'
const props = defineProps(['visible'])
const emit = defineEmits(['update:visible'])
const isVisible = ref(props.visible)
const loginFormRef = ref<FormInstance>()
const title = ref('登录')
const isRegister = ref(false)
const isMobile = ref(utils.checkIsMobile())
const loginForm = reactive({
username: '',
password: '',
captcha: ''
})
const rules = reactive<FormRules<typeof loginForm>>({
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
})
onMounted(() => {
console.log('mounted')
})
watch(
() => props.visible,
(val) => {
console.log('visible', val)
isVisible.value = val
}
)
const onCancel = () => {
console.log('cancel')
emit('update:visible', false)
}
const handleSwitch = () => {
console.log('handleSwitchRegister')
isRegister.value = !isRegister.value
if (isRegister.value) {
title.value = '注册'
} else {
title.value = '登录'
}
}
const onConfirm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
const postData = {
username: loginForm.username,
password: loginForm.password
}
userService
.login(postData)
.then((res: any) => {
console.log('res', res)
ElMessage.success({
message: '登录成功'
})
handleClose()
})
.catch((err: any) => {
console.log('err', err)
ElMessage.error({
message: err.message
})
})
} else {
console.log('error submit!', fields)
}
})
}
const handleClose = () => {
console.log('handleClose')
emit('update:visible', false)
}
</script>
<template>
<el-dialog
v-model="isVisible"
lock-scroll
:close-on-click-modal="false"
:show-close="false"
:title="title"
:width="isMobile ? '80%' : '400px'"
align-center
center
class="wm-login-dialog"
:class="{ 'is-mobile': isMobile }"
>
<el-form
ref="loginFormRef"
style="max-width: 400px"
:model="loginForm"
status-icon
:rules="rules"
label-width="auto"
class="wm-login-form"
>
<el-form-item label="用户名" prop="username">
<el-input class="wm-input" v-model="loginForm.username" type="text" autocomplete="off" />
</el-form-item>
<el-form-item label="密码 " prop="password">
<el-input
class="wm-input"
v-model="loginForm.password"
type="password"
autocomplete="off"
/>
</el-form-item>
<!-- <el-form-item label="验证码" prop="captcha">
<el-input v-model.number="loginForm.captcha" />
</el-form-item> -->
<!-- <div class="register-tips">
<span class="link" @click="handleSwitch">{{ isRegister ? '去登录' : '去注册' }}</span>
</div> -->
</el-form>
<template #footer>
<div class="dialog-footer">
<!-- <el-button @click="onCancel">Cancel</el-button> -->
<el-button type="primary" class="login-btn" @click="onConfirm(loginFormRef)">
登录
</el-button>
</div>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
.wm-login-dialog {
.wm-login-form {
.register-tips {
text-align: right;
.link {
color: #409eff;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
}
.login-btn {
width: 100%;
}
.wm-input {
width: 300px;
}
&.is-mobile {
.wm-input {
width: 100%;
}
}
}
</style>
<style lang="scss">
.wm-login-dialog {
.el-dialog__body {
padding: 10px calc(var(--el-dialog-padding-primary) + 5px) 10px;
}
}
</style>
<script lang="ts" setup>
import userService from '@/api/service/userService'
import utils from '@/utils/utils'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { nextTick, onMounted, reactive, ref, toRaw, watch, watchEffect } from 'vue'
import VueDragResize from 'vue-drag-resize'
const props = defineProps({
visible: {
type: Boolean,
default: false
},
url: {
type: String,
default: ''
},
cropArea: {
type: Array,
default: () => []
}
})
watch(
() => [props.cropArea, props.visible],
(newVal, oldVal) => {
console.log('cropArea', newVal[0])
if (!props.visible) {
showCropBox.value = false
// handleInitCropArea()
}
}
)
const emit = defineEmits(['update:visible', 'crop'])
const isVisible = ref(props.visible)
const loginFormRef = ref<FormInstance>()
const title = ref('裁剪视频')
const isMobile = ref(utils.checkIsMobile())
const cropBoxRef = ref<any>()
const backgroundVideoRef = ref<HTMLVideoElement>()
const croppedCanvasRef = ref<HTMLCanvasElement>()
const rectData = ref({
left: 0,
top: 0,
height: 100,
width: 100
})
const showCropBox = ref(false)
const cropPreviewBox = ref({
width: 0,
height: 0
})
onMounted(() => {
console.log('mounted')
})
watch(
() => props.visible,
(val) => {
console.log('visible', val)
isVisible.value = val
}
)
const onCancel = () => {
console.log('cancel')
handleClose()
}
const onConfirm = async () => {
const video = backgroundVideoRef.value
// 计算裁剪框在视频中的位置
const scaleX = video!.videoWidth / video!.clientWidth
const scaleY = video!.videoHeight / video!.clientHeight
const cropData = {
x: Math.round(rectData.value.left * scaleX),
y: Math.round(rectData.value.top * scaleY),
width: Math.round(rectData.value.width * scaleX),
height: Math.round(rectData.value.height * scaleY)
}
if (cropData.width <= 0 || cropData.height <= 0) {
ElMessage.error('裁剪框尺寸不正确')
return
}
if (cropData.x < 0) {
cropData.x = 0
}
if (cropData.y < 0) {
cropData.y = 0
}
if (cropData.width > video!.videoWidth) {
cropData.width = video!.videoWidth
}
if (cropData.x + cropData.width > video!.videoWidth) {
cropData.width = video!.videoWidth - cropData.x
}
if (cropData.height > video!.videoHeight) {
cropData.height = video!.videoHeight
}
if (cropData.y + cropData.height > video!.videoHeight) {
cropData.height = video!.videoHeight - cropData.y
}
console.log(rectData.value, scaleX, scaleY)
console.log('cropData', cropData)
// 转换为 左上角坐标 右下角坐标 例如:[[100,200], [300,400]]
const cropAreaData = [
[cropData.x, cropData.y],
[cropData.x + cropData.width, cropData.y + cropData.height]
]
emit('crop', cropAreaData)
handleClose()
}
const handleClose = () => {
console.log('handleClose')
emit('update:visible', false)
}
const cropVideoFrame = () => {
try {
const cropBox = cropBoxRef.value.$el.getBoundingClientRect()
const video = backgroundVideoRef.value
const canvas = croppedCanvasRef.value
if (!video || !canvas) {
return
}
const ctx = canvas!.getContext('2d')
const scaleX = video!.videoWidth / video!.clientWidth
const scaleY = video!.videoHeight / video!.clientHeight
canvas!.width = cropBox.width
canvas!.height = cropBox.height
ctx!.drawImage(
video!,
(cropBox.left - video!.getBoundingClientRect().left) * scaleX,
(cropBox.top - video!.getBoundingClientRect().top) * scaleY,
cropBox.width * scaleX,
cropBox.height * scaleY,
0,
0,
cropBox.width,
cropBox.height
)
// 递归调用,实现每一帧都更新
requestAnimationFrame(cropVideoFrame)
} catch (error) {
console.log(error)
}
}
const resize = (newRect: any) => {
// console.log(newRect)
rectData.value = newRect
}
const onVideoLoaded = (val: any) => {
console.log('onVideoLoaded', val)
const video = backgroundVideoRef.value
cropPreviewBox.value = {
width: video?.clientWidth || 0,
height: video?.clientHeight || 0
}
showCropBox.value = true
const cropArea: any = toRaw(props.cropArea)
if (cropArea.length > 0) {
const scaleX = cropPreviewBox.value.width / video!.videoWidth
const scaleY = cropPreviewBox.value.height / video!.videoHeight
console.log('cropArea', cropArea, scaleX, scaleY)
const left = Math.round(cropArea[0][0] * scaleX)
const top = Math.round(cropArea[0][1] * scaleY)
const width = Math.round((cropArea[1][0] - cropArea[0][0]) * scaleX)
const height = Math.round((cropArea[1][1] - cropArea[0][1]) * scaleY)
console.log('init cropArea', left, top, width, height)
rectData.value = {
left,
top,
width,
height
}
} else {
// 全部显示
rectData.value = {
left: 0,
top: 0,
width: video!.clientWidth,
height: video!.clientHeight
}
}
// nextTick(() => {
// cropVideoFrame()
// })
}
const handleInitCropArea = () => {
const video = backgroundVideoRef.value
const cropArea: any = toRaw(props.cropArea)
if (!video) {
return
}
if (cropArea.length > 0) {
const scaleX = cropPreviewBox.value.width / video!.videoWidth
const scaleY = cropPreviewBox.value.height / video!.videoHeight
console.log('cropArea', cropArea, scaleX, scaleY)
const left = Math.round(cropArea[0][0] * scaleX)
const top = Math.round(cropArea[0][1] * scaleY)
const width = Math.round((cropArea[1][0] - cropArea[0][0]) * scaleX)
const height = Math.round((cropArea[1][1] - cropArea[0][1]) * scaleY)
console.log('init cropArea', left, top, width, height)
rectData.value = {
left,
top,
width,
height
}
}
}
</script>
<template>
<el-dialog
v-model="isVisible"
:close-on-click-modal="false"
:show-close="false"
:title="title"
:width="isMobile ? '80%' : '400px'"
align-center
center
lock-scroll
class="wm-video-crop-dialog"
:class="{ 'is-mobile': isMobile }"
>
<!-- 底部视频和裁剪框 -->
<div class="video-crop-container">
<video
ref="backgroundVideoRef"
:src="url"
autoplay
loop
muted
webkit-playsinline
playsinline
class="background-video"
@loadeddata="onVideoLoaded"
v-if="isVisible"
></video>
<VueDragResize
ref="cropBoxRef"
:w="rectData.width"
:h="rectData.height"
:x="rectData.left"
:y="rectData.top"
:parent-limitation="true"
:is-resizable="true"
:is-draggable="true"
class="crop-box"
v-on:resizing="resize"
v-on:dragging="resize"
:isActive="true"
v-if="showCropBox"
></VueDragResize>
</div>
<!-- 显示裁剪后的内容 -->
<div class="cropped-image-container" v-if="false">
<div class="title">裁剪后的视频显示效果</div>
<div
:style="{
width: cropPreviewBox.width + 'px',
height: cropPreviewBox.height + 'px'
}"
>
<canvas ref="croppedCanvasRef"></canvas>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button class="btn" @click="onCancel">取消</el-button>
<el-button type="primary" class="btn confirm" @click="onConfirm"> 裁剪 </el-button>
</div>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
.wm-video-crop-dialog {
.video-crop-container {
position: relative;
width: 100%;
font-size: 0;
.background-video {
width: 100%;
height: 100%;
object-fit: cover;
}
.crop-box {
border-color: var(--el-color-primary-dark-2);
position: absolute;
cursor: move;
.vdr-stick {
background: var(--el-color-primary-dark-2);
border: 1px solid var(--el-color-primary-dark-2);
-webkit-box-shadow: 0 0 2px var(--el-color-primary-dark-2);
box-shadow: 0 0 2px var(--el-color-primary-dark-2);
}
}
}
.cropped-image-container {
margin-top: 10px;
// text-align: center;
.title {
font-size: 16px;
margin-bottom: 10px;
}
}
.brn {
&.confirm {
margin-left: 10px;
}
}
&.is-mobile {
.wm-input {
width: 100%;
}
}
}
</style>
<style lang="scss">
.wm-video-crop-dialog {
.el-dialog__body {
padding: 10px calc(var(--el-dialog-padding-primary) + 5px) 10px;
}
.video-crop-container {
.crop-box {
border-color: var(--el-color-primary);
&::before {
outline: 1px dashed var(--el-color-primary);
}
.vdr-stick {
background: var(--el-color-primary);
border: 1px solid var(--el-color-primary);
-webkit-box-shadow: 0 0 2px var(--el-color-primary);
box-shadow: 0 0 2px var(--el-color-primary);
}
}
}
}
</style>
......@@ -7,6 +7,7 @@ import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import VueDragResize from 'vue-drag-resize'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
......@@ -15,5 +16,6 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
app.use(VueDragResize)
app.mount('#app')
......@@ -5,7 +5,6 @@ import RecordSteps from '../views/home/record_steps.vue'
import CalcRetire from '../views/home/calc_retire.vue'
import GenDigitHumanVideo from '../views/home/gen_digit_human_video.vue'
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
......
import { getToken } from '@/utils/token'
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
isLoggedIn: getToken('access_token') ? true : false
}),
actions: {
login() {
this.isLoggedIn = true
},
logout() {
this.isLoggedIn = false
}
}
})
/**
* 存储tokens
* @param {string} accessToken
* @param {string} refreshToken
*/
export function saveTokens(accessToken: string, refreshToken: string) {
localStorage.setItem('access_token', `Bearer ${accessToken}`)
localStorage.setItem('refresh_token', `Bearer ${refreshToken}`)
}
/**
* 存储access_token
* @param {string} accessToken
*/
export function saveAccessToken(accessToken: string) {
localStorage.setItem('access_token', `Bearer ${accessToken}`)
}
/**
* 获得某个token
* @param {string} tokenKey
*/
export function getToken(tokenKey: string) {
return localStorage.getItem(tokenKey)
}
/**
* 移除token
*/
export function removeToken() {
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token')
}
import _cryptoJs from 'crypto-js';
import _cryptoJs from 'crypto-js'
export default class utils {
// 格式化为json字符串
static formatJson(jsonString: string) {
try {
return JSON.stringify(JSON.parse(jsonString), null, 2); // 对JSON字符串进行格式化处理
return JSON.stringify(JSON.parse(jsonString), null, 2) // 对JSON字符串进行格式化处理
} catch (error) {
return 'Invalid JSON'
}
......@@ -22,22 +22,22 @@ export default class utils {
// 从文本中提取 JSON
static extractJSON(text: string) {
// 正则表达式匹配 JSON 格式的字符串
const jsonRegex = /\[\s*\{\s*"序号"\s*:\s*\d+.*?\}\s*\]/s;
const matches = text.match(jsonRegex);
return matches ? matches[0] : null;
const jsonRegex = /\[\s*\{\s*"序号"\s*:\s*\d+.*?\}\s*\]/s
const matches = text.match(jsonRegex)
return matches ? matches[0] : null
}
// 生成年月日时分秒毫秒字符串
static genDateTimeStr() {
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
const milliseconds = now.getMilliseconds().toString().padStart(3, '0');
const formattedDateTime = `${year}${month}${day}${hours}${minutes}${seconds}${milliseconds}`;
const now = new Date()
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const day = now.getDate().toString().padStart(2, '0')
const hours = now.getHours().toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
const seconds = now.getSeconds().toString().padStart(2, '0')
const milliseconds = now.getMilliseconds().toString().padStart(3, '0')
const formattedDateTime = `${year}${month}${day}${hours}${minutes}${seconds}${milliseconds}`
// console.log(formattedDateTime); // 输出类似:20221231120530123
return formattedDateTime
}
......@@ -45,39 +45,41 @@ export default class utils {
// 对一句话再次拆分文本
static splitDetailText(sentences: string[]) {
// console.log(sentences)
let result_sentences: string[] = [];
let currentSentence = '';
let result_sentences: string[] = []
let currentSentence = ''
for (let i = 0; i < sentences.length; i++) {
const str = sentences[i];
currentSentence += str + ',';
const str = sentences[i]
currentSentence += str + ','
if (i < sentences.length - 1 && (currentSentence + sentences[i + 1]).length <= 20) {
continue;
continue
}
if (i === sentences.length - 2 && sentences[i + 1].length <= 5) {
continue;
continue
}
if (currentSentence.length < 10) {
continue;
continue
}
result_sentences.push(currentSentence.endsWith(",") ? currentSentence.slice(0, -1) : currentSentence);
currentSentence = '';
result_sentences.push(
currentSentence.endsWith(',') ? currentSentence.slice(0, -1) : currentSentence
)
currentSentence = ''
}
// console.log('result_sentences=', result_sentences);
return result_sentences;
return result_sentences
}
static splitMoreText (sentences: string[]) {
static splitMoreText(sentences: string[]) {
// console.log(sentences)
let result_sentences: string[] = [];
let result_sentences: string[] = []
for (let i = 0; i < sentences.length; i++) {
const str = sentences[i];
let tempSentences = str.split(/[!|?|。|!|?|,|,]/);
let newList = utils.splitDetailText(tempSentences);
result_sentences = result_sentences.concat(newList);
const str = sentences[i]
let tempSentences = str.split(/[!|?|。|!|?|,|,]/)
let newList = utils.splitDetailText(tempSentences)
result_sentences = result_sentences.concat(newList)
}
// console.log('result_sentences=', result_sentences);
return result_sentences;
};
return result_sentences
}
// 拆分文本
static splitText(str: string, type: string = 'default') {
......@@ -98,37 +100,47 @@ export default class utils {
// 拆分英文文本
static splitTextEn(str: string) {
str = str.replaceAll('"','').replaceAll('"','')
str = str.replaceAll('"', '').replaceAll('"', '')
// 使用正则表达式拆分文本
let sentences = str.split(/[!|?|.]/);
let sentences = str.split(/[!|?|.]/)
// 过滤掉长度为 0 的句子
sentences = sentences.filter(s => s.length > 0);
sentences = sentences.filter((s) => s.length > 0)
// console.log(sentences)
return sentences
}
// 过滤掉中文字符
static filterChineseAndPunctuation(inputString: string) {
return inputString.replace(/[\u4E00-\u9FA5\u3000-\u303F\uff00-\uffef]/g, '') // 过滤中文字符
return inputString
.replace(/[\u4E00-\u9FA5\u3000-\u303F\uff00-\uffef]/g, '') // 过滤中文字符
.replace(/[^\w\s]|_/g, '') // 过滤标点符号
.replace(/\s+/g, ' '); // 连续多个空格替换为一个空格
.replace(/\s+/g, ' ') // 连续多个空格替换为一个空格
}
// 检查该字符串是否只包含中文标点符号和英文标点符号
static containsOnlyPunctuation(str: string) {
// 使用正则表达式匹配是否只包含标点符号(包括中文标点)
return /^[!-\/:-@\[-`{-~\p{P}\p{S}\s]*$/u.test(str);
return /^[!-\/:-@\[-`{-~\p{P}\p{S}\s]*$/u.test(str)
}
// 加密
static aesEncrypt(word: string) {
var key = _cryptoJs.enc.Utf8.parse('e6ef616dc57343248f6b3e98a07e1dde');
var srcs = _cryptoJs.enc.Utf8.parse(word);
var key = _cryptoJs.enc.Utf8.parse('e6ef616dc57343248f6b3e98a07e1dde')
var srcs = _cryptoJs.enc.Utf8.parse(word)
var encrypted = _cryptoJs.AES.encrypt(srcs, key, {
mode: _cryptoJs.mode.ECB,
padding: _cryptoJs.pad.Pkcs7
});
return encrypted.toString();
})
return encrypted.toString()
}
static checkIsMobile = () => {
const userAgent = navigator.userAgent || navigator.vendor
if (/Mobile|Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent)) {
return true
} else {
return false
}
}
}
<script setup lang="ts">
import text2videoService from "@/api/service/text2videoService";
import utils from "@/utils/utils";
import text2videoService from '@/api/service/text2videoService'
import utils from '@/utils/utils'
import {
ElMessage, genFileId,
ElMessage,
genFileId,
type UploadInstance,
type UploadProps,
type UploadRawFile
} from "element-plus";
import { nextTick, onMounted, reactive, ref } from "vue";
} from 'element-plus'
import { nextTick, onMounted, reactive, ref } from 'vue'
const debug = ref(import.meta.env.MODE === 'production' ? false : true);
const debug = ref(import.meta.env.MODE === 'production' ? false : true)
// const debug = ref(false);
const loading = ref(false);
const loading = ref(false)
const form = reactive({
task_id: "",
birth: "1976-01",
sex_value: "男",
task_id: '',
birth: '1976-01',
sex_value: '男',
sex_option: [
{
value: '男',
label: '男',
label: '男'
},
{
value: '女50岁退休',
label: '女(普通)50岁退',
label: '女(普通)50岁退'
},
{
value: '女55岁退休',
label: '女(管理、灵活就业)55岁退',
},
label: '女(管理、灵活就业)55岁退'
}
],
retire_age: "",
retire_date: "",
how_long_to_retire: "",
});
retire_age: '',
retire_date: '',
how_long_to_retire: ''
})
onMounted(() => {
// 初始化task_id
form.task_id = utils.genDateTimeStr();
console.log('页面加载,task_id=', form.task_id);
document.title = "延迟退休年龄计算器";
});
form.task_id = utils.genDateTimeStr()
console.log('页面加载,task_id=', form.task_id)
document.title = '延迟退休年龄计算器'
})
const calculateRetirement = (birthStr: string, gender: string) => {
console.log(birthStr)
let parts = birthStr.split('-'); // 使用'-'作为分隔符来拆分字符串
let birthYear = parseInt(parts[0], 10); // 年份
let birthMonth = parseInt(parts[1], 10); // 月份
let parts = birthStr.split('-') // 使用'-'作为分隔符来拆分字符串
let birthYear = parseInt(parts[0], 10) // 年份
let birthMonth = parseInt(parts[1], 10) // 月份
const currentYear = new Date().getFullYear();
const birthDate = new Date(`${birthYear}-${birthMonth < 10 ? '0' + birthMonth : birthMonth}-01`);
let retirementAge, delayMonths, retirementYear, retirementMonth;
const currentYear = new Date().getFullYear()
const birthDate = new Date(`${birthYear}-${birthMonth < 10 ? '0' + birthMonth : birthMonth}-01`)
let retirementAge, delayMonths, retirementYear, retirementMonth
if (gender === '男') {
retirementAge = 60;
delayMonths = Math.ceil(((birthYear - 1965)*12 + birthMonth) / 4);
retirementAge = 60
delayMonths = Math.ceil(((birthYear - 1965) * 12 + birthMonth) / 4)
} else if (gender === '女50岁退休') {
retirementAge = 50;
delayMonths = Math.ceil(((birthYear - 1975)*12 + birthMonth) / 2);
retirementAge = 50
delayMonths = Math.ceil(((birthYear - 1975) * 12 + birthMonth) / 2)
} else if (gender === '女55岁退休') {
retirementAge = 55;
delayMonths = Math.ceil(((birthYear - 1970)*12 + birthMonth) / 4);
retirementAge = 55
delayMonths = Math.ceil(((birthYear - 1970) * 12 + birthMonth) / 4)
} else {
console.log("Invalid gender input. Please choose '男', '女50岁退休', or '女55岁退休'.");
return;
console.log("Invalid gender input. Please choose '男', '女50岁退休', or '女55岁退休'.")
return
}
if (birthYear >= 1977 && gender == '男') {
delayMonths = 36;
delayMonths = 36
} else if (birthYear >= 1985 && gender == '女50岁退休') {
delayMonths = 60;
delayMonths = 60
} else if (birthYear >= 1982 && gender == '女55岁退休') {
delayMonths = 36;
delayMonths = 36
}
retirementAge += Math.floor(delayMonths / 12);
retirementAge += Math.floor(delayMonths / 12)
console.log(retirementAge, delayMonths)
let retirementDate = new Date(birthDate);
let retirementDate = new Date(birthDate)
console.log(retirementDate)
retirementDate.setFullYear(retirementDate.getFullYear() + retirementAge);
retirementDate.setMonth(retirementDate.getMonth() + delayMonths % 12);
retirementDate.setFullYear(retirementDate.getFullYear() + retirementAge)
retirementDate.setMonth(retirementDate.getMonth() + (delayMonths % 12))
console.log(retirementDate)
retirementYear = retirementDate.getFullYear();
retirementMonth = retirementDate.getMonth() + 1; // getMonth() returns 0-11
retirementYear = retirementDate.getFullYear()
retirementMonth = retirementDate.getMonth() + 1 // getMonth() returns 0-11
console.log(retirementYear, retirementMonth)
let yearsUntilRetirement = retirementDate.getFullYear() - currentYear -1;
let monthsUntilRetirement = (retirementDate.getMonth() - new Date().getMonth() + 12) % 12 -1;
let yearsUntilRetirement = retirementDate.getFullYear() - currentYear - 1
let monthsUntilRetirement = ((retirementDate.getMonth() - new Date().getMonth() + 12) % 12) - 1
if (monthsUntilRetirement < 0) {
yearsUntilRetirement--;
monthsUntilRetirement += 12;
yearsUntilRetirement--
monthsUntilRetirement += 12
}
if ((birthYear < 1965 && gender == '男') ||
if (
(birthYear < 1965 && gender == '男') ||
(birthYear < 1975 && gender == '女50岁退休') ||
(birthYear < 1970 && gender == '女55岁退休')) {
form.retire_age = `您已退休`;
form.retire_date = `您已退休`;
form.how_long_to_retire = `您已退休`;
(birthYear < 1970 && gender == '女55岁退休')
) {
form.retire_age = `您已退休`
form.retire_date = `您已退休`
form.how_long_to_retire = `您已退休`
} else {
form.retire_age = `${retirementAge}岁+${delayMonths % 12}个月`;
form.retire_date = `${retirementYear}-${retirementMonth < 10 ? '0' + retirementMonth : retirementMonth}-01`;
form.how_long_to_retire = `${yearsUntilRetirement}${monthsUntilRetirement}个月`;
form.retire_age = `${retirementAge}岁+${delayMonths % 12}个月`
form.retire_date = `${retirementYear}-${
retirementMonth < 10 ? '0' + retirementMonth : retirementMonth
}-01`
form.how_long_to_retire = `${yearsUntilRetirement}${monthsUntilRetirement}个月`
}
};
}
</script>
<!-- ============================================================================================================ -->
......@@ -133,12 +135,7 @@ const calculateRetirement = (birthStr: string, gender: string) => {
</el-form-item>
<!-- 性别 -->
<el-form-item label="性别">
<el-select
v-model="form.sex_value"
placeholder="Select"
size="large"
style="width: 240px"
>
<el-select v-model="form.sex_value" placeholder="Select" size="large" style="width: 240px">
<el-option
v-for="item in form.sex_option"
:key="item.value"
......
......@@ -8,10 +8,10 @@ interface MyMarkInterface {
type MyMarkType = Record<number, MyMarkInterface | string>
export const useManyValues = () => {
const screen = "横屏";
const horizontal_img_size = {width: "960", height: "540"};
const vertical_img_size = {width: "540", height: "960"};
const if_need_subtitle = "false";
const screen = '横屏'
const horizontal_img_size = { width: '960', height: '540' }
const vertical_img_size = { width: '540', height: '960' }
const if_need_subtitle = 'false'
// const sd_prompt_prefix = `,best quality,masterpiece,realistic,HDR,UHD,8K,best quality,highres,absurdres,realistic,masterpiece,
// Highly detailed,extreme detail description,Professional,cinematic_lighting,ultra-fine painting,full body,Vivid Colors,
......@@ -49,112 +49,124 @@ export const useManyValues = () => {
// safety panties, safety knickers, beard, furry ,pony, pubic hair, mosaic, excrement, faeces, shit, futa, testis,mutated hands and fingers,
// deformed,bad anatomy,disfigured,poorly drawn face,lowres,mutated,extra limb,ugly,poorly drawn hands,missing limb,floating limbs,
// disconnected limbs,malformed hands,out of focus,long neck,long body,gape,`;
const sd_prompt_prefix = `,best quality,masterpiece,realistic,Highly detailed,Professional,cinematic_lighting,Vivid Colors,physically-based rendering`;
const sd_prompt_prefix = `,best quality,masterpiece,realistic,Highly detailed,Professional,cinematic_lighting,Vivid Colors,physically-based rendering`
// ,malformed,cropped, watermark, username, blurry, JPEG artifacts, signature, 3D, 3D game, 3D game scene, 3D character, QR code, bar code, censored,out of focus
const sd_negative_prompt_prefix = `(mutated hands and fingers:1.5),(mutation, poorly drawn :1.2)`;
const sd_negative_prompt_prefix = `(mutated hands and fingers:1.5),(mutation, poorly drawn :1.2)`
const llms = {
tyqw_online: {'api': 'tyqw', 'name':'线上通义千问'},
baichuan: {'api': 'langchain', 'name':'本地baichuan2-7b'},
qwen_local: {'api': 'langchain', 'name':'本地Qwen-7B-Chat'},
chatgpt: {'api': 'gpt', 'name':'chatgpt'},
kimi: {'api': 'kimi', 'name':'kimi'},
};
tyqw_online: { api: 'tyqw', name: '线上通义千问' },
baichuan: { api: 'langchain', name: '本地baichuan2-7b' },
qwen_local: { api: 'langchain', name: '本地Qwen-7B-Chat' },
chatgpt: { api: 'gpt', name: 'chatgpt' },
kimi: { api: 'kimi', name: 'kimi' }
}
const horizontal_data = {
task_id: "20240209114425596",
task_id: '20240209114425596',
chatgpt_prompt: `生成一个50字的小故事`,
chatgpt_answer: `在一个充满神秘色彩的森林里,活泼的绿衣少年,身着黄色配饰,眼神明亮如蓝宝石,手握小木棒,脸上洋溢着探索的喜悦。他的笑容温暖如阳光,身后是郁郁葱葱的树木,仿佛在邀请勇敢者踏足未知。一个小烟灰缸静静地躺在一旁,见证着他的每一次冒险。`,
chatgpt_answer_roles: [],
all_roles: "",
all_roles: '',
adapt_result_json: [
{
"编号": "1",
"场景描述": "森林里的美味秘密是什么呢",
"场景关键词": "\"葱郁绿林深处,隐藏着一份鲜为人知的美味秘密,它在枝叶间悄然生长,诱惑着探寻者的味蕾与好奇心。\"",
"场景关键词英文": "Deep in the lush green forest, there is a little-known delicious secret. It grows quietly among the branches and leaves, tempting the taste buds and curiosity of the explorer.",
"角色": "",
"角色关键词": "",
"角色关键词英文": "",
"画面描述词": "Deep in the lush green forest, there is a little-known delicious secret. It grows quietly among the branches and leaves, tempting the taste buds and curiosity of the explorer.,",
"本镜配图": "http://wm-tools-backend.frp.wmdigit.com:8888/assets/outputs/20240209114425596/img/1_resized.png?v=20240321152031224",
"local_image_path": "assets/outputs/20240209114425596/img/1_resized.png",
"info": "",
"roles": [],
"info2": "",
编号: '1',
场景描述: '森林里的美味秘密是什么呢',
场景关键词:
'"葱郁绿林深处,隐藏着一份鲜为人知的美味秘密,它在枝叶间悄然生长,诱惑着探寻者的味蕾与好奇心。"',
场景关键词英文:
'Deep in the lush green forest, there is a little-known delicious secret. It grows quietly among the branches and leaves, tempting the taste buds and curiosity of the explorer.',
角色: '',
角色关键词: '',
角色关键词英文: '',
画面描述词:
'Deep in the lush green forest, there is a little-known delicious secret. It grows quietly among the branches and leaves, tempting the taste buds and curiosity of the explorer.,',
本镜配图:
'http://wm-tools-backend.frp.wmdigit.com:8888/assets/outputs/20240209114425596/img/1_resized.png?v=20240321152031224',
local_image_path: 'assets/outputs/20240209114425596/img/1_resized.png',
info: '',
roles: [],
info2: ''
},
{
"编号": "2",
"场景描述": "在葱郁的绿林中",
"场景关键词": "葱郁的绿林中,枝叶茂密如绿色帷幕,阳光斑驳洒落,交织出神秘光影,其间鸟语花香,生机盎然,仿佛隐藏着无尽的秘密与美味。",
"场景关键词英文": "In the lush green forest, the branches and leaves are as dense as a green curtain, the sun is mottled and scattered, interwoven with mysterious light and shadow, during which birds and flowers are fragrant, full of vitality, as if hiding endless secrets and delicacies.",
"角色": "",
"角色关键词": "",
"角色关键词英文": "",
"画面描述词": "In the lush green forest, the branches and leaves are as dense as a green curtain, the sun is mottled and scattered, interwoven with mysterious light and shadow, during which birds and flowers are fragrant, full of vitality, as if hiding endless secrets and delicacies.,",
"本镜配图": "http://wm-tools-backend.frp.wmdigit.com:8888/assets/outputs/20240209114425596/img/2_resized.png?v=20240321152034264",
"local_image_path": "assets/outputs/20240209114425596/img/2_resized.png",
"info": "",
"roles": [],
"info2": "",
编号: '2',
场景描述: '在葱郁的绿林中',
场景关键词:
'葱郁的绿林中,枝叶茂密如绿色帷幕,阳光斑驳洒落,交织出神秘光影,其间鸟语花香,生机盎然,仿佛隐藏着无尽的秘密与美味。',
场景关键词英文:
'In the lush green forest, the branches and leaves are as dense as a green curtain, the sun is mottled and scattered, interwoven with mysterious light and shadow, during which birds and flowers are fragrant, full of vitality, as if hiding endless secrets and delicacies.',
角色: '',
角色关键词: '',
角色关键词英文: '',
画面描述词:
'In the lush green forest, the branches and leaves are as dense as a green curtain, the sun is mottled and scattered, interwoven with mysterious light and shadow, during which birds and flowers are fragrant, full of vitality, as if hiding endless secrets and delicacies.,',
本镜配图:
'http://wm-tools-backend.frp.wmdigit.com:8888/assets/outputs/20240209114425596/img/2_resized.png?v=20240321152034264',
local_image_path: 'assets/outputs/20240209114425596/img/2_resized.png',
info: '',
roles: [],
info2: ''
}
],
final_video: ``,
};
final_video: ``
}
const vertical_data = {
task_id: "20240220181602687",
task_id: '20240220181602687',
chatgpt_prompt: `生成一个50字的科幻小故事,阿凡达系列`,
chatgpt_answer: `森林里的美味秘密是什么呢?在葱郁的绿林中,一只机敏的小松鼠和一只温顺的大熊结伴而行,他们跨越山涧,攀爬树木,只为寻找传说中的金巧蒂。这不仅仅是一种美食,而是一段由新鲜、柔软、香酥编织的传奇。狡猾的狐狸也觊觎这份美味,但在小松鼠和大熊的智勇合作下,美味得以保全。他们分享了这份酥香,友谊也在森林中流传开来。`,
chatgpt_answer_roles: [
{
"角色": "小松鼠",
"角色关键词": "",
"角色关键词英文": "",
"属性": "动物"
角色: '小松鼠',
角色关键词: '',
角色关键词英文: '',
属性: '动物'
},
{
"角色": "大熊",
"角色关键词": "",
"角色关键词英文": "",
"属性": "动物"
角色: '大熊',
角色关键词: '',
角色关键词英文: '',
属性: '动物'
},
{
"角色": "狐狸",
"角色关键词": "",
"角色关键词英文": "",
"属性": "动物"
角色: '狐狸',
角色关键词: '',
角色关键词英文: '',
属性: '动物'
}
],
adapt_result_json: [
{
"编号": "1",
"场景描述": "森林里的美味秘密是什么呢",
"场景关键词": "在葱郁神秘的森林深处,隐藏着一种令人垂涎欲滴的美食秘密,引得动物们纷纷探寻,交织出一段段奇妙冒险。",
"场景关键词英文": "Deep in the lush and mysterious forest, there is a mouth-watering food secret hidden, which attracts animals to explore and interweave a wonderful adventure.",
"角色": "",
"角色关键词": "",
"角色关键词英文": "",
"画面描述词": "Deep in the lush and mysterious forest, there is a mouth-watering food secret hidden, which attracts animals to explore and interweave a wonderful adventure.,",
"本镜配图": "http://wm-tools-backend.frp.wmdigit.com:8888/assets/outputs/20240320160758713/img/1_resized.png?v=20240320160811071",
"local_image_path": "assets/outputs/20240320160758713/img/1_resized.png",
"info": "",
"roles": [],
"info2": "",
编号: '1',
场景描述: '森林里的美味秘密是什么呢',
场景关键词:
'在葱郁神秘的森林深处,隐藏着一种令人垂涎欲滴的美食秘密,引得动物们纷纷探寻,交织出一段段奇妙冒险。',
场景关键词英文:
'Deep in the lush and mysterious forest, there is a mouth-watering food secret hidden, which attracts animals to explore and interweave a wonderful adventure.',
角色: '',
角色关键词: '',
角色关键词英文: '',
画面描述词:
'Deep in the lush and mysterious forest, there is a mouth-watering food secret hidden, which attracts animals to explore and interweave a wonderful adventure.,',
本镜配图:
'http://wm-tools-backend.frp.wmdigit.com:8888/assets/outputs/20240320160758713/img/1_resized.png?v=20240320160811071',
local_image_path: 'assets/outputs/20240320160758713/img/1_resized.png',
info: '',
roles: [],
info2: ''
}
],
all_roles: "小松鼠, 大熊, 狐狸",
final_video: ``,
};
all_roles: '小松鼠, 大熊, 狐狸',
final_video: ``
}
const en_vertical_data = {
task_id: "20240225222232784",
task_id: '20240225222232784',
chatgpt_prompt: `Generate a 50 words science fiction short story, Avatar series`,
chatgpt_answer: `In a futuristic world, humans have developed the ability to upload their consciousness into virtual avatars. These avatars explore distant planets, collecting information for their creators. One day, a group of avatars stumble upon a planet inhabited by sentient beings who are also able to upload their consciousness. A new alliance is formed.`,
chatgpt_answer_roles: [],
adapt_result_json: [],
final_video: ``,
};
final_video: ``
}
const marks = reactive<MyMarkType>({
'-100': '-100%',
......@@ -169,9 +181,9 @@ export const useManyValues = () => {
'-10': '-10%',
0: {
style: {
color: '#1989FA',
color: '#1989FA'
},
label: '标准',
label: '标准'
},
10: '+10%',
20: '+20%',
......@@ -182,193 +194,205 @@ export const useManyValues = () => {
70: '+70%',
80: '+80%',
90: '+90%',
100: '+100%',
});
100: '+100%'
})
const bgm_volume_marks = reactive<MyMarkType>({
0: '-100%',
0.5: '-50%',
1.0: {
style: {
color: '#1989FA',
color: '#1989FA'
},
label: '标准音量',
label: '标准音量'
},
1.5: '+50%',
2.0: '+100%',
});
2.0: '+100%'
})
const voices = [
{
value: 'zh-CN-liaoning-XiaobeiNeural',
gender: 'Female',
label: '女,东北口音',
label: '女,东北口音'
},
{
value: 'zh-CN-shaanxi-XiaoniNeural',
gender: 'Female',
label: '女,陕西口音',
label: '女,陕西口音'
},
{
value: 'zh-CN-XiaoxiaoNeural',
gender: 'Female',
label: '女,像个老师',
label: '女,像个老师'
},
{
value: 'zh-CN-XiaoyiNeural',
gender: 'Female',
label: '女,小女孩',
label: '女,小女孩'
},
{
value: 'zh-CN-YunjianNeural',
gender: 'Male',
label: '男,成熟稳重',
label: '男,成熟稳重'
},
{
value: 'zh-CN-YunxiaNeural',
gender: 'Male',
label: '男,小男孩',
label: '男,小男孩'
},
{
value: 'zh-CN-YunxiNeural',
gender: 'Male',
label: '男,大哥哥',
},{
label: '男,大哥哥'
},
{
value: 'zh-CN-YunyangNeural',
gender: 'Male',
label: '男,像个播音员',
},{
label: '男,像个播音员'
},
{
value: 'zh-HK-HiuGaaiNeural',
gender: 'Female',
label: '女,粤语,中年',
},{
label: '女,粤语,中年'
},
{
value: 'zh-HK-HiuMaanNeural',
gender: 'Female',
label: '女,粤语,年轻',
},{
label: '女,粤语,年轻'
},
{
value: 'zh-HK-WanLungNeural',
gender: 'Male',
label: '男,粤语',
},{
label: '男,粤语'
},
{
value: 'zh-TW-HsiaoChenNeural',
gender: 'Female',
label: '女,台湾腔',
},{
label: '女,台湾腔'
},
{
value: 'zh-TW-HsiaoYuNeural',
gender: 'Female',
label: '女,台湾,偏普通话',
},{
label: '女,台湾,偏普通话'
},
{
value: 'zh-TW-YunJheNeural',
gender: 'Male',
label: '男,台湾腔',
},
];
label: '男,台湾腔'
}
]
const voices_en = [
{
value: 'en-US-BrianNeural',
gender: 'Male',
label: '男,美式磁性',
},
];
label: '男,美式磁性'
}
]
const bgm = [
{
value: '',
label: '无',
label: '无'
},
{
value: '安魂曲',
label: '安魂曲',
label: '安魂曲'
},
{
value: '解忧曲',
label: '解忧曲',
label: '解忧曲'
},
{
value: 'Anacreon',
label: 'Anacreon',
label: 'Anacreon'
},
{
value: 'MySunset',
label: 'MySunset',
label: 'MySunset'
},
{
value: 'WindyHill',
label: 'WindyHill',
label: 'WindyHill'
},
{
value: '雨声',
label: '雨声',
label: '雨声'
},
{
value: '大自然',
label: '大自然',
},
];
label: '大自然'
}
]
const role_attribute_options = [
{
value: '人',
label: '人',
label: '人'
},
{
value: '动物',
label: '动物',
},
];
label: '动物'
}
]
const sd_paras = {
dreamshaperXL_v21TurboDPMSDE: {
model: "dreamshaperXL_v21TurboDPMSDE.safetensors",
sampler_index: "DPM++ SDE Karras",
seed: "-1",
steps: "6",
cfg_scale: "2",
model: 'dreamshaperXL_v21TurboDPMSDE.safetensors',
sampler_index: 'DPM++ SDE Karras',
seed: '-1',
steps: '6',
cfg_scale: '2',
sd_prompt_prefix: sd_prompt_prefix
},
juggernautXL_v9Rdphoto2Lightning: {
model: "juggernautXL_v9Rdphoto2Lightning.safetensors",
sampler_index: "Euler",
seed: "-1",
steps: "8",
cfg_scale: "1.5",
sd_prompt_prefix: sd_prompt_prefix + ", <lora:sdxl_lightning_8step_lora:0.2>",
},
};
model: 'juggernautXL_v9Rdphoto2Lightning.safetensors',
sampler_index: 'Euler',
seed: '-1',
steps: '8',
cfg_scale: '1.5',
sd_prompt_prefix: sd_prompt_prefix + ', <lora:sdxl_lightning_8step_lora:0.2>'
}
}
const marketing_template = {
product_name: "金巧蒂原味猪肉松",
product_description:
`1. 酥而不腻,入口即化
product_name: '金巧蒂原味猪肉松',
product_description: `1. 酥而不腻,入口即化
2. 原料主要采用新鲜猪后腿肉,经过水域立即处理,加上老师傅祖传的配方,结合现代化的生产工艺,锤炼出柔软酥松,绵而不腻,味香可口的“猪肉松”,并全程使用“多力牌”葵花油
3. 选用天然万家香酱油
4. 选用蒂纳海盐
5. 猪肉松的主要营养成分有碳水化合物、脂肪、蛋白质和多种矿物质,胆固醇含量低,且易于人体消化吸收,具有很高的营养价值。但是要注意一点,猪肉松不能与鹌鹑肉同食,吃多容易生黑斑哦
6. 我们“金巧蒂”的产品,确保是在屠宰后2小时内进行加工,绝对锁住肉质口感不流失。猪肉肉质细腻且营养价值丰富,对人体有极高的营养价值及保健作用,如长期食用可延年益寿,并有着汤汁浓郁,绕齿留香的特点`,
target_people: "20-30岁年轻女生",
text_role: "小松鼠",
text_style: "幽默",
story_type: "科幻",
reference: "猪八戒娶媳妇儿的情节",
target_people: '20-30岁年轻女生',
text_role: '小松鼠',
text_style: '幽默',
story_type: '科幻',
reference: '猪八戒娶媳妇儿的情节',
words_num: 100,
result1: "",
result2: "",
};
result1: '',
result2: ''
}
const cover_backcover = {
product_pic: "http://wm-tools-backend.frp.wmdigit.com:8888/assets/2024/03/12/887a52c6-e022-11ee-a7de-9be5c7a16c02_resized.png",
product_pic_local: "assets/2024/03/12/887a52c6-e022-11ee-a7de-9be5c7a16c02_resized.png",
product_pic_with_text: "http://wm-tools-backend.frp.wmdigit.com:8888/assets/2024/03/12/887a52c6-e022-11ee-a7de-9be5c7a16c02_resized_with_text.png",
product_pic_with_text_local: "assets/2024/03/12/887a52c6-e022-11ee-a7de-9be5c7a16c02_resized_with_text.png",
product_pic:
'http://wm-tools-backend.frp.wmdigit.com:8888/assets/2024/03/12/887a52c6-e022-11ee-a7de-9be5c7a16c02_resized.png',
product_pic_local: 'assets/2024/03/12/887a52c6-e022-11ee-a7de-9be5c7a16c02_resized.png',
product_pic_with_text:
'http://wm-tools-backend.frp.wmdigit.com:8888/assets/2024/03/12/887a52c6-e022-11ee-a7de-9be5c7a16c02_resized_with_text.png',
product_pic_with_text_local:
'assets/2024/03/12/887a52c6-e022-11ee-a7de-9be5c7a16c02_resized_with_text.png',
product_pic_titles: [],
product_pic_speech: "",
cover_pic: "http://wm-tools-backend.frp.wmdigit.com:8888/assets/2024/03/12/10170c9e-e035-11ee-a7de-9be5c7a16c02_resized.jpg",
cover_pic_local: "assets/2024/03/12/10170c9e-e035-11ee-a7de-9be5c7a16c02_resized.jpg",
cover_pic_with_text: "http://wm-tools-backend.frp.wmdigit.com:8888/assets/2024/03/12/10170c9e-e035-11ee-a7de-9be5c7a16c02_resized_with_text.jpg",
cover_pic_with_text_local: "assets/2024/03/12/10170c9e-e035-11ee-a7de-9be5c7a16c02_resized_with_text.jpg",
cover_pic_titles: [],
};
product_pic_speech: '',
cover_pic:
'http://wm-tools-backend.frp.wmdigit.com:8888/assets/2024/03/12/10170c9e-e035-11ee-a7de-9be5c7a16c02_resized.jpg',
cover_pic_local: 'assets/2024/03/12/10170c9e-e035-11ee-a7de-9be5c7a16c02_resized.jpg',
cover_pic_with_text:
'http://wm-tools-backend.frp.wmdigit.com:8888/assets/2024/03/12/10170c9e-e035-11ee-a7de-9be5c7a16c02_resized_with_text.jpg',
cover_pic_with_text_local:
'assets/2024/03/12/10170c9e-e035-11ee-a7de-9be5c7a16c02_resized_with_text.jpg',
cover_pic_titles: []
}
return {
screen: screen,
......@@ -389,6 +413,6 @@ export const useManyValues = () => {
role_attribute_options: role_attribute_options,
sd_paras: sd_paras,
marketing_template: marketing_template,
cover_backcover: cover_backcover,
cover_backcover: cover_backcover
}
}
<script setup lang="ts">
import text2videoService from "@/api/service/text2videoService";
import utils from "@/utils/utils";
import text2videoService from '@/api/service/text2videoService'
import userService from '@/api/service/userService'
import LoginDialog from '@/components/LoginDialog.vue'
import VideoCrop from '@/components/VideoCrop.vue'
import { useAuthStore } from '@/stores/state'
import utils from '@/utils/utils'
import {
ElMessage, genFileId,
ElMessage,
genFileId,
type UploadInstance,
type UploadProps,
type UploadRawFile
} from "element-plus";
import { nextTick, onMounted, reactive, ref } from "vue";
} from 'element-plus'
import { nextTick, onMounted, reactive, ref } from 'vue'
const authStore = useAuthStore()
const debug = ref(import.meta.env.MODE === 'production' ? false : true);
const debug = ref(import.meta.env.MODE === 'production' ? false : true)
// const debug = ref(false);
const loading = ref(false);
const title = ref("生成数字人视频");
const loading = ref(false)
const title = ref('生成数字人视频')
const isLoginDialogVisible = ref(false)
const isVideoCropDialogVisible = ref(false)
const cropVideo = ref({
type: 'show_image_or_video',
url: '',
postiion: []
})
const isMobile = ref(utils.checkIsMobile())
const form = reactive({
task_id: "",
lang: "<|zh|>",
task_id: '',
lang: '<|zh|>',
text: `大家好,欢迎来到恐龙爱迪游乐园上海江苏路店,这里是孩子们欢乐的天堂,也是家庭共度美好时光的最佳选择。今天我要特别推荐给大家的是我们这里超受欢迎的项目——海洋球池!
想象一下,踏入一个色彩斑斓的世界,成千上万颗柔软的海洋球在脚下翻滚,就像是走进了一个巨大的彩虹梦。这里不仅是孩子们的最爱,连大人们也会忍不住加入,一起找回童年的快乐。而且,现在我们有一个特别优惠:只需198元,您就可以享受10次畅玩的机会,绝对物超所值!
......@@ -23,41 +37,49 @@ const form = reactive({
安全是我们最重视的方面。我们的海洋球池采用了高品质材料制作的球体,确保每一个球都足够柔软,即使摔倒也不会感到疼痛。此外,专业的安全人员时刻在场,确保每一位游客的安全。
无论是周末的家庭出游,还是朋友间的聚会,海洋球池都是不可错过的选择。快来恐龙爱迪游乐园,让我们一起创造更多美好的回忆吧!`,
sample_audio_path: "",
sample_video_path: "",
show_image_or_video_path: "",
bgm: "",
final_video: "",
});
sample_audio_path: '',
sample_video_path: '',
sample_video_url: '',
show_image_or_video_path: '',
show_image_or_video_url: '',
bgm: '',
final_video: '',
sample_video_crop_area: [],
show_image_or_video_crop_area: []
})
onMounted(() => {
// 初始化task_id
form.task_id = utils.genDateTimeStr();
form.task_id = utils.genDateTimeStr()
console.log('页面加载,task_id=', form.task_id)
document.title = title.value;
document.title = title.value
if (!debug) {
form.text = "";
form.sample_audio_path = "";
form.sample_video_path = "";
form.show_image_or_video_path = "";
form.bgm = "";
form.text = ''
form.sample_audio_path = ''
form.sample_video_path = ''
form.show_image_or_video_path = ''
form.sample_video_url = ''
form.show_image_or_video_url = ''
form.bgm = ''
}
});
if (!authStore.isLoggedIn) {
isLoginDialogVisible.value = true
}
console.log('isMobile=', isMobile.value)
})
// =========== 上传通用 Begin ===========
const actionUrl = ref(
import.meta.env.MODE === 'production'
? '/file'
: import.meta.env.VITE_APP_BASE_API + '/file'
import.meta.env.MODE === 'production' ? '/file' : import.meta.env.VITE_APP_BASE_API + '/file'
)
const handleUploadError = (error: Error) => {
ElMessage({
message: String(error.message),
type: "error",
});
type: 'error'
})
}
const handleBeforeUpload = async (file: any) => {
const isLt1M = file.size / 1024 / 1024 <= 50;
const isLt1M = file.size / 1024 / 1024 <= 50
if (!isLt1M) {
ElMessage.error('文件大小不能超过 50MB')
return false
......@@ -66,17 +88,17 @@ const handleBeforeUpload = async (file: any) => {
// =========== 上传通用 END ===========
// =========== 上传音频 Begin ===========
const upload_sample_audio = ref<UploadInstance>();
const upload_sample_audio = ref<UploadInstance>()
const UploadSampleAudioSuccess = (val: Wm.UploadResult) => {
if (val.code == 0) {
form.sample_audio_path = val.data[0].path;
form.sample_audio_path = val.data[0].path
ElMessage({
message: '上传成功',
type: 'success'
})
} else {
ElMessage({
message: '上传失败:'+val.message,
message: '上传失败:' + val.message,
type: 'error'
})
}
......@@ -96,22 +118,23 @@ const handleUploadSampleAudioExceed: UploadProps['onExceed'] = (files) => {
const handleUploadSampleAudioRemove: UploadProps['onRemove'] = (file, uploadFiles) => {
// 清除已上传的文件
upload_sample_audio.value!.clearFiles()
form.sample_audio_path = '';
form.sample_audio_path = ''
}
// =========== 上传音频 END ===========
// =========== 上传采样视频 Begin ===========
const upload_sample_video = ref<UploadInstance>();
const upload_sample_video = ref<UploadInstance>()
const UploadSampleVideoSuccess = (val: Wm.UploadResult) => {
if (val.code == 0) {
form.sample_video_path = val.data[0].path;
form.sample_video_path = val.data[0].path
form.sample_video_url = val.data[0].url
ElMessage({
message: '上传成功',
type: 'success'
})
} else {
ElMessage({
message: '上传失败:'+val.message,
message: '上传失败:' + val.message,
type: 'error'
})
}
......@@ -131,22 +154,23 @@ const handleUploadSampleVideoExceed: UploadProps['onExceed'] = (files) => {
const handleUploadSampleVideoRemove: UploadProps['onRemove'] = (file, uploadFiles) => {
// 清除已上传的文件
upload_sample_video.value!.clearFiles()
form.sample_video_path = '';
form.sample_video_path = ''
}
// =========== 上传采样视频 END ===========
// =========== 上传拼接视频或图片 Begin ===========
const upload_show_image_or_video = ref<UploadInstance>();
const upload_show_image_or_video = ref<UploadInstance>()
const UploadShowImageOrVideoSuccess = (val: Wm.UploadResult) => {
if (val.code == 0) {
form.show_image_or_video_path = val.data[0].path;
form.show_image_or_video_path = val.data[0].path
form.show_image_or_video_url = val.data[0].url
ElMessage({
message: '上传成功',
type: 'success'
})
} else {
ElMessage({
message: '上传失败:'+val.message,
message: '上传失败:' + val.message,
type: 'error'
})
}
......@@ -166,43 +190,81 @@ const handleUploadShowImageOrVideoExceed: UploadProps['onExceed'] = (files) => {
const handleUploadShowImageOrVideoRemove: UploadProps['onRemove'] = (file, uploadFiles) => {
// 清除已上传的文件
upload_show_image_or_video.value!.clearFiles()
form.show_image_or_video_path = '';
form.show_image_or_video_path = ''
}
// =========== 上传拼接视频或图片 END ===========
// 生成视频
const onGenVideo = async () => {
if (!form.text || form.text.length == 0
|| !form.sample_video_path || form.sample_video_path.length == 0
if (
!form.text ||
form.text.length == 0 ||
!form.sample_video_path ||
form.sample_video_path.length == 0
) {
ElMessage({
message: "文案、出镜人物 均不能为空",
type: "error",
});
return;
message: '文案、出镜人物 均不能为空',
type: 'error'
})
return
}
try {
const video_param = {
let video_param: any = {
task_id: form.task_id,
lang: form.lang,
text: form.text,
sample_audio_path: form.sample_audio_path,
sample_video_path: form.sample_video_path,
show_image_or_video_path: form.show_image_or_video_path,
bgm: "",
bgm: ''
}
const result = await text2videoService.submitGenDigitHumanVideo(video_param);
console.log(result);
form.final_video = "";
form.final_video = result + "?v=" + utils.genDateTimeStr();
} catch(error: any) {
if (form.sample_video_crop_area.length > 0) {
video_param.sample_video_crop_area = JSON.stringify(form.sample_video_crop_area)
}
if (form.show_image_or_video_crop_area.length > 0) {
video_param.show_image_or_video_crop_area = JSON.stringify(form.show_image_or_video_crop_area)
}
const result = await text2videoService.submitGenDigitHumanVideo(video_param)
console.log(result)
form.final_video = ''
form.final_video = result + '?v=' + utils.genDateTimeStr()
} catch (error: any) {
// console.error(error);
ElMessage({
message: error,
type: "error",
});
};
};
type: 'error'
})
}
}
const showCropVideoDialog = (typp: 'show_image_or_video' | 'sample_video') => {
cropVideo.value.type = typp
if (typp == 'show_image_or_video') {
cropVideo.value.url = form.show_image_or_video_url
cropVideo.value.postiion = form.show_image_or_video_crop_area
} else {
cropVideo.value.url = form.sample_video_url
cropVideo.value.postiion = form.sample_video_crop_area
}
// cropVideo.value.url =
// 'http://wm-tools-backend-test.frp.wmdigit.com:8888/assets/2024/10/24/33390c1c-91b6-11ef-adf3-bdd8f0a1d757.mp4'
if (!cropVideo.value.url) {
ElMessage.warning({
message: '请先上传视频'
})
return
}
isVideoCropDialogVisible.value = true
}
const handleCrop = (val: any) => {
console.log('handleCrop', val)
if (cropVideo.value.type == 'show_image_or_video') {
form.show_image_or_video_crop_area = val
} else {
form.sample_video_crop_area = val
}
}
</script>
<!-- ============================================================================================================ -->
......@@ -210,10 +272,20 @@ const onGenVideo = async () => {
<!-- ============================================================================================================ -->
<template>
<main class="home-container">
<main
class="home-container"
:class="{
'home-container-mobile': isMobile
}"
>
<!-- 标题 -->
<el-divider content-position="left">{{ title }}</el-divider>
<el-form :model="form" label-width="114px" v-loading="loading">
<el-form
:model="form"
label-width="114px"
v-loading="loading"
:label-position="isMobile ? 'top' : 'left'"
>
<!-- 文案 -->
<el-form-item label="文案">
<el-input
......@@ -238,6 +310,9 @@ const onGenVideo = async () => {
:on-remove="handleUploadSampleVideoRemove"
>
<el-button type="primary" size="small">上传</el-button>
<el-button type="primary" size="small" @click.stop="showCropVideoDialog('sample_video')">
裁剪
</el-button>
<template #tip>
<div class="el-upload__tip" style="color: #0000ff; background-color: #e6f7ff">
请录制一段出镜人的视频,说话时的嘴型能够清晰分辨,20秒左右即可。上传的文件格式为
......@@ -264,8 +339,8 @@ const onGenVideo = async () => {
<el-button type="primary" size="small">上传</el-button>
<template #tip>
<div class="el-upload__tip" style="color: #0000ff; background-color: #e6f7ff">
这里可以上传一段说话的声音,比如朗读一段文字,20秒左右即可。文件格式为
wav,不大于 50M。也可以不上传,不传则默认用出镜人的声音。
这里可以上传一段说话的声音,比如朗读一段文字,20秒左右即可。文件格式为 wav,不大于
50M。也可以不上传,不传则默认用出镜人的声音。
</div>
</template>
</el-upload>
......@@ -286,6 +361,13 @@ const onGenVideo = async () => {
:on-remove="handleUploadShowImageOrVideoRemove"
>
<el-button type="primary" size="small">上传</el-button>
<el-button
type="primary"
size="small"
@click.stop="showCropVideoDialog('show_image_or_video')"
>
裁剪
</el-button>
<template #tip>
<div class="el-upload__tip" style="color: #0000ff; background-color: #e6f7ff">
这里可以上传一段视频(mp4)或一张图片(jpg, png),
......@@ -299,16 +381,61 @@ const onGenVideo = async () => {
<el-form-item label="生成">
<el-button type="primary" @click="onGenVideo">生成视频</el-button>
</el-form-item>
<el-form-item>
<video :src="form.final_video" controls width="360" height="640"></video>
<el-form-item v-if="form.final_video">
<video :src="form.final_video" class="example-final-video" controls></video>
</el-form-item>
</el-form>
</main>
<LoginDialog v-model:visible="isLoginDialogVisible"></LoginDialog>
<VideoCrop
v-model:visible="isVideoCropDialogVisible"
:url="cropVideo.url"
:cropArea="cropVideo.postiion"
@crop="handleCrop"
></VideoCrop>
</template>
<style lang="scss" scoped>
.home-container {
width: 100%;
.example-final-video-wrapper {
width: 360px;
.example-final-video-container {
position: relative;
width: 360px;
padding-bottom: 177.78%; /* 16:9 比例 (9 / 16 = 0.5625) */
height: 0;
overflow: hidden;
.example-final-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
}
.example-final-video {
width: 360px;
height: 640px;
object-fit: cover;
}
&.home-container-mobile {
// .example-final-video-wrapper {
// width: 100%;
// .example-final-video-container {
// width: 100%;
// }
// }
.example-final-video {
width: 100%;
height: auto;
object-fit: cover;
}
}
}
</style>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
<script setup lang="ts">
import { onMounted, reactive, ref } from "vue";
import { Sunny, UploadFilled } from "@element-plus/icons-vue";
import { ElMessage, genFileId,
import { onMounted, reactive, ref } from 'vue'
import { Sunny, UploadFilled } from '@element-plus/icons-vue'
import {
ElMessage,
genFileId,
type UploadInstance,
type UploadProps,
type UploadRawFile } from "element-plus";
import text2videoService from "@/api/service/text2videoService";
import utils from "@/utils/utils";
type UploadRawFile
} from 'element-plus'
import text2videoService from '@/api/service/text2videoService'
import utils from '@/utils/utils'
import { useManyValues } from './compositions/useManyValues'
const debug = ref(import.meta.env.MODE === 'production' ? false : true);
const loading = ref(false);
const dialogVisible = ref(false);
const dialogData = ref("");
const default_data = useManyValues();
const debug = ref(import.meta.env.MODE === 'production' ? false : true)
const loading = ref(false)
const dialogVisible = ref(false)
const dialogData = ref('')
const default_data = useManyValues()
const form = reactive({
screen: default_data.screen,
if_need_subtitle: default_data.if_need_subtitle,
chatgpt_prompt: "",
chatgpt_answer: "",
chatgpt_prompt: '',
chatgpt_answer: '',
chatgpt_answer_roles: <Wm.RolesItem[]>[],
all_roles: "",
all_roles: '',
adapt_result_json: <Wm.ScriptsItem[]>[],
task_id: "",
final_video: "",
});
const sd_prompt_prefix = default_data.sd_prompt_prefix;
const sd_negative_prompt_prefix = default_data.sd_negative_prompt_prefix;
task_id: '',
final_video: ''
})
const sd_prompt_prefix = default_data.sd_prompt_prefix
const sd_negative_prompt_prefix = default_data.sd_negative_prompt_prefix
const tyqw = {'api': 'tyqw', 'name':'通义千问线上'};
const baichuan = {'api': 'langchain', 'name':'baichuan2-7b'};
const qwen = {'api': 'langchain', 'name':'Qwen-7B-Chat'};
const gpt = {'api': 'gpt', 'name':'chatgpt'};
const tyqw = { api: 'tyqw', name: '通义千问线上' }
const baichuan = { api: 'langchain', name: 'baichuan2-7b' }
const qwen = { api: 'langchain', name: 'Qwen-7B-Chat' }
const gpt = { api: 'gpt', name: 'chatgpt' }
const wenan_llm = gpt.api
const wenan_llm_name = gpt.name
......@@ -46,71 +49,73 @@ const fanyi_llm_name = gpt.name
const voice_rate = ref(-10)
const voice_volume = ref(0)
const voice = ref("en-US-BrianNeural")
const bgm = ref("解忧曲")
const voice = ref('en-US-BrianNeural')
const bgm = ref('解忧曲')
const bgm_volume = ref(0.3)
const pwdCheckDialogVisible = ref(false);
const pwdCheckValue = ref("")
const sub_font_color = ref("#FFFF00")
const pwdCheckDialogVisible = ref(false)
const pwdCheckValue = ref('')
const sub_font_color = ref('#FFFF00')
const sub_bg_color = ref()
const sub_font_size = ref(25)
const sub_position = ref(0.3)
onMounted(() => {
// 初始化示例数据
onChangeScreen(form.screen);
onChangeScreen(form.screen)
// 初始化密码框
if (debug.value == true) {
pwdCheckDialogVisible.value = false;
pwdCheckDialogVisible.value = false
} else {
pwdCheckDialogVisible.value = true;
pwdCheckDialogVisible.value = true
}
});
})
const delay = (ms: any) => new Promise(res => setTimeout(res, ms));
const delay = (ms: any) => new Promise((res) => setTimeout(res, ms))
const onSubmitGpt = () => {
text2videoService
.submitLLM(form.chatgpt_prompt, wenan_llm)
.then((result: string) => {
console.log(form.chatgpt_prompt);
console.log(result);
form.chatgpt_answer = result;
console.log(form.chatgpt_prompt)
console.log(result)
form.chatgpt_answer = result
})
.catch((error: any) => {
// console.error(error);
ElMessage({
message: error,
type: "error",
});
});
};
type: 'error'
})
})
}
const onAdaptRoles = async () => {
if (!form.chatgpt_answer || form.chatgpt_answer.length == 0) {
ElMessage({
message: "文案不能为空",
type: "error",
});
return;
message: '文案不能为空',
type: 'error'
})
return
}
loading.value = true;
loading.value = true
// 推理角色
form.chatgpt_answer_roles = [];
form.chatgpt_answer_roles = []
try {
const adapt_restrict = `
Instructions:
Please understand this story and provide all the characters in it, with multiple characters separated by commas`;
const roles = await text2videoService.submitLLM("story:\n" + form.chatgpt_answer + "\n" + adapt_restrict, role_llm);
Please understand this story and provide all the characters in it, with multiple characters separated by commas`
const roles = await text2videoService.submitLLM(
'story:\n' + form.chatgpt_answer + '\n' + adapt_restrict,
role_llm
)
form.all_roles = roles.replace(/。/g, '').replace(/、/g, ',')
console.log(form.all_roles)
const roles_arr = form.all_roles.split(/[,,]/);
const roles_arr = form.all_roles.split(/[,,]/)
console.log(roles_arr)
async function processRoles() {
for (const one_role of roles_arr) {
await delay(100);
await delay(100)
const adapt_keyword_restrict = `
Instructions:
Please understand this story and provide the keywords for the character "${one_role.trim()}" (gender (can be supplemented with imagination, but must have it), age (can be supplemented with imagination, but must have it),
......@@ -118,108 +123,110 @@ const onAdaptRoles = async () => {
Hair color (can be supplemented with imagination, but must have it), facial color (can be supplemented with imagination, but must have it), facial features (can be supplemented with imagination, but must have it).
Requirement:
Keywords are separated by commas.
As long as the keyword is returned, no additional explanatory text is required.`;
let keywords = await text2videoService.submitLLM("story:\n" + form.chatgpt_answer + "\n" + adapt_keyword_restrict, role_keywords_llm);
As long as the keyword is returned, no additional explanatory text is required.`
let keywords = await text2videoService.submitLLM(
'story:\n' + form.chatgpt_answer + '\n' + adapt_keyword_restrict,
role_keywords_llm
)
keywords = keywords.replace(/。/g, '').replace(/、/g, ',')
form.chatgpt_answer_roles.push({
"角色": one_role.trim(),
"角色关键词": keywords.trim()+",dressed",
"角色关键词英文": "",
"属性": "",
});
角色: one_role.trim(),
角色关键词: keywords.trim() + ',dressed',
角色关键词英文: '',
属性: ''
})
}
}
try {
await processRoles();
await processRoles()
console.log(form.chatgpt_answer_roles)
} catch (error) {
ElMessage({
message: String(error),
type: "error"
});
type: 'error'
})
} finally {
loading.value = false; // 最终关闭loading(无论成功或失败)
loading.value = false // 最终关闭loading(无论成功或失败)
}
} catch (error) {
ElMessage({
message: String(error),
type: "error",
});
type: 'error'
})
} finally {
// 最终关闭loading(无论成功或失败)
loading.value = false;
loading.value = false
}
};
}
const onAdapt = async () => {
if (!form.chatgpt_answer || form.chatgpt_answer.length == 0) {
ElMessage({
message: "文案不能为空",
type: "error",
});
return;
message: '文案不能为空',
type: 'error'
})
return
}
loading.value = true;
form.task_id = utils.genDateTimeStr();
loading.value = true
form.task_id = utils.genDateTimeStr()
console.log(form.task_id)
// 按标点拆分成分镜
const sentences = utils.splitTextEn(form.chatgpt_answer);
const sentences = utils.splitTextEn(form.chatgpt_answer)
console.log(sentences.length)
// 分镜
form.adapt_result_json = []
for (let i = 0; i < sentences.length; i++) {
form.adapt_result_json.push({
"编号": (i + 1).toString(),
"场景描述": sentences[i].trim(),
"场景关键词": "",
"场景关键词英文": "",
"角色": "",
"角色关键词": "",
"角色关键词英文": "",
"画面描述词": "",
"本镜配图": "src/assets/loading.gif",
"local_image_path": "",
"info": "",
"roles": [],
"info2": "",
});
编号: (i + 1).toString(),
场景描述: sentences[i].trim(),
场景关键词: '',
场景关键词英文: '',
角色: '',
角色关键词: '',
角色关键词英文: '',
画面描述词: '',
本镜配图: 'src/assets/loading.gif',
local_image_path: '',
info: '',
roles: [],
info2: ''
})
}
console.log(form.adapt_result_json)
async function processScenes() {
for (const item of form.adapt_result_json) {
await onAdaptOne(item);
await onAdaptOne(item)
// await delay(100);
// await onDrawOne(item);
onDrawOne(item);
onDrawOne(item)
}
}
try {
await processScenes();
await processScenes()
ElMessage({
message: "all scene ok",
type: "success"
});
console.log(form.adapt_result_json);
message: 'all scene ok',
type: 'success'
})
console.log(form.adapt_result_json)
} catch (error) {
ElMessage({
message: String(error),
type: "error"
});
type: 'error'
})
} finally {
loading.value = false; // 最终关闭loading(无论成功或失败)
loading.value = false // 最终关闭loading(无论成功或失败)
}
};
}
const onAdaptOne = async (item: any) => {
if (!item.场景描述) {
ElMessage({
message: "分镜场景描述不能为空",
type: "error",
});
return;
message: '分镜场景描述不能为空',
type: 'error'
})
return
}
// 推理关键词
try {
......@@ -230,138 +237,176 @@ const onAdaptOne = async (item: any) => {
Items (can be supplemented with imagination, but must be present), characters (can be supplemented with imagination, but must be present), camera angles (can be supplemented with imagination, but must be present).
Requirement:
Keywords are separated by commas.
As long as the keyword is returned, no additional explanatory text is required.`;
const keywords = await text2videoService.submitLLM("story:\n" + form.chatgpt_answer + "\n" + adapt_restrict, tuili_llm);
As long as the keyword is returned, no additional explanatory text is required.`
const keywords = await text2videoService.submitLLM(
'story:\n' + form.chatgpt_answer + '\n' + adapt_restrict,
tuili_llm
)
// console.log(keywords)
item.场景关键词 = keywords;
item.场景关键词 = keywords
if (form.chatgpt_answer_roles.length === 0) {
// 总角色为空
item.角色 = '';
item.角色关键词 = '';
item.角色 = ''
item.角色关键词 = ''
} else {
// 总角色不为空
const adapt_role_restrict = `
Instructions:
Please understand this story and for the scene: "${item.场景描述}", select the character in this scene from characters, with multiple characters separated by commas.`;
const item_roles = await text2videoService.submitLLM("story:\n" + form.chatgpt_answer + "\ncharacters:\n"+ form.all_roles +"\n" + adapt_role_restrict, tuili_llm);
Please understand this story and for the scene: "${item.场景描述}", select the character in this scene from characters, with multiple characters separated by commas.`
const item_roles = await text2videoService.submitLLM(
'story:\n' +
form.chatgpt_answer +
'\ncharacters:\n' +
form.all_roles +
'\n' +
adapt_role_restrict,
tuili_llm
)
// console.log(role_keywords)
item.角色 = item_roles;
let role_kws = ""
const item_roles_arr = item_roles.split(/[,,、]/);
item_roles_arr.forEach( one_item_role => {
let temp_role_kws = ""
item.角色 = item_roles
let role_kws = ''
const item_roles_arr = item_roles.split(/[,,、]/)
item_roles_arr.forEach((one_item_role) => {
let temp_role_kws = ''
// 人工匹配角色关键词,先找想同的
for (const i of form.chatgpt_answer_roles) {
if (i["角色"].trim() == one_item_role.trim()) {
temp_role_kws = `[${i["角色"]}: ${i["角色关键词"]}]`;
if (i['角色'].trim() == one_item_role.trim()) {
temp_role_kws = `[${i['角色']}: ${i['角色关键词']}]`
// 找到就ok
break;
break
}
}
// 如果找不到相同的,则模糊匹配
if (! temp_role_kws) {
if (!temp_role_kws) {
for (const i of form.chatgpt_answer_roles) {
if (i["角色"].includes(one_item_role.trim()) || one_item_role.includes(i["角色"].trim())) {
temp_role_kws = `[${i["角色"]}: ${i["角色关键词"]}]`;
if (
i['角色'].includes(one_item_role.trim()) ||
one_item_role.includes(i['角色'].trim())
) {
temp_role_kws = `[${i['角色']}: ${i['角色关键词']}]`
// 匹配到一个就ok
break;
break
}
}
}
role_kws = `${role_kws}${temp_role_kws}`;
role_kws = `${role_kws}${temp_role_kws}`
})
item.角色关键词 = role_kws;
item.角色关键词 = role_kws
}
} catch (error) {
ElMessage({
message: String(error),
type: "error",
});
type: 'error'
})
}
};
}
const onDrawOne = async (item: any) => {
if (!item.场景描述 && !item.场景关键词) {
ElMessage({
message: "场景描述和场景关键词不能都为空",
type: "error",
});
return;
message: '场景描述和场景关键词不能都为空',
type: 'error'
})
return
}
// 翻译+画图
if (!form.task_id) {
form.task_id = utils.genDateTimeStr();
form.task_id = utils.genDateTimeStr()
console.log(form.task_id)
}
try {
item.本镜配图 = "src/assets/loading.gif";
let temp_prompt = ""
if (item.场景描述) {temp_prompt = temp_prompt + `Scene description is: ${item.场景描述}\n`};
if (item.场景关键词) {temp_prompt = temp_prompt + `Scene keywords are: ${item.场景关键词}\n`};
if (item.角色) {temp_prompt = temp_prompt + `Characters in the scene are: ${item.角色}\n`};
if (item.角色关键词) {temp_prompt = temp_prompt + `Character keywords are: ${item.角色关键词}\n`};
item.本镜配图 = 'src/assets/loading.gif'
let temp_prompt = ''
if (item.场景描述) {
temp_prompt = temp_prompt + `Scene description is: ${item.场景描述}\n`
}
if (item.场景关键词) {
temp_prompt = temp_prompt + `Scene keywords are: ${item.场景关键词}\n`
}
if (item.角色) {
temp_prompt = temp_prompt + `Characters in the scene are: ${item.角色}\n`
}
if (item.角色关键词) {
temp_prompt = temp_prompt + `Character keywords are: ${item.角色关键词}\n`
}
const sd_describe = await text2videoService.submitLLM(
`${temp_prompt}
Instructions:
Please understand the above content and return an English description.`, fanyi_llm
);
item.画面描述词 = sd_describe;
const sd_prompt = item.画面描述词 + "," + sd_prompt_prefix;
let width = "960";
let height = "540";
if (form.screen == "竖屏") {
width = "540";
height = "960";
Please understand the above content and return an English description.`,
fanyi_llm
)
item.画面描述词 = sd_describe
const sd_prompt = item.画面描述词 + ',' + sd_prompt_prefix
let width = '960'
let height = '540'
if (form.screen == '竖屏') {
width = '540'
height = '960'
}
// console.log(sd_prompt);
// console.log(sd_negative_prompt_prefix);
const sampler_index = "DPM++ SDE Karras";
const seed = "-1";
const steps = "6";
const cfg_scale = "2";
const sd_img = await text2videoService.submitSD(form.task_id, item.编号, sd_prompt, sd_negative_prompt_prefix, width, height, sampler_index, seed, steps, cfg_scale);
item.本镜配图 = sd_img.domain_image_path+"?v="+utils.genDateTimeStr();
item.local_image_path = sd_img.local_image_path;
const sampler_index = 'DPM++ SDE Karras'
const seed = '-1'
const steps = '6'
const cfg_scale = '2'
const sd_img = await text2videoService.submitSD(
form.task_id,
item.编号,
sd_prompt,
sd_negative_prompt_prefix,
width,
height,
sampler_index,
seed,
steps,
cfg_scale
)
item.本镜配图 = sd_img.domain_image_path + '?v=' + utils.genDateTimeStr()
item.local_image_path = sd_img.local_image_path
} catch (error) {
ElMessage({
message: String(error),
type: "error",
});
item.本镜配图 = ""
type: 'error'
})
item.本镜配图 = ''
}
};
}
const onGenVideo = () => {
if (!form.adapt_result_json || form.adapt_result_json.length == 0 ) {
if (!form.adapt_result_json || form.adapt_result_json.length == 0) {
ElMessage({
message: "必要信息不能为空,请重新执行",
type: "error",
});
return;
}
let is_all_ok = true;
form.adapt_result_json.map(item => {
if (item.编号 == "" || item.场景描述 == "" || item.local_image_path == "") {
message: '必要信息不能为空,请重新执行',
type: 'error'
})
return
}
let is_all_ok = true
form.adapt_result_json.map((item) => {
if (item.编号 == '' || item.场景描述 == '' || item.local_image_path == '') {
ElMessage({
message: `分镜 ${item.编号} 的必要信息为空,请重新执行`,
type: "error",
});
is_all_ok = false;
}
});
if (!is_all_ok) return;
console.log(form.adapt_result_json);
const video_param_detail = form.adapt_result_json.map(item => {
type: 'error'
})
is_all_ok = false
}
})
if (!is_all_ok) return
console.log(form.adapt_result_json)
const video_param_detail = form.adapt_result_json.map((item) => {
return {
idx: item.编号,
text: item.场景描述,
img_path: item.local_image_path
};
});
let para_rate = `${voice_rate.value}%`;
let para_volume = `${voice_volume.value}%`;
if(voice_rate.value >= 0){para_rate = `+${para_rate}`}
if(voice_volume.value >= 0){para_volume = `+${para_volume}`}
}
})
let para_rate = `${voice_rate.value}%`
let para_volume = `${voice_volume.value}%`
if (voice_rate.value >= 0) {
para_rate = `+${para_rate}`
}
if (voice_volume.value >= 0) {
para_volume = `+${para_volume}`
}
const video_param = {
task_id: form.task_id,
if_need_subtitle: form.if_need_subtitle,
......@@ -375,81 +420,80 @@ const onGenVideo = () => {
sub_font_size: String(sub_font_size.value),
sub_font_color: sub_font_color.value,
sub_position: String(1 - sub_position.value),
sub_bg_color: "",
sub_bg_color: ''
}
if (sub_bg_color.value) {
video_param.sub_bg_color = sub_bg_color.value;
video_param.sub_bg_color = sub_bg_color.value
}
text2videoService
.submitGenVideo(video_param)
.then((result: string) => {
console.log(result);
form.final_video = "";
form.final_video = result+"?v="+utils.genDateTimeStr();
console.log(result)
form.final_video = ''
form.final_video = result + '?v=' + utils.genDateTimeStr()
})
.catch((error: any) => {
// console.error(error);
ElMessage({
message: error,
type: "error",
});
});
};
type: 'error'
})
})
}
const clean_demo = () => {
form.chatgpt_prompt = "";
form.chatgpt_answer = "";
form.chatgpt_answer_roles = <Wm.RolesItem[]>[];
form.adapt_result_json = <Wm.ScriptsItem[]>[];
form.task_id = "";
form.final_video = "";
form.chatgpt_prompt = ''
form.chatgpt_answer = ''
form.chatgpt_answer_roles = <Wm.RolesItem[]>[]
form.adapt_result_json = <Wm.ScriptsItem[]>[]
form.task_id = ''
form.final_video = ''
}
const clean_roles = () => {
form.chatgpt_answer_roles = <Wm.RolesItem[]>[];
form.chatgpt_answer_roles = <Wm.RolesItem[]>[]
}
const onChangeScreen = (val: string) => {
if (debug.value == true) {
if (val == "竖屏") {
form.task_id = default_data.en_vertical_data.task_id;
form.chatgpt_prompt = default_data.en_vertical_data.chatgpt_prompt;
form.chatgpt_answer = default_data.en_vertical_data.chatgpt_answer;
form.chatgpt_answer_roles = default_data.en_vertical_data.chatgpt_answer_roles;
form.adapt_result_json = default_data.en_vertical_data.adapt_result_json;
form.final_video = default_data.en_vertical_data.final_video;
if (val == '竖屏') {
form.task_id = default_data.en_vertical_data.task_id
form.chatgpt_prompt = default_data.en_vertical_data.chatgpt_prompt
form.chatgpt_answer = default_data.en_vertical_data.chatgpt_answer
form.chatgpt_answer_roles = default_data.en_vertical_data.chatgpt_answer_roles
form.adapt_result_json = default_data.en_vertical_data.adapt_result_json
form.final_video = default_data.en_vertical_data.final_video
}
}
}
const showsdprompt = (item: any) => {
// alert(item.画面描述词)
dialogData.value = item.画面描述词+ "," +sd_prompt_prefix+'===== negative ====='+sd_negative_prompt_prefix;
dialogVisible.value = true; // 打开对话框
dialogData.value =
item.画面描述词 + ',' + sd_prompt_prefix + '===== negative =====' + sd_negative_prompt_prefix
dialogVisible.value = true // 打开对话框
}
const upload = ref<UploadInstance>()
const actionUrl = ref(
import.meta.env.MODE === 'production'
? '/file'
: import.meta.env.VITE_APP_BASE_API + '/file'
import.meta.env.MODE === 'production' ? '/file' : import.meta.env.VITE_APP_BASE_API + '/file'
)
// 在分镜自定义上传图片时发现个bug
// 第一次上传没问题,但重复进行上传,会更新到最后一行上去。
// 奇怪,没找到原因。暂时先通过迂回的方法来处理。
let uploadItemId = 0
const onClickUpload=(val:any)=>{
const onClickUpload = (val: any) => {
uploadItemId = val.编号 - 1
console.log(uploadItemId);
console.log(uploadItemId)
}
const handleUploadSuccess = (val: Wm.UploadResult) => {
if (val.code == 0){
if (val.code == 0) {
// console.log(val)
form.adapt_result_json[uploadItemId].本镜配图 = val.data[0].url+"?v="+utils.genDateTimeStr();
form.adapt_result_json[uploadItemId].local_image_path = val.data[0].path;
form.adapt_result_json[uploadItemId].本镜配图 = val.data[0].url + '?v=' + utils.genDateTimeStr()
form.adapt_result_json[uploadItemId].local_image_path = val.data[0].path
ElMessage({
message: '上传成功',
type: 'success'
......@@ -474,22 +518,21 @@ const onPwdCheckDialog = () => {
text2videoService
.submitPwdCheck(pwdCheckValue.value)
.then((result: string) => {
if (result == "success") {
pwdCheckDialogVisible.value = false;
if (result == 'success') {
pwdCheckDialogVisible.value = false
} else {
ElMessage({
message: result,
type: "error",
});
type: 'error'
})
}
})
.catch((error: any) => {
ElMessage({
message: error,
type: "error",
});
});
type: 'error'
})
})
}
</script>
......@@ -514,9 +557,7 @@ const onPwdCheckDialog = () => {
<el-input v-model="form.chatgpt_prompt" :autosize="true" type="textarea" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmitGpt"
>生成文案({{ wenan_llm_name }}</el-button
>
<el-button type="primary" @click="onSubmitGpt">生成文案({{ wenan_llm_name }}</el-button>
</el-form-item>
<el-form-item label="文案">
<el-input v-model="form.chatgpt_answer" :autosize="true" type="textarea" />
......@@ -538,20 +579,12 @@ const onPwdCheckDialog = () => {
>
<el-table-column prop="角色" label="角色">
<template v-slot="scope">
<el-input
v-model="scope.row.角色"
:autosize="true"
type="textarea"
></el-input>
<el-input v-model="scope.row.角色" :autosize="true" type="textarea"></el-input>
</template>
</el-table-column>
<el-table-column prop="角色关键词" label="角色关键词">
<template v-slot="scope">
<el-input
v-model="scope.row.角色关键词"
:autosize="true"
type="textarea"
></el-input>
<el-input v-model="scope.row.角色关键词" :autosize="true" type="textarea"></el-input>
</template>
</el-table-column>
</el-table>
......@@ -573,38 +606,22 @@ const onPwdCheckDialog = () => {
<el-table-column prop="编号" label="编号" width="60" />
<el-table-column prop="场景描述" label="场景描述">
<template v-slot="scope">
<el-input
v-model="scope.row.场景描述"
:autosize="true"
type="textarea"
></el-input>
<el-input v-model="scope.row.场景描述" :autosize="true" type="textarea"></el-input>
</template>
</el-table-column>
<el-table-column prop="场景关键词" label="场景关键词">
<template v-slot="scope">
<el-input
v-model="scope.row.场景关键词"
:autosize="true"
type="textarea"
></el-input>
<el-input v-model="scope.row.场景关键词" :autosize="true" type="textarea"></el-input>
</template>
</el-table-column>
<el-table-column prop="角色" label="角色">
<template v-slot="scope">
<el-input
v-model="scope.row.角色"
:autosize="true"
type="textarea"
></el-input>
<el-input v-model="scope.row.角色" :autosize="true" type="textarea"></el-input>
</template>
</el-table-column>
<el-table-column prop="角色关键词" label="角色关键词">
<template v-slot="scope">
<el-input
v-model="scope.row.角色关键词"
:autosize="true"
type="textarea"
></el-input>
<el-input v-model="scope.row.角色关键词" :autosize="true" type="textarea"></el-input>
</template>
</el-table-column>
<el-table-column prop="本镜配图" label="本镜配图" width="300">
......@@ -650,9 +667,7 @@ const onPwdCheckDialog = () => {
:on-exceed="handleExceed"
:data="{ item_id: scope.row.编号 }"
>
<el-button type="primary" @click="onClickUpload(scope.row)"
>上传图片</el-button
>
<el-button type="primary" @click="onClickUpload(scope.row)">上传图片</el-button>
</el-upload>
<div style="margin: 10px 0">
<el-button plain @click="showsdprompt(scope.row)">debug</el-button>
......@@ -661,9 +676,7 @@ const onPwdCheckDialog = () => {
<p>{{ dialogData }}</p>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false"
>ok</el-button
>
<el-button type="primary" @click="dialogVisible = false">ok</el-button>
</div>
</template>
</el-dialog>
......@@ -696,11 +709,7 @@ const onPwdCheckDialog = () => {
</el-form-item>
<el-form-item>
<span style="margin: 20px 20px 0 20px">TTS语音:</span>
<el-select
v-model="voice"
placeholder="Select"
style="width: 400px; margin-top: 20px"
>
<el-select v-model="voice" placeholder="Select" style="width: 400px; margin-top: 20px">
<el-option
v-for="item in default_data.voices_en"
:key="item.value"
......@@ -708,10 +717,9 @@ const onPwdCheckDialog = () => {
:value="item.value"
>
<span style="float: left">{{ item.value }}</span>
<span
style="float: right; color: var(--el-text-color-secondary); font-size: 13px"
>{{ item.label }}</span
>
<span style="float: right; color: var(--el-text-color-secondary); font-size: 13px">{{
item.label
}}</span>
</el-option>
</el-select>
<audio
......@@ -730,10 +738,9 @@ const onPwdCheckDialog = () => {
:value="item.value"
>
<span style="float: left">{{ item.label }}</span>
<span
style="float: right; color: var(--el-text-color-secondary); font-size: 13px"
>{{ item.value }}</span
>
<span style="float: right; color: var(--el-text-color-secondary); font-size: 13px">{{
item.value
}}</span>
</el-option>
</el-select>
<audio
......@@ -756,23 +763,14 @@ const onPwdCheckDialog = () => {
</el-form-item>
<el-form-item>
<span style="margin: 20px 20px">字幕:</span>
<el-switch
v-model="form.if_need_subtitle"
active-value="true"
inactive-value="false"
/>
<el-switch v-model="form.if_need_subtitle" active-value="true" inactive-value="false" />
<div v-if="JSON.parse(form.if_need_subtitle.toLowerCase())">
<span style="margin-left: 30px">字体颜色:</span>
<el-color-picker v-model="sub_font_color" />
<span style="margin-left: 30px">字体背景:</span>
<el-color-picker v-model="sub_bg_color" />
<span style="margin-left: 30px">字体大小:</span>
<el-input-number
v-model="sub_font_size"
:min="1"
:max="50"
controls-position="right"
/>
<el-input-number v-model="sub_font_size" :min="1" :max="50" controls-position="right" />
<span style="margin-left: 30px">在屏幕上的位置:</span>
<el-slider
v-model="sub_position"
......
<script setup lang="ts">
import text2videoService from "@/api/service/text2videoService";
import utils from "@/utils/utils";
import text2videoService from '@/api/service/text2videoService'
import utils from '@/utils/utils'
import {
ElMessage, genFileId,
ElMessage,
genFileId,
type UploadInstance,
type UploadProps,
type UploadRawFile
} from "element-plus";
import { nextTick, onMounted, reactive, ref } from "vue";
} from 'element-plus'
import { nextTick, onMounted, reactive, ref } from 'vue'
const debug = ref(import.meta.env.MODE === 'production' ? false : true);
const debug = ref(import.meta.env.MODE === 'production' ? false : true)
// const debug = ref(false);
const loading = ref(false);
const loading = ref(false)
const form = reactive({
task_id: "",
task_content: "开发一款坦克大战的小游戏",
task_id: '',
task_content: '开发一款坦克大战的小游戏',
prompt: `你是一名高级的程序员。现在需要根据产品需求,设计一份完整的程序编写的思路,比如第一步写什么模块,第二步写什么模块,整个思路应逻辑清晰、流畅。以json格式输出:\n[{"序号": 序号, "描述": 描述, "详细说明": 详细说明}, ...]\n`,
answer: "",
answer_json: [],
});
answer: '',
answer_json: []
})
const llms = {
tyqw_online: {'api': 'tyqw', 'name':'线上通义千问'},
baichuan: {'api': 'langchain', 'name':'本地baichuan2-7b'},
qwen_local: {'api': 'langchain', 'name':'本地Qwen-7B-Chat'},
chatgpt: {'api': 'gpt', 'name':'chatgpt'},
kimi: {'api': 'kimi', 'name':'kimi'},
};
tyqw_online: { api: 'tyqw', name: '线上通义千问' },
baichuan: { api: 'langchain', name: '本地baichuan2-7b' },
qwen_local: { api: 'langchain', name: '本地Qwen-7B-Chat' },
chatgpt: { api: 'gpt', name: 'chatgpt' },
kimi: { api: 'kimi', name: 'kimi' }
}
onMounted(() => {
// 初始化task_id
form.task_id = utils.genDateTimeStr();
form.task_id = utils.genDateTimeStr()
console.log('页面加载,task_id=', form.task_id)
});
})
const onSubmitGpt = () => {
const my_prompt = ref(`${form.prompt}。产品需求如下:${form.task_content}`);
const my_prompt = ref(`${form.prompt}。产品需求如下:${form.task_content}`)
text2videoService
.submitLLM(utils.aesEncrypt(my_prompt.value), utils.aesEncrypt(llms.tyqw_online.api), [], form.task_id, "true")
.submitLLM(
utils.aesEncrypt(my_prompt.value),
utils.aesEncrypt(llms.tyqw_online.api),
[],
form.task_id,
'true'
)
.then((result: string) => {
console.log(result);
form.answer = result;
const jsonText = utils.extractJSON(result);
console.log(jsonText);
form.answer_json = utils.formatJsonObj(jsonText!);
console.log(result)
form.answer = result
const jsonText = utils.extractJSON(result)
console.log(jsonText)
form.answer_json = utils.formatJsonObj(jsonText!)
})
.catch((error: any) => {
console.error(error);
console.error(error)
ElMessage({
message: error,
type: "error",
});
});
};
type: 'error'
})
})
}
</script>
<!-- ============================================================================================================ -->
......@@ -99,9 +106,7 @@ const onSubmitGpt = () => {
<el-table-column width="200" label="操作" align="center">
<template v-slot="scope">
<div style="margin: 5px 0">
<el-button type="primary" size="small" @click=""
>与大模型继续沟通</el-button
>
<el-button type="primary" size="small" @click="">与大模型继续沟通</el-button>
</div>
<!-- <div style="margin: 10px 0">
......
/// <reference path="./types/index.d.ts" />
declare module 'element-plus/dist/locale/zh-cn.mjs'
declare module 'vue-drag-resize';
\ No newline at end of file
......@@ -68,4 +68,19 @@ declare namespace Wm {
"x": int,
"y": int,
}
interface LoginParams {
username: string
password: string
captcha?: string
}
interface RegisterParams {
confirm_password: string
email?: string
group_ids: number[]
nickname: string
password: string
username: 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 to comment