sage/wwwroot/tmp/trag.html
2025-11-03 15:34:22 +08:00

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>