169 lines
4.8 KiB
HTML
169 lines
4.8 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>RAG 可视化编辑器 (纯JS)</title>
|
|
<style>
|
|
body { margin: 0; font-family: sans-serif; display: flex; height: 100vh; }
|
|
#sidebar { width: 180px; background: #f5f5f5; padding: 10px; border-right: 1px solid #ccc; }
|
|
#canvas { flex: 1; position: relative; background: #fafafa; overflow: hidden; }
|
|
.node {
|
|
position: absolute;
|
|
border: 1px solid #333;
|
|
border-radius: 6px;
|
|
padding: 8px;
|
|
background: #fff;
|
|
cursor: move;
|
|
}
|
|
.connection {
|
|
stroke: #333;
|
|
stroke-width: 2;
|
|
fill: none;
|
|
}
|
|
#toolbar { position: absolute; top: 10px; right: 10px; background: #fff; border: 1px solid #ccc; padding: 5px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="sidebar">
|
|
<h4>节点面板</h4>
|
|
<button onclick="addNode('DataSource')">DataSource</button><br>
|
|
<button onclick="addNode('Embedder')">Embedder</button><br>
|
|
<button onclick="addNode('VectorStore')">VectorStore</button><br>
|
|
<button onclick="addNode('Retriever')">Retriever</button><br>
|
|
<button onclick="addNode('LLM')">LLM</button><br>
|
|
<button onclick="addNode('Evaluator')">Evaluator</button><br>
|
|
<button onclick="addNode('Tool')">Tool</button><br>
|
|
<hr>
|
|
<button onclick="exportFlow()">导出JSON</button><br>
|
|
<input type="file" id="importFile" onchange="importFlow(event)">
|
|
</div>
|
|
|
|
<div id="canvas">
|
|
<svg id="connections" width="100%" height="100%" style="position:absolute;top:0;left:0;z-index:0"></svg>
|
|
<div id="toolbar">
|
|
<button onclick="submitFlow()">提交</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let nodes = [];
|
|
let connections = [];
|
|
let currentId = 0;
|
|
let selectedNode = null;
|
|
|
|
function addNode(type, x=100, y=100) {
|
|
const id = 'n'+(currentId++);
|
|
const div = document.createElement('div');
|
|
div.className = 'node';
|
|
div.innerText = type;
|
|
div.style.left = x+'px';
|
|
div.style.top = y+'px';
|
|
div.dataset.id = id;
|
|
div.dataset.type = type;
|
|
div.draggable = true;
|
|
|
|
div.addEventListener('dragstart', e=>{
|
|
selectedNode = div;
|
|
e.dataTransfer.setData("text/plain", id);
|
|
});
|
|
div.addEventListener('dblclick', ()=> {
|
|
if (selectedNode && selectedNode !== div) {
|
|
addConnection(selectedNode.dataset.id, div.dataset.id);
|
|
selectedNode = null;
|
|
} else {
|
|
selectedNode = div;
|
|
}
|
|
});
|
|
|
|
document.getElementById('canvas').appendChild(div);
|
|
nodes.push({id, type, x, y});
|
|
makeDraggable(div);
|
|
}
|
|
|
|
function makeDraggable(el) {
|
|
let offsetX, offsetY;
|
|
el.onmousedown = (e)=>{
|
|
offsetX = e.offsetX;
|
|
offsetY = e.offsetY;
|
|
document.onmousemove = (ev)=>{
|
|
el.style.left = (ev.pageX - offsetX)+'px';
|
|
el.style.top = (ev.pageY - offsetY)+'px';
|
|
redrawConnections();
|
|
};
|
|
document.onmouseup = ()=>{ document.onmousemove=null; };
|
|
};
|
|
}
|
|
|
|
function addConnection(from, to) {
|
|
connections.push({from, to});
|
|
redrawConnections();
|
|
}
|
|
|
|
function redrawConnections() {
|
|
const svg = document.getElementById('connections');
|
|
svg.innerHTML = '';
|
|
connections.forEach(c=>{
|
|
const fromEl = document.querySelector(`[data-id='${c.from}']`);
|
|
const toEl = document.querySelector(`[data-id='${c.to}']`);
|
|
if (fromEl && toEl) {
|
|
const x1 = fromEl.offsetLeft + fromEl.offsetWidth/2;
|
|
const y1 = fromEl.offsetTop + fromEl.offsetHeight/2;
|
|
const x2 = toEl.offsetLeft + toEl.offsetWidth/2;
|
|
const y2 = toEl.offsetTop + toEl.offsetHeight/2;
|
|
const path = document.createElementNS("http://www.w3.org/2000/svg","path");
|
|
path.setAttribute("d", `M${x1},${y1} C${x1+50},${y1} ${x2-50},${y2} ${x2},${y2}`);
|
|
path.setAttribute("class","connection");
|
|
svg.appendChild(path);
|
|
}
|
|
});
|
|
}
|
|
|
|
function exportFlow() {
|
|
const data = {
|
|
nodes: Array.from(document.querySelectorAll('.node')).map(el=>({
|
|
id: el.dataset.id,
|
|
type: el.dataset.type,
|
|
x: el.offsetLeft,
|
|
y: el.offsetTop
|
|
})),
|
|
connections
|
|
};
|
|
const blob = new Blob([JSON.stringify(data,null,2)], {type:"application/json"});
|
|
const a = document.createElement('a');
|
|
a.href = URL.createObjectURL(blob);
|
|
a.download = "flow.json";
|
|
a.click();
|
|
}
|
|
|
|
function importFlow(event) {
|
|
const file = event.target.files[0];
|
|
const reader = new FileReader();
|
|
reader.onload = ()=>{
|
|
const data = JSON.parse(reader.result);
|
|
document.querySelectorAll('.node').forEach(n=>n.remove());
|
|
nodes=[]; connections=[]; currentId=0;
|
|
data.nodes.forEach(n=>addNode(n.type,n.x,n.y));
|
|
connections = data.connections;
|
|
redrawConnections();
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
function submitFlow() {
|
|
const data = {
|
|
nodes: Array.from(document.querySelectorAll('.node')).map(el=>({
|
|
id: el.dataset.id,
|
|
type: el.dataset.type,
|
|
x: el.offsetLeft,
|
|
y: el.offsetTop
|
|
})),
|
|
connections
|
|
};
|
|
console.log("提交流程数据:", data);
|
|
alert("流程已提交,控制台查看数据");
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|