@growthub/cli 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +262 -33
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +35 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +8 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/export-training-traces.mjs +144 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/grade-raw-pairs.mjs +279 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/harvest-cursor-traces.mjs +288 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/upload-graded-traces.mjs +128 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +10 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/alignment-loop.config.json +264 -0
- package/dist/index.js +1416 -2627
- package/package.json +1 -1
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
addTableRow,
|
|
50
50
|
appendRowsToTable,
|
|
51
51
|
createTypedBusinessObject,
|
|
52
|
+
deleteTableRow,
|
|
52
53
|
describeBindingLane,
|
|
53
54
|
effectiveRelations,
|
|
54
55
|
exportTableAsCsv,
|
|
@@ -310,9 +311,13 @@ function ObjectViewPicker({ tables, selectedTable, saving, onSelectSource, onSav
|
|
|
310
311
|
</div>
|
|
311
312
|
)}
|
|
312
313
|
<div className="dm-picker-tabs">
|
|
313
|
-
{[
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
{[
|
|
315
|
+
{ id: "all", label: "All" },
|
|
316
|
+
{ id: "objects", label: "Objects" },
|
|
317
|
+
{ id: "views", label: "Views" },
|
|
318
|
+
].map((item) => (
|
|
319
|
+
<button key={item.id} type="button" className={mode === item.id ? "active" : ""} onClick={() => setMode(item.id)}>
|
|
320
|
+
{item.label}
|
|
316
321
|
</button>
|
|
317
322
|
))}
|
|
318
323
|
</div>
|
|
@@ -1399,10 +1404,23 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave
|
|
|
1399
1404
|
const [filterDraft, setFilterDraft] = useState({ fieldId: "", operator: "eq", value: "" });
|
|
1400
1405
|
const [filterTarget, setFilterTarget] = useState("");
|
|
1401
1406
|
const [menuColumn, setMenuColumn] = useState("");
|
|
1407
|
+
const [selectedRows, setSelectedRows] = useState(() => new Set());
|
|
1408
|
+
const [confirmDeleteSelection, setConfirmDeleteSelection] = useState(false);
|
|
1409
|
+
const [lastSelectedRowIndex, setLastSelectedRowIndex] = useState(null);
|
|
1410
|
+
const [selectMenuOpen, setSelectMenuOpen] = useState(false);
|
|
1411
|
+
const [pageSize, setPageSize] = useState(15);
|
|
1412
|
+
const [pageIndex, setPageIndex] = useState(0);
|
|
1402
1413
|
const fieldInputRef = useRef(null);
|
|
1403
1414
|
|
|
1404
1415
|
useEffect(() => { if (addingField) fieldInputRef.current?.focus(); }, [addingField]);
|
|
1405
|
-
useEffect(() => {
|
|
1416
|
+
useEffect(() => {
|
|
1417
|
+
setSelectedRow(null);
|
|
1418
|
+
setSelectedRows(new Set());
|
|
1419
|
+
setConfirmDeleteSelection(false);
|
|
1420
|
+
setLastSelectedRowIndex(null);
|
|
1421
|
+
setSelectMenuOpen(false);
|
|
1422
|
+
setPageIndex(0);
|
|
1423
|
+
}, [table.id]);
|
|
1406
1424
|
useEffect(() => {
|
|
1407
1425
|
setFieldName("");
|
|
1408
1426
|
setFieldType("text");
|
|
@@ -1430,6 +1448,25 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave
|
|
|
1430
1448
|
() => (settings.views || []).find((view) => view.id === settings.activeViewId) || null,
|
|
1431
1449
|
[settings.views, settings.activeViewId]
|
|
1432
1450
|
);
|
|
1451
|
+
const selectedRowCount = selectedRows.size;
|
|
1452
|
+
const pageCount = Math.max(1, Math.ceil(rowEntries.length / pageSize));
|
|
1453
|
+
const safePageIndex = Math.min(pageIndex, pageCount - 1);
|
|
1454
|
+
const pageStart = safePageIndex * pageSize;
|
|
1455
|
+
const pageEntries = rowEntries.slice(pageStart, pageStart + pageSize);
|
|
1456
|
+
const pageEnd = Math.min(pageStart + pageSize, rowEntries.length);
|
|
1457
|
+
const pageSelectedCount = pageEntries.filter((entry) => selectedRows.has(entry.originalIndex)).length;
|
|
1458
|
+
const allPageSelected = pageEntries.length > 0 && pageSelectedCount === pageEntries.length;
|
|
1459
|
+
|
|
1460
|
+
useEffect(() => {
|
|
1461
|
+
setPageIndex((current) => Math.min(current, pageCount - 1));
|
|
1462
|
+
}, [pageCount]);
|
|
1463
|
+
|
|
1464
|
+
useEffect(() => {
|
|
1465
|
+
setPageIndex(0);
|
|
1466
|
+
setSelectedRow(null);
|
|
1467
|
+
setLastSelectedRowIndex(null);
|
|
1468
|
+
setSelectMenuOpen(false);
|
|
1469
|
+
}, [settings.filter, settings.sort, pageSize]);
|
|
1433
1470
|
|
|
1434
1471
|
function commitField() {
|
|
1435
1472
|
const name = fieldName.trim();
|
|
@@ -1540,6 +1577,78 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave
|
|
|
1540
1577
|
}));
|
|
1541
1578
|
}
|
|
1542
1579
|
|
|
1580
|
+
function toggleRowSelection(originalIndex, visibleIndex, event) {
|
|
1581
|
+
setConfirmDeleteSelection(false);
|
|
1582
|
+
setSelectMenuOpen(false);
|
|
1583
|
+
setSelectedRows((current) => {
|
|
1584
|
+
const next = new Set(current);
|
|
1585
|
+
if (event?.shiftKey && lastSelectedRowIndex !== null) {
|
|
1586
|
+
const start = Math.min(lastSelectedRowIndex, visibleIndex);
|
|
1587
|
+
const end = Math.max(lastSelectedRowIndex, visibleIndex);
|
|
1588
|
+
rowEntries.slice(start, end + 1).forEach((entry) => next.add(entry.originalIndex));
|
|
1589
|
+
} else if (next.has(originalIndex)) {
|
|
1590
|
+
next.delete(originalIndex);
|
|
1591
|
+
} else {
|
|
1592
|
+
next.add(originalIndex);
|
|
1593
|
+
}
|
|
1594
|
+
return next;
|
|
1595
|
+
});
|
|
1596
|
+
setLastSelectedRowIndex(visibleIndex);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
function clearRowSelection() {
|
|
1600
|
+
setSelectedRows(new Set());
|
|
1601
|
+
setConfirmDeleteSelection(false);
|
|
1602
|
+
setLastSelectedRowIndex(null);
|
|
1603
|
+
setSelectMenuOpen(false);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
function selectCurrentPage() {
|
|
1607
|
+
setConfirmDeleteSelection(false);
|
|
1608
|
+
setSelectedRows((current) => {
|
|
1609
|
+
const next = new Set(current);
|
|
1610
|
+
pageEntries.forEach((entry) => next.add(entry.originalIndex));
|
|
1611
|
+
return next;
|
|
1612
|
+
});
|
|
1613
|
+
setLastSelectedRowIndex(pageEntries.length ? pageStart : null);
|
|
1614
|
+
setSelectMenuOpen(false);
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
function toggleCurrentPageSelection() {
|
|
1618
|
+
setConfirmDeleteSelection(false);
|
|
1619
|
+
setSelectedRows((current) => {
|
|
1620
|
+
const next = new Set(current);
|
|
1621
|
+
if (allPageSelected) pageEntries.forEach((entry) => next.delete(entry.originalIndex));
|
|
1622
|
+
else pageEntries.forEach((entry) => next.add(entry.originalIndex));
|
|
1623
|
+
return next;
|
|
1624
|
+
});
|
|
1625
|
+
setLastSelectedRowIndex(pageEntries.length ? pageStart : null);
|
|
1626
|
+
setSelectMenuOpen(false);
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
function selectAllFilteredRows() {
|
|
1630
|
+
setConfirmDeleteSelection(false);
|
|
1631
|
+
setSelectedRows((current) => {
|
|
1632
|
+
const next = new Set(current);
|
|
1633
|
+
rowEntries.forEach((entry) => next.add(entry.originalIndex));
|
|
1634
|
+
return next;
|
|
1635
|
+
});
|
|
1636
|
+
setLastSelectedRowIndex(rowEntries.length ? 0 : null);
|
|
1637
|
+
setSelectMenuOpen(false);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
function deleteSelectedRows() {
|
|
1641
|
+
if (!selectedRows.size) return;
|
|
1642
|
+
if (!confirmDeleteSelection) {
|
|
1643
|
+
setConfirmDeleteSelection(true);
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
const rowIndexes = Array.from(selectedRows).sort((a, b) => b - a);
|
|
1647
|
+
onSave((config) => rowIndexes.reduce((nextConfig, rowIndex) => deleteTableRow(nextConfig, table, rowIndex), config));
|
|
1648
|
+
setSelectedRow(null);
|
|
1649
|
+
clearRowSelection();
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1543
1652
|
const selectedEntry = selectedRow === null ? null : rowEntries[selectedRow];
|
|
1544
1653
|
const selectedRecord = selectedEntry?.row || null;
|
|
1545
1654
|
|
|
@@ -1553,6 +1662,11 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave
|
|
|
1553
1662
|
)}
|
|
1554
1663
|
<div className="dm-db-toolbar">
|
|
1555
1664
|
<div className="dm-filter-chip-row">
|
|
1665
|
+
{selectedRowCount > 0 && (
|
|
1666
|
+
<span className="dm-filter-chip dm-selection-count">
|
|
1667
|
+
{pluralize(selectedRowCount, "record")} selected
|
|
1668
|
+
</span>
|
|
1669
|
+
)}
|
|
1556
1670
|
{settings.filter?.clauses?.map((clause) => (
|
|
1557
1671
|
<button key={`${clause.fieldId}:${clause.operator}`} type="button" className="dm-filter-chip" onClick={() => removeFilter(clause.fieldId)}>
|
|
1558
1672
|
<LucideIcon name={FIELD_TYPE_ICON_NAMES[settings.types?.[clause.fieldId] || inferFieldType(clause.fieldId)] || "Type"} size={12} />
|
|
@@ -1562,9 +1676,24 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave
|
|
|
1562
1676
|
))}
|
|
1563
1677
|
</div>
|
|
1564
1678
|
<div className="dm-records-actions">
|
|
1565
|
-
<
|
|
1566
|
-
<
|
|
1567
|
-
|
|
1679
|
+
<span className="dm-filter-anchor">
|
|
1680
|
+
<button type="button" className="dm-btn-ghost" onClick={() => setFilterTarget((current) => current === "toolbar" ? "" : "toolbar")}>
|
|
1681
|
+
<Filter size={13} />Filter
|
|
1682
|
+
</button>
|
|
1683
|
+
{filterTarget === "toolbar" && (
|
|
1684
|
+
<div className="dm-filter-popover dm-filter-popover-toolbar">
|
|
1685
|
+
<StaticSelect value={filterDraft.fieldId} options={visibleColumns.map((column) => ({ value: column, label: column }))} onChange={(next) => setFilterDraft((current) => ({ ...current, fieldId: next }))} />
|
|
1686
|
+
<StaticSelect value={filterDraft.operator} options={FILTER_OPERATOR_OPTIONS.map((item) => ({ value: item.value, label: item.label }))} onChange={(next) => setFilterDraft((current) => ({ ...current, operator: next }))} />
|
|
1687
|
+
{!["isEmpty", "isNotEmpty"].includes(filterDraft.operator) && (
|
|
1688
|
+
<input value={filterDraft.value} placeholder="Value" onChange={(event) => setFilterDraft((current) => ({ ...current, value: event.target.value }))} />
|
|
1689
|
+
)}
|
|
1690
|
+
<div className="dm-filter-popover-actions">
|
|
1691
|
+
<button type="button" className="dm-btn-outline" onClick={() => setFilterTarget("")}>Cancel</button>
|
|
1692
|
+
<button type="button" className="dm-btn-primary-sm" onClick={applyFilter}>Apply</button>
|
|
1693
|
+
</div>
|
|
1694
|
+
</div>
|
|
1695
|
+
)}
|
|
1696
|
+
</span>
|
|
1568
1697
|
{activeView ? (
|
|
1569
1698
|
<button type="button" className="dm-btn-ghost" onClick={updateCurrentView}>
|
|
1570
1699
|
Update view
|
|
@@ -1589,21 +1718,16 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave
|
|
|
1589
1718
|
<Plus size={13} />Add record
|
|
1590
1719
|
</button>
|
|
1591
1720
|
)}
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
<input value={filterDraft.value} placeholder="Value" onChange={(event) => setFilterDraft((current) => ({ ...current, value: event.target.value }))} />
|
|
1721
|
+
{table.mutable && selectedRowCount > 0 && (
|
|
1722
|
+
<>
|
|
1723
|
+
<button type="button" className="dm-btn-ghost" disabled={saving} onClick={clearRowSelection}>Cancel selection</button>
|
|
1724
|
+
<button type="button" className="dm-btn-danger-sm" disabled={saving} onClick={deleteSelectedRows}>
|
|
1725
|
+
{confirmDeleteSelection ? `Confirm delete ${selectedRowCount}` : "Delete"}
|
|
1726
|
+
</button>
|
|
1727
|
+
</>
|
|
1600
1728
|
)}
|
|
1601
|
-
<div className="dm-filter-popover-actions">
|
|
1602
|
-
<button type="button" className="dm-btn-outline" onClick={() => setFilterTarget("")}>Cancel</button>
|
|
1603
|
-
<button type="button" className="dm-btn-primary-sm" onClick={applyFilter}>Apply</button>
|
|
1604
|
-
</div>
|
|
1605
1729
|
</div>
|
|
1606
|
-
|
|
1730
|
+
</div>
|
|
1607
1731
|
{csvOpen && (
|
|
1608
1732
|
<div className="dm-csv-panel">
|
|
1609
1733
|
<textarea className="dm-csv-textarea" rows={4} value={csvText} onChange={(e) => setCsvText(e.target.value)} placeholder={"Name,Status\nAcme,Active"} />
|
|
@@ -1615,10 +1739,30 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave
|
|
|
1615
1739
|
</div>
|
|
1616
1740
|
)}
|
|
1617
1741
|
<div className="dm-db-grid-wrap">
|
|
1742
|
+
<div className="dm-db-grid-scroll">
|
|
1618
1743
|
<table className="dm-db-grid">
|
|
1619
1744
|
<thead>
|
|
1620
1745
|
<tr>
|
|
1621
|
-
<th className="dm-db-rownum"
|
|
1746
|
+
<th className="dm-db-rownum dm-db-rownum-head">
|
|
1747
|
+
{table.mutable ? (
|
|
1748
|
+
<div className="dm-row-select-head-wrap">
|
|
1749
|
+
<button type="button" className="dm-row-select dm-row-select-all" aria-label={allPageSelected ? "Clear page selection" : "Select current page"} aria-pressed={allPageSelected} onClick={(event) => { event.stopPropagation(); toggleCurrentPageSelection(); }}>
|
|
1750
|
+
<span className="dm-row-select-box" />
|
|
1751
|
+
<span className="dm-row-number">#</span>
|
|
1752
|
+
</button>
|
|
1753
|
+
<button type="button" className="dm-row-select-menu-btn" aria-label="Selection options" aria-expanded={selectMenuOpen} onClick={(event) => { event.stopPropagation(); setSelectMenuOpen((open) => !open); }}>
|
|
1754
|
+
<ChevronDown size={11} />
|
|
1755
|
+
</button>
|
|
1756
|
+
{selectMenuOpen && (
|
|
1757
|
+
<div className="dm-row-select-menu">
|
|
1758
|
+
<button type="button" onClick={selectCurrentPage}>Select page</button>
|
|
1759
|
+
<button type="button" onClick={selectAllFilteredRows}>Select all filtered</button>
|
|
1760
|
+
<button type="button" disabled={!selectedRowCount} onClick={clearRowSelection}>Clear selection</button>
|
|
1761
|
+
</div>
|
|
1762
|
+
)}
|
|
1763
|
+
</div>
|
|
1764
|
+
) : "#"}
|
|
1765
|
+
</th>
|
|
1622
1766
|
{visibleColumns.map((column) => (
|
|
1623
1767
|
<th key={column}>
|
|
1624
1768
|
<button type="button" className="dm-db-head-btn" onClick={() => setMenuColumn((current) => current === column ? "" : column)}>
|
|
@@ -1694,9 +1838,19 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave
|
|
|
1694
1838
|
</tr>
|
|
1695
1839
|
</thead>
|
|
1696
1840
|
<tbody>
|
|
1697
|
-
{
|
|
1698
|
-
|
|
1699
|
-
|
|
1841
|
+
{pageEntries.map(({ row, originalIndex }, rowIndex) => {
|
|
1842
|
+
const visibleIndex = pageStart + rowIndex;
|
|
1843
|
+
const displayIndex = visibleIndex + 1;
|
|
1844
|
+
return (
|
|
1845
|
+
<tr key={`${originalIndex}:${visibleIndex}`} className={`${selectedRow === visibleIndex ? "selected" : ""}${selectedRows.has(originalIndex) ? " multi-selected" : ""}`} onClick={() => setSelectedRow(visibleIndex)}>
|
|
1846
|
+
<td className="dm-db-rownum">
|
|
1847
|
+
{table.mutable ? (
|
|
1848
|
+
<button type="button" className="dm-row-select" aria-label={selectedRows.has(originalIndex) ? `Deselect row ${displayIndex}` : `Select row ${displayIndex}`} aria-pressed={selectedRows.has(originalIndex)} onClick={(event) => { event.stopPropagation(); toggleRowSelection(originalIndex, visibleIndex, event); }}>
|
|
1849
|
+
<span className="dm-row-select-box" />
|
|
1850
|
+
<span className="dm-row-number">{displayIndex}</span>
|
|
1851
|
+
</button>
|
|
1852
|
+
) : displayIndex}
|
|
1853
|
+
</td>
|
|
1700
1854
|
{visibleColumns.map((column) => {
|
|
1701
1855
|
const relation = relationForColumn(table, column);
|
|
1702
1856
|
return (
|
|
@@ -1721,7 +1875,7 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave
|
|
|
1721
1875
|
);})}
|
|
1722
1876
|
{table.mutable && <td className="dm-db-empty-cell" />}
|
|
1723
1877
|
</tr>
|
|
1724
|
-
))}
|
|
1878
|
+
);})}
|
|
1725
1879
|
{table.mutable && (
|
|
1726
1880
|
<tr className="dm-db-new-row" onClick={() => onSave((config) => addTableRow(config, table))}>
|
|
1727
1881
|
<td className="dm-db-rownum">+</td>
|
|
@@ -1730,6 +1884,24 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave
|
|
|
1730
1884
|
)}
|
|
1731
1885
|
</tbody>
|
|
1732
1886
|
</table>
|
|
1887
|
+
</div>
|
|
1888
|
+
<div className="dm-pagination-bar">
|
|
1889
|
+
<span className="dm-pagination-summary">Showing {rowEntries.length ? pageStart + 1 : 0}-{pageEnd} of {rowEntries.length}</span>
|
|
1890
|
+
<div className="dm-pagination-controls">
|
|
1891
|
+
<label className="dm-page-size-control">
|
|
1892
|
+
<span>Rows</span>
|
|
1893
|
+
<select value={pageSize} onChange={(event) => { setPageSize(Number(event.target.value)); setPageIndex(0); }}>
|
|
1894
|
+
<option value={15}>15</option>
|
|
1895
|
+
<option value={25}>25</option>
|
|
1896
|
+
<option value={50}>50</option>
|
|
1897
|
+
<option value={100}>100</option>
|
|
1898
|
+
</select>
|
|
1899
|
+
</label>
|
|
1900
|
+
<button type="button" className="dm-pagination-btn" disabled={safePageIndex === 0} onClick={() => setPageIndex((current) => Math.max(0, current - 1))}>Previous</button>
|
|
1901
|
+
<span className="dm-pagination-page">{safePageIndex + 1} / {pageCount}</span>
|
|
1902
|
+
<button type="button" className="dm-pagination-btn" disabled={safePageIndex >= pageCount - 1} onClick={() => setPageIndex((current) => Math.min(pageCount - 1, current + 1))}>Next</button>
|
|
1903
|
+
</div>
|
|
1904
|
+
</div>
|
|
1733
1905
|
</div>
|
|
1734
1906
|
<DataModelRecordDrawer
|
|
1735
1907
|
table={table}
|
|
@@ -1911,6 +2083,12 @@ function AddObjectSidebar({ open, saving, onClose, onCreate, allTables }) {
|
|
|
1911
2083
|
|
|
1912
2084
|
// ─── Page ─────────────────────────────────────────────────────────────────────
|
|
1913
2085
|
|
|
2086
|
+
// Auto-save tempo: hold local edits in memory + localStorage, only PATCH the
|
|
2087
|
+
// server after this idle window. Keeps growthub.config.json from rewriting on
|
|
2088
|
+
// every keystroke and lets the UI stay snappy on slow disks.
|
|
2089
|
+
const SAVE_DEBOUNCE_MS = 20000;
|
|
2090
|
+
const LOCAL_CACHE_KEY = "growthub.workspace.dataModel.localDraft.v1";
|
|
2091
|
+
|
|
1914
2092
|
export default function DataModelShell() {
|
|
1915
2093
|
const [workspaceConfig, setWorkspaceConfig] = useState(null);
|
|
1916
2094
|
const [authority, setAuthority] = useState(null);
|
|
@@ -1920,6 +2098,8 @@ export default function DataModelShell() {
|
|
|
1920
2098
|
const [message, setMessage] = useState("");
|
|
1921
2099
|
const [selectedSource, setSelectedSource] = useState("");
|
|
1922
2100
|
const [addOpen, setAddOpen] = useState(false);
|
|
2101
|
+
const pendingPatchRef = useRef({});
|
|
2102
|
+
const saveTimerRef = useRef(null);
|
|
1923
2103
|
|
|
1924
2104
|
const load = useCallback(async () => {
|
|
1925
2105
|
setLoading(true);
|
|
@@ -1950,16 +2130,19 @@ export default function DataModelShell() {
|
|
|
1950
2130
|
if (!selectedSource && tables[0]) setSelectedSource(tables[0].source);
|
|
1951
2131
|
}, [selectedSource, tables]);
|
|
1952
2132
|
|
|
1953
|
-
|
|
1954
|
-
|
|
2133
|
+
// Flush any accumulated patch keys to the server. Called by the debounce
|
|
2134
|
+
// timer and on visibilitychange/beforeunload so no local edit is lost.
|
|
2135
|
+
const flushPendingPatch = useCallback(async () => {
|
|
2136
|
+
const patch = pendingPatchRef.current;
|
|
2137
|
+
pendingPatchRef.current = {};
|
|
2138
|
+
if (saveTimerRef.current) {
|
|
2139
|
+
clearTimeout(saveTimerRef.current);
|
|
2140
|
+
saveTimerRef.current = null;
|
|
2141
|
+
}
|
|
2142
|
+
if (Object.keys(patch).length === 0) return;
|
|
1955
2143
|
setSaving(true);
|
|
1956
2144
|
setMessage("");
|
|
1957
|
-
const next = mutate(workspaceConfig);
|
|
1958
2145
|
try {
|
|
1959
|
-
const patch = {};
|
|
1960
|
-
for (const key of ["dashboards", "widgetTypes", "canvas", "dataModel"]) {
|
|
1961
|
-
if (next[key] !== workspaceConfig[key]) patch[key] = next[key];
|
|
1962
|
-
}
|
|
1963
2146
|
const res = await fetch("/api/workspace", {
|
|
1964
2147
|
method: "PATCH",
|
|
1965
2148
|
headers: { "content-type": "application/json" },
|
|
@@ -1969,12 +2152,58 @@ export default function DataModelShell() {
|
|
|
1969
2152
|
if (!res.ok) throw new Error(payload.error || "Save failed");
|
|
1970
2153
|
setWorkspaceConfig(payload.workspaceConfig);
|
|
1971
2154
|
setMessage("Saved");
|
|
2155
|
+
try { window.localStorage.removeItem(LOCAL_CACHE_KEY); } catch {}
|
|
1972
2156
|
} catch (err) {
|
|
1973
2157
|
setMessage(`Error: ${err.message || "Save failed"}`);
|
|
1974
2158
|
} finally {
|
|
1975
2159
|
setSaving(false);
|
|
1976
2160
|
}
|
|
1977
|
-
}, [
|
|
2161
|
+
}, []);
|
|
2162
|
+
|
|
2163
|
+
// Mutate-in-memory immediately so the UI feels instant, persist a draft to
|
|
2164
|
+
// localStorage every change, and only PATCH the server after SAVE_DEBOUNCE_MS
|
|
2165
|
+
// of idleness. Sandbox-environment objects' lastRunId/lastResponse fields
|
|
2166
|
+
// bypass the debounce (they need durability for run telemetry).
|
|
2167
|
+
const save = useCallback((mutate) => {
|
|
2168
|
+
setWorkspaceConfig((current) => {
|
|
2169
|
+
if (!current) return current;
|
|
2170
|
+
const next = mutate(current);
|
|
2171
|
+
const patch = pendingPatchRef.current;
|
|
2172
|
+
let touchedSandboxRun = false;
|
|
2173
|
+
for (const key of ["dashboards", "widgetTypes", "canvas", "dataModel"]) {
|
|
2174
|
+
if (next[key] !== current[key]) patch[key] = next[key];
|
|
2175
|
+
}
|
|
2176
|
+
try {
|
|
2177
|
+
const sandboxKey = JSON.stringify((next.dataModel?.objects || []).find((o) => o.objectType === "sandbox-environment")?.rows || []);
|
|
2178
|
+
const prevSandboxKey = JSON.stringify((current.dataModel?.objects || []).find((o) => o.objectType === "sandbox-environment")?.rows || []);
|
|
2179
|
+
if (sandboxKey !== prevSandboxKey) touchedSandboxRun = true;
|
|
2180
|
+
} catch {}
|
|
2181
|
+
try {
|
|
2182
|
+
window.localStorage.setItem(LOCAL_CACHE_KEY, JSON.stringify({ savedAt: Date.now(), patch }));
|
|
2183
|
+
} catch {}
|
|
2184
|
+
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
|
|
2185
|
+
if (touchedSandboxRun) {
|
|
2186
|
+
// immediate flush: durable sandbox run state must persist
|
|
2187
|
+
Promise.resolve().then(flushPendingPatch);
|
|
2188
|
+
} else {
|
|
2189
|
+
saveTimerRef.current = setTimeout(flushPendingPatch, SAVE_DEBOUNCE_MS);
|
|
2190
|
+
}
|
|
2191
|
+
return next;
|
|
2192
|
+
});
|
|
2193
|
+
}, [flushPendingPatch]);
|
|
2194
|
+
|
|
2195
|
+
// Flush before navigation / tab close so the 20s window never silently drops a draft.
|
|
2196
|
+
useEffect(() => {
|
|
2197
|
+
function handleBeforeUnload() { flushPendingPatch(); }
|
|
2198
|
+
function handleVisibility() { if (document.visibilityState === "hidden") flushPendingPatch(); }
|
|
2199
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
2200
|
+
document.addEventListener("visibilitychange", handleVisibility);
|
|
2201
|
+
return () => {
|
|
2202
|
+
window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
2203
|
+
document.removeEventListener("visibilitychange", handleVisibility);
|
|
2204
|
+
flushPendingPatch();
|
|
2205
|
+
};
|
|
2206
|
+
}, [flushPendingPatch]);
|
|
1978
2207
|
|
|
1979
2208
|
const createObject = useCallback(({ name, objectType, icon }) => {
|
|
1980
2209
|
save((config) => createTypedBusinessObject(config, { name, objectType, icon }));
|
package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css
CHANGED
|
@@ -3965,7 +3965,7 @@ code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0
|
|
|
3965
3965
|
.dm-detail-v2-title h2 { margin: 0; font-size: 15px; font-weight: 600; color: #111827; flex: 1; }
|
|
3966
3966
|
.dm-detail-v2-meta { display: flex; align-items: center; gap: 10px; padding-left: 21px; font-size: 12px; color: #9ca3af; }
|
|
3967
3967
|
.dm-detail-v2-meta code { font-size: 11px; color: #6b7280; background: #f3f4f6; border-radius: 4px; padding: 2px 6px; }
|
|
3968
|
-
.dm-detail-v3 { border: 1px solid #e5e7eb; border-radius: 12px; background: #fff; overflow:
|
|
3968
|
+
.dm-detail-v3 { border: 1px solid #e5e7eb; border-radius: 12px; background: #fff; overflow: hidden; }
|
|
3969
3969
|
.dm-detail-v3-head { padding-bottom: 14px; }
|
|
3970
3970
|
.dm-picker { position: relative; min-width: 280px; }
|
|
3971
3971
|
.dm-picker-trigger { width: min(420px, 100%); display: inline-flex; align-items: center; gap: 10px; min-height: 40px; border: 1px solid #dbe2ea; border-radius: 10px; background: #fff; color: #0f172a; box-shadow: 0 1px 2px rgba(15,23,42,.05); font: inherit; padding: 0 12px; cursor: pointer; text-align: left; }
|
|
@@ -4064,20 +4064,43 @@ code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0
|
|
|
4064
4064
|
.dm-inline-panel input { height: 32px; border: 1px solid #cbd5e1; border-radius: 8px; background: #fff; color: #111827; font: inherit; font-size: 12px; padding: 0 10px; }
|
|
4065
4065
|
.dm-filter-chip-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-height: 32px; }
|
|
4066
4066
|
.dm-filter-chip { display: inline-flex; align-items: center; gap: 6px; height: 30px; border: 1px solid #c7d2fe; border-radius: 8px; background: #eef2ff; color: #3730a3; font: inherit; font-size: 12px; padding: 0 10px; cursor: pointer; }
|
|
4067
|
+
.dm-selection-count { border-color: #d1d5db; background: #fff; color: #475569; cursor: default; }
|
|
4067
4068
|
.dm-filter-chip span { max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4068
4069
|
.dm-filter-popover { position: absolute; z-index: 40; display: grid; gap: 8px; width: 320px; padding: 10px; border: 1px solid #dbe2ea; border-radius: 10px; background: #fff; box-shadow: 0 18px 42px rgba(15,23,42,.18), 0 3px 10px rgba(15,23,42,.08); }
|
|
4069
4070
|
.dm-filter-popover input { height: 32px; border: 1px solid #cbd5e1; border-radius: 8px; background: #fff; color: #111827; font: inherit; font-size: 12px; padding: 0 10px; }
|
|
4070
|
-
.dm-filter-
|
|
4071
|
+
.dm-filter-anchor { position: relative; display: inline-flex; }
|
|
4072
|
+
.dm-filter-popover-toolbar { top: calc(100% + 6px); left: 0; right: auto; }
|
|
4071
4073
|
.dm-filter-popover-column { top: calc(100% + 6px); left: 0; }
|
|
4072
4074
|
.dm-filter-popover-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
|
4073
|
-
.dm-db-grid-wrap { flex: 1; min-height: 420px; overflow:
|
|
4075
|
+
.dm-db-grid-wrap { display: flex; flex: 1; min-height: 420px; flex-direction: column; overflow: hidden; border: 1px solid #dfe3e8; border-radius: 7px; background: #fff; }
|
|
4076
|
+
.dm-db-grid-wrap > .dm-db-grid-scroll { flex: 1 1 auto; overflow: auto; }
|
|
4074
4077
|
.dm-db-grid { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12px; color: #1f2937; }
|
|
4075
4078
|
.dm-db-grid th { position: sticky; top: 0; z-index: 1; height: 32px; background: #f8fafc; color: #475569; border-bottom: 1px solid #dfe3e8; border-right: 1px solid #edf0f3; padding: 0; text-align: left; font-weight: 650; white-space: nowrap; }
|
|
4076
4079
|
.dm-db-grid td { height: 32px; max-width: 260px; border-bottom: 1px solid #f1f5f9; border-right: 1px solid #f1f5f9; padding: 0 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
|
|
4077
4080
|
.dm-db-grid tbody tr:hover td { background: #f8fafc; }
|
|
4078
4081
|
.dm-db-grid tbody tr.selected td { background: #eef2ff; }
|
|
4082
|
+
.dm-db-grid tbody tr.multi-selected td { background: #f8fafc; }
|
|
4079
4083
|
.dm-db-head-btn { display: inline-flex; align-items: center; justify-content: flex-start; width: 100%; height: 32px; gap: 6px; border: 0; background: transparent; color: inherit; font: inherit; font-size: 12px; font-weight: inherit; padding: 0 10px; cursor: pointer; }
|
|
4080
4084
|
.dm-db-rownum { width: 42px; min-width: 42px; text-align: center !important; color: #94a3b8 !important; background: #f8fafc; font-variant-numeric: tabular-nums; }
|
|
4085
|
+
.dm-row-select { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 32px; border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer; }
|
|
4086
|
+
.dm-row-select-box { position: relative; display: none; width: 16px; height: 16px; border: 1px solid #d1d5db; border-radius: 5px; background: #fff; box-sizing: border-box; }
|
|
4087
|
+
.dm-row-select:hover .dm-row-select-box, .dm-row-select[aria-pressed="true"] .dm-row-select-box { border-color: #cbd5e1; background: #fff; box-shadow: 0 1px 3px rgba(15,23,42,.14); }
|
|
4088
|
+
.dm-row-select[aria-pressed="true"] .dm-row-select-box { display: inline-flex; }
|
|
4089
|
+
.dm-row-select[aria-pressed="true"] .dm-row-select-box::after { content: ""; position: absolute; left: 5px; top: 3px; width: 4px; height: 7px; border: solid #334155; border-width: 0 1.5px 1.5px 0; transform: rotate(45deg); }
|
|
4090
|
+
.dm-db-grid tbody tr:hover .dm-row-select-box { display: inline-flex; }
|
|
4091
|
+
.dm-row-select[aria-pressed="true"] .dm-row-number, .dm-db-grid tbody tr:hover .dm-row-select .dm-row-number { display: none; }
|
|
4092
|
+
.dm-db-rownum-head { position: sticky; overflow: visible; z-index: 4 !important; }
|
|
4093
|
+
.dm-row-select-head-wrap { position: relative; display: flex; align-items: center; justify-content: center; height: 32px; }
|
|
4094
|
+
.dm-row-select-all { width: 26px; flex: 0 0 26px; }
|
|
4095
|
+
.dm-row-select-menu-btn { display: none; }
|
|
4096
|
+
.dm-db-rownum-head:hover .dm-row-select-menu-btn, .dm-row-select-menu-btn[aria-expanded="true"] { display: none; }
|
|
4097
|
+
.dm-row-select-menu-btn:hover { background: #eef2f7; color: #475569; }
|
|
4098
|
+
.dm-db-rownum-head:hover .dm-row-select-all .dm-row-select-box, .dm-row-select-all[aria-pressed="true"] .dm-row-select-box { display: inline-flex; }
|
|
4099
|
+
.dm-db-rownum-head:hover .dm-row-select-all .dm-row-number, .dm-row-select-all[aria-pressed="true"] .dm-row-number { display: none; }
|
|
4100
|
+
.dm-row-select-menu { position: absolute; top: calc(100% + 6px); left: 6px; z-index: 50; display: grid; gap: 3px; min-width: 150px; padding: 6px; border: 1px solid #dbe2ea; border-radius: 8px; background: #fff; box-shadow: 0 14px 34px rgba(15,23,42,.16), 0 2px 8px rgba(15,23,42,.08); }
|
|
4101
|
+
.dm-row-select-menu button { display: flex; align-items: center; height: 28px; border: 0; border-radius: 6px; background: transparent; color: #334155; font: inherit; font-size: 12px; padding: 0 8px; text-align: left; cursor: pointer; }
|
|
4102
|
+
.dm-row-select-menu button:hover { background: #f1f5f9; }
|
|
4103
|
+
.dm-row-select-menu button:disabled { opacity: .45; cursor: not-allowed; }
|
|
4081
4104
|
.dm-db-field-type { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; margin-right: 5px; border-radius: 4px; color: #64748b; background: #eef2f7; vertical-align: middle; }
|
|
4082
4105
|
.dm-db-add-field { position: relative; min-width: 118px; }
|
|
4083
4106
|
.dm-db-add-field button { display: inline-flex; align-items: center; gap: 4px; border: 0; background: transparent; color: #64748b; font: inherit; font-size: 12px; cursor: pointer; }
|
|
@@ -4094,6 +4117,15 @@ code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0
|
|
|
4094
4117
|
.dm-field-creator-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
|
4095
4118
|
.dm-db-empty-cell { background: #fbfdff; }
|
|
4096
4119
|
.dm-db-new-row td { color: #64748b; background: #fbfdff; font-weight: 600; }
|
|
4120
|
+
.dm-pagination-bar { display: flex; flex: 0 0 auto; align-items: center; justify-content: space-between; gap: 12px; min-height: 36px; padding: 8px 14px; background: #fff; color: #64748b; font-size: 12px; border-top: 1px solid #edf0f3; }
|
|
4121
|
+
.dm-pagination-summary { white-space: nowrap; }
|
|
4122
|
+
.dm-pagination-controls { display: inline-flex; align-items: center; gap: 8px; }
|
|
4123
|
+
.dm-page-size-control { display: inline-flex; align-items: center; gap: 6px; color: #64748b; }
|
|
4124
|
+
.dm-page-size-control select { height: 28px; border: 1px solid #dbe2ea; border-radius: 6px; background: #fff; color: #334155; font: inherit; font-size: 12px; padding: 0 22px 0 8px; }
|
|
4125
|
+
.dm-pagination-btn { height: 28px; border: 1px solid #dbe2ea; border-radius: 6px; background: #fff; color: #334155; font: inherit; font-size: 12px; padding: 0 10px; cursor: pointer; }
|
|
4126
|
+
.dm-pagination-btn:hover:not(:disabled) { background: #f8fafc; border-color: #cbd5e1; }
|
|
4127
|
+
.dm-pagination-btn:disabled { opacity: .45; cursor: not-allowed; }
|
|
4128
|
+
.dm-pagination-page { min-width: 44px; text-align: center; color: #64748b; }
|
|
4097
4129
|
.dm-db-status { display: inline-flex; align-items: center; gap: 5px; height: 20px; border: 1px solid #e2e8f0; border-radius: 999px; padding: 0 8px; background: #f8fafc; color: #64748b; font-size: 11px; font-weight: 650; }
|
|
4098
4130
|
.dm-db-status span { width: 6px; height: 6px; border-radius: 50%; background: #94a3b8; }
|
|
4099
4131
|
.dm-db-status.ok { border-color: #bbf7d0; background: #f0fdf4; color: #166534; }
|
|
@@ -275,6 +275,7 @@ function deriveManualObjectTable(object) {
|
|
|
275
275
|
source,
|
|
276
276
|
objectType: object.objectType || "custom",
|
|
277
277
|
icon: object.icon || null,
|
|
278
|
+
pickerHidden: Boolean(object.pickerHidden),
|
|
278
279
|
columns,
|
|
279
280
|
rows,
|
|
280
281
|
binding: object.binding || { mode: "manual", source: "Data Model" },
|
|
@@ -1064,7 +1065,13 @@ function resolveLocalReferenceOptions(workspaceConfig, {
|
|
|
1064
1065
|
const pageSize = Math.min(100, Math.max(1, Number(relation.pageSize) || Number(limit) || 25));
|
|
1065
1066
|
const offset = decodeRefCursor(cursor);
|
|
1066
1067
|
|
|
1067
|
-
const
|
|
1068
|
+
const targetObjectId = typeof relation.targetObjectId === "string" && relation.targetObjectId.trim()
|
|
1069
|
+
? relation.targetObjectId.trim()
|
|
1070
|
+
: "";
|
|
1071
|
+
const targets = objects.filter((o) => (
|
|
1072
|
+
o.objectType === relation.targetObjectType
|
|
1073
|
+
&& (!targetObjectId || o.id === targetObjectId)
|
|
1074
|
+
));
|
|
1068
1075
|
const needle = String(query || "").trim().toLowerCase();
|
|
1069
1076
|
|
|
1070
1077
|
const candidates = [];
|
package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/export-training-traces.mjs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* helpers/export-training-traces.mjs — Distillation Pipeline V1, Phase 3
|
|
4
|
+
*
|
|
5
|
+
* Reads `training-traces.rows` from the live workspace, filters rows where
|
|
6
|
+
* qualityScore >= --min-score AND exported == "false", emits an Unsloth-ready
|
|
7
|
+
* JSONL of {instruction, input, output} on disk, then PATCHes the same rows
|
|
8
|
+
* with exported = "true" so they are not re-exported on the next run.
|
|
9
|
+
*
|
|
10
|
+
* Output format (one JSON object per line):
|
|
11
|
+
* {"instruction": "<system + task>", "input": "<user prompt>", "output": "<agent output>"}
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* node helpers/export-training-traces.mjs \
|
|
15
|
+
* --workspace http://localhost:3000 \
|
|
16
|
+
* --traces-object training-traces \
|
|
17
|
+
* --min-score 4 \
|
|
18
|
+
* --out ./antonio/distillation/unsloth-batch-001.jsonl \
|
|
19
|
+
* --instruction "You are growthub-local-expert. Respect AWaC V2 invariants and the PATCH allowlist." \
|
|
20
|
+
* [--dry-run]
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
|
|
26
|
+
function parseArgs(argv) {
|
|
27
|
+
const a = {
|
|
28
|
+
workspace: "http://localhost:3000",
|
|
29
|
+
tracesObject: "training-traces",
|
|
30
|
+
minScore: 4,
|
|
31
|
+
out: "",
|
|
32
|
+
instruction: "You are growthub-local-expert. Respect AWaC V2 invariants and the PATCH allowlist.",
|
|
33
|
+
dryRun: false,
|
|
34
|
+
};
|
|
35
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
36
|
+
const t = argv[i];
|
|
37
|
+
const next = () => String(argv[++i] || "").trim();
|
|
38
|
+
if (t === "--workspace") a.workspace = next().replace(/\/+$/, "");
|
|
39
|
+
else if (t === "--traces-object") a.tracesObject = next();
|
|
40
|
+
else if (t === "--min-score") a.minScore = Number(next()) || 4;
|
|
41
|
+
else if (t === "--out") a.out = next();
|
|
42
|
+
else if (t === "--instruction") a.instruction = next();
|
|
43
|
+
else if (t === "--dry-run") a.dryRun = true;
|
|
44
|
+
else if (t === "--help" || t === "-h") {
|
|
45
|
+
process.stdout.write(
|
|
46
|
+
"Usage: export-training-traces.mjs [--workspace URL] [--traces-object id] [--min-score N] --out <path> [--instruction TEXT] [--dry-run]\n",
|
|
47
|
+
);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!a.out) {
|
|
52
|
+
process.stderr.write("error: --out is required\n");
|
|
53
|
+
process.exit(2);
|
|
54
|
+
}
|
|
55
|
+
return a;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const args = parseArgs(process.argv.slice(2));
|
|
59
|
+
const outAbs = path.resolve(args.out);
|
|
60
|
+
fs.mkdirSync(path.dirname(outAbs), { recursive: true });
|
|
61
|
+
|
|
62
|
+
async function getObjects() {
|
|
63
|
+
const r = await fetch(`${args.workspace}/api/workspace`, { cache: "no-store" });
|
|
64
|
+
if (!r.ok) throw new Error(`GET /api/workspace ${r.status}`);
|
|
65
|
+
return (await r.json()).workspaceConfig.dataModel.objects;
|
|
66
|
+
}
|
|
67
|
+
async function patchObjects(objects) {
|
|
68
|
+
const r = await fetch(`${args.workspace}/api/workspace`, {
|
|
69
|
+
method: "PATCH",
|
|
70
|
+
headers: { "content-type": "application/json" },
|
|
71
|
+
body: JSON.stringify({ dataModel: { objects } }),
|
|
72
|
+
});
|
|
73
|
+
if (!r.ok) throw new Error(`PATCH ${r.status}: ${(await r.text()).slice(0, 300)}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const objects = await getObjects();
|
|
77
|
+
const tracesIdx = objects.findIndex((o) => o.id === args.tracesObject);
|
|
78
|
+
if (tracesIdx < 0) {
|
|
79
|
+
process.stderr.write(`error: object ${args.tracesObject} not found in workspace\n`);
|
|
80
|
+
process.exit(3);
|
|
81
|
+
}
|
|
82
|
+
const tracesObj = objects[tracesIdx];
|
|
83
|
+
const allRows = Array.isArray(tracesObj.rows) ? tracesObj.rows : [];
|
|
84
|
+
|
|
85
|
+
const eligible = allRows
|
|
86
|
+
.map((row, idx) => ({ row, idx }))
|
|
87
|
+
.filter(({ row }) =>
|
|
88
|
+
Number(row.qualityScore) >= args.minScore &&
|
|
89
|
+
String(row.exported || "false").toLowerCase() !== "true" &&
|
|
90
|
+
String(row.inputPrompt || "").trim() &&
|
|
91
|
+
String(row.agentOutput || "").trim(),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (eligible.length === 0) {
|
|
95
|
+
process.stdout.write(
|
|
96
|
+
JSON.stringify(
|
|
97
|
+
{
|
|
98
|
+
ok: true,
|
|
99
|
+
out: outAbs,
|
|
100
|
+
eligible: 0,
|
|
101
|
+
exported: 0,
|
|
102
|
+
totalRows: allRows.length,
|
|
103
|
+
reason: "no rows match score >= min-score AND exported == false",
|
|
104
|
+
},
|
|
105
|
+
null,
|
|
106
|
+
2,
|
|
107
|
+
) + "\n",
|
|
108
|
+
);
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const outStream = fs.createWriteStream(outAbs, { encoding: "utf8" });
|
|
113
|
+
for (const { row } of eligible) {
|
|
114
|
+
const sample = {
|
|
115
|
+
instruction: args.instruction,
|
|
116
|
+
input: String(row.inputPrompt),
|
|
117
|
+
output: String(row.agentOutput),
|
|
118
|
+
};
|
|
119
|
+
outStream.write(`${JSON.stringify(sample)}\n`);
|
|
120
|
+
}
|
|
121
|
+
await new Promise((r) => outStream.end(r));
|
|
122
|
+
|
|
123
|
+
if (!args.dryRun) {
|
|
124
|
+
const eligibleIdx = new Set(eligible.map((e) => e.idx));
|
|
125
|
+
const updatedRows = allRows.map((row, i) => (eligibleIdx.has(i) ? { ...row, exported: "true" } : row));
|
|
126
|
+
const nextObjects = objects.map((o, i) => (i !== tracesIdx ? o : { ...o, rows: updatedRows }));
|
|
127
|
+
await patchObjects(nextObjects);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
process.stdout.write(
|
|
131
|
+
JSON.stringify(
|
|
132
|
+
{
|
|
133
|
+
ok: true,
|
|
134
|
+
out: outAbs,
|
|
135
|
+
totalRows: allRows.length,
|
|
136
|
+
eligible: eligible.length,
|
|
137
|
+
exported: args.dryRun ? 0 : eligible.length,
|
|
138
|
+
dryRun: args.dryRun,
|
|
139
|
+
format: "unsloth-jsonl-v1 ({instruction, input, output})",
|
|
140
|
+
},
|
|
141
|
+
null,
|
|
142
|
+
2,
|
|
143
|
+
) + "\n",
|
|
144
|
+
);
|