feat: 更换转视频的带提示输入框

This commit is contained in:
zhangyh
2025-11-20 17:26:05 +08:00
parent e154fc3de0
commit 85cb0e9792
8 changed files with 174 additions and 634 deletions

9
package-lock.json generated
View File

@@ -35,7 +35,8 @@
"vue-i18n": "^9.6.1",
"vue-router": "^4.0.3",
"vuedraggable": "^4.1.0",
"vuex": "^4.0.0"
"vuex": "^4.0.0",
"x-sender": "^1.1.6"
},
"devDependencies": {
"@types/three": "^0.174.0",
@@ -10106,6 +10107,12 @@
}
}
},
"node_modules/x-sender": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/x-sender/-/x-sender-1.1.6.tgz",
"integrity": "sha512-es24YnTY1+g3TdDVrEgRVW8uW2nYPyHjQveBgZxk8JrB7809yd8AkYptrLgqL1trpUZtMILVW+2GIoB0V5HfVQ==",
"license": "MIT"
},
"node_modules/xml-name-validator": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz",

View File

@@ -41,7 +41,8 @@
"vue-i18n": "^9.6.1",
"vue-router": "^4.0.3",
"vuedraggable": "^4.1.0",
"vuex": "^4.0.0"
"vuex": "^4.0.0",
"x-sender": "^1.1.6"
},
"devDependencies": {
"@types/three": "^0.174.0",
@@ -105,4 +106,4 @@
"not dead",
"not ie 11"
]
}
}

View File

@@ -687,6 +687,7 @@ export default defineComponent({
return params
}
let getPrductimg = async () => {
let obj = getData()
// let imageStrength = productimg.productimgSimilarity == 100? 95 :productimg.productimgSimilarity
let imageStrength = (70 / 100) * productimg.productimgSimilarity
@@ -1233,11 +1234,8 @@ export default defineComponent({
const showProductList = ref(false)
const productType = ref('first')
const fullProductImages = computed(() => {
return productimg.likeDesignCollectionList.flatMap(item =>
(item.childList || []).filter(
child => child.resultType !== 'PoseTransfer'
)
(item.childList || []).filter(child => child.resultType !== 'PoseTransfer')
)
})
const handleOpenProduct = (type: 'first' | 'last') => {

View File

@@ -0,0 +1,55 @@
<template>
<div id="sender" ref="senderRef"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeMount } from 'vue'
import XSender, { Options } from 'x-sender'
import 'x-sender/style'
const props = defineProps({
nodeList: {
type: Array,
default: () => []
}
})
const senderRef = ref<HTMLElement | null>(null)
let sender: XSender | null = null
const options: Options = {
placeholder: '请输入内容'
}
const chatNode = [[]]
const createSender = () => {
if (sender) {
sender.destroy()
}
sender = new XSender(senderRef.value!, options)
sender.reset({
clearHistory: true,
chatNode: [props.nodeList]
})
}
const getContext = () => {
const text = sender.getText()
return text
}
onMounted(() => {
createSender()
})
onBeforeMount(() => {
if (sender) {
sender.destroy()
sender = null
}
})
defineExpose({
getContext
})
</script>
<style lang="less" scoped></style>

View File

@@ -168,15 +168,22 @@
</div>
</div>
<div class="prompt-input-container" v-show="!showMotion">
<div class="title" style="display: flex; gap: .5rem;">
{{ $t('ProductImg.Prompt') }}
<a :href="
locale == 'CHINESE_SIMPLIFIED'?
'https://aida-user-manual-chinese.super.site/2b08f755cedd80a985cffdf2af80c538':
'https://aida-user-manual.super.site/advanced-tool/animated-product-image/to-product-video-prompt-assist '" target="_blank">
<i class="fi fi-rs-interrogation tips-icon" style="font-size: 1.8rem; color: #000;"/>
</a>
</div>
<div class="title" style="display: flex; gap: 0.5rem">
{{ $t('ProductImg.Prompt') }}
<a
:href="
locale == 'CHINESE_SIMPLIFIED'
? 'https://aida-user-manual-chinese.super.site/2b08f755cedd80a985cffdf2af80c538'
: 'https://aida-user-manual.super.site/advanced-tool/animated-product-image/to-product-video-prompt-assist '
"
target="_blank"
>
<i
class="fi fi-rs-interrogation tips-icon"
style="font-size: 1.8rem; color: #000"
/>
</a>
</div>
<!-- <div class="title">
<span>{{ $t('ProductImg.Prompt') }}</span>
<i class="fi fi-rs-interrogation tips-icon" />
@@ -334,7 +341,7 @@ export default defineComponent({
generalDrag,
promptInput,
Tips,
Product
Product,
},
props: {
isDesignPage: {
@@ -1539,11 +1546,11 @@ export default defineComponent({
}
.prompt-input-container {
margin-top: 4rem;
.title{
.title {
display: flex;
align-items: center;
column-gap: 1rem;
i{
i {
display: flex;
cursor: pointer;
}

View File

@@ -1,30 +1,67 @@
export const getFirstAndLastFrame = (t: (key: string) => string) => [
{ id: '1', type: 'text', value: t('poseTransfer.firstAndLastFrameText1') },
{ id: '1', type: 'Write', text: t('poseTransfer.firstAndLastFrameText1') },
{
id: '2',
type: 'input',
value: '',
type: 'Input',
text: '',
key: 'content1',
placeholder: t('poseTransfer.firstAndLastFramePlaceholder1')
},
{ id: '3', type: 'text', value: t('poseTransfer.firstAndLastFrameText2') },
{ id: '4', type: 'input', value: '', placeholder: t('poseTransfer.firstAndLastFramePlaceholder2') },
{ id: '3', type: 'Write', text: t('poseTransfer.firstAndLastFrameText2') },
{
id: '4',
type: 'Input',
text: '',
key: 'content2',
placeholder: t('poseTransfer.firstAndLastFramePlaceholder2')
},
{
id: '5',
type: 'text',
value: t('poseTransfer.firstAndLastFrameText3')
type: 'Write',
text: t('poseTransfer.firstAndLastFrameText3')
}
]
export const getFirstFrame = (t: (key: string) => string) => [
{ id: '1', type: 'text', value: t('poseTransfer.firstFrameText1') },
{ id: '2', type: 'input', value: '', placeholder: t('poseTransfer.firstFramePlaceholder1') },
{ id: '3', type: 'text', value: t('poseTransfer.firstFrameText2') },
{ id: '4', type: 'input', value: '', placeholder: t('poseTransfer.firstFramePlaceholder2') },
{ id: '5', type: 'text', value: t('poseTransfer.firstFrameText3') },
{ id: '6', type: 'input', value: '', placeholder: t('poseTransfer.firstFramePlaceholder3') },
{ id: '7', type: 'text', value: t('poseTransfer.firstFrameText4') },
{ id: '8', type: 'input', value: '', placeholder: t('poseTransfer.firstFramePlaceholder4') },
{ id: '9', type: 'text', value: t('poseTransfer.firstFrameText5') },
{ id: '10', type: 'input', value: '', placeholder: t('poseTransfer.firstFramePlaceholder5') },
{ id: '11', type: 'text', value: t('poseTransfer.firstFrameText6') }
{ id: '1', type: 'Write', text: t('poseTransfer.firstFrameText1') },
{
id: '2',
type: 'Input',
text: '',
key: 'content1',
placeholder: t('poseTransfer.firstFramePlaceholder1')
},
{ id: '3', type: 'Write', text: t('poseTransfer.firstFrameText2') },
{
id: '4',
type: 'Input',
text: '',
key: 'content2',
placeholder: t('poseTransfer.firstFramePlaceholder2')
},
{ id: '5', type: 'Write', text: t('poseTransfer.firstFrameText3') },
{
id: '6',
type: 'Input',
text: '',
key: 'content3',
placeholder: t('poseTransfer.firstFramePlaceholder3')
},
{ id: '7', type: 'Write', text: t('poseTransfer.firstFrameText4') },
{
id: '8',
type: 'Input',
text: '',
key: 'content4',
placeholder: t('poseTransfer.firstFramePlaceholder4')
},
{ id: '9', type: 'Write', text: t('poseTransfer.firstFrameText5') },
{
id: '10',
type: 'Input',
text: '',
key: 'content5',
placeholder: t('poseTransfer.firstFramePlaceholder5')
},
{ id: '11', type: 'Write', text: t('poseTransfer.firstFrameText6') }
]

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref, reactive, toRefs, nextTick, useTemplateRef, watch } from 'vue'
import Sender from './Sender.vue'
interface ContentItem {
id: string
@@ -15,541 +16,25 @@ const props = defineProps({
}
})
const data = reactive({
// content: [
// { id: '1', type: 'text', value: '11111' },
// { id: '2', type: 'input', value: '222', placeholder: '[请输入内容]' },
// { id: '3', type: 'text', value: '333333' },
// { id: '4', type: 'input', value: '', placeholder: '[请输入内容]' }
// ] as ContentItem[]
content: [{ id: '1', type: 'text', value: '' }]
})
const editableArea = ref<HTMLElement>()
const { content } = toRefs(data)
const cursorState = ref({
isContainerClick: false
})
// 添加中文输入状态管理
const compositionState = reactive({
isComposing: false,
currentInputIndex: -1
})
// 检查并删除末尾的空文本框
const removeLastEmptyTextIfNeeded = () => {
const lastItem = data.content[data.content.length - 1]
if (lastItem && lastItem.type === 'text' && lastItem.value === '') {
data.content.pop()
return true
}
return false
}
// 确保末尾有空文本框
const ensureEmptyTextAtEnd = () => {
const lastItem = data.content[data.content.length - 1]
if (!lastItem || lastItem.type !== 'text' || lastItem.value !== '') {
const newItem: ContentItem = {
id: Date.now().toString(),
type: 'text',
value: ''
}
data.content.push(newItem)
return true
}
return false
}
// 元素信息获取
const getCurrentElementInfo = () => {
const selection = window.getSelection()
if (!selection || selection.rangeCount === 0) return null
const range = selection.getRangeAt(0)
const node = range.startContainer
let element: HTMLElement | null = null
if (node.nodeType === Node.TEXT_NODE) {
element = (node as Text).parentElement
} else {
element = node as HTMLElement
}
if (!element) return null
let index = -1
let type: 'text' | 'input' = 'text'
let isAtStart = false
let isAtEnd = false
if (element.classList.contains('text-field')) {
index = parseInt(element.getAttribute('data-index') || '-1')
type = 'text'
const textContent = element.textContent || ''
isAtStart = range.startOffset === 0
isAtEnd = range.startOffset === textContent.length
} else if (element.classList.contains('input-content')) {
const parent = element.parentElement
index = parseInt(parent?.getAttribute('data-index') || '-1')
type = 'input'
const item = data.content[index]
if (element.classList.contains('has-placeholder')) {
// placeholder状态下光标在任意位置都认为是"在元素内"
isAtStart = range.startOffset === 0
isAtEnd = true
} else {
// 正常内容状态
const textContent = item.value
isAtStart = range.startOffset === 0
isAtEnd = range.startOffset === textContent.length
}
}
return { index, type, element, isAtStart, isAtEnd }
}
//光标设置
const setCursorToElement = (element: HTMLElement, position: 'start' | 'end') => {
const selection = window.getSelection()
const range = document.createRange()
if (
element.classList.contains('input-content') &&
element.classList.contains('has-placeholder')
) {
// placeholder状态的特殊处理
range.selectNodeContents(element)
range.collapse(position === 'start')
} else if (element.childNodes.length > 0) {
const targetNode = position === 'start' ? element.firstChild! : element.lastChild!
if (targetNode.nodeType === Node.TEXT_NODE) {
const offset = position === 'start' ? 0 : (targetNode.textContent || '').length
range.setStart(targetNode, offset)
range.setEnd(targetNode, offset)
} else {
range.selectNodeContents(element)
range.collapse(position === 'start')
}
} else {
range.selectNodeContents(element)
range.collapse(position === 'start')
}
selection?.removeAllRanges()
selection?.addRange(range)
}
// 键盘事件处理
const handleKeydown = (event: KeyboardEvent) => {
const elementInfo = getCurrentElementInfo()
if (!elementInfo) return
const { index, type, isAtStart, isAtEnd } = elementInfo
switch (event.key) {
case 'Backspace':
if (isAtStart && index > 0) {
event.preventDefault()
handleCrossElementDelete(index)
} else if (
type === 'input' &&
elementInfo.element?.classList.contains('has-placeholder')
) {
event.preventDefault()
}
// 其他情况让浏览器正常处理删除
break
case 'ArrowLeft':
if (isAtStart && index > 0) {
event.preventDefault()
navigateToElement(index - 1, 'end')
}
break
case 'ArrowRight':
if (isAtEnd && index < data.content.length - 1) {
event.preventDefault()
navigateToElement(index + 1, 'start')
} else if (isAtEnd && index === data.content.length - 1) {
// 在最后一个元素末尾按右箭头,确保有一个空文本框
ensureEmptyTextAtEnd()
nextTick(() => {
navigateToElement(index + 1, 'start')
})
}
break
}
}
// 跨元素删除逻辑
const handleCrossElementDelete = (currentIndex: number) => {
const prevIndex = currentIndex - 1
const prevItem = data.content[prevIndex]
if (prevItem.type === 'input') {
if (prevItem.value.trim() === '') {
// 删除空输入框
data.content.splice(prevIndex, 1)
nextTick(() => {
// 删除输入框后,先删除末尾的空文本框
removeLastEmptyTextIfNeeded()
// 然后聚焦到正确的位置
if (prevIndex < data.content.length) {
focusElement(prevIndex, 'end')
} else if (data.content.length > 0) {
focusElement(data.content.length - 1, 'end')
}
})
} else {
// 删除输入框最后一个字符,但保留输入框
const newValue = prevItem.value.slice(0, -1)
data.content[prevIndex].value = newValue
updateInputDisplay(prevIndex)
nextTick(() => focusElement(prevIndex, 'end'))
}
} else {
// 文本框:移动到前一个文本框末尾,让浏览器正常删除
// 先删除末尾的空文本框
removeLastEmptyTextIfNeeded()
focusElement(prevIndex, 'end')
}
}
// 导航到元素
const navigateToElement = (targetIndex: number, position: 'start' | 'end') => {
const targetItem = data.content[targetIndex]
const element =
targetItem.type === 'text'
? (editableArea.value?.querySelector(
`.text-field[data-index="${targetIndex}"]`
) as HTMLElement)
: (editableArea.value?.querySelector(
`.input-field[data-index="${targetIndex}"] .input-content`
) as HTMLElement)
if (element) setCursorToElement(element, position)
}
// 焦点设置
const focusElement = (index: number, position: 'start' | 'end') => {
const item = data.content[index]
const element =
item.type === 'text'
? (editableArea.value?.querySelector(
`.text-field[data-index="${index}"]`
) as HTMLElement)
: (editableArea.value?.querySelector(
`.input-field[data-index="${index}"] .input-content`
) as HTMLElement)
if (element) setCursorToElement(element, position)
}
// 输入框显示管理
const updateInputDisplay = (index: number) => {
const item = data.content[index]
if (item.type !== 'input') return
const inputElement = editableArea.value?.querySelector(
`.input-field[data-index="${index}"] .input-content`
) as HTMLElement
if (!inputElement) return
const showPlaceholder = item.value.trim() === '' && item.placeholder
if (showPlaceholder) {
inputElement.classList.add('has-placeholder')
inputElement.textContent = item.placeholder
} else {
inputElement.classList.remove('has-placeholder')
inputElement.textContent = item.value
}
}
const handleInputChange = (index: number, event: Event) => {
// 如果是中文输入过程中,不处理
if (compositionState.isComposing && compositionState.currentInputIndex === index) {
return
}
const target = event.target as HTMLSpanElement
const item = data.content[index]
// 如果当前显示placeholder但内容已改变比如通过粘贴清除placeholder
if (
target.classList.contains('has-placeholder') &&
target.textContent !== item.placeholder
) {
target.classList.remove('has-placeholder')
const newValue = target.textContent || ''
data.content[index].value = newValue
// 如果粘贴后内容为空,重新显示 placeholder
if (newValue.trim() === '' && item.placeholder) {
target.classList.add('has-placeholder')
target.textContent = item.placeholder
}
return
}
// 正常情况下的处理
if (!target.classList.contains('has-placeholder')) {
const newValue = target.textContent || ''
data.content[index].value = newValue
// 如果内容变空显示placeholder
if (newValue.trim() === '' && item.placeholder) {
target.classList.add('has-placeholder')
target.textContent = item.placeholder
}
}
}
// 增强中文输入事件处理
const handleCompositionStart = (index: number, event: CompositionEvent) => {
compositionState.isComposing = true
compositionState.currentInputIndex = index
const target = event.target as HTMLSpanElement
// 如果是placeholder状态开始中文输入时清除placeholder
if (target.classList.contains('has-placeholder')) {
target.classList.remove('has-placeholder')
target.textContent = ''
data.content[index].value = ''
}
}
// 添加专门的粘贴事件处理
const handleInputPaste = (event: ClipboardEvent, index: number) => {
const target = event.target as HTMLSpanElement
const item = data.content[index]
// 如果当前显示 placeholder先清除它
if (target.classList.contains('has-placeholder')) {
event.preventDefault()
target.classList.remove('has-placeholder')
target.textContent = ''
// 获取粘贴的内容
const pasteData = event.clipboardData?.getData('text') || ''
document.execCommand('insertText', false, pasteData)
// 更新值
const newValue = target.textContent || ''
data.content[index].value = newValue
// 如果粘贴后内容为空,重新显示 placeholder
if (newValue.trim() === '' && item.placeholder) {
target.classList.add('has-placeholder')
target.textContent = item.placeholder
}
}
}
const handleInputKeydown = (event: KeyboardEvent, index: number) => {
const target = event.target as HTMLSpanElement
// 如果是中文输入过程中,不处理普通按键
if (compositionState.isComposing) {
return
}
if (event.key === 'Backspace') {
// 如果显示placeholder阻止删除
if (target.classList.contains('has-placeholder')) {
event.preventDefault()
return
}
const selection = window.getSelection()
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0)
const isAtStart = range.startOffset === 0
// 如果光标在输入框开头阻止默认行为让外部的handleBackspace处理
if (isAtStart) {
event.preventDefault()
return
}
}
} else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
// 普通字符输入 - 只有在非中文输入状态下才处理
if (target.classList.contains('has-placeholder')) {
event.preventDefault()
target.textContent = event.key
target.classList.remove('has-placeholder')
data.content[index].value = event.key
// 移动光标到末尾
nextTick(() => {
setCursorToElement(target, 'end')
})
}
}
// 添加对 Ctrl+V 的处理
else if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
// 延迟处理,等待粘贴内容实际插入
setTimeout(() => {
handlePasteInInput(index, target)
}, 0)
}
}
const handlePasteInInput = (index: number, element: HTMLElement) => {
const item = data.content[index]
// 如果当前显示 placeholder清除它
if (element.classList.contains('has-placeholder')) {
element.classList.remove('has-placeholder')
// 获取粘贴后的实际内容
const newValue = element.textContent || ''
data.content[index].value = newValue
// 如果粘贴后内容为空,重新显示 placeholder
if (newValue.trim() === '' && item.placeholder) {
element.classList.add('has-placeholder')
element.textContent = item.placeholder
}
} else {
// 正常情况,直接更新值
const newValue = element.textContent || ''
data.content[index].value = newValue
// 检查是否需要显示 placeholder
if (newValue.trim() === '' && item.placeholder) {
element.classList.add('has-placeholder')
element.textContent = item.placeholder
}
}
}
const handleCompositionEnd = (index: number, event: CompositionEvent) => {
// 延迟设置 isComposing 为 false确保所有相关事件都处理完毕
setTimeout(() => {
compositionState.isComposing = false
compositionState.currentInputIndex = -1
}, 0)
const target = event.target as HTMLSpanElement
const newValue = target.textContent || ''
data.content[index].value = newValue
// 如果中文输入后内容为空显示placeholder
const item = data.content[index]
if (newValue.trim() === '' && item.placeholder) {
// 延迟显示 placeholder确保光标位置正确
nextTick(() => {
if (target.textContent?.trim() === '') {
target.classList.add('has-placeholder')
target.textContent = item.placeholder
}
})
}
}
// 添加输入框焦点事件处理,确保中文输入状态正确重置
const handleInputFocus = (index: number) => {
compositionState.currentInputIndex = index
}
// 修改输入框的 blur 处理
const handleInputBlur = (index: number) => {
// 延迟重置状态,避免与 compositionend 冲突
setTimeout(() => {
compositionState.isComposing = false
compositionState.currentInputIndex = -1
}, 100)
updateInputDisplay(index)
}
// 容器点击处理
const handleContainerClick = (event: MouseEvent) => {
const target = event.target as HTMLElement
if (target === editableArea.value) {
event.preventDefault()
cursorState.value.isContainerClick = true
// 确保末尾有空文本框并聚焦到它
ensureEmptyTextAtEnd()
nextTick(() => {
focusElement(data.content.length - 1, 'start')
})
}
}
// 初始化
const initPlaceholders = () => {
nextTick(() => {
data.content.forEach((_, index) => updateInputDisplay(index))
// 确保初始状态下有一个空文本框
ensureEmptyTextAtEnd()
})
}
const getFullText = () => {
if (assistModel.value) {
return data.content
.map(item => {
if (item.type === 'text') {
return item.value
} else {
// 如果 input 没有输入 value则用 placeholder 填充,并去掉首尾的 []
if (item.value) {
return ` ${item.value} `
} else if (item.placeholder) {
let placeholderText = item.placeholder
// 去掉首尾的 []
if (placeholderText.startsWith('[') && placeholderText.endsWith(']')) {
placeholderText = placeholderText.slice(1, -1)
}
return ` ${placeholderText} `
}
return ''
}
})
.join('')
return senderRef.value?.getContext().replaceAll('[', '').replaceAll(']', '')
}
return textareaValue.value
}
const textareaValue = ref('')
const assistModel = ref(false)
const syncContentFromProps = (
newContent: ContentItem[] = props.content as ContentItem[]
) => {
data.content = JSON.parse(JSON.stringify(newContent || []))
if (assistModel.value) {
initPlaceholders()
}
}
const senderRef = useTemplateRef<HTMLDivElement>('senderRef')
const handleClickAssistBtn = () => {
assistModel.value = !assistModel.value
if (assistModel.value) {
syncContentFromProps()
}
}
watch(
() => props.content,
newVal => {
syncContentFromProps(newVal as ContentItem[])
},
{ deep: true }
)
const textareaRef = useTemplateRef<HTMLTextAreaElement>('textareaRef')
defineExpose({
getFullText,
content
getFullText
// content
})
</script>
@@ -566,39 +51,9 @@ defineExpose({
<span>{{ $t('ProductImg.PromptAssit') }}</span>
</div>
</div>
<!-- {{ data.content }} -->
<div
v-show="assistModel"
ref="editableArea"
class="promptInput"
@keydown="handleKeydown"
@click="handleContainerClick"
>
<div class="promptinput-wrapper">
<template v-for="(item, index) in data.content" :key="item.id">
<span
v-if="item.type === 'text'"
class="text-field"
:data-index="index"
contenteditable="plaintext-only"
>
{{ item.value }}
</span>
<span v-else class="input-field" :data-index="index">
<span
class="input-content"
contenteditable="plaintext-only"
@input="e => handleInputChange(index, e)"
@keydown="e => handleInputKeydown(e, index)"
@focus="() => handleInputFocus(index)"
@blur="() => handleInputBlur(index)"
@paste="e => handleInputPaste(e, index)"
@compositionstart="e => handleCompositionStart(index, e)"
@compositionend="e => handleCompositionEnd(index, e)"
></span>
</span>
</template>
<div v-show="assistModel" class="sender-container">
<div class="sender-wrapper">
<Sender ref="senderRef" :nodeList="content" />
</div>
<div class="asistant-btn" @click="handleClickAssistBtn">
<i class="fi fi-bs-magic-wand asistant-icon"></i>
@@ -627,38 +82,18 @@ defineExpose({
position: relative;
padding-bottom: 4rem;
box-sizing: content-box;
.promptinput-wrapper {
}
.sender-container {
position: relative;
border-radius: 10px;
border: 2px solid #dcdfe6;
padding: 1.5rem 1.5rem 3rem;
font-size: 1.8rem;
.sender-wrapper {
min-height: 12rem;
max-height: 14rem;
overflow-y: auto;
}
.text-field {
display: inline;
outline: none;
padding: 0.2rem 0;
font-size: 1.8rem;
min-width: 2px;
font-weight: 400;
}
.input-field {
display: inline-block;
margin: 0 0.2rem;
padding: 0.2rem 1rem;
font-size: 1.8rem;
border-radius: 4px;
.input-content {
outline: none;
display: inline-block;
min-width: 2rem;
&.has-placeholder {
color: #b9b9b9;
}
}
margin-bottom: 1rem;
}
}

View File

@@ -367,22 +367,22 @@ export default {
TipsStart: 'User can use ',
TipsEnd: 'Edit Product Image to generate first or last frames.',
PormptPlaceholder: 'Enter the scene you want to describe...',
firstAndLastFrameText1: 'As the video progresses, use',
firstAndLastFramePlaceholder1: '[Camera Movement]',
firstAndLastFrameText2: 'to follow the motion, under',
firstAndLastFramePlaceholder2: '[Light]',
firstAndLastFrameText1: 'As the video progresses, use ',
firstAndLastFramePlaceholder1: 'Camera Movement',
firstAndLastFrameText2: ' to follow the motion, under',
firstAndLastFramePlaceholder2: 'Light',
firstAndLastFrameText3:
', maintaining full consistency of model identity, styling, and outfit across all frames.',
firstFrameText1: 'Set the',
firstFramePlaceholder1: '[Scene]',
firstFrameText2: ', where the model',
firstFramePlaceholder2: '[Motion]',
firstFrameText3: 'use a',
firstFramePlaceholder3: '[Camera Movement]',
firstFrameText4: 'combined with a',
firstFramePlaceholder4: '[Camera Movement]',
firstFrameText5: ', under',
firstFramePlaceholder5: '[Light]',
' , maintaining full consistency of model identity, styling, and outfit across all frames.',
firstFrameText1: 'Set the ',
firstFramePlaceholder1: 'Scene',
firstFrameText2: ', where the model ',
firstFramePlaceholder2: 'Motion',
firstFrameText3: ' use a ',
firstFramePlaceholder3: 'Camera Movement',
firstFrameText4: ' combined with a ',
firstFramePlaceholder4: 'Camera Movement',
firstFrameText5: ', under ',
firstFramePlaceholder5: 'Light',
firstFrameText6: ', complementing the look.'
},
LibraryPage: {