Skip to content

Commit 025b4b6

Browse files
committed
feat: text to video node
1 parent 86ecea8 commit 025b4b6

4 files changed

Lines changed: 194 additions & 3 deletions

File tree

‎apps/application/workflow/nodes/text_to_video_node/text_to_video_node.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ def execute(self):
9393
question = self.workflow_manage.generate_prompt(prompt)
9494
self.write_context("question", question)
9595

96-
message_list = [*history_message, question]
97-
self.write_context("message_list", message_list)
96+
# message_list = [*history_message, question]
97+
# self.write_context("message_list", [{"content": m.content, "role": m.type} for m in message_list],)
9898
self.write_context("dialogue_type", dialogue_type)
9999
self.write_context("negative_prompt", self.workflow_manage.generate_prompt(negative_prompt))
100100

@@ -119,7 +119,7 @@ def execute(self):
119119

120120
self.write_context("answer", video_label)
121121
self.write_context("video", video_list)
122-
self.write_context("chat_model", ttv_model)
122+
# self.write_context("chat_model", ttv_model)
123123

124124
if is_result:
125125
node_info = NodeInfo(self.get_node_id(), self.get_node_name(), Status.SUCCESS)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<template>
2+
<el-avatar class="ai-avatar-gradient" shape="square">
3+
<img src="@/assets/workflow/icon_ai_chat.svg" style="width: 75%" alt="" />
4+
</el-avatar>
5+
</template>
6+
<script setup lang="ts"></script>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import TextToVideoNodeVue from './index.vue'
2+
import { WorkflowNodeModel, WorkflowNodeView } from '@/workflow-canvas/core/workflow-node'
3+
import { WorkflowNodeType } from '@/workflow-canvas/types.ts'
4+
5+
class TextToVideoNodeView extends WorkflowNodeView {
6+
constructor(props: ConstructorParameters<typeof WorkflowNodeView>[0]) {
7+
super(props, TextToVideoNodeVue)
8+
}
9+
}
10+
11+
export default {
12+
type: WorkflowNodeType.TextToVideoGenerateNode,
13+
model: WorkflowNodeModel,
14+
view: TextToVideoNodeView,
15+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<script setup lang="ts">
2+
import { computed, inject, onMounted, ref, useTemplateRef } from 'vue'
3+
import { set } from 'lodash'
4+
import { QuestionFilled } from '@element-plus/icons-vue'
5+
import type { FormInstance } from 'element-plus'
6+
import ModelSelect from '@/components/business/model-select/index.vue'
7+
import NodeCascader from '@/workflow-canvas/core/NodeCascader.vue'
8+
import NodeContainer from '@/workflow-canvas/core/NodeContainer.vue'
9+
import { useWorkflowStore } from '@/workflow-canvas/store'
10+
import type { BaseNodeModel } from '@logicflow/core'
11+
import type { ModelItem, ModelProviderItem } from '@/api/types'
12+
13+
defineOptions({ name: 'WorkflowTextToVideoNode' })
14+
const getModel = inject('getModel') as () => BaseNodeModel
15+
const apiType = (inject('apiType') as string) || 'workspace'
16+
const model = getModel()
17+
18+
interface TextToVideoNodeForm {
19+
model_id: string
20+
model_id_type: 'custom' | 'reference'
21+
model_id_reference: string[]
22+
prompt: string
23+
negative_prompt: string
24+
model_params_setting: Record<string, unknown>
25+
}
26+
27+
const formRef = useTemplateRef<FormInstance>('formRef')
28+
const modelCascaderRef = useTemplateRef<InstanceType<typeof NodeCascader>>('modelCascaderRef')
29+
30+
const store = useWorkflowStore(apiType)
31+
const modelList = ref<Array<ModelItem>>([])
32+
const providerOptions = ref<Array<ModelProviderItem>>([])
33+
34+
const formData = computed<TextToVideoNodeForm>({
35+
get: () => {
36+
if (!model.properties.node_data) {
37+
set(model.properties, 'node_data', {
38+
model_id: '',
39+
model_id_type: 'custom',
40+
model_id_reference: [],
41+
prompt: '{{开始.question}}',
42+
negative_prompt: '',
43+
model_params_setting: {},
44+
})
45+
}
46+
const data = model.properties.node_data as TextToVideoNodeForm
47+
if (data.model_id_type === undefined) set(data, 'model_id_type', 'custom')
48+
if (!Array.isArray(data.model_id_reference)) set(data, 'model_id_reference', [])
49+
if (data.prompt === undefined) set(data, 'prompt', '{{开始.question}}')
50+
if (data.negative_prompt === undefined) set(data, 'negative_prompt', '')
51+
if (!data.model_params_setting) set(data, 'model_params_setting', {})
52+
return data
53+
},
54+
set: (value) => (model.properties.node_data = value),
55+
})
56+
57+
function validate() {
58+
return Promise.all([
59+
formData.value.model_id_type === 'reference'
60+
? modelCascaderRef.value?.validate()
61+
: Promise.resolve(),
62+
formRef.value?.validate(),
63+
]).catch((error) => Promise.reject({ node: model, errMessage: error }))
64+
}
65+
66+
onMounted(() => {
67+
set(model, 'validate', validate)
68+
store.getModelList({ model_type: 'TTV' }).then((data) => {
69+
modelList.value = data
70+
})
71+
store.getProviderList().then((data) => {
72+
providerOptions.value = data
73+
})
74+
})
75+
</script>
76+
77+
<template>
78+
<NodeContainer :node-model="model">
79+
<h6 class="mb-3">节点设置</h6>
80+
<el-form
81+
ref="formRef"
82+
:model="formData"
83+
label-position="top"
84+
require-asterisk-position="right"
85+
@submit.prevent
86+
>
87+
<el-form-item
88+
:prop="formData.model_id_type === 'reference' ? 'model_id_reference' : 'model_id'"
89+
:rules="{ required: true, message: '请选择或填写文生视频模型', trigger: 'change' }"
90+
>
91+
<template #label>
92+
<div class="flex-between gap-3 w-full">
93+
<span>文生视频模型</span>
94+
<el-select
95+
v-model="formData.model_id_type"
96+
:teleported="false"
97+
class="w-30!"
98+
size="small"
99+
@change="formData.model_id_reference = []"
100+
>
101+
<el-option label="引用变量" value="reference" />
102+
<el-option label="自定义" value="custom" />
103+
</el-select>
104+
</div>
105+
</template>
106+
<NodeCascader
107+
v-if="formData.model_id_type === 'reference'"
108+
ref="modelCascaderRef"
109+
v-model="formData.model_id_reference"
110+
:node-model="model"
111+
class="w-full"
112+
placeholder="请选择变量"
113+
/>
114+
<ModelSelect
115+
v-else
116+
placeholder="请输入文生视频模型 ID"
117+
:options="modelList"
118+
:provider-options="providerOptions"
119+
v-model="formData.model_id"
120+
></ModelSelect>
121+
</el-form-item>
122+
123+
<el-form-item
124+
label="提示词(正向)"
125+
prop="prompt"
126+
:rules="{ required: true, message: '请输入正向提示词', trigger: 'blur' }"
127+
>
128+
<template #label>
129+
<div class="flex items-center gap-1">
130+
<span>提示词(正向)</span>
131+
<el-tooltip effect="dark" placement="right" content="正向提示词,用来描述期望生成的视频中应包含的元素和视觉特点。">
132+
<MkIcon :icon="QuestionFilled" class="cursor-help text-N600" />
133+
</el-tooltip>
134+
</div>
135+
</template>
136+
<el-input
137+
v-model="formData.prompt"
138+
:rows="5"
139+
:placeholder="`正向提示词,可以引用变量,如 {{开始.question}}`"
140+
type="textarea"
141+
/>
142+
</el-form-item>
143+
144+
<el-form-item label="提示词(负向)" prop="negative_prompt">
145+
<template #label>
146+
<div class="flex items-center gap-1">
147+
<span>提示词(负向)</span>
148+
<el-tooltip effect="dark" placement="right" content="反向提示词,用来描述不希望在画面中看到的内容,可以对画面进行限制。">
149+
<MkIcon :icon="QuestionFilled" class="cursor-help text-N600" />
150+
</el-tooltip>
151+
</div>
152+
</template>
153+
<el-input
154+
v-model="formData.negative_prompt"
155+
:rows="4"
156+
placeholder="请描述不想生成的图片内容,比如:颜色、血腥内容"
157+
type="textarea"
158+
/>
159+
</el-form-item>
160+
</el-form>
161+
</NodeContainer>
162+
</template>
163+
<style lang="scss" scoped>
164+
:deep(.el-form-item__label) {
165+
width: 100%;
166+
}
167+
:deep(.el-form-item) {
168+
margin-bottom: 16px;
169+
}
170+
</style>

0 commit comments

Comments
 (0)