Files
FiDA_Front/src/views/home/components/Input.vue

1417 lines
34 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="assist-input-wrapper flex flex-col" :class="{ agent: isAgentMode }">
<div class="animate-container flex-1 flex flex-col">
<div class="scroll-content flex-col">
<div v-if="uploadedImages.length > 0" class="image-preview-list flex wrap">
<div
v-for="(image, index) in uploadedImages"
:key="index"
class="image-preview-item"
>
<img
:src="image.url"
:alt="image.name"
class="preview-image"
@click="previewImage(image.url)"
/>
<div class="image-remove-btn" @click="removeImage(index)">
<SvgIcon name="delete" size="16" />
</div>
</div>
</div>
<div class="editor-wrapper">
<div v-if="showPlaceholder" class="editor-placeholder">
{{ $t('Input.placeholder') }}
</div>
<div
ref="editorRef"
class="editor"
contenteditable="true"
@input="handleEditorInput"
@paste="handleEditorPaste"
@keydown="handleKeyDown"
></div>
</div>
</div>
<div class="operate flex align-center space-between">
<div class="left flex align-center">
<div class="agent-operate flex flex-center">
<el-popover
placement="top"
trigger="click"
popper-class="agent-plus-popover"
>
<template #reference>
<SvgIcon name="plus" color="#0D0D0D" size="16" />
</template>
<template #default>
<div class="agent-modal flex flex-col">
<div class="file flex align-center" @click="triggerFileUpload">
<img src="@/assets/icons/attach.svg" class="file-icon" />
<span>Upload files</span>
</div>
<div class="gap"></div>
<div class="report flex align-center" @click="toogltReportTag">
<SvgIcon color="#5A5A5A" name="light" size="11" />
<span>Trending report</span>
</div>
</div>
</template>
</el-popover>
</div>
<div
class="attach flex flex-center"
@click="triggerFileUpload"
v-if="!isAgentMode"
>
<img src="@/assets/icons/attach.svg" />
</div>
<input
ref="fileInputRef"
type="file"
accept="image/*"
style="display: none"
@change="handleFileChange"
/>
<el-select
v-if="!isAgentMode"
v-model="typeValue"
:placeholder="$t('Input.typePlaceholder')"
>
<el-option
v-for="item in typeOptions"
class="input-option"
:key="item.value"
:label="$t(item.label)"
:value="item.value"
/>
</el-select>
<el-select
v-if="!isAgentMode"
v-model="areaValue"
:placeholder="$t('Input.areaPlaceholder')"
>
<el-option
v-for="item in areaOptions"
class="input-option"
:key="item.value"
:label="$t(item.label)"
:value="item.value"
/>
</el-select>
<div v-if="!isAgentMode" class="fida-style-select-wrapper">
<el-select
v-model="styleValue"
:placeholder="$t('Input.stylePlaceholder')"
@focus="openStylePopup"
/>
<el-popover
v-model:visible="stylePopupVisible"
placement="top"
:width="342"
:show-arrow="false"
trigger="click"
popper-class="fida-style-select-popover"
>
<template #reference>
<div class="fida-style-select-trigger"></div>
</template>
<div class="fida-style-popover-content flex flex-col">
<div class="fida-style-popover-header">
{{ $t('Input.chooseStyle') }}
</div>
<div class="fida-style-popover-grid">
<div
v-for="item in styleOptions"
:key="item.value"
class="fida-style-popover-item flex flex-center"
:class="{ 'is-selected': tempSelectedValue === item.value }"
@click="selectStyle(item.value)"
>
<img
:src="getStyleImage(typeValue, item.value)"
class="style-bg"
/>
<span class="fida-option-label flex flex-center">{{
item.label
}}</span>
<img
v-show="tempSelectedValue === item.value"
src="@/assets/images/checked.png"
class="checked-item-icon"
/>
</div>
</div>
<div class="fida-style-popover-footer flex flex-center">
<button class="fida-confirm-btn" @click="confirmStyle">
{{ $t('Input.confirm') }}
</button>
</div>
</div>
</el-popover>
</div>
<el-popover
v-model:visible="settingPopupVisible"
placement="top"
:width="342"
:show-arrow="false"
trigger="click"
popper-class="fida-setting-popover"
>
<template #reference>
<img src="@/assets/images/setting.png" class="setting-icon" />
</template>
<div class="fida-setting-popover-content flex flex-col">
<div class="fida-setting-popover-header">
{{ $t('Input.styleTitle') }}
</div>
<div class="fida-setting-slider-list">
<div
v-for="item in settingOptions"
:key="item.label"
class="fida-setting-slider-item"
>
<div class="fida-slider-label">{{ $t(item.label) }}</div>
<div class="fida-slider-row flex align-center">
<el-slider
class="setting-popover-slider"
v-model="item.value"
:show-tooltip="false"
/>
<span class="fida-slider-value">{{ item.value }}%</span>
</div>
</div>
</div>
</div>
</el-popover>
</div>
<div class="right">
<div
class="create-btn flex flex-center"
v-if="!isAgentMode"
@click="handleCreateProject"
>
<img src="@/assets/images/shining.png" class="shining-icon" alt="" />
<span class="create-btn-text">{{ $t('Input.createProject') }}</span>
</div>
<div v-else class="sender-btn flex flex-center" @click="handleSendAgent">
<img
v-show="!generating"
src="@/assets/images/sender.png"
alt=""
class="sender-icon"
/>
<div v-show="generating" class="sender-pause" />
</div>
</div>
</div>
</div>
<div
v-if="!isAgentMode"
class="report-btn flex space-between align-center"
@click="toogltReportTag"
>
<SvgIcon class="light-icon" color="#FFDB56" name="light" size="16" />
<span>{{ $t('Input.trendingReport') }}</span>
</div>
<Preview v-model="showPreview" :url="previewUrl" />
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch, nextTick, onMounted } from 'vue'
import { areaList } from '@/utils/area'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useAgentStore, useProjectStore } from '@/stores'
import lightIcon from '@/assets/images/light-icon.png'
import closeIcon from '@/assets/images/close-icon.png'
import restoreIcon from '@/assets/images/restore.png'
import restoreCloseIcon from '@/assets/images/tag-close.png'
import { createProject } from '@/api/agent'
import { getStyleImage } from './style'
import { uploadImage } from '@/api/upload'
import MyEvent from '@/utils/myEvent'
import Preview from '@/components/Preview/Preview.vue'
const router = useRouter()
const agentStore = useAgentStore()
const projectStore = useProjectStore()
const props = withDefaults(
defineProps<{
isAgentMode?: boolean
generating?: boolean
}>(),
{
isAgentMode: false,
generating: false
}
)
const emits = defineEmits(['send', 'pause'])
const { t } = useI18n()
// 图片上传相关
const fileInputRef = ref<HTMLInputElement | null>(null)
const uploadedImages = ref<Array<{ url: string; name: string }>>([])
// 触发文件上传
const triggerFileUpload = () => {
fileInputRef.value?.click()
}
// 处理文件选择
const handleFileChange = (event: Event) => {
const input = event.target as HTMLInputElement
if (input.files) {
Array.from(input.files).forEach((file) => {
// 只处理图片文件
if (file.type.startsWith('image/')) {
const formData = new FormData()
formData.append('file', file)
uploadImage(formData).then((res) => {
const reader = new FileReader()
reader.onload = (e) => {
uploadedImages.value.push({
url: e.target?.result as string,
name: file.name,
path: res
})
}
reader.readAsDataURL(file)
})
}
})
}
nextTick(() => {
editorRef.value?.focus()
})
input.value = ''
}
// 移除图片
const removeImage = (index: number) => {
uploadedImages.value.splice(index, 1)
}
const styleKeys: string[] = [
'Venetian Modern',
'Coastal',
'Maximalism',
'Memphis',
'Verdant',
'Century Chrome',
'Modern Revival',
'Transitional',
"Tuscan 2000's",
'Kitsch-core',
'Bauhaus',
'Constructivism',
'Nordic Noir',
'Dopamine',
'Squiggle'
]
const editorRef = ref<HTMLDivElement | null>(null)
const inputValue = ref<string>('')
const reportTags = ref([])
const customPlaceholder = ref<HTMLElement | null>(null)
// 控制占位符显示
const showPlaceholder = computed(() => {
if (!editorRef.value) return true
if (inputValue.value || reportTags.value.length > 0) return false
const editor = editorRef.value
const textContent = editor.textContent?.replace(/[\s\u200B\n]/g, '').trim() || '' // 移除空格、零宽空格、换行
const isEmptyHTML = editor.innerHTML === '' || editor.innerHTML === '<br>' // 处理浏览器 <br> 插入
const hasMeaningfulChildren = Array.from(editor.children).some(
(child) => child.tagName !== 'BR' && !child.classList.contains('editor-tag') // 排除 <br> 和 tags如果 tags 算非空)
)
// 如果纯文本为空、无有意义子元素、且不是仅剩 <br>,则显示 placeholder
return textContent === '' && !hasMeaningfulChildren && isEmptyHTML
})
// 打字机效果显示placeholder
let typewriterTimeout: ReturnType<typeof setTimeout> | null = null
const typeWriterEffect = (element: HTMLElement, text: string, index: number = 0) => {
if (index < text.length) {
element.innerText += text.charAt(index)
typewriterTimeout = setTimeout(() => {
typeWriterEffect(element, text, index + 1)
}, 30) // 每个字符间隔30ms
}
}
// 停止打字机效果
const stopTypewriter = () => {
if (typewriterTimeout) {
clearTimeout(typewriterTimeout)
typewriterTimeout = null
}
}
// 导出给父组件调用的方法
const addReportTag = (text?: string) => {
// 使用传入的文本,如果没有传入则使用默认的翻译文本
const tagText = text || t('Input.trendingReport')
const tag = document.createElement('div')
tag.contentEditable = 'false'
const imgLeft = document.createElement('img')
const imgClose = document.createElement('img')
const textSpan = document.createElement('span')
imgClose.className = 'close-icon'
if (text) {
tag.className = 'editor-tag restore flex-center'
imgLeft.className = 'restore-icon'
imgLeft.src = restoreIcon as unknown as string
imgClose.src = restoreCloseIcon as unknown as string
imgClose.className = 'close-icon restore'
textSpan.className = 'restore-text'
} else {
tag.className = 'editor-tag report-btn flex-center'
imgLeft.className = 'light-icon'
imgLeft.src = lightIcon as unknown as string
imgClose.src = closeIcon as unknown as string
}
textSpan.innerText = tagText
imgClose.addEventListener('click', (ev) => {
ev.stopPropagation()
stopTypewriter() // 关闭标签时停止打字机效果
// remove tag when close clicked
tag.remove()
const idx = reportTags.value.indexOf(tag)
if (idx > -1) reportTags.value.splice(idx, 1)
if (customPlaceholder.value) {
customPlaceholder.value.remove()
customPlaceholder.value = null
}
})
// assemble
tag.appendChild(imgLeft)
tag.appendChild(textSpan)
tag.appendChild(imgClose)
// Insert tag at the current cursor position
const selection = window.getSelection()
// 检查selection是否在editorRef内部不在则强制使用editorRef
const isInEditor =
editorRef.value && selection && editorRef.value.contains(selection.anchorNode)
if (isInEditor && selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0)
range.insertNode(tag)
// Insert a zero-width space text node after the tag so the caret can be placed there
const zwsp = document.createTextNode('\u200B')
if (tag.parentNode) tag.parentNode.insertBefore(zwsp, tag.nextSibling)
// Create a new collapsed range positioned inside the zwsp (after the tag)
const newRange = document.createRange()
newRange.setStart(zwsp, 1)
newRange.collapse(true)
selection.removeAllRanges()
selection.addRange(newRange)
// ensure editor has focus
editorRef.value && (editorRef.value as HTMLElement).focus()
} else if (editorRef.value) {
// If no selection in editor, append directly to editor and place caret after
editorRef.value.appendChild(tag)
const zwsp = document.createTextNode('\u200B')
editorRef.value.appendChild(zwsp)
const sel = window.getSelection()
if (sel) {
const r = document.createRange()
r.setStart(zwsp, 1)
r.collapse(true)
sel.removeAllRanges()
sel.addRange(r)
}
editorRef.value && (editorRef.value as HTMLElement).focus()
}
reportTags.value.push(tag)
const placeholderSpan = document.createElement('span')
placeholderSpan.className = 'custom-placeholder'
// 初始为空字符串,稍后通过打字机效果填充
placeholderSpan.innerText = ''
if (tag.parentNode) {
tag.parentNode.insertBefore(placeholderSpan, tag.nextSibling)
const zwsp = document.createTextNode('\u200B')
tag.parentNode.insertBefore(zwsp, placeholderSpan.nextSibling)
const newRange = document.createRange()
newRange.setStart(placeholderSpan, 0)
newRange.collapse(true)
selection.removeAllRanges()
selection.addRange(newRange)
selection.addRange(newRange)
}
customPlaceholder.value = placeholderSpan
// 打字机效果显示placeholder文本
const placeholderText =
'Generate a furniture trending report for 2026, including popular styles and design directions.'
typeWriterEffect(placeholderSpan, placeholderText)
const removePlaceholderOnInput = () => {
stopTypewriter() // 用户输入时停止打字机效果
if (placeholderSpan.parentNode) {
placeholderSpan.remove()
customPlaceholder.value = null
}
editorRef.value?.removeEventListener('input', removePlaceholderOnInput)
}
editorRef.value?.addEventListener('input', removePlaceholderOnInput)
}
const toogltReportTag = () => {
stopTypewriter() // 移除标签时停止打字机效果
// 清理掉已被删除的标签引用(从 DOM 中移除的元素)
reportTags.value = reportTags.value.filter((tag) => tag.parentNode !== null)
if (reportTags.value.length > 0) {
// 移除所有标签及其关联的零宽空格
reportTags.value.forEach((tag) => {
if (
tag.nextSibling &&
tag.nextSibling.nodeType === Node.TEXT_NODE &&
tag.nextSibling.textContent === '\u200B'
) {
tag.nextSibling.remove()
}
tag.remove()
})
reportTags.value = []
if (customPlaceholder.value) {
customPlaceholder.value.remove()
customPlaceholder.value = null
}
} else {
// 添加标签
addReportTag()
}
}
// 清理编辑器中的零宽字符
const cleanupEditor = () => {
if (!editorRef.value) return
const editor = editorRef.value
// 移除所有零宽字符
if (editor.textContent) {
const cleanedText = editor.textContent.replace(/[\u200B-\u200D\uFEFF]/g, '')
// 检查是否真的为空
if (!cleanedText.trim() && editor.children.length === 0) {
// 完全清空
editor.innerHTML = ''
editor.textContent = ''
reportTags.value = []
customPlaceholder.value = null
stopTypewriter()
}
}
}
const handleEditorInput = () => {
if (!editorRef.value) return
// 提取纯文本排除插入的report标签
let text = ''
const walker = document.createTreeWalker(editorRef.value, NodeFilter.SHOW_TEXT, null)
let node: Node | null
while ((node = walker.nextNode())) {
// 使用 closest() 检查当前节点的祖先元素是否包含需要排除的 class
if (node.parentElement?.closest('.custom-placeholder')) continue
if (node.parentElement?.closest('.editor-tag')) continue
text += node.textContent
}
// 移除末尾的空格和零宽空格
text = text.replace(/[\s\u200B]+$/, '')
inputValue.value = text
// 检查编辑器是否真正为空(没有可见子元素且没有文本)
const editor = editorRef.value
const hasChildElements = editor.children.length > 0
const hasTextContent = editor.textContent?.replace(/[\s\u200B]/g, '').trim().length > 0
// 如果编辑器完全为空,清空它以显示占位符
// 同时也要清空 reportTags 和 customPlaceholder
if (!hasChildElements && !hasTextContent) {
editor.innerHTML = ''
reportTags.value = []
customPlaceholder.value = null
stopTypewriter()
}
// 自动调整高度
autoResizeEditor()
}
const handleEditorPaste = (e: ClipboardEvent) => {
e.preventDefault()
const text = e.clipboardData?.getData('text/plain') || ''
document.execCommand('insertText', false, text)
}
const autoResizeEditor = () => {
const editor = editorRef.value
if (editor) {
if (props.isAgentMode) {
// editor.style.height = '6rem'
// editor.style.overflowY = 'auto'
return
} else {
// editor.style.height = 'auto'
// const maxHeight =
// 20 * parseFloat(getComputedStyle(document.documentElement).fontSize || '16')
// editor.style.height = Math.min(editor.scrollHeight, maxHeight) + 'px'
}
}
}
const handleKeyDown = (e) => {
// 检测回车
if (e.key === 'Enter') {
e.preventDefault()
if (props.isAgentMode) {
handleSendAgent()
} else {
handleCreateProject()
}
return
}
if (e.key === 'Backspace') {
const selection = window.getSelection()
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0)
if (range.collapsed) {
let nodeToDelete: Node | null = null
const startContainer = range.startContainer
const startOffset = range.startOffset
if (startContainer.nodeType === Node.TEXT_NODE) {
// Cursor at the end of a text node, check next sibling
if (startOffset === startContainer.length) {
nodeToDelete = startContainer.nextSibling
}
} else if (startContainer.nodeType === Node.ELEMENT_NODE) {
// Cursor positioned between child nodes
nodeToDelete = startContainer.childNodes[startOffset]
}
// 检查 nodeToDelete 是否存在以及是否为元素节点
if (
nodeToDelete &&
nodeToDelete.nodeType === Node.ELEMENT_NODE &&
(nodeToDelete as Element).classList
) {
const element = nodeToDelete as Element
const isEditorTag = element.classList.contains('editor-tag')
const isReportTag = element.classList.contains('report-tag')
const isCustomPlaceholder = element.classList.contains('custom-placeholder')
if (isEditorTag || isReportTag || isCustomPlaceholder) {
e.preventDefault()
element.remove()
// 如果删除的是 customPlaceholder停止打字机效果
if (isCustomPlaceholder) {
stopTypewriter()
customPlaceholder.value = null
}
// 从 reportTags 中移除
const index = reportTags.value.indexOf(nodeToDelete as Element)
if (index > -1) {
reportTags.value.splice(index, 1)
}
// 删除标签后清理零宽字符并检查是否为空
nextTick(() => {
cleanupEditor()
})
return
}
}
nextTick(() => {
if (editorRef.value) {
const text = editorRef.value.textContent || ''
const cleanedText = text.replace(/[\u200B-\u200D\uFEFF]/g, '')
if (editorRef.value.innerHTML === '<br>' || !cleanedText.trim()) {
editorRef.value.innerHTML = ''
// 检查是否完全为空,需要显示占位符
if (editorRef.value.children.length === 0) {
reportTags.value = []
customPlaceholder.value = null
stopTypewriter()
}
}
}
})
}
}
}
}
const handleSendAgent = async () => {
if (props.generating) {
emits('pause')
return
}
if (!inputValue.value.trim()) return
const imageUrlList = uploadedImages.value.map((item) => item.path)
const payload = {
text: inputValue.value.trim(),
images: imageUrlList,
tempImages: uploadedImages.value
}
if (reportTags.value.length > 0) {
payload.useReport = true
}
emits('send', payload)
// 发送后清空图片列表
uploadedImages.value = []
// 发送后清空输入框
if (editorRef.value) {
editorRef.value.innerHTML = ''
}
inputValue.value = ''
}
// 监听 inputValue 外部变化
watch(inputValue, () => {
nextTick(() => {
autoResizeEditor()
})
})
// 初始化编辑器高度
onMounted(() => {
nextTick(() => {
autoResizeEditor()
})
})
const typeValue = ref<string>('')
const areaValue = ref<string>('')
const styleValue = ref<string>('')
const tempSelectedValue = ref<string>('')
const stylePopupVisible = ref(false)
const settingPopupVisible = ref(false)
const settingOptions = ref([
{ label: 'Input.settingOptions.first', value: 50 },
{ label: 'Input.settingOptions.second', value: 50 },
{ label: 'Input.settingOptions.third', value: 50 }
])
const openStylePopup = () => {
// 打开弹窗时初始化临时选中值为当前选中值
tempSelectedValue.value = styleValue.value
stylePopupVisible.value = true
}
const selectStyle = (value: string) => {
tempSelectedValue.value = value
}
const confirmStyle = () => {
// 点击确认后才真正赋值
styleValue.value = tempSelectedValue.value
stylePopupVisible.value = false
}
const confirmSetting = () => {
settingPopupVisible.value = false
}
const typeOptions = ref<any[]>([
{
label: 'Input.types.sofa',
value: 'Sofa'
},
{
label: 'Input.types.desk',
value: 'Desk'
},
{
label: 'Input.types.chair',
value: 'Chair'
}
])
const areaOptions = ref<any[]>(areaList)
const styleOptions = ref<any[]>(
styleKeys.map((key) => ({
label: key,
// label: `Input.styles.${key}`,
value: key
}))
)
const handleCreateProject = async () => {
if (!inputValue.value.trim()) {
return
}
const params = {
type: typeValue.value,
area: areaValue.value,
style: styleValue.value,
useReport: reportTags.value.length > 0,
temperature: 0.7
}
const projectres = await createProject(params)
// console.log('projectres', projectres)
projectStore.setId(projectres)
MyEvent.emit('updateProjectList')
// 保存初始数据到 store
agentStore.setInitialProjectData({
text: inputValue.value.trim(),
images: uploadedImages.value.map((item) => item.path),
tempImages: uploadedImages.value,
...params
})
// console.log('Create project with:', params)
router.push(`/home/agent/${projectres}`, { query: params })
uploadedImages.value = []
}
const showPreview = ref(false)
const previewUrl = ref('')
const previewImage = (url: string) => {
showPreview.value = true
previewUrl.value = url
}
// 暴露方法给父组件
defineExpose({
addReportTag
})
</script>
<style lang="less" scoped>
.report-btn {
position: absolute;
bottom: -7.4rem;
height: 4.4rem;
border-radius: 2.2rem;
width: 19.7rem;
padding: 0 2rem;
font-size: 1.8rem;
background-color: #fff;
border: 1.1px solid #f6f4ef1a;
cursor: pointer;
.c-svg {
width: 1.5rem;
height: 2rem;
}
}
.assist-input-wrapper {
min-height: 23.5rem;
max-height: 43.5rem;
width: 106.3rem;
margin: 0 auto;
padding: 0;
position: relative;
.animate-container {
overflow-y: hidden;
}
&:not(.agent) .animate-container {
box-shadow: 0px 0.5rem 1.4rem 0px #0000001a;
transition: all 0.3s ease;
border-radius: 2.8rem;
background-color: #fff;
border: 0.1rem solid #00000005;
&:not(.agent):hover {
box-shadow: 0px 0.5rem 3.36rem 2.2rem #f1ede999;
transform: translateY(-1rem);
}
}
.scroll-content {
display: flex;
flex-direction: column;
flex: 1;
overflow-y: auto;
padding: 3.4rem 1.7rem 1.7rem;
}
.editor-wrapper {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
min-height: 0;
}
.editor-placeholder {
position: absolute;
z-index: 0;
pointer-events: none;
top: 0;
left: 0;
padding: 0 1.4rem 1.4rem;
font-size: 2rem;
font-family: 'InterRegular';
font-weight: 400;
color: #999;
pointer-events: none;
white-space: pre-wrap;
word-wrap: break-word;
width: calc(100% - 2.8rem);
box-sizing: border-box;
}
.editor {
width: 100%;
flex: 1;
border: none;
outline: none;
padding: 0 1.4rem 1.4rem;
font-size: 1.8rem;
font-family: 'InterRegular';
font-weight: 400;
color: #000000;
overflow-y: auto;
overflow-x: hidden;
line-height: 2.8rem;
white-space: pre-wrap;
word-wrap: break-word;
&::-webkit-scrollbar {
width: 0;
display: none;
}
&::-webkit-scrollbar-thumb {
display: none;
}
&::-webkit-scrollbar-track {
display: none;
}
// 占位符
&:empty::before {
content: attr(placeholder);
color: #999;
pointer-events: none;
}
}
// 图片预览区域样式
.image-preview-list {
padding: 0 1.4rem 1rem;
column-gap: 1rem;
max-height: 15rem;
overflow-y: auto;
flex-shrink: 0;
.image-preview-item {
position: relative;
width: 8.6rem;
height: 8.6rem;
border-radius: 1.5rem;
overflow: hidden;
flex-shrink: 0;
border: 0.1rem solid #cdcdcd;
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 0.8rem;
cursor: pointer;
}
.image-remove-btn {
position: absolute;
top: 0.6rem;
right: 0.6rem;
width: 1.6rem;
height: 1.6rem;
color: #fff;
border-radius: 50%;
// display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
cursor: pointer;
display: none;
}
&:hover .image-remove-btn {
display: flex;
}
}
}
.operate {
flex-shrink: 0;
margin-top: auto;
padding: 0 1.7rem 1.7rem;
.left {
column-gap: 2rem;
}
.agent-operate {
width: 3.2rem;
height: 3.2rem;
cursor: pointer;
border-radius: 50%;
&:hover {
background-color: #f0f0f0;
}
}
.attach {
width: 4rem;
height: 4rem;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
cursor: pointer;
img {
width: 1.65rem;
height: 1.86rem;
}
}
.el-select {
width: 13.9rem;
height: 4rem;
:deep(.el-select__wrapper) {
border-radius: 0.8rem;
height: 100%;
box-shadow: none;
border: 0.1rem solid rgba(0, 0, 0, 0.1);
font-weight: 500;
font-size: 1.4rem;
min-height: initial;
.el-select__placeholder {
color: #000;
}
.el-select__icon {
color: #000;
}
}
}
.fida-style-select-wrapper {
position: relative;
width: 13.9rem;
height: 4rem;
}
.fida-style-select-trigger {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
cursor: pointer;
}
.setting-icon {
width: 2.4rem;
height: 2.4rem;
cursor: pointer;
}
.create-btn {
background-color: #ff7a51;
height: 4rem;
width: 13rem;
color: #fff;
border-radius: 4.2rem;
font-family: 'MSemiBold';
font-weight: 600;
font-size: 1.28rem;
cursor: pointer;
.shining-icon {
width: 1.4rem;
height: 1.4rem;
}
}
}
}
.input-option {
// padding: 0 1rem;
margin: 0 0.6rem;
padding: 0 0.8rem 0 1rem;
color: #0d0d0d;
font-weight: 510;
font-size: 1.3rem;
height: 3rem;
line-height: 3rem;
&.el-select-dropdown__item.is-hovering {
background-color: rgba(13, 13, 13, 0.02);
// border-radius: 0.6rem;
}
}
.agent {
padding: 1.2rem;
box-shadow: none;
border-radius: 1.5rem;
border: 0.1rem solid #0000001a;
.scroll-content {
padding: 0;
flex: 1;
overflow: auto;
.editor {
font-family: 'Regular';
font-weight: 400;
font-size: 1.4rem;
min-height: initial;
max-height: initial;
padding: 0;
height: 100%;
min-height: 5rem;
line-height: 1.4rem;
}
.editor-placeholder {
font-family: 'Regular';
font-size: 1.4rem;
padding: 0;
line-height: 1.4rem;
}
}
.operate {
padding: 1.2rem 0 0;
margin: 0;
.right {
display: flex;
align-items: center;
.sender-btn {
width: 3.2rem;
height: 3.2rem;
cursor: pointer;
background-color: #ff7a51;
border-radius: 50%;
&:hover {
background-color: #f8693d;
}
.sender-icon {
width: 1.3rem;
height: 1.3rem;
}
.sender-pause {
width: 1rem;
height: 1rem;
background-color: #fff;
}
}
}
}
}
</style>
<style lang="less">
.custom-placeholder {
color: #999;
margin-left: 0.5rem;
pointer-events: none;
}
.fida-style-select-popover {
width: 34.2rem !important;
padding: 0 !important;
border-radius: 0.6rem !important;
box-shadow: 0px 5px 20px 0px rgba(0, 0, 0, 0.15) !important;
background-color: #fff !important;
border: none !important;
}
.fida-style-popover-content {
padding: 2rem 2.4rem 2.4rem;
}
.fida-style-popover-header {
font-weight: 500;
font-size: 1.6rem;
color: #000;
margin-bottom: 2rem;
padding: 2rem 2.4rem !important;
// padding: 1.8rem 2rem 1.5rem;
// border-bottom: 0.1rem solid #f0f0f0;
}
.fida-style-popover-grid {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
height: 28.5rem;
overflow-y: auto;
// display: grid;
// grid-template-columns: repeat(3, 1fr);
// gap: 1rem;
}
.fida-style-popover-item {
height: 9.1rem;
width: 9.1rem;
border-radius: 1.4rem;
cursor: pointer;
position: relative;
border: none;
.checked-item-icon {
position: absolute;
bottom: 0;
right: 0;
transform: translate(50%, 50%);
width: 2.4rem;
height: 2.4rem;
}
.style-bg {
width: 100%;
height: 100%;
border-radius: 1.4rem;
}
.fida-option-label {
font-weight: 500;
font-size: 1.2rem;
color: #fff;
text-align: center;
padding: 0.5rem;
position: absolute;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 1.4rem;
}
&.is-selected {
border: 0.3rem solid #000;
.fida-option-label {
display: none;
}
}
}
.fida-style-popover-footer {
// border-top: 0.1rem solid #f0f0f0;
padding: 2.4rem 0 !important;
margin-top: 2.4rem;
.fida-confirm-btn {
margin: 0 auto;
width: 15.7rem;
height: 3.4rem;
line-height: 3.4rem;
background-color: #ff7a51;
color: #fff;
border: none;
border-radius: 3.8rem;
font-weight: 500;
font-size: 1.4rem;
cursor: pointer;
}
}
.fida-setting-popover {
padding: 0 !important;
border-radius: 0.6rem !important;
background-color: #fff !important;
border: none !important;
width: 25.6rem;
height: 23.9rem;
box-shadow: 0px 11px 20px 0px #0000001a;
border-radius: 0.6rem;
}
.fida-setting-popover-header {
font-weight: 400;
font-size: 1.4rem;
color: #000;
margin-bottom: 2rem !important;
}
.fida-setting-popover-content {
padding: 1.6rem 1.4rem 2.2rem !important;
}
.fida-setting-slider-list {
display: flex;
flex-direction: column;
row-gap: 1rem;
}
.fida-setting-slider-item {
.fida-slider-label {
font-weight: 400;
font-size: 1.2rem;
color: #000;
margin-bottom: 1rem;
}
.fida-slider-row {
column-gap: 2.6rem;
.el-slider {
flex: 1;
}
.fida-slider-value {
font-weight: 400;
font-size: 1.4rem;
color: #000;
}
}
}
.setting-popover-slider {
--el-slider-height: 0.4rem;
height: fit-content;
.el-slider__runway {
height: var(--el-slider-height);
background-color: #e8e8e8;
border-radius: 0.2rem;
}
.el-slider__bar {
height: var(--el-slider-height);
background-color: #000;
border-radius: 0.2rem;
}
.el-slider__button-wrapper {
width: fit-content;
height: fit-content;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
}
.el-slider__button {
width: 1rem;
height: 1rem;
background-color: #000;
border-radius: 50%;
border: none;
}
.el-slider__stop {
display: none;
}
}
/* 动态添加的编辑器标签样式 */
.assist-input-wrapper .editor .editor-tag {
width: 15.6rem;
height: 3.1rem;
display: inline-flex;
border: 1px solid #0000001a;
font-weight: 500;
font-size: 1.3rem;
column-gap: 0;
margin: 0 0.5rem;
vertical-align: middle;
border-radius: 2.2rem;
&.restore {
width: auto;
max-width: 100%;
display: inline-flex;
border: none;
border-radius: 0.4rem;
background-color: rgba(0, 0, 0, 0.05);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
align-items: center;
justify-content: space-between;
padding: 0 0.9rem 0 0.7rem;
box-sizing: border-box;
}
span {
margin: 0 0.7rem 0 1.2rem;
&.restore-text {
flex: 1;
min-width: 0;
max-width: 52rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.light-icon {
width: 0.9rem;
height: 1.2rem;
flex-shrink: 0;
}
.close-icon {
width: 0.9rem;
height: 0.9rem;
cursor: pointer;
flex-shrink: 0;
&.restore {
width: 0.5rem;
height: 0.5rem;
}
}
.restore-icon {
width: 1.2rem;
height: 1.2rem;
}
}
.agent-plus-popover {
display: flex;
justify-content: flex-start;
background: transparent !important;
box-shadow: none !important;
border: none !important;
padding: 0 !important;
margin-bottom: -1rem;
width: fit-content !important;
min-width: initial !important;
//pointer-events: none;
.el-popper__arrow:before {
display: none;
}
.agent-modal {
// width: 14.6rem;
// height: 8.5rem;
row-gap: 1.2rem;
font-family: 'Medium';
font-weight: 500;
font-size: 1.3rem;
color: #222;
background-color: #fff;
border: 1px solid #d9d9d9;
box-shadow: 0px 6.53px 32.63px 0px #0000000d;
border-radius: 1rem;
padding: 1.2rem 1.4rem;
transform: translateX(calc(50% - 1.6rem));
.c-svg {
width: initial;
width: 1rem;
height: 1.3rem;
}
.file,
.report {
line-height: 1.8rem;
column-gap: 1rem;
cursor: pointer;
}
.file {
.file-icon {
width: 1.1rem;
height: 1.3rem;
}
}
.gap {
height: 0.05rem;
background-color: #d4d4d4;
}
}
}
</style>