Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
T
text2video-frontend
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
周成波
text2video-frontend
Commits
c0bbf709
Commit
c0bbf709
authored
Mar 02, 2024
by
周成波
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
大改了一波
parent
53878490
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
1039 additions
and
116 deletions
+1039
-116
components.d.ts
components.d.ts
+0
-2
waiting.png
src/assets/waiting.png
+0
-0
useManyValues.ts
src/views/home/compositions/useManyValues.ts
+36
-4
index--bak20240301.vue
src/views/home/index--bak20240301.vue
+752
-0
index.vue
src/views/home/index.vue
+241
-104
index_en.vue
src/views/home/index_en.vue
+8
-6
lib.wm.api.d.ts
typings/types/wm/lib.wm.api.d.ts
+2
-0
No files found.
components.d.ts
View file @
c0bbf709
...
...
@@ -8,7 +8,6 @@ export {}
declare
module
'vue'
{
export
interface
GlobalComponents
{
ElButton
:
typeof
import
(
'element-plus/es'
)[
'ElButton'
]
ElCol
:
typeof
import
(
'element-plus/es'
)[
'ElCol'
]
ElColorPicker
:
typeof
import
(
'element-plus/es'
)[
'ElColorPicker'
]
ElDialog
:
typeof
import
(
'element-plus/es'
)[
'ElDialog'
]
ElDivider
:
typeof
import
(
'element-plus/es'
)[
'ElDivider'
]
...
...
@@ -20,7 +19,6 @@ declare module 'vue' {
ElOption
:
typeof
import
(
'element-plus/es'
)[
'ElOption'
]
ElRadio
:
typeof
import
(
'element-plus/es'
)[
'ElRadio'
]
ElRadioGroup
:
typeof
import
(
'element-plus/es'
)[
'ElRadioGroup'
]
ElRow
:
typeof
import
(
'element-plus/es'
)[
'ElRow'
]
ElSelect
:
typeof
import
(
'element-plus/es'
)[
'ElSelect'
]
ElSlider
:
typeof
import
(
'element-plus/es'
)[
'ElSlider'
]
ElSwitch
:
typeof
import
(
'element-plus/es'
)[
'ElSwitch'
]
...
...
src/assets/waiting.png
0 → 100644
View file @
c0bbf709
1.02 KB
src/views/home/compositions/useManyValues.ts
View file @
c0bbf709
...
...
@@ -48,6 +48,13 @@ export const useManyValues = () => {
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
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'
},
};
const
horizontal_data
=
{
task_id
:
"20240209114425596"
,
chatgpt_prompt
:
`生成一个50字的小故事`
,
...
...
@@ -60,7 +67,7 @@ export const useManyValues = () => {
const
vertical_data
=
{
task_id
:
"20240220181602687"
,
chatgpt_prompt
:
`生成一个50字的科幻小故事,阿凡达系列`
,
chatgpt_answer
:
``
,
chatgpt_answer
:
`
在奇幻的熊猫王国,阿宝——不再是功夫大师,而化身为西游世界里的神秘旅者。一日,阿宝巧遇唐僧师徒四人,他们正被一只诡异黑影困扰,经书被盗,疑云密布。画面中,阿宝手持九齿钉耙,取代八戒成为护法,勇闯妖洞。峰回路转,黑影竟是被封印的悟空分身,误入歧途。阿宝凭借智慧与武力,解开误会,反转剧情,助悟空分身归于本体,携手抵御真正邪魔,恢复取经历程的和平景象。
`
,
chatgpt_answer_roles
:
[],
adapt_result_json
:
[],
final_video
:
``
,
...
...
@@ -181,7 +188,7 @@ export const useManyValues = () => {
gender
:
'Male'
,
label
:
'男,台湾腔'
,
},
]
]
;
const
voices_en
=
[
{
...
...
@@ -189,7 +196,7 @@ export const useManyValues = () => {
gender
:
'Male'
,
label
:
'男,美式磁性'
,
},
]
]
;
const
bgm
=
[
{
...
...
@@ -224,12 +231,36 @@ export const useManyValues = () => {
value
:
'大自然'
,
label
:
'大自然'
,
},
]
];
const
role_attribute_options
=
[
{
value
:
'人'
,
label
:
'人'
,
},
{
value
:
'动物'
,
label
:
'动物'
,
},
{
value
:
'物品'
,
label
:
'物品'
,
},
{
value
:
'其他生物'
,
label
:
'其他生物'
,
},
{
value
:
'未知'
,
label
:
'未知'
,
},
];
return
{
screen
:
screen
,
sd_prompt_prefix
:
sd_prompt_prefix
,
sd_negative_prompt_prefix
:
sd_negative_prompt_prefix
,
llms
:
llms
,
horizontal_data
:
horizontal_data
,
vertical_data
:
vertical_data
,
if_need_subtitle
:
if_need_subtitle
,
...
...
@@ -239,5 +270,6 @@ export const useManyValues = () => {
voices_en
:
voices_en
,
bgm
:
bgm
,
bgm_volume_marks
:
bgm_volume_marks
,
role_attribute_options
:
role_attribute_options
,
}
}
src/views/home/index--bak20240301.vue
0 → 100644
View file @
c0bbf709
<
script
setup
lang=
"ts"
>
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"
;
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
form
=
reactive
({
screen
:
default_data
.
screen
,
if_need_subtitle
:
default_data
.
if_need_subtitle
,
chatgpt_prompt
:
""
,
chatgpt_answer
:
""
,
chatgpt_answer_roles
:
<
Wm
.
RolesItem
[]
>
[],
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
;
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
=
tyqw
.
api
const
wenan_llm_name
=
tyqw
.
name
const
role_llm
=
tyqw
.
api
const
role_llm_name
=
tyqw
.
name
const
role_keywords_llm
=
qwen
.
api
const
role_keywords_llm_name
=
qwen
.
name
const
tuili_llm
=
qwen
.
api
const
tuili_llm_name
=
qwen
.
name
const
fanyi_llm
=
qwen
.
api
const
fanyi_llm_name
=
qwen
.
name
const
voice_rate
=
ref
(
-
15
)
const
voice_volume
=
ref
(
0
)
const
voice
=
ref
(
"zh-CN-YunjianNeural"
)
const
bgm
=
ref
(
"解忧曲"
)
const
bgm_volume
=
ref
(
0.3
)
const
pwdCheckDialogVisible
=
ref
(
false
);
const
pwdCheckValue
=
ref
(
""
)
const
sub_font_color
=
ref
(
"#FFFF00"
)
const
sub_font_size
=
ref
(
30
)
const
sub_position
=
ref
(
0.3
)
onMounted
(()
=>
{
// 初始化示例数据
onChangeScreen
(
form
.
screen
);
// 初始化密码框
if
(
debug
.
value
==
true
)
{
pwdCheckDialogVisible
.
value
=
false
;
}
else
{
pwdCheckDialogVisible
.
value
=
true
;
}
});
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
;
})
.
catch
((
error
:
any
)
=>
{
// console.error(error);
ElMessage
({
message
:
error
,
type
:
"error"
,
});
});
};
const
onAdaptRoles
=
async
()
=>
{
if
(
!
form
.
chatgpt_answer
||
form
.
chatgpt_answer
.
length
==
0
)
{
ElMessage
({
message
:
"文案不能为空"
,
type
:
"error"
,
});
return
;
}
loading
.
value
=
true
;
// 推理角色
form
.
chatgpt_answer_roles
=
[];
try
{
const
adapt_restrict
=
`
指令:
请理解这个故事,给出这个故事中的所有角色,多个角色以逗号分隔`
;
const
roles
=
await
text2videoService
.
submitLLM
(
"故事:
\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
(
/
[
,,
]
/
);
console
.
log
(
roles_arr
)
async
function
processRoles
()
{
for
(
const
one_role
of
roles_arr
)
{
await
delay
(
100
);
const
adapt_keyword_restrict
=
`
指令:
请理解这个故事,给出这个角色“
${
one_role
.
trim
()}
”的关键词(性别(可以发挥想象进行补充,但一定要有),年龄(可以发挥想象进行补充,但一定要有),
肤色(可以发挥想象进行补充,但一定要有),衣服(可以发挥想象进行补充,但一定要有),发型(可以发挥想象进行补充,但一定要有),
发色(可以发挥想象进行补充,但一定要有),脸色(可以发挥想象进行补充,但一定要有),五官特点(可以发挥想象进行补充,但一定要有))。
要求:
关键词以逗号分隔。
只要返回关键词,不需要其他的说明文字。`
;
let
keywords
=
await
text2videoService
.
submitLLM
(
"故事:
\n
"
+
form
.
chatgpt_answer
+
"
\n
"
+
adapt_keyword_restrict
,
role_keywords_llm
);
keywords
=
keywords
.
replace
(
/。/g
,
''
).
replace
(
/、/g
,
','
)
// await delay(100);
// const adapt_attribute_restrict = `
// 指令:
// 请理解这个故事,从“人,动物,物品,其他生物,未知”中选择这个角色“${one_role.trim()}”的属性。只能在所给选项中选择,并且只需要返回选择的结果,不需要其他的说明文字。`;
// let attribute = await text2videoService.submitLLM("故事:\n" + form.chatgpt_answer + "\n" + adapt_attribute_restrict, role_keywords_llm);
// attribute = attribute.replace(/。/g, '').replace(/、/g, ',')
form
.
chatgpt_answer_roles
.
push
({
"角色"
:
one_role
.
trim
(),
"角色关键词"
:
keywords
.
trim
()
+
",穿着衣服"
,
// "属性": attribute.trim(),
"属性"
:
""
,
"角色英文关键词"
:
""
,
});
}
}
try
{
await
processRoles
();
console
.
log
(
form
.
chatgpt_answer_roles
)
}
catch
(
error
)
{
ElMessage
({
message
:
String
(
error
),
type
:
"error"
});
}
finally
{
loading
.
value
=
false
;
// 最终关闭loading(无论成功或失败)
}
}
catch
(
error
)
{
ElMessage
({
message
:
String
(
error
),
type
:
"error"
,
});
}
finally
{
// 最终关闭loading(无论成功或失败)
loading
.
value
=
false
;
}
};
const
onAdapt
=
async
()
=>
{
if
(
!
form
.
chatgpt_answer
||
form
.
chatgpt_answer
.
length
==
0
)
{
ElMessage
({
message
:
"文案不能为空"
,
type
:
"error"
,
});
return
;
}
loading
.
value
=
true
;
form
.
task_id
=
utils
.
genDateTimeStr
();
console
.
log
(
form
.
task_id
)
// 按标点拆分成分镜
const
sentences
=
utils
.
splitText
(
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"
:
""
,
});
}
console
.
log
(
form
.
adapt_result_json
)
async
function
processScenes
()
{
for
(
const
item
of
form
.
adapt_result_json
)
{
await
onAdaptOne
(
item
);
// await delay(100);
// await onDrawOne(item);
onDrawOne
(
item
);
}
}
try
{
await
processScenes
();
ElMessage
({
message
:
"all scene ok"
,
type
:
"success"
});
console
.
log
(
form
.
adapt_result_json
);
}
catch
(
error
)
{
ElMessage
({
message
:
String
(
error
),
type
:
"error"
});
}
finally
{
loading
.
value
=
false
;
// 最终关闭loading(无论成功或失败)
}
};
const
onAdaptOne
=
async
(
item
:
any
)
=>
{
if
(
!
item
.
场景描述
)
{
ElMessage
({
message
:
"分镜场景描述不能为空"
,
type
:
"error"
,
});
return
;
}
// 推理关键词
try
{
const
adapt_restrict
=
`
指令:
请理解这个故事,给出这个场景“
${
item
.
场景描述
}
”的关键词(年代(可以发挥想象进行补充,但一定要有),空间(可以发挥想象进行补充,但一定要有),
时间段(可以发挥想象进行补充,但一定要有),地理环境(可以发挥想象进行补充,但一定要有),天气(可以发挥想象进行补充,但一定要有),
物品(可以发挥想象进行补充,但一定要有),人物(可以发挥想象进行补充,但一定要有),镜头角度(可以发挥想象进行补充,但一定要有))。
要求:
关键词以逗号分隔。
只要返回关键词,不需要其他的说明文字。`
;
const
keywords
=
await
text2videoService
.
submitLLM
(
"故事:
\n
"
+
form
.
chatgpt_answer
+
"
\n
"
+
adapt_restrict
,
tuili_llm
);
// console.log(keywords)
item
.
场景关键词
=
keywords
;
if
(
form
.
chatgpt_answer_roles
.
length
===
0
)
{
// 总角色为空
item
.
角色
=
''
;
item
.
角色关键词
=
''
;
}
else
{
// 总角色不为空
const
adapt_role_restrict
=
`
指令:
请理解这个故事,针对其中的这个场景:“
${
item
.
场景描述
}
”,从所有角色中选择本场景的角色,多个角色以逗号分隔。`
;
const
item_roles
=
await
text2videoService
.
submitLLM
(
"故事:
\n
"
+
form
.
chatgpt_answer
+
"
\n
所有角色:
\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
=
""
// 人工匹配角色关键词,先找想同的
for
(
const
i
of
form
.
chatgpt_answer_roles
)
{
if
(
i
[
"角色"
].
trim
()
==
one_item_role
.
trim
())
{
temp_role_kws
=
`【
${
i
[
"角色"
]}
:
${
i
[
"角色关键词"
]}
】`
;
// 找到就ok
break
;
}
}
// 如果找不到相同的,则模糊匹配
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
[
"角色关键词"
]}
】`
;
// 匹配到一个就ok
break
;
}
}
}
role_kws
=
`
${
role_kws
}${
temp_role_kws
}
`
;
})
item
.
角色关键词
=
role_kws
;
}
}
catch
(
error
)
{
ElMessage
({
message
:
String
(
error
),
type
:
"error"
,
});
}
};
const
onDrawOne
=
async
(
item
:
any
)
=>
{
if
(
!
item
.
场景描述
&&
!
item
.
场景关键词
)
{
ElMessage
({
message
:
"场景描述和场景关键词不能都为空"
,
type
:
"error"
,
});
return
;
}
// 翻译+画图
if
(
!
form
.
task_id
)
{
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
+
`场景描述为:
${
item
.
场景描述
}
\n`
};
if
(
item
.
场景关键词
)
{
temp_prompt
=
temp_prompt
+
`场景关键词为:
${
item
.
场景关键词
}
\n`
};
if
(
item
.
角色
)
{
temp_prompt
=
temp_prompt
+
`场景中的角色有:
${
item
.
角色
}
\n`
};
if
(
item
.
角色关键词
)
{
temp_prompt
=
temp_prompt
+
`角色关键词为:
${
item
.
角色关键词
}
\n`
};
const
sd_describe
=
await
text2videoService
.
submitLLM
(
`
${
temp_prompt
}
指令:
请理解以上内容,并返回一段英文的描述。`
,
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
;
}
catch
(
error
)
{
ElMessage
({
message
:
String
(
error
),
type
:
"error"
,
});
item
.
本镜配图
=
""
}
};
const
onGenVideo
=
()
=>
{
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
==
""
)
{
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
=>
{
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
}
`
}
const
video_param
=
{
task_id
:
form
.
task_id
,
if_need_subtitle
:
form
.
if_need_subtitle
,
lang
:
"zh"
,
task_info
:
video_param_detail
,
rate
:
para_rate
,
volume
:
para_volume
,
voice
:
voice
.
value
,
bgm
:
bgm
.
value
,
bgm_volume
:
bgm_volume
.
value
,
sub_font_size
:
String
(
sub_font_size
.
value
),
sub_font_color
:
sub_font_color
.
value
,
sub_position
:
String
(
1
-
sub_position
.
value
),
}
text2videoService
.
submitGenVideo
(
video_param
)
.
then
((
result
:
string
)
=>
{
console
.
log
(
result
);
form
.
final_video
=
""
;
form
.
final_video
=
result
+
"?v="
+
utils
.
genDateTimeStr
();
})
.
catch
((
error
:
any
)
=>
{
// console.error(error);
ElMessage
({
message
:
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
=
""
;
}
const
clean_roles
=
()
=>
{
form
.
chatgpt_answer_roles
=
<
Wm
.
RolesItem
[]
>
[];
}
const
onChangeScreen
=
(
val
:
string
)
=>
{
if
(
debug
.
value
==
true
)
{
if
(
val
==
"横屏"
)
{
form
.
task_id
=
default_data
.
horizontal_data
.
task_id
;
form
.
chatgpt_prompt
=
default_data
.
horizontal_data
.
chatgpt_prompt
;
form
.
chatgpt_answer
=
default_data
.
horizontal_data
.
chatgpt_answer
;
form
.
chatgpt_answer_roles
=
default_data
.
horizontal_data
.
chatgpt_answer_roles
;
form
.
adapt_result_json
=
default_data
.
horizontal_data
.
adapt_result_json
;
form
.
final_video
=
default_data
.
horizontal_data
.
final_video
;
}
else
{
form
.
task_id
=
default_data
.
vertical_data
.
task_id
;
form
.
chatgpt_prompt
=
default_data
.
vertical_data
.
chatgpt_prompt
;
form
.
chatgpt_answer
=
default_data
.
vertical_data
.
chatgpt_answer
;
form
.
chatgpt_answer_roles
=
default_data
.
vertical_data
.
chatgpt_answer_roles
;
form
.
adapt_result_json
=
default_data
.
vertical_data
.
adapt_result_json
;
form
.
final_video
=
default_data
.
vertical_data
.
final_video
;
}
}
}
const
showsdprompt
=
(
item
:
any
)
=>
{
// alert(item.画面描述词)
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'
)
const
handleUploadSuccess
=
(
val
:
Wm
.
UploadResult
)
=>
{
if
(
val
.
code
==
0
){
// console.log(val)
const
id
=
parseInt
(
val
.
message
)
-
1
;
form
.
adapt_result_json
[
id
].
本镜配图
=
val
.
data
[
0
].
url
+
"?v="
+
utils
.
genDateTimeStr
();
form
.
adapt_result_json
[
id
].
local_image_path
=
val
.
data
[
0
].
path
;
ElMessage
({
message
:
'上传成功'
,
type
:
'success'
})
}
else
{
ElMessage
({
message
:
'上传失败'
,
type
:
'error'
})
}
}
const
handleExceed
:
UploadProps
[
'onExceed'
]
=
(
files
)
=>
{
upload
.
value
!
.
clearFiles
()
const
file
=
files
[
0
]
as
UploadRawFile
file
.
uid
=
genFileId
()
upload
.
value
!
.
handleStart
(
file
)
upload
.
value
!
.
submit
()
}
const
onPwdCheckDialog
=
()
=>
{
text2videoService
.
submitPwdCheck
(
pwdCheckValue
.
value
)
.
then
((
result
:
string
)
=>
{
if
(
result
==
"success"
)
{
pwdCheckDialogVisible
.
value
=
false
;
}
else
{
ElMessage
({
message
:
result
,
type
:
"error"
,
});
}
})
.
catch
((
error
:
any
)
=>
{
ElMessage
({
message
:
error
,
type
:
"error"
,
});
});
}
const
onDeleteOne
=
(
item
:
any
)
=>
{
try
{
let
delete_no
=
item
.
编号
;
// 删除记录
form
.
adapt_result_json
=
form
.
adapt_result_json
.
filter
(
item
=>
item
.
编号
!==
delete_no
);
// 重新对记录进行编号
form
.
adapt_result_json
=
form
.
adapt_result_json
.
map
((
item
,
index
)
=>
{
return
{...
item
,
编号
:
(
index
+
1
).
toString
()};
});
}
catch
(
error
)
{
ElMessage
({
message
:
String
(
error
),
type
:
"error"
,
});
}
};
</
script
>
<
template
>
<main
class=
"home-container"
>
<!-- 标题 -->
<el-divider
content-position=
"left"
>
text2video
</el-divider>
<el-form
:model=
"form"
label-width=
"114px"
v-loading=
"loading"
>
<el-form-item>
<div>
<el-radio-group
v-model=
"form.screen"
@
change=
"onChangeScreen"
>
<el-radio
label=
"横屏"
size=
"large"
border
/>
<el-radio
label=
"竖屏"
size=
"large"
border
/>
</el-radio-group>
</div>
</el-form-item>
<el-form-item>
<el-button
type=
"success"
@
click=
"clean_demo"
>
清除所有数据
</el-button>
</el-form-item>
<!-- Prompt到文案 -->
<el-form-item
label=
"Prompt"
>
<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-form-item>
<el-form-item
label=
"文案"
>
<el-input
v-model=
"form.chatgpt_answer"
:autosize=
"true"
type=
"textarea"
/>
</el-form-item>
<!-- 角色 -->
<el-form-item>
<el-button
type=
"primary"
@
click=
"onAdaptRoles"
>
推理角色(
{{
role_llm_name
}}
)、推理角色关键词(
{{
role_keywords_llm_name
}}
)
</el-button>
<el-button
plain
@
click=
"clean_roles"
>
清空总角色列表
</el-button>
</el-form-item>
<el-form-item
label=
"角色"
>
<el-table
:data=
"form.chatgpt_answer_roles"
border
style=
"width: 100%; z-index: calc(var(--el-table-index) -1)"
>
<el-table-column
prop=
"角色"
label=
"角色"
width=
"300"
>
<template
v-slot=
"scope"
>
<el-input
v-model=
"scope.row.角色"
:autosize=
"true"
type=
"textarea"
></el-input>
</
template
>
</el-table-column>
<el-table-column
prop=
"属性"
label=
"属性"
width=
"300"
>
<
template
v-slot=
"scope"
>
<el-select
v-model=
"scope.row.属性"
filterable
allow-create
:reserve-keyword=
"false"
>
<el-option
v-for=
"item in default_data.role_attribute_options"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
</
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>
</
template
>
</el-table-column>
</el-table>
</el-form-item>
<!-- 分镜 -->
<el-form-item>
<el-button
type=
"primary"
@
click=
"onAdapt"
>
分镜、推理场景关键词({{tuili_llm_name}})、英文描述({{fanyi_llm_name}})、绘图
</el-button>
</el-form-item>
<el-form-item
label=
"分镜"
>
<el-table
:data=
"form.adapt_result_json"
border
style=
"width: 100%; z-index: calc(var(--el-table-index) -1)"
>
<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>
</
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>
</
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>
</
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>
</
template
>
</el-table-column>
<el-table-column
prop=
"本镜配图"
label=
"本镜配图"
width=
"300"
>
<
template
v-slot=
"scope"
>
<div>
<el-image
:src=
"scope.row.本镜配图"
:zoom-rate=
"1.2"
:max-scale=
"1.5"
:min-scale=
"0.5"
:preview-src-list=
"[scope.row.本镜配图]"
fit=
"cover"
:hide-on-click-modal=
"true"
/>
</div>
</
template
>
</el-table-column>
<el-table-column
width=
"120"
label=
"操作"
align=
"center"
>
<!--
<template v-slot:header>
<el-button type="danger" size="default" @click="">批量绘制所有图片</el-button>
</template>
-->
<
template
v-slot=
"scope"
>
<div
style=
"margin: 10px 0"
><el-button
type=
"primary"
size=
"default"
@
click=
"onAdaptOne(scope.row)"
>
推理关键词
</el-button></div>
<div
style=
"margin: 10px 0"
><el-button
type=
"primary"
size=
"default"
@
click=
"onDrawOne(scope.row)"
>
翻译、绘图
</el-button></div>
<el-upload
class=
"upload-demo"
ref=
"upload"
list-type=
"picture"
:show-file-list=
"false"
:limit=
"1"
:action=
"actionUrl"
:on-success=
"handleUploadSuccess"
:on-exceed=
"handleExceed"
:data=
"
{item_id: scope.row.编号}"
>
<el-button
type=
"primary"
>
上传图片
</el-button>
</el-upload>
<div
style=
"margin: 10px 0"
><el-button
plain
@
click=
"showsdprompt(scope.row)"
>
debug
</el-button></div>
<el-dialog
v-model=
dialogVisible
width=
"80%"
>
<p>
{{
dialogData
}}
</p>
<template
#
footer
>
<div
class=
"dialog-footer"
>
<el-button
type=
"primary"
@
click=
"dialogVisible = false"
>
ok
</el-button>
</div>
</
template
>
</el-dialog>
<div
style=
"margin: 10px 0"
><el-button
type=
"danger"
size=
"default"
@
click=
"onDeleteOne(scope.row)"
>
删除本镜
</el-button></div>
</template>
</el-table-column>
</el-table>
</el-form-item>
<!-- 生成视频 -->
<el-form-item
label=
"设置"
>
<span
style=
"margin: 0 20px"
>
TTS语速:
</span>
<el-slider
v-model=
"voice_rate"
show-input
:min=
"-50"
:max=
"50"
:marks=
"default_data.marks"
style=
"width: 900px"
/>
</el-form-item>
<el-form-item>
<span
style=
"margin: 0 20px"
>
TTS音量:
</span>
<el-slider
v-model=
"voice_volume"
show-input
:min=
"-80"
:max=
"80"
:marks=
"default_data.marks"
style=
"width: 900px"
/>
</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-option
v-for=
"item in default_data.voices"
:key=
"item.value"
:label=
"item.value"
: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>
</el-option>
</el-select>
<audio
:src=
"'src/assets/edge-tts-voices/' + voice + '.mp3'"
controls
style=
"height: 30px; margin: 20px 0 0 10px;"
></audio>
</el-form-item>
<el-form-item>
<span
style=
"margin: 0 20px"
>
背景音乐:
</span>
<el-select
v-model=
"bgm"
placeholder=
"无"
style=
"width: 400px;"
>
<el-option
v-for=
"item in default_data.bgm"
:key=
"item.value"
:label=
"item.value"
: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>
</el-option>
</el-select>
<audio
:src=
"'src/assets/bgm/' + bgm + '.mp3'"
controls
style=
"height: 30px; margin-left:10px;"
></audio>
</el-form-item>
<el-form-item>
<span
style=
"margin: 0 20px"
>
背景音量:
</span>
<el-slider
v-model=
"bgm_volume"
show-input
:step=
"0.1"
:min=
"0"
:max=
"2"
:marks=
"default_data.bgm_volume_marks"
style=
"width: 600px"
/>
</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"
/>
<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-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"
:step=
"0.1"
:min=
"0"
:max=
"1"
show-input
vertical
height=
"100px"
/>
</div>
</el-form-item>
<el-form-item>
<el-button
type=
"primary"
@
click=
"onGenVideo"
>
生成视频
</el-button>
</el-form-item>
<el-form-item>
<video
:src=
"form.final_video"
controls
></video>
</el-form-item>
</el-form>
<!-- 授权密码框 -->
<el-dialog
v-model=
pwdCheckDialogVisible
title=
"请输入密码"
width=
"20%"
:close-on-click-modal=
"false"
:close-on-press-escape=
"false"
:show-close=
"false"
>
<el-form
:model=
"form"
>
<el-form-item
label=
"密码"
>
<el-input
v-model=
"pwdCheckValue"
autocomplete=
"off"
type=
"password"
show-password
/>
</el-form-item>
</el-form>
<
template
#
footer
>
<div
class=
"dialog-footer"
>
<el-button
type=
"primary"
@
click=
"onPwdCheckDialog()"
>
ok
</el-button>
</div>
</
template
>
</el-dialog>
</main>
</template>
<
style
lang=
"scss"
scoped
>
.home-container
{
width
:
100%
;
}
</
style
>
<
style
lang=
"scss"
>
.home-container
{
.el-table
.el-table__cell
{
z-index
:
calc
(
var
(
--
el-table-index
)
-1
);
}
}
</
style
>
src/views/home/index.vue
View file @
c0bbf709
<
script
setup
lang=
"ts"
>
import
{
onMounted
,
reactive
,
ref
}
from
"vue"
;
import
{
onMounted
,
reactive
,
ref
,
nextTick
}
from
"vue"
;
import
{
Sunny
,
UploadFilled
}
from
"@element-plus/icons-vue"
;
import
{
ElMessage
,
genFileId
,
type
UploadInstance
,
...
...
@@ -28,23 +28,14 @@ const form = reactive({
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
wenan_llm
=
qwen
.
api
const
wenan_llm_name
=
qwen
.
name
const
role_llm
=
tyqw
.
api
const
role_llm_name
=
tyqw
.
name
const
role_keywords_llm
=
qwen
.
api
const
role_keywords_llm_name
=
qwen
.
name
const
tuili_llm
=
qwen
.
api
const
tuili_llm_name
=
qwen
.
name
const
fanyi_llm
=
qwen
.
api
const
fanyi_llm_name
=
qwen
.
name
const
voice_rate
=
ref
(
-
15
)
const
wen_an_llm
=
default_data
.
llms
.
tyqw_online
;
const
role_llm
=
default_data
.
llms
.
tyqw_online
;
const
role_keywords_llm
=
default_data
.
llms
.
qwen_local
;
const
tuili_llm
=
default_data
.
llms
.
qwen_local
;
const
tuili_keyword_llm
=
default_data
.
llms
.
tyqw_online
;
const
fanyi_llm
=
default_data
.
llms
.
qwen_local
;
const
voice_rate
=
ref
(
-
10
)
const
voice_volume
=
ref
(
0
)
const
voice
=
ref
(
"zh-CN-YunjianNeural"
)
const
bgm
=
ref
(
"解忧曲"
)
...
...
@@ -52,8 +43,8 @@ const bgm_volume = ref(0.3)
const
pwdCheckDialogVisible
=
ref
(
false
);
const
pwdCheckValue
=
ref
(
""
)
const
sub_font_color
=
ref
(
"#FFFF00"
)
const
sub_font_size
=
ref
(
30
)
const
sub_position
=
ref
(
0.
2
)
const
sub_font_size
=
ref
(
25
)
const
sub_position
=
ref
(
0.
4
)
onMounted
(()
=>
{
...
...
@@ -71,7 +62,7 @@ const delay = (ms: any) => new Promise(res => setTimeout(res, ms));
const
onSubmitGpt
=
()
=>
{
text2videoService
.
submitLLM
(
form
.
chatgpt_prompt
,
wen
an_llm
)
.
submitLLM
(
form
.
chatgpt_prompt
,
wen
_an_llm
.
api
)
.
then
((
result
:
string
)
=>
{
console
.
log
(
form
.
chatgpt_prompt
);
console
.
log
(
result
);
...
...
@@ -94,36 +85,75 @@ const onAdaptRoles = async () => {
});
return
;
}
loading
.
value
=
true
;
// 推理角色
form
.
chatgpt_answer_roles
=
[];
try
{
const
adapt_restrict
=
`
指令:
请理解这个故事,给出这个故事中的所有角色,多个角色以逗号分隔`
;
const
roles
=
await
text2videoService
.
submitLLM
(
"故事:
\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
(
/
[
,,
]
/
);
console
.
log
(
roles_arr
)
const
adapt_restrict
=
`请理解这个故事:“
${
form
.
chatgpt_answer
}
”,给出这个故事中的所有角色,多个角色以逗号分隔。\n要求:只返回角色名称即可,不要添加其他的内容。`
;
let
roles
=
await
text2videoService
.
submitLLM
(
adapt_restrict
,
role_llm
.
api
);
form
.
all_roles
=
roles
.
replace
(
/。|(|)/g
,
''
).
replace
(
/、/g
,
','
);
console
.
log
(
form
.
all_roles
);
const
roles_arr
=
form
.
all_roles
.
split
(
/
[
,,
]
/
);
console
.
log
(
roles_arr
);
// 推理属性
const
attribute_options
=
default_data
.
role_attribute_options
.
map
(
option
=>
option
.
value
);
for
(
const
one_role
of
roles_arr
)
{
// const adapt_attribute_restrict = `根据这个故事:“${form.chatgpt_answer}”,你认为这个角色:“${one_role}”是“${String(attribute_options)}”中的哪一种?请返回选择的结果。\n要求:只返回选择的结果即可,不要添加其他的内容。`;
const
adapt_attribute_restrict
=
`你认为这个角色:“
${
one_role
}
”是“
${
String
(
attribute_options
)}
”中的哪一种?请返回选择的结果。\n要求:只返回选择的结果即可,不要添加其他的内容。`
;
let
attribute
=
await
text2videoService
.
submitLLM
(
adapt_attribute_restrict
,
role_keywords_llm
.
api
);
form
.
chatgpt_answer_roles
.
push
({
"角色"
:
one_role
.
trim
(),
"角色关键词"
:
""
,
"角色英文关键词"
:
""
,
"属性"
:
attribute
,
});
}
console
.
log
(
form
.
chatgpt_answer_roles
)
};
const
onAdaptRolesKeywords
=
async
()
=>
{
if
(
!
form
.
chatgpt_answer_roles
||
form
.
chatgpt_answer_roles
.
length
==
0
)
{
ElMessage
({
message
:
"总角色不能为空"
,
type
:
"error"
,
});
return
;
}
for
(
const
one_role
of
form
.
chatgpt_answer_roles
)
{
if
(
!
one_role
.
属性
)
{
ElMessage
({
message
:
`请选择
${
one_role
.
角色
}
的属性`
,
type
:
"error"
,
});
return
;
}
}
loading
.
value
=
true
;
// 推理角色关键词
try
{
async
function
processRoles
()
{
for
(
const
one_role
of
roles_arr
)
{
for
(
const
one_role
of
form
.
chatgpt_answer_roles
)
{
await
delay
(
100
);
const
adapt_keyword_restrict
=
`
指令:
请理解这个故事,给出这个角色“
${
one_role
.
trim
()}
”的关键词(性别(可以发挥想象进行补充,但一定要有),年龄(可以发挥想象进行补充,但一定要有),
肤色(可以发挥想象进行补充,但一定要有),衣服(可以发挥想象进行补充,但一定要有),发型(可以发挥想象进行补充,但一定要有),
发色(可以发挥想象进行补充,但一定要有),脸色(可以发挥想象进行补充,但一定要有),五官特点(可以发挥想象进行补充,但一定要有))。
要求:
关键词以逗号分隔。
只要返回关键词,不需要其他的说明文字。`
;
let
keywords
=
await
text2videoService
.
submitLLM
(
"故事:
\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
()
+
",穿着衣服"
});
let
adapt_keyword_restrict
=
`请理解这个故事:“
${
form
.
chatgpt_answer
}
”,给出其中这个角色“
${
one_role
.
角色
}
”的关键词,以逗号分隔。包括但不限于:`
;
let
temp_restrict
=
""
;
if
(
one_role
.
属性
.
includes
(
"人"
))
{
temp_restrict
=
`性别(可以发挥想象进行补充,但一定要有), 年龄(可以发挥想象进行补充,但一定要有),
肤色(可以发挥想象进行补充,但一定要有), 衣服(可以发挥想象进行补充,但一定要有), 发型(可以发挥想象进行补充,但一定要有),
发色(可以发挥想象进行补充,但一定要有), 脸色(可以发挥想象进行补充,但一定要有), 五官特点(可以发挥想象进行补充,但一定要有)`
;
}
else
{
temp_restrict
=
`体型(可以发挥想象进行补充,但一定要有), 颜色(可以发挥想象进行补充,但一定要有),
四肢(可以发挥想象进行补充,但一定要有), 五官特点(可以发挥想象进行补充,但一定要有),
身体(皮毛,羽毛,鳞片,肤色等,可以发挥想象进行补充,没有就不提供)`
;
}
adapt_keyword_restrict
=
adapt_keyword_restrict
+
temp_restrict
+
"。回答限制在30个字左右。"
;
let
keywords
=
await
text2videoService
.
submitLLM
(
adapt_keyword_restrict
,
role_keywords_llm
.
api
);
keywords
=
keywords
.
replace
(
/。/g
,
''
).
replace
(
/、/g
,
','
);
one_role
.
角色关键词
=
keywords
;
await
delay
(
100
);
const
adapt_attribute_restrict_en
=
`你现在扮演专业的英语翻译的角色。请将这段文字“
${
keywords
}
”翻译为英语。\n要求:只返回英语即可,不要返回其他内容。`
;
let
keywords_en
=
await
text2videoService
.
submitLLM
(
adapt_attribute_restrict_en
,
role_keywords_llm
.
api
);
keywords_en
=
keywords_en
.
replace
(
/"/g
,
''
);
one_role
.
角色英文关键词
=
keywords_en
;
}
}
try
{
...
...
@@ -134,8 +164,6 @@ const onAdaptRoles = async () => {
message
:
String
(
error
),
type
:
"error"
});
}
finally
{
loading
.
value
=
false
;
// 最终关闭loading(无论成功或失败)
}
}
catch
(
error
)
{
ElMessage
({
...
...
@@ -148,6 +176,7 @@ const onAdaptRoles = async () => {
}
};
const
onAdapt
=
async
()
=>
{
if
(
!
form
.
chatgpt_answer
||
form
.
chatgpt_answer
.
length
==
0
)
{
ElMessage
({
...
...
@@ -156,7 +185,6 @@ const onAdapt = async () => {
});
return
;
}
loading
.
value
=
true
;
form
.
task_id
=
utils
.
genDateTimeStr
();
console
.
log
(
form
.
task_id
)
// 按标点拆分成分镜
...
...
@@ -172,21 +200,28 @@ const onAdapt = async () => {
"角色"
:
""
,
"角色关键词"
:
""
,
"画面描述词"
:
""
,
"本镜配图"
:
"src/assets/
loading.gif
"
,
"本镜配图"
:
"src/assets/
waiting.png
"
,
"local_image_path"
:
""
,
});
}
console
.
log
(
form
.
adapt_result_json
)
}
const
onAdaptScene
=
async
()
=>
{
if
(
!
form
.
adapt_result_json
||
form
.
adapt_result_json
.
length
==
0
)
{
ElMessage
({
message
:
"分镜不能为空"
,
type
:
"error"
,
});
return
;
}
loading
.
value
=
true
;
async
function
processScenes
()
{
for
(
const
item
of
form
.
adapt_result_json
)
{
await
onAdaptOne
(
item
);
// await delay(100);
// await onDrawOne(item);
onDrawOne
(
item
);
await
onAdaptOneScene
(
item
);
}
}
try
{
await
processScenes
();
ElMessage
({
...
...
@@ -202,42 +237,82 @@ const onAdapt = async () => {
}
finally
{
loading
.
value
=
false
;
// 最终关闭loading(无论成功或失败)
}
};
const
onAdaptOne
=
async
(
item
:
any
)
=>
{
const
onAdaptOneScene
=
async
(
item
:
any
)
=>
{
if
(
!
item
.
场景描述
)
{
ElMessage
({
message
:
"分镜场景描述不能为空"
,
message
:
`分镜
${
item
.
编号
}
场景描述不能为空`
,
type
:
"error"
,
});
return
;
}
// 推理
关键词
// 推理
场景
try
{
const
adapt_restrict
=
`
指令:
请理解这个故事,给出这个场景“
${
item
.
场景描述
}
”的关键词(年代(可以发挥想象进行补充,但一定要有),空间(可以发挥想象进行补充,但一定要有),
时间段(可以发挥想象进行补充,但一定要有),地理环境(可以发挥想象进行补充,但一定要有),天气(可以发挥想象进行补充,但一定要有),
物品(可以发挥想象进行补充,但一定要有),人物(可以发挥想象进行补充,但一定要有),镜头角度(可以发挥想象进行补充,但一定要有))。
要求:
关键词以逗号分隔。
只要返回关键词,不需要其他的说明文字。`
;
const
keywords
=
await
text2videoService
.
submitLLM
(
"故事:
\n
"
+
form
.
chatgpt_answer
+
"
\n
"
+
adapt_restrict
,
tuili_llm
);
const
adapt_restrict
=
`你现在扮演专业的英语翻译的角色。请将这段文字“
${
item
.
场景描述
}
”翻译为英语。\n要求:只返回英语即可,不要返回其他内容。`
;
const
keywords
=
await
text2videoService
.
submitLLM
(
adapt_restrict
,
tuili_llm
.
api
);
// console.log(keywords)
item
.
场景关键词
=
keywords
;
}
catch
(
error
)
{
ElMessage
({
message
:
String
(
error
),
type
:
"error"
,
});
}
};
const
onAdaptSceneRoles
=
async
()
=>
{
if
(
!
form
.
adapt_result_json
||
form
.
adapt_result_json
.
length
==
0
)
{
ElMessage
({
message
:
"分镜不能为空"
,
type
:
"error"
,
});
return
;
}
loading
.
value
=
true
;
async
function
processScenes
()
{
for
(
const
item
of
form
.
adapt_result_json
)
{
await
onAdaptOneSceneRoles
(
item
);
}
}
try
{
await
processScenes
();
console
.
log
(
form
.
adapt_result_json
);
}
catch
(
error
)
{
ElMessage
({
message
:
String
(
error
),
type
:
"error"
});
}
finally
{
loading
.
value
=
false
;
// 最终关闭loading(无论成功或失败)
}
};
const
onAdaptOneSceneRoles
=
async
(
item
:
any
)
=>
{
if
(
!
item
.
场景描述
)
{
ElMessage
({
message
:
`分镜
${
item
.
编号
}
场景描述不能为空`
,
type
:
"error"
,
});
return
;
}
// 推理角色
try
{
if
(
form
.
chatgpt_answer_roles
.
length
===
0
)
{
// 总角色为空
item
.
角色
=
''
;
item
.
角色关键词
=
''
;
}
else
{
// 总角色不为空
const
adapt_role_restrict
=
`
指令:
请理解这个故事,针对其中的这个场景:“
${
item
.
场景描述
}
”,从所有角色中选择本场景的角色,多个角色以逗号分隔。`
;
const
item_roles
=
await
text2videoService
.
submitLLM
(
"故事:
\n
"
+
form
.
chatgpt_answer
+
"
\n
所有角色:
\n
"
+
form
.
all_roles
+
"
\n
"
+
adapt_role_restrict
,
tuili_llm
);
// console.log(
role_keyword
s)
item
.
角色
=
item_roles
;
// const adapt_role_restrict = `请理解这个故事:“${form.chatgpt_answer}”,针对其中的这个场景:“${item.场景描述}”,从所有角色:“${form.all_roles}”中选择本场景的角色,多个角色以逗号分隔。`;
const
adapt_role_restrict
=
`整个故事(“
${
form
.
chatgpt_answer
}
),\n\n本章节(
${
item
.
场景描述
}
),\n\n角色列表(
${
form
.
all_roles
}
),\n\n你是程序员,请返回给本章节出现的角色,注意,不要发挥想象,必须从角色列表中选出本章节出现过的角色,如果多个用逗号隔开。`
;
const
item_roles
=
await
text2videoService
.
submitLLM
(
adapt_role_restrict
,
tuili_keyword_llm
.
api
)
;
// console.log(adapt_role_restrict)
// console.log(
item_role
s)
item
.
角色
=
item_roles
.
trim
()
;
let
role_kws
=
""
const
item_roles_arr
=
item_roles
.
split
(
/
[
,,、
]
/
);
item_roles_arr
.
forEach
(
one_item_role
=>
{
...
...
@@ -245,7 +320,7 @@ const onAdaptOne = async (item: any) => {
// 人工匹配角色关键词,先找想同的
for
(
const
i
of
form
.
chatgpt_answer_roles
)
{
if
(
i
[
"角色"
].
trim
()
==
one_item_role
.
trim
())
{
temp_role_kws
=
`
【
${
i
[
"角色"
]}
:
${
i
[
"角色关键词"
]}
】
`
;
temp_role_kws
=
`
[
${
i
[
"角色英文关键词"
]}
]
`
;
// 找到就ok
break
;
}
...
...
@@ -254,7 +329,7 @@ const onAdaptOne = async (item: any) => {
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
[
"角色关键词"
]}
】
`
;
temp_role_kws
=
`
[
${
i
[
"角色英文关键词"
]}
]
`
;
// 匹配到一个就ok
break
;
}
...
...
@@ -272,10 +347,26 @@ const onAdaptOne = async (item: any) => {
}
};
const
onDraw
=
async
()
=>
{
if
(
!
form
.
adapt_result_json
||
form
.
adapt_result_json
.
length
==
0
)
{
ElMessage
({
message
:
"分镜不能为空"
,
type
:
"error"
,
});
return
;
}
for
(
const
item
of
form
.
adapt_result_json
)
{
onDrawOne
(
item
);
}
};
const
onDrawOne
=
async
(
item
:
any
)
=>
{
if
(
!
item
.
场景描述
&&
!
item
.
场景关键词
)
{
item
.
本镜配图
=
"src/assets/loading.gif"
;
if
(
!
item
.
场景关键词
&&
!
item
.
角色关键词
)
{
ElMessage
({
message
:
"
场景描述和场景关键词不能都
为空"
,
message
:
"
英文描述不能
为空"
,
type
:
"error"
,
});
return
;
...
...
@@ -286,18 +377,19 @@ const onDrawOne = async (item: any) => {
console
.
log
(
form
.
task_id
)
}
try
{
item
.
本镜配图
=
"src/assets/loading.gif"
;
let
temp_prompt
=
""
if
(
item
.
场景描述
)
{
temp_prompt
=
temp_prompt
+
`场景描述为:
${
item
.
场景描述
}
\n`
};
if
(
item
.
场景关键词
)
{
temp_prompt
=
temp_prompt
+
`场景关键词为:
${
item
.
场景关键词
}
\n`
};
if
(
item
.
角色
)
{
temp_prompt
=
temp_prompt
+
`场景中的角色有:
${
item
.
角色
}
\n`
};
if
(
item
.
角色关键词
)
{
temp_prompt
=
temp_prompt
+
`角色关键词为:
${
item
.
角色关键词
}
\n`
};
const
sd_describe
=
await
text2videoService
.
submitLLM
(
`
${
temp_prompt
}
指令:
请理解以上内容,并返回一段英文的描述。`
,
fanyi_llm
);
item
.
画面描述词
=
sd_describe
;
// let temp_prompt = ""
// if (item.场景描述) {temp_prompt = temp_prompt + `场景描述为:${item.场景描述}\n`};
// if (item.场景关键词) {temp_prompt = temp_prompt + `场景关键词为:${item.场景关键词}\n`};
// if (item.角色) {temp_prompt = temp_prompt + `场景中的角色有:${item.角色}\n`};
// if (item.角色关键词) {temp_prompt = temp_prompt + `角色关键词为:${item.角色关键词}\n`};
// const sd_describe = await text2videoService.submitLLM(
// `${temp_prompt}
// 指令:
// 请理解以上内容,并返回一段英文的描述。`, fanyi_llm.api
// );
// item.画面描述词 = sd_describe;
item
.
画面描述词
=
item
.
场景关键词
+
","
+
item
.
角色关键词
;
const
sd_prompt
=
item
.
画面描述词
+
","
+
sd_prompt_prefix
;
let
width
=
"960"
;
let
height
=
"540"
;
...
...
@@ -323,6 +415,7 @@ const onDrawOne = async (item: any) => {
}
};
const
onGenVideo
=
()
=>
{
if
(
!
form
.
adapt_result_json
||
form
.
adapt_result_json
.
length
==
0
)
{
ElMessage
({
...
...
@@ -396,6 +489,9 @@ const clean_demo = () => {
const
clean_roles
=
()
=>
{
form
.
chatgpt_answer_roles
=
<
Wm
.
RolesItem
[]
>
[];
}
const
clean_scenes
=
()
=>
{
form
.
adapt_result_json
=
<
Wm
.
ScriptsItem
[]
>
[];
}
const
onChangeScreen
=
(
val
:
string
)
=>
{
if
(
debug
.
value
==
true
)
{
...
...
@@ -419,7 +515,7 @@ const onChangeScreen = (val: string) => {
const
showsdprompt
=
(
item
:
any
)
=>
{
// alert(item.画面描述词)
dialogData
.
value
=
item
.
画面描述词
+
","
+
sd_prompt_prefix
+
'===== negative ====='
+
sd_negative_prompt_prefix
;
dialogData
.
value
=
`
${
item
.
场景关键词
}
,
${
item
.
角色关键词
}
,
${
sd_prompt_prefix
}
===== 反向提示词 =====
${
sd_negative_prompt_prefix
}
`
;
dialogVisible
.
value
=
true
;
// 打开对话框
}
...
...
@@ -478,6 +574,23 @@ const onPwdCheckDialog = () => {
});
}
const
onDeleteOne
=
(
item
:
any
)
=>
{
try
{
let
delete_no
=
item
.
编号
;
// 删除记录
form
.
adapt_result_json
=
form
.
adapt_result_json
.
filter
(
item
=>
item
.
编号
!==
delete_no
);
// 重新对记录进行编号
form
.
adapt_result_json
=
form
.
adapt_result_json
.
map
((
item
,
index
)
=>
{
return
{...
item
,
编号
:
(
index
+
1
).
toString
()};
});
}
catch
(
error
)
{
ElMessage
({
message
:
String
(
error
),
type
:
"error"
,
});
}
};
</
script
>
<
template
>
...
...
@@ -501,33 +614,55 @@ 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"
>
生成文案(
{{
wen
an_llm_
name
}}
)
</el-button>
<el-button
type=
"primary"
@
click=
"onSubmitGpt"
>
生成文案(
{{
wen
_an_llm
.
name
}}
)
</el-button>
</el-form-item>
<el-form-item
label=
"文案"
>
<el-input
v-model=
"form.chatgpt_answer"
:autosize=
"true"
type=
"textarea"
/>
</el-form-item>
<!-- 角色 -->
<el-form-item>
<el-button
type=
"primary"
@
click=
"onAdaptRoles"
>
推理角色(
{{
role_llm_name
}}
)、推理角色关键词(
{{
role_keywords_llm_name
}}
)
</el-button>
<el-button
type=
"primary"
@
click=
"onAdaptRoles"
>
推理角色(
{{
role_llm
.
name
}}
)
</el-button>
<el-button
type=
"primary"
@
click=
"onAdaptRolesKeywords"
>
推理关键词(
{{
role_keywords_llm
.
name
}}
)
</el-button>
<el-button
plain
@
click=
"clean_roles"
>
清空总角色列表
</el-button>
</el-form-item>
<el-form-item
label=
"角色"
>
<el-table
:data=
"form.chatgpt_answer_roles"
border
style=
"width: 100%; z-index: calc(var(--el-table-index) -1)"
>
<el-table-column
prop=
"角色"
label=
"角色"
>
<el-table-column
prop=
"角色"
label=
"角色"
width=
"300"
>
<template
v-slot=
"scope"
>
<el-input
v-model=
"scope.row.角色"
:autosize=
"true"
type=
"textarea"
></el-input>
</
template
>
</el-table-column>
<el-table-column
prop=
"属性"
label=
"属性"
width=
"300"
>
<
template
v-slot=
"scope"
>
<el-select
v-model=
"scope.row.属性"
filterable
allow-create
:reserve-keyword=
"false"
>
<el-option
v-for=
"item in default_data.role_attribute_options"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
</
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>
</
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>
</
template
>
</el-table-column>
</el-table>
</el-form-item>
<!-- 分镜 -->
<el-form-item>
<el-button
type=
"primary"
@
click=
"onAdapt"
>
分镜、推理场景关键词({{tuili_llm_name}})、英文描述({{fanyi_llm_name}})、绘图
</el-button>
<el-button
type=
"primary"
@
click=
"onAdapt"
>
分镜
</el-button>
<el-button
type=
"primary"
@
click=
"onAdaptScene"
>
推理场景({{tuili_llm.name}})
</el-button>
<el-button
type=
"primary"
@
click=
"onAdaptSceneRoles"
>
推理场景角色({{tuili_keyword_llm.name}})
</el-button>
<el-button
type=
"primary"
@
click=
"onDraw"
>
绘图
</el-button>
<el-button
plain
@
click=
"clean_scenes"
>
清空分镜列表
</el-button>
</el-form-item>
<el-form-item
label=
"分镜"
>
<el-table
:data=
"form.adapt_result_json"
border
style=
"width: 100%; z-index: calc(var(--el-table-index) -1)"
>
...
...
@@ -537,7 +672,7 @@ const onPwdCheckDialog = () => {
<el-input
v-model=
"scope.row.场景描述"
:autosize=
"true"
type=
"textarea"
></el-input>
</
template
>
</el-table-column>
<el-table-column
prop=
"场景关键词"
label=
"场景
关键词
"
>
<el-table-column
prop=
"场景关键词"
label=
"场景
英文描述
"
>
<
template
v-slot=
"scope"
>
<el-input
v-model=
"scope.row.场景关键词"
:autosize=
"true"
type=
"textarea"
></el-input>
</
template
>
...
...
@@ -568,8 +703,9 @@ const onPwdCheckDialog = () => {
</template>
-->
<
template
v-slot=
"scope"
>
<div
style=
"margin: 10px 0"
><el-button
type=
"primary"
size=
"default"
@
click=
"onAdaptOne(scope.row)"
>
推理关键词
</el-button></div>
<div
style=
"margin: 10px 0"
><el-button
type=
"primary"
size=
"default"
@
click=
"onDrawOne(scope.row)"
>
翻译、绘图
</el-button></div>
<div
style=
"margin: 10px 0"
><el-button
type=
"primary"
size=
"small"
@
click=
"onAdaptOneScene(scope.row)"
>
推理场景
</el-button></div>
<div
style=
"margin: 10px 0"
><el-button
type=
"primary"
size=
"small"
@
click=
"onAdaptOneSceneRoles(scope.row)"
>
推理角色
</el-button></div>
<div
style=
"margin: 10px 0"
><el-button
type=
"primary"
size=
"small"
@
click=
"onDrawOne(scope.row)"
>
绘图
</el-button></div>
<el-upload
class=
"upload-demo"
ref=
"upload"
...
...
@@ -581,9 +717,9 @@ const onPwdCheckDialog = () => {
:on-exceed=
"handleExceed"
:data=
"
{item_id: scope.row.编号}"
>
<el-button
type=
"primary"
>
上传图片
</el-button>
<el-button
type=
"primary"
size=
"small"
>
上传图片
</el-button>
</el-upload>
<div
style=
"margin: 10px 0"
><el-button
plain
@
click=
"showsdprompt(scope.row)"
>
debug
</el-button></div>
<div
style=
"margin: 10px 0"
><el-button
plain
size=
"small"
@
click=
"showsdprompt(scope.row)"
>
debug
</el-button></div>
<el-dialog
v-model=
dialogVisible
width=
"80%"
...
...
@@ -595,6 +731,7 @@ const onPwdCheckDialog = () => {
</div>
</
template
>
</el-dialog>
<!-- <div style="margin: 10px 0"><el-button type="danger" size="small" @click="onDeleteOne(scope.row)">删除本镜</el-button></div> -->
</template>
</el-table-column>
</el-table>
...
...
@@ -652,7 +789,7 @@ const onPwdCheckDialog = () => {
</el-form-item>
<el-form-item>
<span
style=
"margin: 0 20px"
>
背景音量:
</span>
<el-slider
v-model=
"bgm_volume"
show-input
step=
"0.1"
:min=
"0"
:max=
"2"
:marks=
"default_data.bgm_volume_marks"
style=
"width: 600px"
/>
<el-slider
v-model=
"bgm_volume"
show-input
:
step=
"0.1"
:min=
"0"
:max=
"2"
:marks=
"default_data.bgm_volume_marks"
style=
"width: 600px"
/>
</el-form-item>
<el-form-item>
<span
style=
"margin: 20px 20px"
>
字幕:
</span>
...
...
@@ -663,7 +800,7 @@ const onPwdCheckDialog = () => {
<span
style=
"margin-left:30px;"
>
字体大小:
</span>
<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"
step=
"0.1"
:min=
"0"
:max=
"1"
show-input
vertical
height=
"100px"
/>
<el-slider
v-model=
"sub_position"
:
step=
"0.1"
:min=
"0"
:max=
"1"
show-input
vertical
height=
"100px"
/>
</div>
</el-form-item>
<el-form-item>
...
...
@@ -674,7 +811,7 @@ const onPwdCheckDialog = () => {
</el-form-item>
</el-form>
<!-- 授权密码框 -->
<el-dialog
<el-dialog
v-model=
pwdCheckDialogVisible
title=
"请输入密码"
width=
"20%"
...
...
@@ -684,7 +821,7 @@ const onPwdCheckDialog = () => {
>
<el-form
:model=
"form"
>
<el-form-item
label=
"密码"
>
<el-input
v-model=
"pwdCheckValue"
autocomplete=
"off"
type=
"password"
show-password
/>
<el-input
v-model=
"pwdCheckValue"
autocomplete=
"off"
type=
"password"
show-password
@
keyup
.
enter=
"onPwdCheckDialog()"
/>
</el-form-item>
</el-form>
<
template
#
footer
>
...
...
src/views/home/index_en.vue
View file @
c0bbf709
...
...
@@ -52,8 +52,8 @@ const bgm_volume = ref(0.3)
const
pwdCheckDialogVisible
=
ref
(
false
);
const
pwdCheckValue
=
ref
(
""
)
const
sub_font_color
=
ref
(
"#FFFF00"
)
const
sub_font_size
=
ref
(
30
)
const
sub_position
=
ref
(
0.
2
)
const
sub_font_size
=
ref
(
25
)
const
sub_position
=
ref
(
0.
4
)
onMounted
(()
=>
{
...
...
@@ -122,7 +122,9 @@ const onAdaptRoles = async () => {
keywords
=
keywords
.
replace
(
/。/g
,
''
).
replace
(
/、/g
,
','
)
form
.
chatgpt_answer_roles
.
push
({
"角色"
:
one_role
.
trim
(),
"角色关键词"
:
keywords
.
trim
()
+
",dressed"
"角色关键词"
:
keywords
.
trim
()
+
",dressed"
,
"角色英文关键词"
:
""
,
"属性"
:
""
,
});
}
}
...
...
@@ -645,7 +647,7 @@ const onPwdCheckDialog = () => {
</el-form-item>
<el-form-item>
<span
style=
"margin: 0 20px"
>
背景音量:
</span>
<el-slider
v-model=
"bgm_volume"
show-input
step=
"0.1"
:min=
"0"
:max=
"2"
:marks=
"default_data.bgm_volume_marks"
style=
"width: 600px"
/>
<el-slider
v-model=
"bgm_volume"
show-input
:
step=
"0.1"
:min=
"0"
:max=
"2"
:marks=
"default_data.bgm_volume_marks"
style=
"width: 600px"
/>
</el-form-item>
<el-form-item>
<span
style=
"margin: 20px 20px"
>
字幕:
</span>
...
...
@@ -656,7 +658,7 @@ const onPwdCheckDialog = () => {
<span
style=
"margin-left:30px;"
>
字体大小:
</span>
<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"
step=
"0.1"
:min=
"0"
:max=
"1"
show-input
vertical
height=
"100px"
/>
<el-slider
v-model=
"sub_position"
:
step=
"0.1"
:min=
"0"
:max=
"1"
show-input
vertical
height=
"100px"
/>
</div>
</el-form-item>
<el-form-item>
...
...
@@ -677,7 +679,7 @@ const onPwdCheckDialog = () => {
>
<el-form
:model=
"form"
>
<el-form-item
label=
"密码"
>
<el-input
v-model=
"pwdCheckValue"
autocomplete=
"off"
type=
"password"
show-password
/>
<el-input
v-model=
"pwdCheckValue"
autocomplete=
"off"
type=
"password"
show-password
@
keyup
.
enter=
"onPwdCheckDialog()"
/>
</el-form-item>
</el-form>
<
template
#
footer
>
...
...
typings/types/wm/lib.wm.api.d.ts
View file @
c0bbf709
...
...
@@ -24,6 +24,8 @@ declare namespace Wm {
interface
RolesItem
{
"角色"
:
string
,
"角色关键词"
:
string
,
"角色英文关键词"
:
string
,
"属性"
:
string
,
}
interface
UploadResult
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment