window.dash_clientside = Object.assign({}, window.dash_clientside, { aichat_clientside: { handleUserNewMessageSend: (_, __, ___, value,chatDataList,current_files) => { // 判断问题是否为空 if (!value || value === '@') { return window.dash_clientside.no_update; } // 若本次回调由control+enter组合触发 if (window.dash_clientside.callback_context.triggered_id === 'control-enter-keypress') { return [ `${value}\n`,// 为value追加换行符 window.dash_clientside.no_update, window.dash_clientside.no_update, window.dash_clientside.no_update ] } if(chatDataList.length > 0){ if(chatDataList[chatDataList.length -1].type === 'question'){ return [ window.dash_clientside.no_update, window.dash_clientside.no_update, window.dash_clientside.no_update, window.dash_clientside.no_update ] }else{ if(!chatDataList[chatDataList.length -1].is_end){ return [ window.dash_clientside.no_update, window.dash_clientside.no_update, window.dash_clientside.no_update, window.dash_clientside.no_update ] } } } // 否则,视作独立enter触发,执行新内容发送 let chatData = {"type":"question","content":value,"time": formatDate(new Date()), "file": JSON.stringify(current_files)} chatDataList.push(chatData) return [ '', // 重置输入框内容, {"current_problem":value}, chatDataList, [] ]; }, handleStreamResponse: (data, chat_data_list,current_problem,answer_list) => { if(current_problem === null || current_problem.question === ''){ return window.dash_clientside.no_update; } // todo 绑定返回消息中的chat_id 来确定是哪条记录 对应起来 if (chat_data_list.length >0){ if (chat_data_list[chat_data_list.length - 1].type === 'question'){ chat_data_list.push({"type":"answer","content":[],"copy_text":"","is_end":false,"is_stop":false}) answer_list.push({"index":chat_data_list.length-1, "content":[], "is_end":false}) } if (data && data[data.length - 1]) { data = JSON.parse(data[data.length -1]) console.log(data) if (data.docs && data.docs[0].length>0){ //说明是超链接 let last_answer = answer_list[answer_list.length - 1].content if (last_answer.length > 0) { if(last_answer[last_answer.length - 1].type === 'text'){ answer_list[answer_list.length-1].content[last_answer.length - 1].content += data.docs[0] }else{ answer_list[answer_list.length - 1].content.push({"content":data.docs[0],"type":"text"}) } }else{ answer_list[answer_list.length - 1].content.push({"content":data.docs[0],"type":"docs"}) } }else if (data.G6_ER && data.G6_ER.length > 0){ //说明是ER图 answer_list[answer_list.length - 1].content.push({"content":data.G6_ER,"type":"G6_ER"}) }else if (data.html_image && data.html_image.length > 0){ //说明是html的echarts图 answer_list[answer_list.length - 1].content.push({"content":data.html_image,"type":"html_image"}) }else if (data.table && data.table.length > 0){ //说明是 表格 answer_list[answer_list.length - 1].content.push({"content":data.table,"type":"table"}) }else { // 纯文本 let last_answer = answer_list[answer_list.length - 1].content data.choices[0].delta.content = data.choices[0].delta.content.replace("\n","\n\n") if (last_answer.length > 0) { if(last_answer[last_answer.length - 1].type === 'text'){ answer_list[answer_list.length-1].content[last_answer.length - 1].content += data.choices[0].delta.content }else{ answer_list[answer_list.length - 1].content.push({"content":data.choices[0].delta.content,"type":"text"}) } }else{ answer_list[answer_list.length - 1].content.push({"content":data.choices[0].delta.content,"type":"text"}) } } if (!chat_data_list[answer_list[answer_list.length - 1].index].is_stop){ chat_data_list[chat_data_list.length - 1].content =answer_list[answer_list.length - 1].content } if (data.is_end){ answer_list[answer_list.length - 1].is_end = true answer_list[answer_list.length - 1].time = formatDate(new Date()) if (!chat_data_list[answer_list[answer_list.length - 1].index].is_stop){ chat_data_list[chat_data_list.length - 1].is_end = true chat_data_list[chat_data_list.length - 1].time = formatDate(new Date()) let chat_content = chat_data_list[chat_data_list.length - 1].content let copy_text = "" for (let i = 0; i < chat_content.length; i++) { if (chat_content[i].type === 'text'){ copy_text += chat_content[i].content } } chat_data_list[chat_data_list.length - 1].copy_text = copy_text } } return [chat_data_list, answer_list] } } return window.dash_clientside.no_update; }, handleChatListAreaScroll: (height) => { // 自动滚动到底部操作 let scrollTarget = document.getElementById('chat-scrollbar') // 滚动到底部 scrollTarget.scrollTo({ top: scrollTarget.scrollHeight }); }, renderG6: (id,chat_data_list) => { for (let i = 0; i < chat_data_list.length; i++) { let chat_Data = chat_data_list[i] if (chat_Data.type === "answer"){ let content = chat_Data.content const eleArr = document.getElementsByClassName("G6_ER") if (eleArr.length>0){ for (let k = 0; k < eleArr.length; k++) { const ele = eleArr[k]; let indexStr = JSON.parse(ele.id).index.split("_") let index = indexStr[0] let idx = indexStr[1] let g6data = chat_data_list[index].content[idx] const isInBBox = (point, bbox) => { const { x, y } = point; const { minX, minY, maxX, maxY } = bbox; return x < maxX && x > minX && y > minY && y < maxY; }; const itemHeight = 20; G6.registerBehavior("dice-er-scroll", { getDefaultCfg() { return { multiple: true, }; }, getEvents() { return { itemHeight, wheel: "scorll", click: "click", "node:mousemove": "move", "node:mousedown": "mousedown", "node:mouseup": "mouseup" }; }, scorll(e) { e.preventDefault(); const { graph } = this; const nodes = graph.getNodes().filter((n) => { const bbox = n.getBBox(); return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox); }); const x = e.deltaX || e.movementX; let y = e.deltaY || e.movementY; if (!y && navigator.userAgent.indexOf('Firefox') > -1) y = (-e.wheelDelta * 125) / 3 if (nodes) { const edgesToUpdate = new Set(); nodes.forEach((node) => { const model = node.getModel(); if (model.attrs.length < 2) { return; } const idx = model.startIndex || 0; let startX = model.startX || 0.5; let startIndex = idx + y * 0.02; startX -= x; if (startIndex < 0) { startIndex = 0; } if (startX > 0) { startX = 0; } if (startIndex > model.attrs.length - 1) { startIndex = model.attrs.length - 1; } graph.updateItem(node, { startIndex, startX, }); node.getEdges().forEach(edge => edgesToUpdate.add(edge)) }); // G6 update the related edges when graph.updateItem with a node according to the new properties // here you need to update the related edges manualy since the new properties { startIndex, startX } for the nodes are custom, and cannot be recognized by G6 edgesToUpdate.forEach(edge => edge.refresh()) } }, click(e) { const { graph } = this; const item = e.item; const shape = e.shape; if (!item) { return; } if (shape.get("name") === "collapse") { graph.updateItem(item, { collapsed: true, size: [300, 50], }); setTimeout(() => graph.layout(), 100); } else if (shape.get("name") === "expand") { graph.updateItem(item, { collapsed: false, size: [300, 80], }); setTimeout(() => graph.layout(), 100); } }, mousedown(e) { this.isMousedown = true; }, mouseup(e) { this.isMousedown = false; }, move(e) { if (this.isMousedown) return; const name = e.shape.get("name"); const item = e.item; if (name && name.startsWith("item")) { this.graph.updateItem(item, { selectedIndex: Number(name.split("-")[1]), }); } else { this.graph.updateItem(item, { selectedIndex: NaN, }); } }, }); G6.registerEdge("dice-er-edge", { draw(cfg, group) { const edge = group.cfg.item; const sourceNode = edge.getSource().getModel(); const targetNode = edge.getTarget().getModel(); const sourceIndex = sourceNode.attrs.findIndex( (e) => e.key === cfg.sourceKey ); const sourceStartIndex = sourceNode.startIndex || 0; let sourceY = 15; if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) { sourceY = 30 + (sourceIndex - sourceStartIndex + 0.5) * itemHeight; sourceY = Math.min(sourceY, 80); } const targetIndex = targetNode.attrs.findIndex( (e) => e.key === cfg.targetKey ); const targetStartIndex = targetNode.startIndex || 0; let targetY = 15; if (!targetNode.collapsed && targetIndex > targetStartIndex - 1) { targetY = (targetIndex - targetStartIndex + 0.5) * itemHeight + 30; targetY = Math.min(targetY, 80); } const startPoint = { ...cfg.startPoint }; const endPoint = { ...cfg.endPoint }; startPoint.y = startPoint.y + sourceY; endPoint.y = endPoint.y + targetY; let shape; if (sourceNode.id !== targetNode.id) { shape = group.addShape("path", { attrs: { stroke: "#5B8FF9", path: [ ["M", startPoint.x, startPoint.y], [ "C", endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y, endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y, endPoint.x, endPoint.y, ], ], endArrow: true, }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: "path-shape", }); } else if (!sourceNode.collapsed) { let gap = Math.abs((startPoint.y - endPoint.y) / 3); if (startPoint["index"] === 1) { gap = -gap; } shape = group.addShape("path", { attrs: { stroke: "#5B8FF9", path: [ ["M", startPoint.x, startPoint.y], [ "C", startPoint.x - gap, startPoint.y, startPoint.x - gap, endPoint.y, startPoint.x, endPoint.y, ], ], endArrow: true, }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: "path-shape", }); } return shape; }, afterDraw(cfg, group) { const labelCfg = cfg.labelCfg || {}; const edge = group.cfg.item; const sourceNode = edge.getSource().getModel(); const targetNode = edge.getTarget().getModel(); if (sourceNode.collapsed && targetNode.collapsed) { return; } const path = group.find( (element) => element.get("name") === "path-shape" ); const labelStyle = G6.Util.getLabelPosition(path, 0.5, 0, 0, true); const label = group.addShape("text", { attrs: { ...labelStyle, text: cfg.label || '', fill: "#000", textAlign: "center", stroke: "#fff", lineWidth: 1, }, }); label.rotateAtStart(labelStyle.rotate); }, }); G6.registerNode("dice-er-box", { draw(cfg, group) { const width = 100; const height = 96; const itemCount = 10; const boxStyle = { stroke: "#096DD9", radius: 4, }; const { attrs = [], startIndex = 0, selectedIndex, collapsed, icon, } = cfg; const list = attrs; const afterList = list.slice( Math.floor(startIndex), Math.floor(startIndex + itemCount - 1) ); const offsetY = (0.5 - (startIndex % 1)) * itemHeight + 30; group.addShape("rect", { attrs: { fill: boxStyle.stroke, height: 30, width, radius: [boxStyle.radius, boxStyle.radius, 0, 0], }, draggable: true, }); let fontLeft = 12; if (icon && icon.show !== false) { group.addShape("image", { attrs: { x: 8, y: 8, height: 16, width: 16, ...icon, }, }); fontLeft += 18; } group.addShape("text", { attrs: { y: 22, x: fontLeft, fill: "#fff", text: cfg.label, fontSize: 10, fontWeight: 500, }, }); group.addShape("rect", { attrs: { x: 0, y: collapsed ? 30 : 80, height: 15, width, fill: "#eee", radius: [0, 0, boxStyle.radius, boxStyle.radius], cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: collapsed ? "expand" : "collapse", }); group.addShape("text", { attrs: { x: width / 2 - 6, y: (collapsed ? 30 : 80) + 12, text: collapsed ? "+" : "-", width, fill: "#000", radius: [0, 0, boxStyle.radius, boxStyle.radius], cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: collapsed ? "expand" : "collapse", }); const keyshape = group.addShape("rect", { attrs: { x: 0, y: 0, width, height: collapsed ? 45 : height, ...boxStyle, }, draggable: true, }); if (collapsed) { return keyshape; } const listContainer = group.addGroup({}); listContainer.setClip({ type: "rect", attrs: { x: -8, y: 30, width: width + 16, height: 80 - 30, }, }); listContainer.addShape({ type: "rect", attrs: { x: 1, y: 30, width: width - 2, height: 80 - 30, fill: "#fff", }, draggable: true, }); if (list.length > itemCount) { const barStyle = { width: 4, padding: 0, boxStyle: { stroke: "#00000022", }, innerStyle: { fill: "#00000022", }, }; listContainer.addShape("rect", { attrs: { y: 30, x: width - barStyle.padding - barStyle.width, width: barStyle.width, height: height - 30, ...barStyle.boxStyle, }, }); const indexHeight = afterList.length > itemCount ? (afterList.length / list.length) * height : 10; listContainer.addShape("rect", { attrs: { y: 30 + barStyle.padding + (startIndex / list.length) * (height - 30), x: width - barStyle.padding - barStyle.width, width: barStyle.width, height: Math.min(height, indexHeight), ...barStyle.innerStyle, }, }); } if (afterList) { afterList.forEach((e, i) => { const isSelected = Math.floor(startIndex) + i === Number(selectedIndex); let { key = "", type } = e; if (type) { key += " - " + type; } const label = key.length > 26 ? key.slice(0, 24) + "..." : key; listContainer.addShape("rect", { attrs: { x: 1, y: i * itemHeight - itemHeight / 2 + offsetY, width: width - 4, height: itemHeight, radius: 2, lineWidth: 1, cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: `item-${Math.floor(startIndex) + i}-content`, draggable: true, }); if (!cfg.hideDot) { listContainer.addShape("circle", { attrs: { x: 0, y: i * itemHeight + offsetY, r: 3, stroke: boxStyle.stroke, fill: "white", radius: 2, lineWidth: 1, cursor: "pointer", }, }); listContainer.addShape("circle", { attrs: { x: width, y: i * itemHeight + offsetY, r: 3, stroke: boxStyle.stroke, fill: "white", radius: 2, lineWidth: 1, cursor: "pointer", }, }); } listContainer.addShape("text", { attrs: { x: 12, y: i * itemHeight + offsetY + 6, text: label, fontSize: 10, fill: "#000", fontFamily: "Avenir,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol", full: e, fontWeight: isSelected ? 500 : 100, cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: `item-${Math.floor(startIndex) + i}`, }); }); } return keyshape; }, getAnchorPoints() { return [ [0, 0], [1, 0], ]; }, }); const dataTransform = (data) => { const nodes = []; const edges = []; for(let i = 0;i { if (attr.relation) { attr.relation.forEach((relation) => { edges.push({ source: node.id, target: relation.nodeId, sourceKey: attr.key, targetKey: relation.key, label: relation.label, }); }); } }); } } return { nodes, edges, }; } const width = ele.scrollWidth; const height = (ele.scrollHeight || 500) - 20; const graph = new G6.Graph({ container: ele, width:width, height:height, defaultNode: { size: [300, 200], type: 'dice-er-box', color: '#5B8FF9', style: { fill: '#9EC9FF', lineWidth: 3, }, labelCfg: { style: { fill: 'black', fontSize: 10, }, }, }, defaultEdge: { type: 'dice-er-edge', style: { stroke: '#e2e2e2', lineWidth: 4, endArrow: true, }, }, modes: { default: ['dice-er-scroll', 'drag-node', 'drag-canvas'], }, layout: { type: 'dagre', rankdir: 'LR', align: 'UL', controlPoints: true, nodesepFunc: () => 0.2, ranksepFunc: () => 0.5, }, animate: true, fitView: true }) graph.data(dataTransform(g6data.content)); graph.render(); } } } } return window.dash_clientside.no_update; }, full_g6_modal: (style,g6_data) => { if (style.display === 'block'){ const ele = document.getElementById("g6-full-screen-body") ele.innerHTML = "" const isInBBox = (point, bbox) => { const { x, y } = point; const { minX, minY, maxX, maxY } = bbox; return x < maxX && x > minX && y > minY && y < maxY; }; const itemHeight = 20; G6.registerBehavior("dice-er-scroll", { getDefaultCfg() { return { multiple: true, }; }, getEvents() { return { itemHeight, wheel: "scorll", click: "click", "node:mousemove": "move", "node:mousedown": "mousedown", "node:mouseup": "mouseup" }; }, scorll(e) { e.preventDefault(); const { graph } = this; const nodes = graph.getNodes().filter((n) => { const bbox = n.getBBox(); return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox); }); const x = e.deltaX || e.movementX; let y = e.deltaY || e.movementY; if (!y && navigator.userAgent.indexOf('Firefox') > -1) y = (-e.wheelDelta * 125) / 3 if (nodes) { const edgesToUpdate = new Set(); nodes.forEach((node) => { const model = node.getModel(); if (model.attrs.length < 2) { return; } const idx = model.startIndex || 0; let startX = model.startX || 0.5; let startIndex = idx + y * 0.02; startX -= x; if (startIndex < 0) { startIndex = 0; } if (startX > 0) { startX = 0; } if (startIndex > model.attrs.length - 1) { startIndex = model.attrs.length - 1; } graph.updateItem(node, { startIndex, startX, }); node.getEdges().forEach(edge => edgesToUpdate.add(edge)) }); // G6 update the related edges when graph.updateItem with a node according to the new properties // here you need to update the related edges manualy since the new properties { startIndex, startX } for the nodes are custom, and cannot be recognized by G6 edgesToUpdate.forEach(edge => edge.refresh()) } }, click(e) { const { graph } = this; const item = e.item; const shape = e.shape; if (!item) { return; } if (shape.get("name") === "collapse") { graph.updateItem(item, { collapsed: true, size: [300, 50], }); setTimeout(() => graph.layout(), 100); } else if (shape.get("name") === "expand") { graph.updateItem(item, { collapsed: false, size: [300, 80], }); setTimeout(() => graph.layout(), 100); } }, mousedown(e) { this.isMousedown = true; }, mouseup(e) { this.isMousedown = false; }, move(e) { if (this.isMousedown) return; const name = e.shape.get("name"); const item = e.item; if (name && name.startsWith("item")) { this.graph.updateItem(item, { selectedIndex: Number(name.split("-")[1]), }); } else { this.graph.updateItem(item, { selectedIndex: NaN, }); } }, }); G6.registerEdge("dice-er-edge", { draw(cfg, group) { const edge = group.cfg.item; const sourceNode = edge.getSource().getModel(); const targetNode = edge.getTarget().getModel(); const sourceIndex = sourceNode.attrs.findIndex( (e) => e.key === cfg.sourceKey ); const sourceStartIndex = sourceNode.startIndex || 0; let sourceY = 15; if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) { sourceY = 30 + (sourceIndex - sourceStartIndex + 0.5) * itemHeight; sourceY = Math.min(sourceY, 80); } const targetIndex = targetNode.attrs.findIndex( (e) => e.key === cfg.targetKey ); const targetStartIndex = targetNode.startIndex || 0; let targetY = 15; if (!targetNode.collapsed && targetIndex > targetStartIndex - 1) { targetY = (targetIndex - targetStartIndex + 0.5) * itemHeight + 30; targetY = Math.min(targetY, 80); } const startPoint = { ...cfg.startPoint }; const endPoint = { ...cfg.endPoint }; startPoint.y = startPoint.y + sourceY; endPoint.y = endPoint.y + targetY; let shape; if (sourceNode.id !== targetNode.id) { shape = group.addShape("path", { attrs: { stroke: "#5B8FF9", path: [ ["M", startPoint.x, startPoint.y], [ "C", endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y, endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y, endPoint.x, endPoint.y, ], ], endArrow: true, }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: "path-shape", }); } else if (!sourceNode.collapsed) { let gap = Math.abs((startPoint.y - endPoint.y) / 3); if (startPoint["index"] === 1) { gap = -gap; } shape = group.addShape("path", { attrs: { stroke: "#5B8FF9", path: [ ["M", startPoint.x, startPoint.y], [ "C", startPoint.x - gap, startPoint.y, startPoint.x - gap, endPoint.y, startPoint.x, endPoint.y, ], ], endArrow: true, }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: "path-shape", }); } return shape; }, afterDraw(cfg, group) { const labelCfg = cfg.labelCfg || {}; const edge = group.cfg.item; const sourceNode = edge.getSource().getModel(); const targetNode = edge.getTarget().getModel(); if (sourceNode.collapsed && targetNode.collapsed) { return; } const path = group.find( (element) => element.get("name") === "path-shape" ); const labelStyle = G6.Util.getLabelPosition(path, 0.5, 0, 0, true); const label = group.addShape("text", { attrs: { ...labelStyle, text: cfg.label || '', fill: "#000", textAlign: "center", stroke: "#fff", lineWidth: 1, }, }); label.rotateAtStart(labelStyle.rotate); }, }); G6.registerNode("dice-er-box", { draw(cfg, group) { const width = 100; const height = 96; const itemCount = 10; const boxStyle = { stroke: "#096DD9", radius: 4, }; const { attrs = [], startIndex = 0, selectedIndex, collapsed, icon, } = cfg; const list = attrs; const afterList = list.slice( Math.floor(startIndex), Math.floor(startIndex + itemCount - 1) ); const offsetY = (0.5 - (startIndex % 1)) * itemHeight + 30; group.addShape("rect", { attrs: { fill: boxStyle.stroke, height: 30, width, radius: [boxStyle.radius, boxStyle.radius, 0, 0], }, draggable: true, }); let fontLeft = 12; if (icon && icon.show !== false) { group.addShape("image", { attrs: { x: 8, y: 8, height: 16, width: 16, ...icon, }, }); fontLeft += 18; } group.addShape("text", { attrs: { y: 22, x: fontLeft, fill: "#fff", text: cfg.label, fontSize: 10, fontWeight: 500, }, }); group.addShape("rect", { attrs: { x: 0, y: collapsed ? 30 : 80, height: 15, width, fill: "#eee", radius: [0, 0, boxStyle.radius, boxStyle.radius], cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: collapsed ? "expand" : "collapse", }); group.addShape("text", { attrs: { x: width / 2 - 6, y: (collapsed ? 30 : 80) + 12, text: collapsed ? "+" : "-", width, fill: "#000", radius: [0, 0, boxStyle.radius, boxStyle.radius], cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: collapsed ? "expand" : "collapse", }); const keyshape = group.addShape("rect", { attrs: { x: 0, y: 0, width, height: collapsed ? 45 : height, ...boxStyle, }, draggable: true, }); if (collapsed) { return keyshape; } const listContainer = group.addGroup({}); listContainer.setClip({ type: "rect", attrs: { x: -8, y: 30, width: width + 16, height: 80 - 30, }, }); listContainer.addShape({ type: "rect", attrs: { x: 1, y: 30, width: width - 2, height: 80 - 30, fill: "#fff", }, draggable: true, }); if (list.length > itemCount) { const barStyle = { width: 4, padding: 0, boxStyle: { stroke: "#00000022", }, innerStyle: { fill: "#00000022", }, }; listContainer.addShape("rect", { attrs: { y: 30, x: width - barStyle.padding - barStyle.width, width: barStyle.width, height: height - 30, ...barStyle.boxStyle, }, }); const indexHeight = afterList.length > itemCount ? (afterList.length / list.length) * height : 10; listContainer.addShape("rect", { attrs: { y: 30 + barStyle.padding + (startIndex / list.length) * (height - 30), x: width - barStyle.padding - barStyle.width, width: barStyle.width, height: Math.min(height, indexHeight), ...barStyle.innerStyle, }, }); } if (afterList) { afterList.forEach((e, i) => { const isSelected = Math.floor(startIndex) + i === Number(selectedIndex); let { key = "", type } = e; if (type) { key += " - " + type; } const label = key.length > 26 ? key.slice(0, 24) + "..." : key; listContainer.addShape("rect", { attrs: { x: 1, y: i * itemHeight - itemHeight / 2 + offsetY, width: width - 4, height: itemHeight, radius: 2, lineWidth: 1, cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: `item-${Math.floor(startIndex) + i}-content`, draggable: true, }); if (!cfg.hideDot) { listContainer.addShape("circle", { attrs: { x: 0, y: i * itemHeight + offsetY, r: 3, stroke: boxStyle.stroke, fill: "white", radius: 2, lineWidth: 1, cursor: "pointer", }, }); listContainer.addShape("circle", { attrs: { x: width, y: i * itemHeight + offsetY, r: 3, stroke: boxStyle.stroke, fill: "white", radius: 2, lineWidth: 1, cursor: "pointer", }, }); } listContainer.addShape("text", { attrs: { x: 12, y: i * itemHeight + offsetY + 6, text: label, fontSize: 10, fill: "#000", fontFamily: "Avenir,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol", full: e, fontWeight: isSelected ? 500 : 100, cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: `item-${Math.floor(startIndex) + i}`, }); }); } return keyshape; }, getAnchorPoints() { return [ [0, 0], [1, 0], ]; }, }); const dataTransform = (data) => { const nodes = []; const edges = []; for(let i = 0;i { if (attr.relation) { attr.relation.forEach((relation) => { edges.push({ source: node.id, target: relation.nodeId, sourceKey: attr.key, targetKey: relation.key, label: relation.label, }); }); } }); } } return { nodes, edges, }; } const width = ele.scrollWidth; const height = (ele.scrollHeight || 500) - 20; const graph = new G6.Graph({ container: ele, width:width, height:height, defaultNode: { size: [300, 200], type: 'dice-er-box', color: '#5B8FF9', style: { fill: '#9EC9FF', lineWidth: 3, }, labelCfg: { style: { fill: 'black', fontSize: 10, }, }, }, defaultEdge: { type: 'dice-er-edge', style: { stroke: '#e2e2e2', lineWidth: 4, endArrow: true, }, }, modes: { default: ['dice-er-scroll', 'drag-node', 'drag-canvas'], }, layout: { type: 'dagre', rankdir: 'LR', align: 'UL', controlPoints: true, nodesepFunc: () => 0.2, ranksepFunc: () => 0.5, }, animate: true, fitView: true }) graph.data(dataTransform(g6_data)); graph.render(); } return window.dash_clientside.no_update; }, refresh_chat_div: (style,chat_data_list) => { if (style.display === 'block'){ if (chat_data_list.length>0){ for (let i = 0; i < chat_data_list.length; i++) { let chat_data = chat_data_list[i] if (chat_data.type === 'answer'){ let chat_content = chat_data.content for (let j = 0; j < chat_content.length; j++) { let content = chat_content[j] if (content.type === 'G6_ER'){ let g6Arr = document.getElementsByClassName("G6_ER") for (let k = 0; k < g6Arr.length; k++) { let g6 = g6Arr[k] if (JSON.parse(g6.id).index === i+'_'+j){ g6.innerHTML = '' let g6data = chat_data_list[i].content[j] const isInBBox = (point, bbox) => { const { x, y } = point; const { minX, minY, maxX, maxY } = bbox; return x < maxX && x > minX && y > minY && y < maxY; }; const itemHeight = 20; G6.registerBehavior("dice-er-scroll", { getDefaultCfg() { return { multiple: true, }; }, getEvents() { return { itemHeight, wheel: "scorll", click: "click", "node:mousemove": "move", "node:mousedown": "mousedown", "node:mouseup": "mouseup" }; }, scorll(e) { e.preventDefault(); const { graph } = this; const nodes = graph.getNodes().filter((n) => { const bbox = n.getBBox(); return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox); }); const x = e.deltaX || e.movementX; let y = e.deltaY || e.movementY; if (!y && navigator.userAgent.indexOf('Firefox') > -1) y = (-e.wheelDelta * 125) / 3 if (nodes) { const edgesToUpdate = new Set(); nodes.forEach((node) => { const model = node.getModel(); if (model.attrs.length < 2) { return; } const idx = model.startIndex || 0; let startX = model.startX || 0.5; let startIndex = idx + y * 0.02; startX -= x; if (startIndex < 0) { startIndex = 0; } if (startX > 0) { startX = 0; } if (startIndex > model.attrs.length - 1) { startIndex = model.attrs.length - 1; } graph.updateItem(node, { startIndex, startX, }); node.getEdges().forEach(edge => edgesToUpdate.add(edge)) }); // G6 update the related edges when graph.updateItem with a node according to the new properties // here you need to update the related edges manualy since the new properties { startIndex, startX } for the nodes are custom, and cannot be recognized by G6 edgesToUpdate.forEach(edge => edge.refresh()) } }, click(e) { const { graph } = this; const item = e.item; const shape = e.shape; if (!item) { return; } if (shape.get("name") === "collapse") { graph.updateItem(item, { collapsed: true, size: [300, 50], }); setTimeout(() => graph.layout(), 100); } else if (shape.get("name") === "expand") { graph.updateItem(item, { collapsed: false, size: [300, 80], }); setTimeout(() => graph.layout(), 100); } }, mousedown(e) { this.isMousedown = true; }, mouseup(e) { this.isMousedown = false; }, move(e) { if (this.isMousedown) return; const name = e.shape.get("name"); const item = e.item; if (name && name.startsWith("item")) { this.graph.updateItem(item, { selectedIndex: Number(name.split("-")[1]), }); } else { this.graph.updateItem(item, { selectedIndex: NaN, }); } }, }); G6.registerEdge("dice-er-edge", { draw(cfg, group) { const edge = group.cfg.item; const sourceNode = edge.getSource().getModel(); const targetNode = edge.getTarget().getModel(); const sourceIndex = sourceNode.attrs.findIndex( (e) => e.key === cfg.sourceKey ); const sourceStartIndex = sourceNode.startIndex || 0; let sourceY = 15; if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) { sourceY = 30 + (sourceIndex - sourceStartIndex + 0.5) * itemHeight; sourceY = Math.min(sourceY, 80); } const targetIndex = targetNode.attrs.findIndex( (e) => e.key === cfg.targetKey ); const targetStartIndex = targetNode.startIndex || 0; let targetY = 15; if (!targetNode.collapsed && targetIndex > targetStartIndex - 1) { targetY = (targetIndex - targetStartIndex + 0.5) * itemHeight + 30; targetY = Math.min(targetY, 80); } const startPoint = { ...cfg.startPoint }; const endPoint = { ...cfg.endPoint }; startPoint.y = startPoint.y + sourceY; endPoint.y = endPoint.y + targetY; let shape; if (sourceNode.id !== targetNode.id) { shape = group.addShape("path", { attrs: { stroke: "#5B8FF9", path: [ ["M", startPoint.x, startPoint.y], [ "C", endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y, endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y, endPoint.x, endPoint.y, ], ], endArrow: true, }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: "path-shape", }); } else if (!sourceNode.collapsed) { let gap = Math.abs((startPoint.y - endPoint.y) / 3); if (startPoint["index"] === 1) { gap = -gap; } shape = group.addShape("path", { attrs: { stroke: "#5B8FF9", path: [ ["M", startPoint.x, startPoint.y], [ "C", startPoint.x - gap, startPoint.y, startPoint.x - gap, endPoint.y, startPoint.x, endPoint.y, ], ], endArrow: true, }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: "path-shape", }); } return shape; }, afterDraw(cfg, group) { const labelCfg = cfg.labelCfg || {}; const edge = group.cfg.item; const sourceNode = edge.getSource().getModel(); const targetNode = edge.getTarget().getModel(); if (sourceNode.collapsed && targetNode.collapsed) { return; } const path = group.find( (element) => element.get("name") === "path-shape" ); const labelStyle = G6.Util.getLabelPosition(path, 0.5, 0, 0, true); const label = group.addShape("text", { attrs: { ...labelStyle, text: cfg.label || '', fill: "#000", textAlign: "center", stroke: "#fff", lineWidth: 1, }, }); label.rotateAtStart(labelStyle.rotate); }, }); G6.registerNode("dice-er-box", { draw(cfg, group) { const width = 100; const height = 96; const itemCount = 10; const boxStyle = { stroke: "#096DD9", radius: 4, }; const { attrs = [], startIndex = 0, selectedIndex, collapsed, icon, } = cfg; const list = attrs; const afterList = list.slice( Math.floor(startIndex), Math.floor(startIndex + itemCount - 1) ); const offsetY = (0.5 - (startIndex % 1)) * itemHeight + 30; group.addShape("rect", { attrs: { fill: boxStyle.stroke, height: 30, width, radius: [boxStyle.radius, boxStyle.radius, 0, 0], }, draggable: true, }); let fontLeft = 12; if (icon && icon.show !== false) { group.addShape("image", { attrs: { x: 8, y: 8, height: 16, width: 16, ...icon, }, }); fontLeft += 18; } group.addShape("text", { attrs: { y: 22, x: fontLeft, fill: "#fff", text: cfg.label, fontSize: 10, fontWeight: 500, }, }); group.addShape("rect", { attrs: { x: 0, y: collapsed ? 30 : 80, height: 15, width, fill: "#eee", radius: [0, 0, boxStyle.radius, boxStyle.radius], cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: collapsed ? "expand" : "collapse", }); group.addShape("text", { attrs: { x: width / 2 - 6, y: (collapsed ? 30 : 80) + 12, text: collapsed ? "+" : "-", width, fill: "#000", radius: [0, 0, boxStyle.radius, boxStyle.radius], cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: collapsed ? "expand" : "collapse", }); const keyshape = group.addShape("rect", { attrs: { x: 0, y: 0, width, height: collapsed ? 45 : height, ...boxStyle, }, draggable: true, }); if (collapsed) { return keyshape; } const listContainer = group.addGroup({}); listContainer.setClip({ type: "rect", attrs: { x: -8, y: 30, width: width + 16, height: 80 - 30, }, }); listContainer.addShape({ type: "rect", attrs: { x: 1, y: 30, width: width - 2, height: 80 - 30, fill: "#fff", }, draggable: true, }); if (list.length > itemCount) { const barStyle = { width: 4, padding: 0, boxStyle: { stroke: "#00000022", }, innerStyle: { fill: "#00000022", }, }; listContainer.addShape("rect", { attrs: { y: 30, x: width - barStyle.padding - barStyle.width, width: barStyle.width, height: height - 30, ...barStyle.boxStyle, }, }); const indexHeight = afterList.length > itemCount ? (afterList.length / list.length) * height : 10; listContainer.addShape("rect", { attrs: { y: 30 + barStyle.padding + (startIndex / list.length) * (height - 30), x: width - barStyle.padding - barStyle.width, width: barStyle.width, height: Math.min(height, indexHeight), ...barStyle.innerStyle, }, }); } if (afterList) { afterList.forEach((e, i) => { const isSelected = Math.floor(startIndex) + i === Number(selectedIndex); let { key = "", type } = e; if (type) { key += " - " + type; } const label = key.length > 26 ? key.slice(0, 24) + "..." : key; listContainer.addShape("rect", { attrs: { x: 1, y: i * itemHeight - itemHeight / 2 + offsetY, width: width - 4, height: itemHeight, radius: 2, lineWidth: 1, cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: `item-${Math.floor(startIndex) + i}-content`, draggable: true, }); if (!cfg.hideDot) { listContainer.addShape("circle", { attrs: { x: 0, y: i * itemHeight + offsetY, r: 3, stroke: boxStyle.stroke, fill: "white", radius: 2, lineWidth: 1, cursor: "pointer", }, }); listContainer.addShape("circle", { attrs: { x: width, y: i * itemHeight + offsetY, r: 3, stroke: boxStyle.stroke, fill: "white", radius: 2, lineWidth: 1, cursor: "pointer", }, }); } listContainer.addShape("text", { attrs: { x: 12, y: i * itemHeight + offsetY + 6, text: label, fontSize: 10, fill: "#000", fontFamily: "Avenir,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol", full: e, fontWeight: isSelected ? 500 : 100, cursor: "pointer", }, // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type name: `item-${Math.floor(startIndex) + i}`, }); }); } return keyshape; }, getAnchorPoints() { return [ [0, 0], [1, 0], ]; }, }); const dataTransform = (data) => { const nodes = []; const edges = []; for(let i = 0;i { if (attr.relation) { attr.relation.forEach((relation) => { edges.push({ source: node.id, target: relation.nodeId, sourceKey: attr.key, targetKey: relation.key, label: relation.label, }); }); } }); } } return { nodes, edges, }; } const width = g6.scrollWidth; const height = (g6.scrollHeight || 500) - 20; const graph = new G6.Graph({ container: g6, width:width, height:height, defaultNode: { size: [300, 200], type: 'dice-er-box', color: '#5B8FF9', style: { fill: '#9EC9FF', lineWidth: 3, }, labelCfg: { style: { fill: 'black', fontSize: 10, }, }, }, defaultEdge: { type: 'dice-er-edge', style: { stroke: '#e2e2e2', lineWidth: 4, endArrow: true, }, }, modes: { default: ['dice-er-scroll', 'drag-node', 'drag-canvas'], }, layout: { type: 'dagre', rankdir: 'LR', align: 'UL', controlPoints: true, nodesepFunc: () => 0.2, ranksepFunc: () => 0.5, }, animate: true, fitView: true }) if (g6data){ graph.data(dataTransform(g6data.content)); graph.render(); } } } } if (content.type === 'html_image'){ let htmlArr = document.getElementsByClassName("iframeDoc") for (let k = 0; k < htmlArr.length; k++) { let html = htmlArr[k] if (JSON.parse(html.id).index === i+'_'+j){ html.contentWindow.location.reload() } } } } } } } } return window.dash_clientside.no_update; }, down_load_table: (nClicks,chat_data_list) => { if (nClicks != null && window.dash_clientside.callback_context.triggered_id && window.dash_clientside.callback_context.triggered_id.type === 'down_load_table' && (window.dash_clientside.callback_context.triggered[0].value)){ let id_index = window.dash_clientside.callback_context.triggered_id.index let chat_idx = id_index.split("_")[0] let idx = id_index.split("_")[1] let tableData = chat_data_list[parseInt(chat_idx)].content[parseInt(idx)].content let sheet = XLSX.utils.json_to_sheet(tableData) let sheetName = 'sheet1' let workbook = { SheetNames: [sheetName], Sheets: {} }; workbook.Sheets[sheetName] = sheet; // 生成excel的配置项 let wopts = { bookType: 'xlsx', // 要生成的文件类型 bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性 type: 'binary' }; let wbout = XLSX.write(workbook, wopts); let blob = new Blob([s2ab(wbout)], {type:"application/octet-stream"}); // 字符串转ArrayBuffer function s2ab(s) { let buf = new ArrayBuffer(s.length); let view = new Uint8Array(buf); for (let i=0; i!==s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; return buf; } blob = URL.createObjectURL(blob); let aLink = document.createElement('a'); aLink.href = blob; aLink.download = "导出表格.xlsx"; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效 let event; if(window.MouseEvent) event = new MouseEvent('click'); else { event = document.createEvent('MouseEvents'); event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); } aLink.dispatchEvent(event); } } } }); function padZero(num) { return num < 10 ? '0' + num : num; } function formatDate(date) { const year = date.getFullYear(); const month = padZero(date.getMonth() + 1); // 月份从0开始,所以需要+1 const day = padZero(date.getDate()); const hours = padZero(date.getHours()); const minutes = padZero(date.getMinutes()); const seconds = padZero(date.getSeconds()); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }