Skip to content

Commit d48bde7

Browse files
committed
working traces
1 parent 9538bfc commit d48bde7

File tree

10 files changed

+338
-90
lines changed

10 files changed

+338
-90
lines changed

ell-studio/src/App.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
44
import Sidebar from './components/Sidebar';
55
import Home from './pages/Home';
66
import LMP from './pages/LMP';
7-
import Traces from './pages/Traces';
7+
import Traces from './pages/Invocations';
88
import { ThemeProvider } from './contexts/ThemeContext';
99
import './styles/globals.css';
1010
import './styles/sourceCode.css';

ell-studio/src/components/HierarchicalTable.js

Lines changed: 227 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,75 @@
1-
import React, { useMemo, useRef, useEffect, useState } from 'react';
1+
import React, { useMemo, useRef, useEffect, useState, useCallback } from 'react';
22
import { FiChevronDown, FiArrowUp, FiArrowDown, FiChevronLeft, FiChevronRight, FiChevronsLeft, FiChevronsRight } from 'react-icons/fi';
33
import { HierarchicalTableProvider, useHierarchicalTable } from './HierarchicalTableContext';
44
import { Checkbox } from "components/common/Checkbox"
5+
// Update the SmoothLine component
6+
const SmoothLine = ({ startX, startY, endX: endXPreprocess, endY, color, animated, opacity, offset }) => {
7+
const endX = endXPreprocess;
58

9+
const endYAdjustment = !animated ? 0 : -5
10+
const midX = startX - offset;
611

7-
const TableRow = ({ item, schema, level = 0, onRowClick, columnWidths, updateWidth, rowClassName }) => {
8-
const { expandedRows, selectedRows, toggleRow, toggleSelection, isItemSelected } = useHierarchicalTable();
12+
const path = `
13+
M ${startX} ${startY}
14+
C ${midX} ${startY}, ${midX} ${startY}, ${midX} ${(startY + endY) / 2}
15+
S ${midX} ${endY}, ${endX + endYAdjustment} ${endY}
16+
`;
17+
const duration = '1s';
18+
return (
19+
<g>
20+
<path
21+
d={path}
22+
stroke={color}
23+
fill="none"
24+
strokeWidth="1"
25+
strokeDasharray={animated ? "5,5" : "none"}
26+
className={`transition-all duration-${duration} ease-in-out ${animated ? "animated-dash" : ""}`}
27+
opacity={opacity}
28+
/>
29+
{animated && (
30+
<>
31+
<path
32+
d={path}
33+
stroke={color}
34+
fill="none"
35+
strokeWidth="2"
36+
strokeDasharray="5,5"
37+
className={`animated-dash-overlay transition-opacity duration-${duration} ease-in-out`}
38+
opacity={opacity}
39+
/>
40+
<marker
41+
id="arrowhead"
42+
markerWidth="6"
43+
markerHeight="4"
44+
refX="0"
45+
refY="2"
46+
orient="auto"
47+
>
48+
<polygon points="0 0, 6 2, 0 4" fill={color} />
49+
</marker>
50+
<path
51+
d={path}
52+
stroke={color}
53+
fill="none"
54+
strokeWidth="1.5"
55+
markerEnd="url(#arrowhead)"
56+
className={`animated-dash transition-opacity duration-${duration} ease-in-out`}
57+
opacity={opacity}
58+
/>
59+
</>
60+
)}
61+
</g>
62+
);
63+
};
64+
65+
66+
67+
const TableRow = ({ item, schema, level = 0, onRowClick, columnWidths, updateWidth, rowClassName, setRowRef, links, linkColumn }) => {
68+
const { expandedRows, selectedRows, toggleRow, toggleSelection, isItemSelected, setHoveredRow, sortedData } = useHierarchicalTable();
969
const hasChildren = item.children && item.children.length > 0;
1070
const isExpanded = expandedRows[item.id];
1171
const isSelected = isItemSelected(item);
1272
const [isNew, setIsNew] = useState(true);
13-
1473

1574
const customRowClassName = rowClassName ? rowClassName(item) : '';
1675

@@ -21,6 +80,37 @@ const TableRow = ({ item, schema, level = 0, onRowClick, columnWidths, updateWid
2180
}
2281
}, [isNew]);
2382

83+
const rowRef = useRef(null);
84+
85+
useEffect(() => {
86+
if (!rowRef.current) return;
87+
88+
const updatePosition = () => {
89+
const tableRect = rowRef.current.closest('table').getBoundingClientRect();
90+
const rowRect = rowRef.current.getBoundingClientRect();
91+
const relativeX = rowRect.left - tableRect.left;
92+
const relativeY = rowRect.top - tableRect.top + rowRect.height / 2;
93+
setRowRef(item.id, { id: item.id, x: relativeX, y: relativeY, visible: true });
94+
};
95+
96+
// Initial position update
97+
updatePosition();
98+
99+
// Create a ResizeObserver
100+
const resizeObserver = new ResizeObserver(updatePosition);
101+
102+
// Observe both the row and the table
103+
resizeObserver.observe(rowRef.current);
104+
resizeObserver.observe(rowRef.current.closest('table'));
105+
106+
// Clean up
107+
return () => {
108+
setRowRef(item.id, {visible: false});
109+
resizeObserver.disconnect();
110+
};
111+
}, [item.id, setRowRef, sortedData.length, expandedRows]);
112+
113+
24114
return (
25115
<React.Fragment>
26116
<tr
@@ -31,6 +121,8 @@ const TableRow = ({ item, schema, level = 0, onRowClick, columnWidths, updateWid
31121
onClick={() => {
32122
if (onRowClick) onRowClick(item);
33123
}}
124+
onMouseEnter={() => setHoveredRow(item.id)}
125+
onMouseLeave={() => setHoveredRow(null)}
34126
>
35127
<td className="py-3 px-4 w-12">
36128
<Checkbox
@@ -40,15 +132,15 @@ const TableRow = ({ item, schema, level = 0, onRowClick, columnWidths, updateWid
40132
/>
41133
</td>
42134
<td className="py-3 px-4 w-12 relative" style={{ paddingLeft: `${level * 20 + 16}px` }}>
43-
{hasChildren ? (
44-
<span onClick={(e) => { e.stopPropagation(); toggleRow(item.id); }}>
45-
{isExpanded ? <FiChevronDown className="text-gray-400 text-base" /> : <FiChevronRight className="text-gray-400 text-base" />}
46-
</span>
47-
) : (
48-
<span className="w-4 h-4 inline-block relative">
49-
<span className="absolute left-1/2 top-1/2 w-1.5 h-1.5 bg-gray-600 rounded-full transform -translate-x-1/2 -translate-y-1/2"></span>
50-
</span>
51-
)}
135+
<div className="flex items-center">
136+
{hasChildren && (
137+
<span onClick={(e) => { e.stopPropagation(); toggleRow(item.id); }}
138+
>
139+
{isExpanded ? <FiChevronDown className="text-gray-400 text-base" /> : <FiChevronRight className="text-gray-400 text-base" />}
140+
</span>
141+
)}
142+
</div>
143+
52144
</td>
53145
{schema.columns.map((column, index) => {
54146
const content = column.render ? column.render(item) : item[column.key];
@@ -60,18 +152,26 @@ const TableRow = ({ item, schema, level = 0, onRowClick, columnWidths, updateWid
60152
style={{
61153
...column.style,
62154
maxWidth: maxWidth !== Infinity ? `${maxWidth}px` : undefined,
63-
width: `${Math.min(columnWidths[column.key] || 0, maxWidth)}px`
155+
width: `${Math.min(columnWidths[column.key] || 0, maxWidth)}px`,
64156
}}
157+
65158
title={typeof content === 'string' ? content : ''}
66159
>
67-
{content}
160+
<div style={{
161+
marginLeft: column.key === linkColumn ? `${level * 20 + 16}px` : 0,
162+
width: column.key === linkColumn ? '100%' : 'auto'
163+
}}>
164+
<div ref={column.key === linkColumn ? rowRef : null}>
165+
{content}
166+
</div>
167+
</div>
68168
</td>
69169
</React.Fragment>
70170
);
71171
})}
72172
</tr>
73173
{hasChildren && isExpanded && item.children.map(child => (
74-
<TableRow key={child.id} item={child} schema={schema} level={level + 1} onRowClick={onRowClick} columnWidths={columnWidths} updateWidth={updateWidth} rowClassName={rowClassName} />
174+
<TableRow key={child.id} item={child} schema={schema} level={level + 1} onRowClick={onRowClick} columnWidths={columnWidths} updateWidth={updateWidth} rowClassName={rowClassName} setRowRef={setRowRef} links={links} linkColumn={linkColumn} />
75175
))}
76176
</React.Fragment>
77177
);
@@ -120,8 +220,14 @@ const TableHeader = ({ schema, columnWidths, updateWidth }) => {
120220
);
121221
};
122222

123-
const TableBody = ({ schema, onRowClick, columnWidths, updateWidth, rowClassName }) => {
223+
const TableBody = ({ schema, onRowClick, columnWidths, updateWidth, rowClassName, setRowRef, links, linkColumn }) => {
124224
const { sortedData } = useHierarchicalTable();
225+
const [, forceUpdate] = useState({});
226+
227+
useEffect(() => {
228+
// Force a re-render to trigger position updates
229+
forceUpdate({});
230+
}, [sortedData]);
125231

126232
return (
127233
<tbody>
@@ -134,6 +240,9 @@ const TableBody = ({ schema, onRowClick, columnWidths, updateWidth, rowClassName
134240
columnWidths={columnWidths}
135241
updateWidth={updateWidth}
136242
rowClassName={rowClassName}
243+
setRowRef={setRowRef}
244+
links={links}
245+
linkColumn={linkColumn}
137246
/>
138247
))}
139248
</tbody>
@@ -190,10 +299,10 @@ const PaginationControls = ({ currentPage, totalPages, onPageChange, pageSize, t
190299
);
191300
};
192301

193-
const HierarchicalTable = ({ schema, data, onRowClick, onSelectionChange, initialSortConfig, rowClassName, currentPage, onPageChange, pageSize, totalItems, omitColumns, expandAll }) => {
302+
const HierarchicalTable = ({ schema, data, onRowClick, onSelectionChange, initialSortConfig, rowClassName, currentPage, onPageChange, pageSize, totalItems, omitColumns, expandAll, links, linkColumn }) => {
194303
const [columnWidths, setColumnWidths] = useState({});
195304
const [isExpanded, setIsExpanded] = useState(false);
196-
305+
const [rowRefs, setRowRefs] = useState({});
197306

198307
const updateWidth = (key, width, maxWidth) => {
199308
setColumnWidths(prev => ({
@@ -202,6 +311,25 @@ const HierarchicalTable = ({ schema, data, onRowClick, onSelectionChange, initia
202311
}));
203312
};
204313

314+
const tableRef = useRef(null);
315+
const [tableOffset, setTableOffset] = useState({ x: 0, y: 0 });
316+
317+
useEffect(() => {
318+
if (tableRef.current) {
319+
const rect = tableRef.current.getBoundingClientRect();
320+
setTableOffset({ x: rect.left, y: rect.top });
321+
}
322+
}, []);
323+
324+
const setRowRef = useCallback((id, ref) => {
325+
setRowRefs(prev => {
326+
if (JSON.stringify(prev[id]) === JSON.stringify(ref)) {
327+
return prev;
328+
}
329+
return { ...prev, [id]: ref };
330+
});
331+
}, []);
332+
205333
useEffect(() => {
206334
const initialWidths = {};
207335
schema.columns.forEach(column => {
@@ -233,7 +361,7 @@ const HierarchicalTable = ({ schema, data, onRowClick, onSelectionChange, initia
233361
setIsExpanded={setIsExpanded}
234362
expandAll={expandAll}
235363
>
236-
<div className="overflow-x-auto hide-scrollbar">
364+
<div className="overflow-x-auto hide-scrollbar relative" ref={tableRef}>
237365
<table className="w-full">
238366
<TableHeader
239367
schema={filteredSchema}
@@ -246,8 +374,23 @@ const HierarchicalTable = ({ schema, data, onRowClick, onSelectionChange, initia
246374
columnWidths={columnWidths}
247375
updateWidth={updateWidth}
248376
rowClassName={rowClassName}
377+
setRowRef={setRowRef}
378+
links={links}
379+
linkColumn={linkColumn}
249380
/>
250381
</table>
382+
383+
{/* Update SVG rendering for direct lines */}
384+
<svg
385+
className="absolute top-0 left-0 w-full h-full pointer-events-none"
386+
style={{ overflow: 'visible' }}
387+
>
388+
<LinkLines
389+
links={links}
390+
rowRefs={rowRefs}
391+
tableOffset={tableOffset}
392+
/>
393+
</svg>
251394
</div>
252395
{onPageChange && (
253396
<PaginationControls
@@ -261,5 +404,69 @@ const HierarchicalTable = ({ schema, data, onRowClick, onSelectionChange, initia
261404
</HierarchicalTableProvider>
262405
);
263406
};
407+
const LinkLines = ({ links, rowRefs, tableOffset }) => {
408+
const { hoveredRow, expandedRows } = useHierarchicalTable();
409+
// Memoize the grouping and sorting of links
410+
const groupedLinks = useMemo(() => {
411+
const grouped = links?.reduce((acc, link) => {
412+
if (!acc[link.from]) acc[link.from] = [];
413+
// Check for uniqueness of the link
414+
const isUnique = !acc[link.from].some(existingLink =>
415+
existingLink.from === link.from && existingLink.to === link.to
416+
);
417+
418+
if (isUnique) {
419+
acc[link.from].push(link);
420+
} else {
421+
console.warn(`Duplicate link found: from ${link.from} to ${link.to}`);
422+
}
423+
return acc;
424+
}, {});
425+
426+
// Sort each group by distance
427+
Object.values(grouped).forEach(group => {
428+
group.sort((a, b) => {
429+
const distA = Math.abs(rowRefs[a.to]?.y - rowRefs[a.from]?.y);
430+
const distB = Math.abs(rowRefs[b.to]?.y - rowRefs[b.from]?.y);
431+
return distA - distB;
432+
});
433+
});
434+
435+
return grouped;
436+
}, [links, rowRefs]);
437+
438+
439+
440+
return links?.map((link, index) => {
441+
const startRow = rowRefs[link.from];
442+
const endRow = rowRefs[link.to];
443+
444+
445+
// Only render the link if both rows are expanded
446+
if (startRow && endRow && startRow?.visible && endRow?.visible) {
447+
const isHighlighted = hoveredRow === link.from ||
448+
hoveredRow === link.to;
449+
const offset = (groupedLinks[link.from].indexOf(link)+ 2) * 5; // Multiply by 20 for spacing
450+
const color = isHighlighted
451+
? (hoveredRow === link.from ? '#f97316' : '#3b82f6') // Orange if going from, Blue if coming to
452+
: '#4a5568';
453+
return (
454+
<SmoothLine
455+
key={index}
456+
startX={startRow.x}
457+
startY={startRow.y}
458+
endX={endRow.x}
459+
endY={endRow.y}
460+
color={color}
461+
animated={isHighlighted}
462+
opacity={isHighlighted ? 1 : 0.3}
463+
offset={offset}
464+
/>
465+
);
466+
}
467+
468+
return null;
469+
});
470+
};
264471

265472
export default HierarchicalTable;

ell-studio/src/components/HierarchicalTableContext.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const HierarchicalTableProvider = ({ children, data, onSelectionChange, i
88
const [expandedRows, setExpandedRows] = useState({});
99
const [selectedRows, setSelectedRows] = useState({});
1010
const [sortConfig, setSortConfig] = useState(initialSortConfig || { key: null, direction: 'asc' });
11+
const [hoveredRow, setHoveredRow] = useState(null);
1112
useEffect(() => {
1213
const allParentRowsCollapsed = data.every(item => !expandedRows[item.id]);
1314
setIsExpanded(!allParentRowsCollapsed);
@@ -109,6 +110,8 @@ export const HierarchicalTableProvider = ({ children, data, onSelectionChange, i
109110
sortConfig,
110111
onSort,
111112
sortedData,
113+
hoveredRow,
114+
setHoveredRow,
112115
};
113116

114117
return (

0 commit comments

Comments
 (0)