Spaces:
Runtime error
Runtime error
Thomas G. Lopes
commited on
Commit
·
aecbec0
1
Parent(s):
6c3123d
fix overlaps
Browse files
src/routes/canvas/chat-node.svelte
CHANGED
|
@@ -23,7 +23,7 @@
|
|
| 23 |
};
|
| 24 |
let { id, data }: Props = $props();
|
| 25 |
|
| 26 |
-
let { updateNodeData, updateNode, getNode } = useSvelteFlow();
|
| 27 |
onMount(() => {
|
| 28 |
if (!data.modelId) data.modelId = models.trending[0]?.id;
|
| 29 |
if (!data.provider) data.provider = "auto";
|
|
@@ -140,6 +140,110 @@
|
|
| 140 |
if (!data.response) return "";
|
| 141 |
return marked(data.response);
|
| 142 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
</script>
|
| 144 |
|
| 145 |
<div
|
|
@@ -221,9 +325,16 @@
|
|
| 221 |
onclick={() => {
|
| 222 |
const curr = getNode(id);
|
| 223 |
const newNodeId = crypto.randomUUID();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
const newNode: Node = {
|
| 225 |
id: newNodeId,
|
| 226 |
-
position:
|
| 227 |
data: {
|
| 228 |
query: data.query,
|
| 229 |
response: data.response,
|
|
@@ -263,9 +374,17 @@
|
|
| 263 |
hover:bg-gray-900 focus:ring-2 focus:ring-gray-900/20 focus:outline-none active:scale-[0.98]"
|
| 264 |
onclick={() => {
|
| 265 |
const curr = getNode(id);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
const newNode: Node = {
|
| 267 |
id: crypto.randomUUID(),
|
| 268 |
-
position:
|
| 269 |
data: { query: "", response: "", modelId: data.modelId, provider: data.provider },
|
| 270 |
type: "chat",
|
| 271 |
width: undefined,
|
|
|
|
| 23 |
};
|
| 24 |
let { id, data }: Props = $props();
|
| 25 |
|
| 26 |
+
let { updateNodeData, updateNode, getNode, getViewport } = useSvelteFlow();
|
| 27 |
onMount(() => {
|
| 28 |
if (!data.modelId) data.modelId = models.trending[0]?.id;
|
| 29 |
if (!data.provider) data.provider = "auto";
|
|
|
|
| 140 |
if (!data.response) return "";
|
| 141 |
return marked(data.response);
|
| 142 |
});
|
| 143 |
+
|
| 144 |
+
// Helper function to get actual node dimensions from DOM, converted to flow coordinates
|
| 145 |
+
function getNodeDimensions(nodeId: string) {
|
| 146 |
+
const nodeElement = document.querySelector(`[data-id="${nodeId}"]`);
|
| 147 |
+
if (nodeElement) {
|
| 148 |
+
const rect = nodeElement.getBoundingClientRect();
|
| 149 |
+
const viewport = getViewport();
|
| 150 |
+
// Convert from screen coordinates to flow coordinates
|
| 151 |
+
const flowWidth = rect.width / viewport.zoom;
|
| 152 |
+
const flowHeight = rect.height / viewport.zoom;
|
| 153 |
+
return { width: flowWidth, height: flowHeight };
|
| 154 |
+
}
|
| 155 |
+
// Fallback to default size
|
| 156 |
+
return { width: 500, height: 200 };
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
// Helper function to find a non-overlapping position
|
| 160 |
+
function findAvailablePosition(
|
| 161 |
+
startX: number,
|
| 162 |
+
startY: number,
|
| 163 |
+
newNodeWidth = 500,
|
| 164 |
+
newNodeHeight = 200,
|
| 165 |
+
constrainBelow = false,
|
| 166 |
+
) {
|
| 167 |
+
const spacing = 40;
|
| 168 |
+
let x = startX;
|
| 169 |
+
let y = startY;
|
| 170 |
+
|
| 171 |
+
// Check if position overlaps with any existing node
|
| 172 |
+
const isOverlapping = (testX: number, testY: number) => {
|
| 173 |
+
return nodes.current.some(node => {
|
| 174 |
+
if (node.id === id) return false; // Don't check against self
|
| 175 |
+
|
| 176 |
+
const existingDims = getNodeDimensions(node.id);
|
| 177 |
+
const nodeWidth = existingDims.width;
|
| 178 |
+
const nodeHeight = existingDims.height;
|
| 179 |
+
|
| 180 |
+
// Check for overlap with proper spacing
|
| 181 |
+
return !(
|
| 182 |
+
testX >= node.position.x + nodeWidth + spacing ||
|
| 183 |
+
testX + newNodeWidth + spacing <= node.position.x ||
|
| 184 |
+
testY >= node.position.y + nodeHeight + spacing ||
|
| 185 |
+
testY + newNodeHeight + spacing <= node.position.y
|
| 186 |
+
);
|
| 187 |
+
});
|
| 188 |
+
};
|
| 189 |
+
|
| 190 |
+
// For add node (constrainBelow = true), maintain same Y level and search horizontally
|
| 191 |
+
if (constrainBelow) {
|
| 192 |
+
const fixedY = y; // Always use the same Y distance from parent
|
| 193 |
+
|
| 194 |
+
// Try the preferred X position first
|
| 195 |
+
if (!isOverlapping(x, fixedY)) return { x, y: fixedY };
|
| 196 |
+
|
| 197 |
+
// Search horizontally at the fixed Y level, checking each position carefully
|
| 198 |
+
let testX = x;
|
| 199 |
+
let attempts = 0;
|
| 200 |
+
while (attempts < 50) {
|
| 201 |
+
// Try moving right by small increments
|
| 202 |
+
testX += 50;
|
| 203 |
+
if (!isOverlapping(testX, fixedY)) {
|
| 204 |
+
return { x: testX, y: fixedY };
|
| 205 |
+
}
|
| 206 |
+
attempts++;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
// Try going left from original position
|
| 210 |
+
testX = x;
|
| 211 |
+
attempts = 0;
|
| 212 |
+
while (attempts < 50) {
|
| 213 |
+
testX -= 50;
|
| 214 |
+
if (!isOverlapping(testX, fixedY)) {
|
| 215 |
+
return { x: testX, y: fixedY };
|
| 216 |
+
}
|
| 217 |
+
attempts++;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
// Fallback: force position far to the right
|
| 221 |
+
return { x: x + 2000, y: fixedY };
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
// For duplicate (constrainBelow = false), use spiral pattern
|
| 225 |
+
let offset = 0;
|
| 226 |
+
while (offset < 1000) {
|
| 227 |
+
// Try right
|
| 228 |
+
if (!isOverlapping(x + offset, y)) return { x: x + offset, y };
|
| 229 |
+
// Try left
|
| 230 |
+
if (!isOverlapping(x - offset, y)) return { x: x - offset, y };
|
| 231 |
+
// Try down
|
| 232 |
+
if (!isOverlapping(x, y + offset)) return { x, y: y + offset };
|
| 233 |
+
// Try up
|
| 234 |
+
if (!isOverlapping(x, y - offset)) return { x, y: y - offset };
|
| 235 |
+
// Try diagonal combinations
|
| 236 |
+
if (!isOverlapping(x + offset, y + offset)) return { x: x + offset, y: y + offset };
|
| 237 |
+
if (!isOverlapping(x - offset, y + offset)) return { x: x - offset, y: y + offset };
|
| 238 |
+
if (!isOverlapping(x + offset, y - offset)) return { x: x + offset, y: y - offset };
|
| 239 |
+
if (!isOverlapping(x - offset, y - offset)) return { x: x - offset, y: y - offset };
|
| 240 |
+
|
| 241 |
+
offset += 50;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
// Fallback: return original position with larger offset
|
| 245 |
+
return { x: x + 150, y: y + 150 };
|
| 246 |
+
}
|
| 247 |
</script>
|
| 248 |
|
| 249 |
<div
|
|
|
|
| 325 |
onclick={() => {
|
| 326 |
const curr = getNode(id);
|
| 327 |
const newNodeId = crypto.randomUUID();
|
| 328 |
+
const currentDims = getNodeDimensions(id);
|
| 329 |
+
const preferredPos = findAvailablePosition(
|
| 330 |
+
(curr?.position.x ?? 100) + 50,
|
| 331 |
+
(curr?.position.y ?? 0) + 50,
|
| 332 |
+
currentDims.width,
|
| 333 |
+
currentDims.height,
|
| 334 |
+
);
|
| 335 |
const newNode: Node = {
|
| 336 |
id: newNodeId,
|
| 337 |
+
position: preferredPos,
|
| 338 |
data: {
|
| 339 |
query: data.query,
|
| 340 |
response: data.response,
|
|
|
|
| 374 |
hover:bg-gray-900 focus:ring-2 focus:ring-gray-900/20 focus:outline-none active:scale-[0.98]"
|
| 375 |
onclick={() => {
|
| 376 |
const curr = getNode(id);
|
| 377 |
+
const currentDims = getNodeDimensions(id);
|
| 378 |
+
const preferredPos = findAvailablePosition(
|
| 379 |
+
curr?.position.x ?? 100,
|
| 380 |
+
(curr?.position.y ?? 0) + size.height + 40,
|
| 381 |
+
currentDims.width,
|
| 382 |
+
currentDims.height,
|
| 383 |
+
true, // constrainBelow = true for Add Node
|
| 384 |
+
);
|
| 385 |
const newNode: Node = {
|
| 386 |
id: crypto.randomUUID(),
|
| 387 |
+
position: preferredPos,
|
| 388 |
data: { query: "", response: "", modelId: data.modelId, provider: data.provider },
|
| 389 |
type: "chat",
|
| 390 |
width: undefined,
|