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

396 lines
9.3 KiB
Vue
Raw Normal View History

2026-02-03 13:15:39 +08:00
<template>
2026-02-10 13:05:24 +08:00
<div class="assist-input-wrapper flex flex-col" :class="{ agent: isAgentMode }">
2026-03-05 11:46:41 +08:00
<div class="animate-container flex-1 flex flex-col">
<div class="scroll-content flex-col">
2026-04-30 14:18:19 +08:00
<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"
/>
2026-02-10 13:05:24 +08:00
</div>
2026-03-05 11:46:41 +08:00
2026-04-30 14:18:19 +08:00
<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"
2026-04-30 14:18:19 +08:00
:translate="translate"
@file-change="handleFileChange"
@toggle-report="handleToggleReportTag"
2026-04-30 14:18:19 +08:00
@create="handleCreateProject"
@send="handleSendAgent"
/>
2026-02-10 13:05:24 +08:00
</div>
2026-03-05 11:46:41 +08:00
2026-04-30 14:18:19 +08:00
<ReportShortcutButton
2026-03-09 10:56:38 +08:00
v-if="!isAgentMode"
2026-04-30 14:18:19 +08:00
:is-cn="isCn"
:label="$t('Input.trendingReport')"
@click="handleToggleReportTag"
2026-04-30 14:18:19 +08:00
/>
2026-03-09 14:02:07 +08:00
<Preview v-model="showPreview" :url="previewUrl" />
2026-02-10 13:05:24 +08:00
</div>
2026-02-03 13:15:39 +08:00
</template>
<script setup lang="ts">
2026-04-30 14:18:19 +08:00
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
2026-02-10 13:05:24 +08:00
import { areaList } from '@/utils/area'
import { useI18n } from 'vue-i18n'
2026-02-24 13:53:01 +08:00
import { useRouter } from 'vue-router'
2026-02-25 11:00:31 +08:00
import { useAgentStore, useProjectStore } from '@/stores'
import { createProject } from '@/api/agent'
import MyEvent from '@/utils/myEvent'
2026-03-09 13:36:11 +08:00
import Preview from '@/components/Preview/Preview.vue'
2026-04-30 14:18:19 +08:00
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
2026-04-30 14:18:19 +08:00
} from './input/options'
import { useInputEditor } from './input/useInputEditor'
import { useInputImages } from './input/useInputImages'
import type { OptionItem, OptionTagKind, ParameterTag, SettingOption } from './input/types'
2026-02-10 13:05:24 +08:00
2026-02-24 13:53:01 +08:00
const router = useRouter()
const agentStore = useAgentStore()
2026-02-25 11:00:31 +08:00
const projectStore = useProjectStore()
2026-02-24 13:53:01 +08:00
2026-02-10 13:05:24 +08:00
const props = withDefaults(
defineProps<{
isAgentMode?: boolean
2026-02-25 11:00:31 +08:00
generating?: boolean
2026-02-10 13:05:24 +08:00
}>(),
{
2026-02-25 11:00:31 +08:00
isAgentMode: false,
generating: false
2026-02-10 13:05:24 +08:00
}
)
2026-02-25 11:00:31 +08:00
const emits = defineEmits(['send', 'pause'])
2026-02-10 13:05:24 +08:00
2026-03-24 16:57:40 +08:00
const { t, locale } = useI18n()
2026-04-30 14:18:19 +08:00
const translate = (key: string) => t(key)
2026-03-24 16:57:40 +08:00
const isCn = computed(() => {
return locale.value === 'CHINESE_SIMPLIFIED'
})
2026-04-30 14:18:19 +08:00
const isAgentModeRef = computed(() => props.isAgentMode)
2026-02-10 13:05:24 +08:00
2026-04-30 14:18:19 +08:00
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,
2026-04-30 14:18:19 +08:00
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: () => {
2026-02-26 16:48:08 +08:00
if (props.isAgentMode) {
handleSendAgent()
2026-04-30 14:18:19 +08:00
return
2026-02-23 14:53:29 +08:00
}
2026-04-30 14:18:19 +08:00
handleCreateProject()
2026-02-11 16:32:38 +08:00
}
2026-04-30 14:18:19 +08:00
})
2026-02-11 16:32:38 +08:00
2026-04-30 14:18:19 +08:00
const {
uploadedImages,
quoteList,
showPreview,
previewUrl,
handleFileChange,
removeImage,
previewImage,
handleQuote,
clearImages
} = useInputImages(focusEditor)
2026-02-26 16:48:08 +08:00
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)
}))
})
2026-02-10 13:05:24 +08:00
watch(inputValue, () => {
nextTick(() => {
autoResizeEditor()
})
})
2026-04-30 13:57:41 +08:00
watch(typeValue, () => {
nextTick(() => {
syncOptionTag('type')
})
})
watch(areaValue, () => {
nextTick(() => {
syncOptionTag('area')
})
})
watch(styleValue, () => {
nextTick(() => {
syncOptionTag('style')
})
})
watch(locale, () => {
nextTick(() => {
syncAllOptionTags()
})
})
2026-04-30 14:18:19 +08:00
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[]
2026-04-30 14:18:19 +08:00
useReport?: boolean
} = {
text: inputValue.value.trim(),
images: imageUrlList,
tempImages: uploadedImages.value,
quoteList: quoteList.value,
parameterTags: selectedParameterTags.value
2026-04-30 14:18:19 +08:00
}
if (reportTags.value.length > 0) {
payload.useReport = true
}
emits('send', payload)
clearImages()
clearEditorText()
}
2026-02-25 11:00:31 +08:00
const handleCreateProject = async () => {
if (!inputValue.value.trim()) {
return
}
2026-02-24 13:53:01 +08:00
const params = {
2026-04-10 15:54:13 +08:00
type: typeValue.value || '',
area: areaValue.value || '',
style: styleValue.value || '',
useReport: reportTags.value.length > 0,
2026-02-25 11:00:31 +08:00
temperature: 0.7
2026-02-24 13:53:01 +08:00
}
2026-04-30 14:18:19 +08:00
const projectres = await createProject({
...params,
region: params.area
} as any)
2026-02-25 11:00:31 +08:00
projectStore.setId(projectres)
MyEvent.emit('updateProjectList')
2026-02-24 13:53:01 +08:00
agentStore.setInitialProjectData({
text: inputValue.value.trim(),
2026-03-02 15:08:03 +08:00
images: uploadedImages.value.map((item) => item.path),
tempImages: uploadedImages.value,
2026-04-30 14:18:19 +08:00
needSuggestion: false,
quoteList: quoteList.value,
parameterTags: selectedParameterTags.value,
2026-02-25 11:00:31 +08:00
...params
2026-02-24 13:53:01 +08:00
})
2026-04-30 14:18:19 +08:00
router.push({
path: `/home/agent/${projectres}`,
query: {
type: params.type,
area: params.area,
style: params.style,
useReport: String(params.useReport),
temperature: String(params.temperature)
}
})
2026-03-02 15:08:03 +08:00
uploadedImages.value = []
2026-02-24 13:53:01 +08:00
}
2026-02-26 16:48:08 +08:00
const handleInitInput = () => {
inputValue.value = ''
uploadedImages.value = []
quoteList.value = []
2026-04-30 14:18:19 +08:00
toggleReportTag(true)
clearEditorText()
resetOptionTags()
2026-04-30 13:57:41 +08:00
nextTick(() => {
syncAllOptionTags()
})
}
onMounted(() => {
MyEvent.add('quote', handleQuote)
MyEvent.add('projectChange', handleInitInput)
nextTick(() => {
2026-04-30 13:57:41 +08:00
syncAllOptionTags()
autoResizeEditor()
})
})
2026-04-30 14:18:19 +08:00
2026-03-24 16:57:40 +08:00
onUnmounted(() => {
stopReportTypewriter()
2026-03-24 16:57:40 +08:00
MyEvent.remove('quote', handleQuote)
MyEvent.remove('projectChange', handleInitInput)
2026-03-24 16:57:40 +08:00
})
2026-04-30 14:18:19 +08:00
2026-02-26 16:48:08 +08:00
defineExpose({
addReportTag
})
2026-02-03 13:15:39 +08:00
</script>
<style lang="less" scoped>
2026-02-10 13:05:24 +08:00
.assist-input-wrapper {
min-height: 23.5rem;
max-height: 43.5rem;
width: 106.3rem;
margin: 0 auto;
padding: 0;
position: relative;
2026-04-30 14:18:19 +08:00
2026-03-06 13:26:14 +08:00
.animate-container {
overflow-y: hidden;
}
2026-02-10 13:05:24 +08:00
2026-03-05 11:46:41 +08:00
&:not(.agent) .animate-container {
box-shadow: 0px 0.5rem 1.4rem 0px #0000001a;
transition: all 0.3s ease;
2026-03-05 11:46:41 +08:00
border-radius: 2.8rem;
background-color: #fff;
border: 0.1rem solid #00000005;
2026-04-30 14:18:19 +08:00
&:hover {
2026-03-05 11:46:41 +08:00
box-shadow: 0px 0.5rem 3.36rem 2.2rem #f1ede999;
transform: translateY(-1rem);
2026-02-10 13:05:24 +08:00
}
}
2026-04-30 14:18:19 +08:00
2026-02-10 13:05:24 +08:00
.scroll-content {
2026-02-24 11:17:01 +08:00
display: flex;
flex-direction: column;
2026-02-23 14:53:29 +08:00
flex: 1;
overflow-y: auto;
2026-02-10 13:05:24 +08:00
padding: 3.4rem 1.7rem 1.7rem;
}
2026-04-30 14:18:19 +08:00
&.agent {
padding: 1.2rem;
box-shadow: none;
border-radius: 1.5rem;
border: 0.1rem solid #0000001a;
2026-02-10 13:05:24 +08:00
2026-04-30 14:18:19 +08:00
.scroll-content {
padding: 0;
2026-02-10 13:05:24 +08:00
flex: 1;
2026-04-30 14:18:19 +08:00
overflow: auto;
2026-03-17 14:16:14 +08:00
}
}
}
2026-02-03 15:46:05 +08:00
</style>