feat: assistant

This commit is contained in:
2026-03-30 17:11:57 +08:00
parent f9067e335d
commit b3881a871e
5 changed files with 188 additions and 117 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

View File

@@ -1,43 +1,103 @@
<template> <template>
<div class="list-wrapper flex flex-col"> <div class="list-wrapper flex flex-col">
<div class="greeting list-item flex">
<div class="thumb">
<img src="@/assets/images/assistant-head.png" class="assistant-head" />
</div>
<div class="list-item-content-text">
{{$t('Assistant.greeting')}}
</div>
</div>
<div <div
class="list-item flex align-center" class="list-item flex"
v-for="item in props.messageList" v-for="(item, index) in props.messageList"
:key="item.id" :key="item.id"
:class="item.role" :class="item.role"
> >
<div class="thumb">
<img
v-show="index > 0"
src="@/assets/images/assistant-head.png"
class="assistant-head"
/>
</div>
<div class="list-item-content-text">{{ item.content }}</div> <div class="list-item-content-text">{{ item.content }}</div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { VueMarkdown } from '@crazydos/vue-markdown'
import type { CustomAttrs } from '@crazydos/vue-markdown'
import rehypeRaw from 'rehype-raw'
const customAttrs: CustomAttrs = {
img: {
style: 'max-width: 100%;'
},
a: (node, combinedAttrs) => {
if (typeof node.properties.href === 'string') {
return { target: '_blank', rel: 'noopener noreferrer' }
} else {
return {}
}
},
heading: {
style: {
fontFamily: 'SemiBold',
lineHeight: 2
// fontSize: '1.4rem'
}
},
p: {
style: {
fontSize: '1rem',
lineHeight: 1.5
}
}
}
const props = defineProps<{ const props = defineProps<{
messageList: Array<any> messageList: Array<any>
}>() }>()
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.list-wrapper { .list-wrapper {
row-gap: 1.6rem; row-gap: 1.2rem;
.list-item { .list-item {
min-height: 3.86rem; column-gap: 1rem;
width: fit-content; white-space: pre-line;
&.user { &.greeting {
width: 26.2rem; color: #d58c4d;
align-self: end; font-family: 'Regular';
}
.thumb {
width: 4rem;
height: 4rem;
.assistant-head {
width: 100%;
height: 100%;
}
}
.list-item-content-text {
min-height: 3.86rem;
width: fit-content;
// &.user {
// width: 26.2rem;
// align-self: end;
// }
font-size: 1.4rem;
font-family: 'Regular';
color: #333;
padding: 1.1rem 1.7rem;
background: linear-gradient(#fffcf4, #fffcf4) padding-box,
linear-gradient(
119.03deg,
rgba(233, 121, 60, 0.3) 1.61%,
rgba(255, 207, 144, 0.3) 101.01%
)
border-box;
border: 1px solid transparent;
border-radius: 0.8rem;
} }
font-size: 1.4rem;
font-family: 'Regular';
color: #333;
padding: 1.1rem 1.7rem;
background: linear-gradient(#fffcf4, #fffcf4) padding-box,
linear-gradient(
119.03deg,
rgba(233, 121, 60, 0.3) 1.61%,
rgba(255, 207, 144, 0.3) 101.01%
)
border-box;
border: 1px solid transparent;
border-radius: 3.18rem;
} }
} }
</style> </style>

View File

@@ -62,7 +62,7 @@
<threeModel :currentData="currentData" /> <threeModel :currentData="currentData" />
</template> </template>
</baseModal> </baseModal>
<!-- <Assistant /> --> <Assistant />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -110,7 +110,7 @@
const vueFlow = ref<any>() const vueFlow = ref<any>()
const nodeTypes = ref([NODE_TYPE.INPUT, NODE_TYPE.SECONDARY, NODE_TYPE.OUTPUT, NODE_TYPE.ALONE]) const nodeTypes = ref([NODE_TYPE.INPUT, NODE_TYPE.SECONDARY, NODE_TYPE.OUTPUT, NODE_TYPE.ALONE])
// 状态管理器 // 状态管理器
const stateManager = new StateManager({ vueFlow,sketchId:props.config.imgId }) const stateManager = new StateManager({ vueFlow, sketchId: props.config.imgId })
provide('stateManager', stateManager) provide('stateManager', stateManager)
// 事件管理器 // 事件管理器
@@ -197,25 +197,30 @@
const downloadData = ref<any>({ const downloadData = ref<any>({
amount: 0, amount: 0,
progress: 0, progress: 0,
status: 'success',//success status: 'success' //success
}) })
const exportFlow = async () => { const exportFlow = async () => {
// console.log(vueFlow.value) // console.log(vueFlow.value)
// console.log(vueFlow.value.toImage) // console.log(vueFlow.value.toImage)
let arr = stateManager.nodes.value.filter((v) => v.data.type === NODE_COMPONENT.RESULT_IMAGE) let arr = stateManager.nodes.value.filter(
(v) => v.data.type === NODE_COMPONENT.RESULT_IMAGE
)
let imgList = [] let imgList = []
arr.forEach((v,i) => { arr.forEach((v, i) => {
v.data.data.imageProcessTasks.forEach((item,index) => { v.data.data.imageProcessTasks.forEach((item, index) => {
let url = item.url let url = item.url
let name = url?.split(".").pop().split("?").shift(); let name = url?.split('.').pop().split('?').shift()
imgList.push({url:url,name:`${v.data.type}${i}-${index == 0?'':index}.${name}`}) imgList.push({
url: url,
name: `${v.data.type}${i}-${index == 0 ? '' : index}.${name}`
})
}) })
}) })
downloadData.value.amount = imgList.length downloadData.value.amount = imgList.length
downloadData.value.status = 'loading' downloadData.value.status = 'loading'
await downImgListToZip(imgList,(progress)=>{ await downImgListToZip(imgList, (progress) => {
downloadData.value.progress = progress downloadData.value.progress = progress
if(progress == downloadData.value.amount){ if (progress == downloadData.value.amount) {
downloadData.value.status = 'success' downloadData.value.status = 'success'
} }
}) })

View File

@@ -82,7 +82,7 @@ export default {
privacyPolicy: 'Privacy Policy', privacyPolicy: 'Privacy Policy',
view: 'View', view: 'View',
remainingNum: 'Remaining number of times to upload profile picture:', remainingNum: 'Remaining number of times to upload profile picture:',
notFound: 'Project not found', notFound: 'Project not found'
}, },
Country: { Country: {
unitedStates: 'United States', unitedStates: 'United States',
@@ -205,52 +205,55 @@ export default {
download: 'Download' download: 'Download'
}, },
DepthCanvas: { DepthCanvas: {
layer: "Layer", layer: 'Layer',
editDetails: "Edit Details", editDetails: 'Edit Details',
export: "Export", export: 'Export',
save: "Save", save: 'Save',
workbench: "Workbench", workbench: 'Workbench',
position: "Position", position: 'Position',
size: "Size", size: 'Size',
appearance: "Appearance", appearance: 'Appearance',
opacity: "Opacity", opacity: 'Opacity',
cornerRadius: "Cor Radius", cornerRadius: 'Cor Radius',
strokeWidth: "Stroke Width", strokeWidth: 'Stroke Width',
color: "Color", color: 'Color',
image: "Image", image: 'Image',
settings: "Settings", settings: 'Settings',
rotation: "Rotation", rotation: 'Rotation',
scale: "Scale", scale: 'Scale',
gapX: "Gap X", gapX: 'Gap X',
gapY: "Gap Y", gapY: 'Gap Y',
offset: "Offset", offset: 'Offset',
emptyLayer: "Empty Layer", emptyLayer: 'Empty Layer',
aiGroupLayer: "AI Group Layer", aiGroupLayer: 'AI Group Layer',
textLayer: "Text Layer", textLayer: 'Text Layer',
rectLayer: "Rect Layer", rectLayer: 'Rect Layer',
lineLayer: "Line Layer", lineLayer: 'Line Layer',
ellipseLayer: "Ellipse Layer", ellipseLayer: 'Ellipse Layer',
triangleLayer: "Triangle Layer", triangleLayer: 'Triangle Layer',
starLayer: "Star Layer", starLayer: 'Star Layer',
arrowLayer: "Arrow Layer", arrowLayer: 'Arrow Layer',
imageLayer: "Image Layer", imageLayer: 'Image Layer',
mergeLayer: "Merge Layer", mergeLayer: 'Merge Layer',
rectangle: "Rectangle", rectangle: 'Rectangle',
line: "Line", line: 'Line',
arrow: "Arrow", arrow: 'Arrow',
ellipse: "Ellipse", ellipse: 'Ellipse',
triangle: "Triangle", triangle: 'Triangle',
star: "Star", star: 'Star',
add: "Add", add: 'Add',
remove: "Remove", remove: 'Remove',
brush: "Brush", brush: 'Brush',
erase: "Erase", erase: 'Erase',
create: "Create", create: 'Create',
reset: "Reset" reset: 'Reset'
}, },
clipDialog: { clipDialog: {
title: 'Upload your profile photo', title: 'Upload your profile photo',
cancel: 'Cancel', cancel: 'Cancel',
confirm: 'Save' confirm: 'Save'
},
Assistant: {
greeting: `Hi, I'm Fiphant, your design assistant 👋 I'm here to help you get started.\n \n I've set up two ways for you to begin — you can use To Real Style to turn your sketch into a render straight away, or start with Surface Edit to swap materials or add a print first.\n \n Once we have a product image to work with, I'll walk you through color, scene, and 3D — and we'll wrap up with your final exported views.\n\nMy suggestion: start with To Real Style and get a feel for the overall direction ✨`
} }
} }

View File

@@ -83,7 +83,7 @@ export default {
privacyPolicy: '隐私政策', privacyPolicy: '隐私政策',
view: '查看', view: '查看',
remainingNum: '剩余上传头像次数:', remainingNum: '剩余上传头像次数:',
notFound: '项目不存在', notFound: '项目不存在'
}, },
Country: { Country: {
unitedStates: '美国', unitedStates: '美国',
@@ -201,52 +201,55 @@ export default {
download: '下载' download: '下载'
}, },
DepthCanvas: { DepthCanvas: {
layer: "图层", layer: '图层',
editDetails: "编辑详情", editDetails: '编辑详情',
export: "导出", export: '导出',
save: "保存", save: '保存',
workbench: "工作台", workbench: '工作台',
position: "位置", position: '位置',
size: "大小", size: '大小',
appearance: "外观", appearance: '外观',
opacity: "透明度", opacity: '透明度',
cornerRadius: "圆角半径", cornerRadius: '圆角半径',
strokeWidth: "边框宽度", strokeWidth: '边框宽度',
color: "颜色", color: '颜色',
image: "图片", image: '图片',
settings: "设置", settings: '设置',
rotation: "旋转角度", rotation: '旋转角度',
scale: "缩放", scale: '缩放',
gapX: "水平间距", gapX: '水平间距',
gapY: "垂直间距", gapY: '垂直间距',
offset: "偏移量", offset: '偏移量',
emptyLayer: "空图层", emptyLayer: '空图层',
aiGroupLayer: "智能选区组", aiGroupLayer: '智能选区组',
textLayer: "文本图层", textLayer: '文本图层',
rectLayer: "矩形图层", rectLayer: '矩形图层',
lineLayer: "直线图层", lineLayer: '直线图层',
ellipseLayer: "椭圆图层", ellipseLayer: '椭圆图层',
triangleLayer: "三角形图层", triangleLayer: '三角形图层',
starLayer: "五角星图层", starLayer: '五角星图层',
arrowLayer: "箭头图层", arrowLayer: '箭头图层',
imageLayer: "图片图层", imageLayer: '图片图层',
mergeLayer: "合并图层", mergeLayer: '合并图层',
rectangle: "矩形", rectangle: '矩形',
line: "直线", line: '直线',
arrow: "箭头", arrow: '箭头',
ellipse: "椭圆", ellipse: '椭圆',
triangle: "三角形", triangle: '三角形',
star: "五角星", star: '五角星',
add: "添加", add: '添加',
remove: "删除", remove: '删除',
brush: "画笔", brush: '画笔',
erase: "擦除", erase: '擦除',
create: "创建", create: '创建',
reset: "重置" reset: '重置'
}, },
clipDialog: { clipDialog: {
title: '上传您的个人资料照片', title: '上传您的个人资料照片',
cancel: '取消', cancel: '取消',
confirm: '保存' confirm: '保存'
},
Assistant: {
greeting: 'Hi我是你的设计助手 Fiphant 👋 我来帮你快速上手这个画布。'
} }
} }