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

396 lines
9.3 KiB
Vue

<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">
<InputImagePreviewList
:uploaded-images="uploadedImages"
:quote-list="quoteList"
@preview="previewImage"
@remove="removeImage"
/>
<InputEditor
:show-placeholder="showPlaceholder"
:placeholder="$t('Input.placeholder')"
:is-agent-mode="isAgentMode"
@ready="setEditorElement"
@input="handleEditorInput"
@paste="handleEditorPaste"
@keydown="handleKeyDown"
/>
</div>
<InputToolbar
v-model:type-value="typeValue"
v-model:area-value="areaValue"
v-model:style-value="styleValue"
v-model:setting-options="settingOptions"
:is-agent-mode="isAgentMode"
:generating="generating"
:type-options="typeOptions"
:area-options="areaOptions"
:style-options="styleOptions"
:type-placeholder="$t('Input.typePlaceholder')"
:area-placeholder="$t('Input.areaPlaceholder')"
:style-placeholder="$t('Input.stylePlaceholder')"
:style-title="$t('Input.chooseStyle')"
:setting-title="$t('Input.styleTitle')"
:confirm-text="$t('Input.confirm')"
:create-text="$t('Input.createProject')"
:parameters-disabled="isReportSelected"
:translate="translate"
@file-change="handleFileChange"
@toggle-report="handleToggleReportTag"
@create="handleCreateProject"
@send="handleSendAgent"
/>
</div>
<ReportShortcutButton
v-if="!isAgentMode"
:is-cn="isCn"
:label="$t('Input.trendingReport')"
@click="handleToggleReportTag"
/>
<Preview v-model="showPreview" :url="previewUrl" />
</div>
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { areaList } from '@/utils/area'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useAgentStore, useProjectStore } from '@/stores'
import { createProject } from '@/api/agent'
import MyEvent from '@/utils/myEvent'
import Preview from '@/components/Preview/Preview.vue'
import InputEditor from './input/InputEditor.vue'
import InputImagePreviewList from './input/InputImagePreviewList.vue'
import InputToolbar from './input/InputToolbar.vue'
import ReportShortcutButton from './input/ReportShortcutButton.vue'
import {
createSettingOptions,
createStyleOptions,
createTypeOptions,
optionTagOrder
} from './input/options'
import { useInputEditor } from './input/useInputEditor'
import { useInputImages } from './input/useInputImages'
import type { OptionItem, OptionTagKind, ParameterTag, SettingOption } from './input/types'
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, locale } = useI18n()
const translate = (key: string) => t(key)
const isCn = computed(() => {
return locale.value === 'CHINESE_SIMPLIFIED'
})
const isAgentModeRef = computed(() => props.isAgentMode)
const typeValue = ref<string>('')
const areaValue = ref<string>('')
const styleValue = ref<string>('')
const settingOptions = ref<SettingOption[]>(createSettingOptions())
const typeOptions = ref<OptionItem[]>(createTypeOptions())
const areaOptions = ref<OptionItem[]>(areaList)
const styleOptions = ref<OptionItem[]>(createStyleOptions())
const {
inputValue,
reportTags,
optionTagKinds,
showPlaceholder,
setEditorElement,
focusEditor,
stopReportTypewriter,
addReportTag,
toggleReportTag,
handleEditorInput,
handleEditorPaste,
handleKeyDown,
autoResizeEditor,
syncOptionTag,
syncAllOptionTags,
clearEditorText,
resetOptionTags
} = useInputEditor({
isAgentMode: isAgentModeRef,
t: translate,
typeValue,
areaValue,
styleValue,
typeOptions,
areaOptions,
styleOptions,
onSubmit: () => {
if (props.isAgentMode) {
handleSendAgent()
return
}
handleCreateProject()
}
})
const {
uploadedImages,
quoteList,
showPreview,
previewUrl,
handleFileChange,
removeImage,
previewImage,
handleQuote,
clearImages
} = useInputImages(focusEditor)
const isReportSelected = computed(() => {
return reportTags.value.some((tag) => tag.parentNode)
})
const clearParameterValues = () => {
typeValue.value = ''
areaValue.value = ''
styleValue.value = ''
}
const handleToggleReportTag = () => {
const shouldSelectReport = !isReportSelected.value
toggleReportTag()
if (shouldSelectReport) {
clearParameterValues()
}
}
const getTranslatedOptionLabel = (options: OptionItem[], value: string) => {
const option = options.find((item) => item.value === value)
return option ? translate(option.label) : value
}
const getParameterTagLabel = (kind: OptionTagKind) => {
if (kind === 'type') {
return getTranslatedOptionLabel(typeOptions.value, typeValue.value)
}
if (kind === 'area') {
return getTranslatedOptionLabel(areaOptions.value, areaValue.value)
}
const option = styleOptions.value.find((item) => item.value === styleValue.value)
return option?.label || styleValue.value
}
const hasParameterValue = (kind: OptionTagKind) => {
if (kind === 'type') return Boolean(typeValue.value)
if (kind === 'area') return Boolean(areaValue.value)
return Boolean(styleValue.value)
}
const selectedParameterTags = computed<ParameterTag[]>(() => {
const currentKinds = optionTagKinds.value.filter(hasParameterValue)
const missingKinds = optionTagOrder.filter((kind) => {
return hasParameterValue(kind) && !currentKinds.includes(kind)
})
return [...currentKinds, ...missingKinds].map((kind) => ({
kind,
label: getParameterTagLabel(kind)
}))
})
watch(inputValue, () => {
nextTick(() => {
autoResizeEditor()
})
})
watch(typeValue, () => {
nextTick(() => {
syncOptionTag('type')
})
})
watch(areaValue, () => {
nextTick(() => {
syncOptionTag('area')
})
})
watch(styleValue, () => {
nextTick(() => {
syncOptionTag('style')
})
})
watch(locale, () => {
nextTick(() => {
syncAllOptionTags()
})
})
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: string
images: any[]
tempImages: typeof uploadedImages.value
quoteList: string[]
parameterTags: ParameterTag[]
useReport?: boolean
} = {
text: inputValue.value.trim(),
images: imageUrlList,
tempImages: uploadedImages.value,
quoteList: quoteList.value,
parameterTags: selectedParameterTags.value
}
if (reportTags.value.length > 0) {
payload.useReport = true
}
emits('send', payload)
clearImages()
clearEditorText()
}
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,
region: params.area
} as any)
projectStore.setId(projectres)
MyEvent.emit('updateProjectList')
agentStore.setInitialProjectData({
text: inputValue.value.trim(),
images: uploadedImages.value.map((item) => item.path),
tempImages: uploadedImages.value,
needSuggestion: false,
quoteList: quoteList.value,
parameterTags: selectedParameterTags.value,
...params
})
router.push({
path: `/home/agent/${projectres}`,
query: {
type: params.type,
area: params.area,
style: params.style,
useReport: String(params.useReport),
temperature: String(params.temperature)
}
})
uploadedImages.value = []
}
const handleInitInput = () => {
inputValue.value = ''
uploadedImages.value = []
quoteList.value = []
toggleReportTag(true)
clearEditorText()
resetOptionTags()
nextTick(() => {
syncAllOptionTags()
})
}
onMounted(() => {
MyEvent.add('quote', handleQuote)
MyEvent.add('projectChange', handleInitInput)
nextTick(() => {
syncAllOptionTags()
autoResizeEditor()
})
})
onUnmounted(() => {
stopReportTypewriter()
MyEvent.remove('quote', handleQuote)
MyEvent.remove('projectChange', handleInitInput)
})
defineExpose({
addReportTag
})
</script>
<style lang="less" scoped>
.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;
&: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;
}
&.agent {
padding: 1.2rem;
box-shadow: none;
border-radius: 1.5rem;
border: 0.1rem solid #0000001a;
.scroll-content {
padding: 0;
flex: 1;
overflow: auto;
}
}
}
</style>