Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/FiDA_Front

This commit is contained in:
2026-03-27 16:57:46 +08:00
49 changed files with 1583 additions and 389 deletions

View File

@@ -56,7 +56,7 @@
const isGenerating = ref(false)
const isPaused = ref(false) // 标记是否为主动暂停
const params = reactive<AgentParamsType>({
projectID: projectStore.state.id,
projectID: null,
message: '',
token: userStore.state.token,
versionID: '',
@@ -170,13 +170,16 @@
const abortController = createAbortController()
// console.log('token---', params.token, '参数---', params)
params.projectID = projectStore.state.id
try {
const urlParams = new URLSearchParams<AgentParamsType>({
...params,
configParams: JSON.stringify(params.configParams)
})
const BASEURL = import.meta.env.VITE_APP_URL
// console.log('params', params)
// debugger
const response = await fetch(`${BASEURL}${chatUrl}?${urlParams.toString()}`, {
method: 'GET',
signal: abortController.signal
@@ -496,6 +499,7 @@
let combinedThinkingText = item.reasoning || ''
let combinedImageUrl = item.image_url || null
let reportName = item.reportName || null
let webAddress = item.webAddress || null
// 继续往后找连续的 assistant 消息
let j = i + 1
while (j < dialogue.length && dialogue[j].role === 'assistant') {
@@ -508,6 +512,12 @@
if (dialogue[j].reportName) {
reportName = dialogue[j].reportName
}
if (dialogue[j].webAddress) {
combinedContent += `<slot slot-name="url"></slot>`
webAddress = dialogue[j].webAddress
// console.log('webAddress22222222222222', dialogue[j].webAddress)
// debugger
}
j++
}
@@ -523,6 +533,7 @@
thinkingText: combinedThinkingText,
text: combinedContent,
image_url: combinedImageUrl,
webAddress: !!webAddress ? JSON.parse(webAddress) : null,
isUser: false,
id: result.length + 1,
sessionId: sessionId

View File

@@ -76,7 +76,7 @@
<span>{{ content.webAddress?.length }} web pages have been retrieved.</span>
</div>
</div>
<Pause v-show="showStop && isLast" />
<Pause v-show="showStop" :key="props.content.createTime" />
<div
v-show="!content.streaming"
class="operate flex"
@@ -136,6 +136,14 @@
isLast: Boolean
}>()
// watch(
// () => props.content,
// (newVal) => {
// console.log('props', newVal)
// },
// { deep: true,immediate: true }
// )
const emit = defineEmits(['regenerate'])
const userAvatar = computed(() => {
@@ -321,12 +329,14 @@
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
}
.message-context {
line-height: 2rem;
font-size: 1.4rem;
width: 82%;
word-break: break-word;
}
&.is-user .message-context {
width: fit-content;

View File

@@ -13,11 +13,12 @@
width: 100%;
height: 3.6rem;
line-height: 3.6rem;
column-gap: 0.6rem;
padding: 0 1.2rem;
column-gap: 0.6rem;
padding: 0 1.2rem;
background-color: #fffcf4;
border-radius: 0.4rem;
margin-top: 1rem;
border-radius: 0.4rem;
margin-top: 1rem;
color: #ff7a51;
&::before {
content: '';
@@ -30,22 +31,18 @@
rgba(233, 121, 60, 0.3) 1.61%,
rgba(255, 207, 144, 0.3) 101.01%
);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
}
.c-svg{
width: initial;
.svg-icon{
width: 1.2rem;
height: 1.2rem;
}
}
.c-svg {
width: initial;
.svg-icon {
width: 1.2rem;
height: 1.2rem;
}
}
}
</style>

View File

@@ -1,8 +1,8 @@
<template>
<div class="report-card" :class="{ 'is-url': isUrl, 'is-sketch': isSketch }">
<div class="report-card-header">
<span v-if="!isUrl && !isSketch">{{ title || '' }}</span>
<div v-else class="web-sources flex align-center">
<!-- <span v-if="!isUrl && !isSketch">{{ title || '' }}</span> -->
<div class="web-sources flex align-center">
<span>{{ title || '' }}</span>
<img src="@/assets/images/link.png" class="link-icon" />
</div>
@@ -35,16 +35,19 @@
.report-card {
cursor: pointer;
width: 100%;
margin: 2.4rem 0;
height: 11.2rem;
margin: 1.2rem 0 0;
min-height: 11.2rem;
background: url('@/assets/images/report-card.png') no-repeat;
background-size: 100% 100%;
padding: 2.9rem;
overflow: hidden;
position: relative;
margin-bottom: 0;
&.is-url {
background: url('@/assets/images/link-card.png') no-repeat;
background-size: 100% 100%;
margin: 2.4rem 0;
}
&.is-sketch {
background: url('@/assets/images/sketch-card.png') no-repeat;
@@ -61,7 +64,7 @@
&-header {
font-family: 'Medium';
font-size: 1.6rem;
font-size: 1.2rem;
margin-bottom: 1.3rem;
overflow: hidden;
text-overflow: ellipsis;
@@ -80,7 +83,7 @@
&-content {
font-family: 'Regular';
font-weight: 300;
font-size: 1.6rem;
font-size: 1.2rem;
color: #7c7c7c;
}
}

View File

@@ -36,10 +36,13 @@
import { useProjectStore } from '@/stores'
import { getProjectInfo } from '@/api/agent'
import { clearNodeChat, getNodeAncestors } from '@/api/versitonTree'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import MyEvent from '@/utils/myEvent'
import { useI18n } from 'vue-i18n'
const route = useRoute()
const router = useRouter()
const { t } = useI18n()
const projectStore = useProjectStore()
const previewRef = ref(null)
@@ -90,6 +93,11 @@
const handleGetProjectInfoAndHistory = () => {
handleOpenSketch()
getProjectInfo({ id: route.params.id }).then((res) => {
if(!res) {
router.push({ name: 'mainInput' })
ElMessage.warning(t('Home.notFound'))
return
}
if (res) agentRef.value.setChatInfo(res)
let data = res?.project || res
if (data?.latestNodeId) data.nodeId = data.latestNodeId
@@ -125,6 +133,7 @@
projectStore.clearProject()
if (newVal) {
handleGetProjectInfoAndHistory()
MyEvent.emit('projectChange')
}
}
)

View File

@@ -14,9 +14,9 @@
<img
:src="image.url || image"
class="preview-image"
@click="previewImage(image.url)"
@click="previewImage(image.url || image)"
/>
<div class="image-remove-btn" @click="removeImage(index)">
<div class="image-remove-btn" @click="removeImage(index, image)">
<SvgIcon name="delete" size="16" />
</div>
</div>
@@ -305,8 +305,16 @@
}
// 移除图片
const removeImage = (index: number) => {
uploadedImages.value.splice(index, 1)
const removeImage = (index: number, item: any) => {
if (quoteList.value.includes(item)) {
const quoteIndex = quoteList.value.indexOf(item)
if (quoteIndex > -1) {
quoteList.value.splice(quoteIndex, 1)
}
return
} else {
uploadedImages.value.splice(index, 1)
}
}
const styleKeys: string[] = [
@@ -472,8 +480,7 @@
customPlaceholder.value = placeholderSpan
// 打字机效果显示placeholder文本
const placeholderText =
'Generate a furniture trending report for 2026, including popular styles and design directions.'
const placeholderText = t('Input.reportPlaceholder')
typeWriterEffect(placeholderSpan, placeholderText)
const removePlaceholderOnInput = () => {
@@ -487,12 +494,12 @@
editorRef.value?.addEventListener('input', removePlaceholderOnInput)
}
const toogltReportTag = () => {
const toogltReportTag = (clear = false) => {
stopTypewriter() // 移除标签时停止打字机效果
// 清理掉已被删除的标签引用(从 DOM 中移除的元素)
reportTags.value = reportTags.value.filter((tag) => tag.parentNode !== null)
if (reportTags.value.length > 0) {
if (reportTags.value.length > 0 ) {
// 移除所有标签及其关联的零宽空格
reportTags.value.forEach((tag) => {
if (
@@ -715,14 +722,6 @@
})
})
// 初始化编辑器高度
onMounted(() => {
MyEvent.add('quote', handleQuote)
nextTick(() => {
autoResizeEditor()
})
})
const typeValue = ref<string>('')
const areaValue = ref<string>('')
const styleValue = ref<string>('')
@@ -815,10 +814,31 @@
}
const handleQuote = (url: string) => {
quoteList.value.push(url)
const hasQuoted = quoteList.value.includes(url)
if (hasQuoted) return
quoteList.value[0] = url
}
const handleInitInput = () => {
inputValue.value = ''
uploadedImages.value = []
quoteList.value = []
toogltReportTag(true)
if (editorRef.value) {
editorRef.value.innerHTML = ''
}
}
onMounted(() => {
MyEvent.add('quote', handleQuote)
MyEvent.add('projectChange', handleInitInput)
nextTick(() => {
autoResizeEditor()
})
})
onUnmounted(() => {
MyEvent.remove('quote', handleQuote)
MyEvent.remove('projectChange', handleInitInput)
})
// 暴露方法给父组件
defineExpose({
@@ -1057,10 +1077,11 @@
width: 13rem;
color: #fff;
border-radius: 4.2rem;
font-family: 'MSemiBold';
font-family: 'SemiBold';
font-weight: 600;
font-size: 1.28rem;
font-size: 1.3rem;
cursor: pointer;
column-gap: 0.3rem;
.shining-icon {
width: 1.4rem;
height: 1.4rem;

View File

@@ -16,13 +16,13 @@
<span class="title" v-show="!isCollapse">{{ $t('Home.home') }}</span>
</div> -->
<div class="menu-item" @click="onHistory" :class="{ active: showHistory }">
<span class="icon"><svg-icon name="history" size="24" /></span>
<span class="icon"><svg-icon name="history" size="16" /></span>
<span class="title" v-show="!isCollapse">{{ $t('Home.history') }}</span>
<span class="icon jiantou" v-show="!isCollapse"
><svg-icon name="arrow-right" size="14" />
</span>
</div>
<div class="history-list" v-show="!isCollapse && showHistory">
<div class="history-list mini-scrollbar" v-show="!isCollapse && showHistory">
<div v-for="item in list" :key="item.name" class="history-item">
<div v-if="item.title" class="title">{{ item.name }}</div>
<div
@@ -54,7 +54,7 @@
v-model:visible="item.visible"
>
<template #reference>
<span @click.stop="item.visible = !item.visible" class="icon">
<span @click.stop="openPopover(item)" class="icon">
<svg-icon name="more" size="16" />
</span>
</template>
@@ -207,6 +207,12 @@
}
})
}
const openPopover = (item: any) => {
list.value.forEach((item: any) => {
item.visible = false
})
item.visible = !item.visible
}
onMounted(() => {
MyEvent.add('newTitle', replaceTitle)
})
@@ -307,7 +313,8 @@
> .history-list {
flex: 1;
overflow-y: auto;
width: 23.2rem;
width: 26.4rem;
padding: 0 1.5rem;
margin: 1rem auto 0;
> .history-item {
width: 100%;

View File

@@ -31,6 +31,7 @@
<div class="label">{{ $t('Home.logoutDevice') }}</div>
<button class="logout-btn" @click="logout">{{ $t('Home.logout') }}</button>
</div>
<clip-dialog ref="clipDialogRef" />
</template>
<script setup lang="ts">
@@ -41,6 +42,8 @@
import { useI18n } from 'vue-i18n'
import { uploadImage } from '@/api/upload'
import { UpdateUserAvatar, getAvatarLimit } from '@/api/user'
import clipDialog from '@/components/clipDialog.vue'
import { fileToBase64, base64Tofile } from '../../../components/Canvas/tools/tools'
const router = useRouter()
const { locale } = useI18n()
const userInfoStore = useUserInfoStore()
@@ -50,6 +53,7 @@
{ label: '中文', value: 'CHINESE_SIMPLIFIED' }
])
const remainingNum = ref(0)
const clipDialogRef = ref<typeof clipDialog>()
const changeLang = (value: string) => {
locale.value = value
localStorage.setItem('language', value)
@@ -72,13 +76,17 @@
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/png, image/jpeg, image/jpg'
input.addEventListener('change', (e) => {
input.addEventListener('change', async (e) => {
const file = e.target.files[0]
const formData = new FormData()
formData.append('avatar', file)
UpdateUserAvatar(formData).then((res) => {
userInfoStore.updateUserInfo({avatar: res})
getAvatarLimitNum()
let base64Img = await fileToBase64(file)
clipDialogRef.value?.open(base64Img).then((base64: string)=>{
const fileData = base64Tofile(base64,file.name)
const formData = new FormData()
formData.append('avatar', fileData)
UpdateUserAvatar(formData).then((res) => {
userInfoStore.updateUserInfo({avatar: res})
getAvatarLimitNum()
})
})
})
input.click()

View File

@@ -11,12 +11,12 @@
import { useRoute } from 'vue-router'
const route = useRoute()
const url =
'https://www.minio-api.aida.com.hk/fida-test/furniture/sketches/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20260320%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260320T020948Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=7dc192bac887bce7b02c99d7037c08d9d684310f00add9b0e63b74b36ee63d37'
'https://minio-api.aida.com.hk/fida-public-bucket/furniture/sketches/e3082a38-55d2-4313-ad53-55aad715cf67.png'
const openCanvas = () => {
myEvent.emit('openFlowCanvas', { url })
}
const openDepthCanvas = () => {
myEvent.emit('openDepthCanvas', { url, canvasId: '69c34539ce996b52f07e625f' })
myEvent.emit('openDepthCanvas', { url, canvasId: '' })
}
onMounted(() => {
if (route.query.depth) {

View File

@@ -62,6 +62,7 @@
}
})
} else {
data.vibe = JSON.parse(data.vibe)
onSubmit(data)
}
}

View File

@@ -3,9 +3,9 @@
<p class="title" v-html="$t('Nuic.nuic2Title')"></p>
<div class="list">
<div v-for="v in list" :key="v.id" @click="v.active = !v.active">
<img :src="v.url" draggable="false" />
<img :src="v.imageUrl" draggable="false" />
<div class="active" v-show="v.active">
<span>{{ v.title }}</span>
<span>{{ v.styleName }}</span>
</div>
</div>
</div>
@@ -22,28 +22,35 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import { GetUserStyleImages } from '@/api/user'
const router = useRouter()
const emit = defineEmits(['next'])
const list = ref([
{ id: 1, url: '/image/nuic/style-1.png', title: '凳子', active: false },
{ id: 2, url: '/image/nuic/style-2.png', title: '沙发', active: false },
{ id: 3, url: '/image/nuic/style-3.png', title: '凳子', active: false },
{ id: 4, url: '/image/nuic/style-4.png', title: '桌子', active: false },
{ id: 5, url: '/image/nuic/style-5.png', title: '桌子', active: false },
{ id: 6, url: '/image/nuic/style-6.png', title: '桌子', active: false },
{ id: 7, url: '/image/nuic/style-7.png', title: '沙发', active: false },
{ id: 8, url: '/image/nuic/style-8.png', title: '桌子', active: false }
])
const list = ref([])
const pageSize = ref(8)
const pageNum = ref(1)
const totalPages = ref(1)
const onNext = () => {
const data = {
vibe: list.value
.filter((v) => v.active)
.map((v) => v.id)
.join(',')
vibe: JSON.stringify(list.value.filter((v) => v.active).map((v) => v.id))
}
emit('next', data)
}
const onLoadMore = () => {}
const onLoadMore = () => {
GetUserStyleImages({
pageNum: pageNum.value,
pageSize: pageSize.value
}).then((res) => {
if (!res) return
list.value = res.images.map((v) => ({
...v,
active: false
}))
totalPages.value = res.totalPages
pageNum.value++
if (pageNum.value > totalPages.value) pageNum.value = 1
})
}
onLoadMore()
</script>
<style lang="less" scoped>