diff --git a/.env.development b/.env.development index f844c2d..8eca712 100644 --- a/.env.development +++ b/.env.development @@ -1,5 +1,5 @@ # VITE_APP_URL = http://192.168.31.82:8771 VITE_APP_URL = http://18.167.251.121:10015 # VITE_APP_URL = http://192.168.31.118:8080 -# VITE_APP_URL = http://192.168.31.82:8755 +VITE_APP_URL = http://192.168.31.82:8755 VITE_GOOGLE_CLIENT_ID = 216037134725-7q8vqp0ohtmohlosltkfg7bd2v29rm5a.apps.googleusercontent.com diff --git a/package-lock.json b/package-lock.json index 8e9f9b4..2f7bd38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "pinia": "^2.0.32", "pinia-persistedstate-plugin": "^0.1.0", "pinia-plugin-persistedstate": "^3.1.0", + "three": "^0.148.0", "vue": "^3.2.47", "vue-draggable-plus": "^0.6.1", "vue-i18n": "^11.2.8", @@ -8556,6 +8557,12 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/three": { + "version": "0.148.0", + "resolved": "https://registry.npmmirror.com/three/-/three-0.148.0.tgz", + "integrity": "sha512-8uzVV+qhTPi0bOFs/3te3RW6hb3urL8jYEl6irjCWo/l6sr8MPNMcClFev/MMYeIxr0gmDcoXTy/8LXh/LXkfw==", + "license": "MIT" + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz", @@ -16065,6 +16072,11 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "three": { + "version": "0.148.0", + "resolved": "https://registry.npmmirror.com/three/-/three-0.148.0.tgz", + "integrity": "sha512-8uzVV+qhTPi0bOFs/3te3RW6hb3urL8jYEl6irjCWo/l6sr8MPNMcClFev/MMYeIxr0gmDcoXTy/8LXh/LXkfw==" + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz", diff --git a/package.json b/package.json index a89a047..c003462 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "pinia": "^2.0.32", "pinia-persistedstate-plugin": "^0.1.0", "pinia-plugin-persistedstate": "^3.1.0", + "three": "^0.148.0", "vue": "^3.2.47", "vue-draggable-plus": "^0.6.1", "vue-i18n": "^11.2.8", diff --git a/src/api/agent.ts b/src/api/agent.ts index 5646e63..257e01e 100644 --- a/src/api/agent.ts +++ b/src/api/agent.ts @@ -8,15 +8,10 @@ export interface AgentParamsType { imageUrlList?: string[] // 图片URL列表 configParams: Record // 其他配置参数 token: string + needSuggestion?: boolean + useReport: bolean } -export const fetchAgentReply = (data: AgentParamsType): Promise => { - return request({ - url: '/api/ai-design/chat', - method: 'get', - data, - meta: { responseAll: true } - }) -} +export const chatUrl = '/api/ai-design/chat' export interface CreateProjectParamsType { type: string @@ -33,11 +28,11 @@ export const createProject = (data: CreateProjectParamsType): Promise => { * @param data 获取项目信息参数 * @param data.id 项目id * @returns 获取项目信息 -*/ + */ export const getProjectInfo = (data) => { return request({ url: `/api/project/${data.id}`, - method: 'get', + method: 'get' }) } @@ -47,7 +42,7 @@ export const getProjectInfo = (data) => { * @param params.page 页码 * @param params.size 每页数量 * @returns 获取项目版本列表 -*/ + */ export const getProjectList = (params) => { return request({ url: `/api/project/list`, @@ -66,7 +61,7 @@ export const getProjectList = (params) => { * @param data.style 项目风格 * @param data.temperature 项目温度 * @returns 修改项目信息 -*/ + */ export const updateProject = (id: string, data: Object) => { return request({ url: `/api/project/${id}`, @@ -78,10 +73,10 @@ export const updateProject = (id: string, data: Object) => { * 删除项目 * @param id 项目id * @returns 删除项目 -*/ + */ export const deleteProject = (id: string) => { return request({ url: `/api/project/${id}`, - method: 'delete', + method: 'delete' }) } diff --git a/src/api/flow-canvas.ts b/src/api/flow-canvas.ts new file mode 100644 index 0000000..062708d --- /dev/null +++ b/src/api/flow-canvas.ts @@ -0,0 +1,54 @@ +import request from '@/utils/request' + +/** + * 获取sketch的画布详情 + * @param data 获取sketch的画布详情的参数 + * @param data.id sketch id + * @returns 获取sketch的画布详情 + */ +export interface getSketchFlowCanvasData { + id: string +} +export const getSketchFlowCanvas = (data:getSketchFlowCanvasData) => { + return request({ + url: `/api/canvas/detail/${data.id}`, + method: 'get', + }) +} + +/** + * 保存或者更新sketch的画布详情 + * @param data 获取sketch的画布详情的参数 + * @param data.id sketch id + * @param data.canvasData sketch id + * @returns 获取sketch的画布详情 + */ +export interface saveSketchFlowCanvasData { + id?: string + canvasData: string +} +export const putSketchFlowCanvas = (data:saveSketchFlowCanvasData) => { + return request({ + url: `/api/canvas/detail/${data.id}`, + method: 'put', + data: data.canvasData + }) +} + +/** + * 删除sketch和画布详情 + * @param data 删除sketch的画布详情的参数 + * @param data.id sketch id + * @param data.versionNodeId 节点id + * @returns 获取sketch的画布详情 + */ +export interface deleteSketchFlowCanvasData { + id?: string + versionNodeId?: string +} +export const deleteSketchFlowCanvas = (data:deleteSketchFlowCanvasData) => { + return request({ + url: `/api/canvas/detail/${data.versionNodeId}/${data.id}`, + method: 'delete', + }) +} diff --git a/src/assets/images/delete.png b/src/assets/images/delete.png new file mode 100644 index 0000000..3f1a999 Binary files /dev/null and b/src/assets/images/delete.png differ diff --git a/src/assets/images/restore-sketch.png b/src/assets/images/restore-sketch.png new file mode 100644 index 0000000..51a7162 Binary files /dev/null and b/src/assets/images/restore-sketch.png differ diff --git a/src/components/Canvas/FlowCanvas/components/tools/threeModel/detail.vue b/src/components/Canvas/FlowCanvas/components/tools/threeModel/detail.vue new file mode 100644 index 0000000..d828c77 --- /dev/null +++ b/src/components/Canvas/FlowCanvas/components/tools/threeModel/detail.vue @@ -0,0 +1,123 @@ + + + \ No newline at end of file diff --git a/src/components/Canvas/FlowCanvas/components/tools/threeModel/index.vue b/src/components/Canvas/FlowCanvas/components/tools/threeModel/index.vue new file mode 100644 index 0000000..7f12bfa --- /dev/null +++ b/src/components/Canvas/FlowCanvas/components/tools/threeModel/index.vue @@ -0,0 +1,49 @@ + + + \ No newline at end of file diff --git a/src/components/Canvas/FlowCanvas/components/tools/threeModel/model.vue b/src/components/Canvas/FlowCanvas/components/tools/threeModel/model.vue new file mode 100644 index 0000000..bdab5fa --- /dev/null +++ b/src/components/Canvas/FlowCanvas/components/tools/threeModel/model.vue @@ -0,0 +1,188 @@ + + + \ No newline at end of file diff --git a/src/components/Canvas/FlowCanvas/components/tools/threeModel/threeTool.ts b/src/components/Canvas/FlowCanvas/components/tools/threeModel/threeTool.ts new file mode 100644 index 0000000..2236ced --- /dev/null +++ b/src/components/Canvas/FlowCanvas/components/tools/threeModel/threeTool.ts @@ -0,0 +1,125 @@ +import * as THREE from 'three'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; +import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' + +export const initThree = (threeDom)=>{ + const scene = new THREE.Scene(); + const group = new THREE.Group() + scene.add(group) + + //创建相机对象 + // this.camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000); + const camera = new THREE.PerspectiveCamera(45, threeDom.offsetWidth / threeDom.offsetHeight, 0.1, 10000); + camera.position.set(0, 90, 6); //设置相机位置 + camera.lookAt(scene.position); //设置相机方向(指向的场景对象) + /** + * 创建渲染器对象 + */ + let width = threeDom.offsetWidth; //窗口宽度 + let height = threeDom.offsetHeight; //窗口高度 + const renderer = new THREE.WebGLRenderer({ + antialias: true, + logarithmicDepthBuffer: true,//深度缓存 防止模型闪烁重影 + }); + + renderer.toneMapping = THREE.ACESFilmicToneMapping;//设置色调 + renderer.toneMappingExposure = 1.3; + + renderer.shadowMap.enabled = true; + renderer.setPixelRatio(window.devicePixelRatio); + renderer.setSize(width, height); //设置渲染区域尺寸 + renderer.setClearColor(0xffffff, 1); //设置背景颜色 + threeDom.innerHTML = ''; + threeDom.appendChild(renderer.domElement); + + // 设置渲染器大小 + //环境光 + let ambient = new THREE.AmbientLight(0xffffff,.8); + scene.add(ambient); + let controls = new OrbitControls(camera,renderer.domElement)//监听鼠标、键盘事件; + // controls.minDistance = 500; // 设置相机与焦点的最小距离 + // controls.maxDistance = 4000; // 设置相机与焦点的最大距离 + controls.mouseButtons = { + // LEFT:THREE.MOUSE.PAN, // 左键 拖动(默认旋转:ROTATE) + LEFT:THREE.MOUSE.ROTATE, // 左键 拖动(默认旋转:ROTATE) + MIDDLE:THREE.MOUSE.DOLLY, // 滑轮 缩放 + RIGHT:THREE.MOUSE.PAN // 右键 旋转(默认拖动:PAN) + // RIGHT:THREE.MOUSE.ROTAafTE // 右键 旋转(默认拖动:PAN) + } + /** + * 光源设置 + */ + //点光源 + /** + * AmbientLight 环境光 + PointLight 点光源 + DirectionalLight 平行光,比如太阳光 + SpotLight 聚光源 + */ + const pointLight = new THREE.DirectionalLight(0xffffff,.5); + pointLight.intensity = 1.2 + pointLight.castShadow = true//开启阴影 + pointLight.shadow.mapSize = new THREE.Vector2(width, height) + scene.add(pointLight); //点光源添加到场景中 + // pointLight.position.set(400, 200, 300); //点光源位置 + pointLight.position.y = 400; + pointLight.position.z = 200; + pointLight.position.x = 200; + let floorGeometry = new THREE.PlaneGeometry(5000, 3000)//地板大小 + let floorMaterial = new THREE.MeshPhongMaterial({ color: "#7e7ab0", }) + let floorMesh = new THREE.Mesh(floorGeometry, floorMaterial); + floorMesh.rotation.x = -0.5 * Math.PI; + floorMesh.receiveShadow = true; + floorMesh.position.y = -0.001; + // scene.add(floorMesh); + const textureLoader = new THREE.TextureLoader(); + // const texture = textureLoader.load('/3dModel/sketch-thick.jpg'); + scene.background = new THREE.Color("#fff"); + return {scene,group,camera,renderer,ambient,controls,pointLight} +} +export const clearModel = (group,scene)=>{ + const oldGroup:any = group.value; + group.value = new THREE.Group(); + scene.value.add(group.value); + scene.value.remove(oldGroup); +} + + +export const addModel = async ( + url:any, + controls:OrbitControls, + camera:THREE.PerspectiveCamera, + pointLight:THREE.DirectionalLight, + group:THREE.Group, + load:any)=>{ + await new Promise((resolve, reject) => { + var fbxLoader = new GLTFLoader(); + let drac = new DRACOLoader() + drac.setDecoderPath('/draco/') + fbxLoader.setDRACOLoader(drac) + // fbxLoader.load('/3dModel/222/1111.glb', + fbxLoader.load(url, + + (obj:any) => { + let scene = obj.scene; + var box = new THREE.Box3().setFromObject(scene); + var center = box.getCenter(new THREE.Vector3()); + controls.value.target.copy(center); + // controls.autoRotate = true + camera.value.position.y = center.y; + camera.value.position.z = 1000; + pointLight.value.position.y = 250; + pointLight.value.position.z = 1250; + group.value.add(scene); + resolve('') + },(xhr:any) => { // 加载进度回调 + const percent = xhr.total == 0?100:(xhr.loaded / xhr.total * 100).toFixed(2); + load.value.progress = percent + // updateProgressBar(Number(percent)); + },(error:any) => { // 加载失败回调 + console.error('模型加载失败:', error); + reject('') + }) + }) +} \ No newline at end of file diff --git a/src/components/Canvas/FlowCanvas/flow-canvas.vue b/src/components/Canvas/FlowCanvas/flow-canvas.vue index 76c3450..56a7893 100644 --- a/src/components/Canvas/FlowCanvas/flow-canvas.vue +++ b/src/components/Canvas/FlowCanvas/flow-canvas.vue @@ -53,6 +53,9 @@ @home="() => fitView({ maxZoom: 1 })" /> + + + + + diff --git a/src/components/Canvas/components/fullscreen-dialog.vue b/src/components/Canvas/components/fullscreen-dialog.vue index cca3d74..ae94f3b 100644 --- a/src/components/Canvas/components/fullscreen-dialog.vue +++ b/src/components/Canvas/components/fullscreen-dialog.vue @@ -69,6 +69,7 @@ position: absolute; top: 3rem; right: 3rem; + --my-info-bgColor: #fff; } > .close-btn { cursor: pointer; diff --git a/src/components/MyInfo.vue b/src/components/MyInfo.vue index d763bdb..0d7a345 100644 --- a/src/components/MyInfo.vue +++ b/src/components/MyInfo.vue @@ -72,9 +72,9 @@ min-width: 18rem; height: 4.3rem; margin-right: 1rem; - background-color: rgba(255, 252, 244, 1); border: 1px solid #ffcf90; border-radius: 0.8rem; + background-color: var(--my-info-bgColor, rgba(255, 252, 244, 1)); > .credits { flex: 1; font-size: 1.3rem; diff --git a/src/stores/agent.ts b/src/stores/agent.ts index 658e586..34c458e 100644 --- a/src/stores/agent.ts +++ b/src/stores/agent.ts @@ -7,23 +7,20 @@ import MyEvent from '@/utils/myEvent' // Agent 项目初始数据 store +type InitialProjectData = { + text: string + images: Array<{ url: string; name: string }> + type: string + area: string + style: string + useReport:boolean + needSuggestion:boolean +} export const useAgentStore = defineStore('agent', () => { - const initialProjectData = ref<{ - text: string - images: Array<{ url: string; name: string }> - type: string - area: string - style: string - } | null>(null) + const initialProjectData = ref(null) // 保存项目初始数据 - const setInitialProjectData = (data: { - text: string - images: Array<{ url: string; name: string }> - type: string - area: string - style: string - }) => { + const setInitialProjectData = (data: InitialProjectData) => { initialProjectData.value = data } diff --git a/src/views/home/agent/components/Agent.vue b/src/views/home/agent/components/Agent.vue index cfacb7b..6612829 100644 --- a/src/views/home/agent/components/Agent.vue +++ b/src/views/home/agent/components/Agent.vue @@ -24,10 +24,10 @@ import { ref, reactive, computed, onUnmounted, onMounted, nextTick, watch } from 'vue' import List from './List.vue' import Input from '../../components/Input.vue' - import { fetchAgentReply } from '@/api/agent' + import { chatUrl } from '@/api/agent' import type { AgentParamsType } from '@/api/agent' - import { useUserInfoStore, useProjectStore } from '@/stores' - import { useAgentStore } from '@/stores/agent' + import { useUserInfoStore, useProjectStore, useAgentStore } from '@/stores' + import MyEvent from '@/utils/myEvent' const userStore = useUserInfoStore() const agentStore = useAgentStore() @@ -54,6 +54,8 @@ message: '', token: userStore.state.token, versionID: '', + needSuggestion: false, + useReport: false, configParams: { type: '', region: '', @@ -99,6 +101,8 @@ style: initialData.style, temperature: 0.7 } + params.needSuggestion = initialData.needSuggestion || false + params.useReport = initialData.useReport handleSendMessage({ text: initialData.text, images: initialData.images, @@ -158,7 +162,7 @@ configParams: JSON.stringify(params.configParams) }) const BASEURL = import.meta.env.VITE_APP_URL - const response = await fetch(`${BASEURL}/api/ai-design/chat?${urlParams.toString()}`, { + const response = await fetch(`${BASEURL}${chatUrl}?${urlParams.toString()}`, { method: 'GET', signal: abortController.signal }) @@ -221,7 +225,7 @@ } buffer += decoder.decode(value, { stream: true }) - + // 优先按空行拆分事件块(SSE标准) let events = buffer.split(/\n\n/) buffer = events.pop() // 保留不完整块 @@ -232,7 +236,7 @@ // 过滤掉 id: 等字段,只取 data: let isNodeIdEvent = false - if (event.startsWith('event:')) { + if (event.includes('nodeId')) { isNodeIdEvent = true // continue } @@ -245,23 +249,30 @@ flag = false break } + if (event.includes('todo') || event.includes('webAddress')) { + break + } - const dataLines = event - .split(/\n/) - .filter((line) => line.startsWith('data:')) - .map((line) => line.replace(/^data:\s*/, '').trim()) + const dataLines = event + .split(/\n/) + .filter((line) => line.startsWith('data:')) + .map((line) => line.replace(/^data:\s*/, '').trim()) + .filter((content) => content.startsWith('{') || content.startsWith('[')) // console.log('dataLInes', dataLines) if (isNodeIdEvent) { params.versionID = dataLines[0] projectStore.setProject({ nodeId: dataLines[0] }) } + if (event.includes('tool')) { + MyEvent.emit('loading-sketch') + } if (dataLines.length === 0) continue const jsonText = dataLines.join('\n') try { const jsonData = JSON.parse(jsonText) - // console.log('jsonData', jsonData) + console.log('jsonData', jsonData) // 赋值 project_id 和 version_id // if (jsonData.project_id) params.projectID = jsonData.project_id @@ -371,9 +382,9 @@ while (i < dialogue.length) { const item = dialogue[i] - if (item.image_url) { - existingImgList.push(item.image_url) - } + // if (item.image_url) { + // existingImgList.push(item.image_url) + // } if (item.role === 'user') { // user 角色直接添加 @@ -391,9 +402,9 @@ // 继续往后找连续的 assistant 消息 let j = i + 1 while (j < dialogue.length && dialogue[j].role === 'assistant') { - if (dialogue[j].image_url) { - existingImgList.push(dialogue[j].image_url) - } + // if (dialogue[j].image_url) { + // existingImgList.push(dialogue[j].image_url) + // } combinedContent += dialogue[j].content || '' j++ } @@ -448,11 +459,14 @@ const { ancestors, current } = data - const imgList = [] + let imgList = [] const ancestorsList = [] let ancestorsIdCounter = 1 if (ancestors) { ancestors.forEach((item) => { + if (item.sketchIDAndUrl) { + imgList = imgList.concat(item.sketchIDAndUrl) + } const list = processDialogue(item.dialogue, 0, imgList) // 重新设置 id list.forEach((el) => { @@ -462,18 +476,19 @@ }) } const currentList = processDialogue(current?.dialogue, 0, imgList) - // 重新设置 id + if (current.sketchIDAndUrl) { + imgList = imgList.concat(current.sketchIDAndUrl) + } + currentList.forEach((el, index) => { el.id = index + 1 + ancestorsList.length }) // 延迟设置新数据,确保 UI 有时间响应清空操作 nextTick(() => { - messageList.value = [...ancestorsList, ...currentList] params.versionID = current?.id sketchList.value = imgList - console.log('11111111111111',params.versionID); }) } diff --git a/src/views/home/agent/components/Preview.vue b/src/views/home/agent/components/Preview.vue index 2743d80..dbe22b0 100644 --- a/src/views/home/agent/components/Preview.vue +++ b/src/views/home/agent/components/Preview.vue @@ -9,8 +9,34 @@ v-for="(item, index) in sketchList" :key="'sketch-item-' + index" > - -
+ + + + +
Edit
@@ -20,6 +46,9 @@ @load="handleImageLoad(index)" />
+
+ loading +
@@ -120,13 +149,20 @@ diff --git a/src/views/home/agent/index.vue b/src/views/home/agent/index.vue index 910a793..dcfe397 100644 --- a/src/views/home/agent/index.vue +++ b/src/views/home/agent/index.vue @@ -6,7 +6,7 @@
- +
{ + sketchList.value = sketchList.value.filter((item) => { + if (typeof item === 'object') { + return Object.keys(item)[0] !== deletedId + } + return true + }) + } + const versionTreeData = ref({ drawer: false }) diff --git a/src/views/home/components/Input.vue b/src/views/home/components/Input.vue index 0640ebd..f0529eb 100644 --- a/src/views/home/components/Input.vue +++ b/src/views/home/components/Input.vue @@ -20,7 +20,6 @@
-
{{ $t('Input.placeholder') }}
@@ -506,8 +505,9 @@ let node: Node | null while ((node = walker.nextNode())) { - if (node.parentElement?.classList.contains('custom-placeholder')) continue - if (node.parentElement?.classList.contains('editor-tag')) continue + // 使用 closest() 检查当前节点的祖先元素是否包含需要排除的 class + if (node.parentElement?.closest('.custom-placeholder')) continue + if (node.parentElement?.closest('.editor-tag')) continue text += node.textContent } @@ -732,14 +732,15 @@ ) 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) @@ -1035,7 +1036,7 @@ min-height: 5rem; line-height: 1.4rem; } - .editor-placeholder{ + .editor-placeholder { font-family: 'Regular'; font-size: 1.4rem; padding: 0;