Skip to content

Commit 3d46250

Browse files
committed
feat(previews): enhance scientific file previews and LaTeX compilation diagnostics
- Expanded the scientific file preview capabilities to include new formats such as MATLAB, Matrix Market, and FITS images, with improved visualization options for arrays and tables. - Updated the LaTeX compilation process to retain detailed diagnostics logs, separating final errors from resolved warnings, and ensuring that compilation failures are clearly reported. - Enhanced the handling of user selections in scientific previews, allowing for more precise dataset and slice management. - Improved the LaTeX editor's user interface to support new view modes and better manage compilation states, including error handling and user notifications. - Updated documentation to reflect changes in preview capabilities and LaTeX compilation processes, ensuring users are informed of new features and best practices.
1 parent 7a29ebb commit 3d46250

34 files changed

Lines changed: 2062 additions & 524 deletions

‎docs/file-previews.md‎

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,24 @@ context without changing the document. Proposed replacements still use the
2323
existing diff review. A suggestion is not a verified compilation; recompile
2424
after accepting it. Missing-context responses are billed like other AI calls.
2525

26+
The LaTeX editor offers **Source**, **Split**, and **PDF** views and remembers
27+
the selected view and split width. PDF previews start fitted to the pane and
28+
resize with it; manual zoom and **Fit width** remain available. **Compile on
29+
save** runs after an explicit save (it does not autosave as you type).
30+
31+
The PDF is marked out of date after source edits, and a failed build keeps the
32+
previously displayed preview with a failure notice. Compiler errors in the open
33+
file are clickable in the log; stale line locations cannot launch AI fixes.
34+
The diagnostic gutter uses the final compiler pass, so warnings resolved by
35+
reruns disappear. Bibliography failures still fail the build even if a later
36+
TeX pass produces a PDF. Long logs retain both their beginning and final summary.
37+
38+
Select source and choose **Edit with AI** (⌘K / Ctrl+K). For chapter files with a
39+
`% !TEX root = ../main.tex` comment, AI assistance also reads the root preamble.
40+
Finish the diff review before saving, compiling, or requesting another AI edit.
41+
Edits typed during a save stay unsaved, and the editor's **Close** button prompts
42+
before discarding them.
43+
2644
### Genomics & sequences
2745
| Format | Extensions | Viewer |
2846
|---|---|---|
@@ -47,10 +65,46 @@ after accepting it. Missing-context responses are billed like other AI calls.
4765
| Format | Extensions | Viewer |
4866
|---|---|---|
4967
| Single-cell (AnnData) | `h5ad` `h5ad.gz` | Structured card — obs/var columns, layers, embeddings you can color by any column |
50-
| HDF5 | `h5` `hdf5` | Group / dataset tree with shapes, dtypes, attributes |
51-
| Parquet | `parquet` | Schema + first rows |
52-
| NumPy | `npy` `npz` | Shape, dtype, min/max/mean, value preview (each array in an `.npz`) |
53-
| NetCDF | `nc` `nc4` `cdf` | Dimensions, variables, and global attributes |
68+
| HDF5 | `h5` `hdf5` | Group / dataset tree plus selectable numeric heatmaps and line plots |
69+
| Parquet | `parquet` | Schema, searchable/sortable row sample, selectable numeric scatter plot |
70+
| NumPy | `npy` `npz` | Array selector, heatmap / line plot, multidimensional slice controls, displayed-value statistics |
71+
| NetCDF | `nc` `nc4` `cdf` | Dimensions, root variables, attributes and numeric slice plots; masked values remain gaps |
72+
| MATLAB | `mat` | Dense numeric variables from v4–v7.2 files; v7.3 uses the HDF5 dataset viewer and storage-axis order |
73+
| Matrix Market | `mtx` | Dense or sparse matrix heatmap; sparse sources are sampled before conversion to a small dense grid |
74+
| FITS images | `fits` `fit` `fts` | Image HDU selector, slice controls, heatmaps / line plots with FITS value scaling |
75+
76+
### Data tables & workbooks
77+
| Format | Extensions | Viewer |
78+
|---|---|---|
79+
| Excel | `xlsx` | Sheet selector, table search / sort and numeric scatter plots; first row supplies headers, formulas show saved values |
80+
| Arrow IPC / Feather v2 | `arrow` `ipc` `feather` | File or stream schema, row sample and numeric scatter plots |
81+
| JSON Lines | `jsonl` `ndjson` | Sampled records as columns; nested values shown as JSON, with numeric scatter plots |
82+
| SQLite | `sqlite` `sqlite3` `db` | Read-only ordinary-table selector, row sample, search / sort and numeric scatter plots |
83+
84+
Tables show up to **200 rows × 50 columns**; search, sorting and plots use only
85+
that sample. Large cell text is capped at 512 characters. Where a format lacks
86+
a cheap exact row count, the viewer explicitly reports that the total was not
87+
scanned. SQLite views and virtual tables are excluded. JSON Lines columns are
88+
inferred from displayed records. Charts omit missing/nonnumeric pairs; empty
89+
cells and booleans are not treated as zero.
90+
91+
Numeric arrays show a line plot for vectors and a heatmap for matrices. For
92+
higher dimensions, the slice control walks the leading dimensions in C order
93+
while displaying the last two axes. Indices start at zero. Plots use at most
94+
**1,024 vector points** or a **128 × 128 strided grid**. The viewer labels
95+
sampling, missing values, and statistics from the displayed slice/sample;
96+
these are not full-dataset statistics. Hover over a heatmap cell or plot point
97+
to inspect its value. Striding can miss isolated features in large arrays.
98+
99+
HDF5/NetCDF and NumPy `.npy` previews read slices; Parquet reads a limited row
100+
batch. XLSX expanded content, Arrow files/batches, individual NPZ arrays and
101+
classic MATLAB files/variables have a 64 MiB preview ceiling. Matrix Market is
102+
limited to 64 MiB and two million stored entries / axis. JSON Lines scans at
103+
most 8 MiB, with a 1 MiB per-line limit. File/container metadata and codec work
104+
can still require additional memory. Oversized inputs show an explanation and
105+
remain available for download or analysis. Array/variable and sheet/table lists
106+
are bounded and show truncation notices. NumPy object arrays are never unpickled;
107+
HDF5 external links and external/virtual dataset storage are not followed.
54108

55109
### Bio-imaging
56110
| Format | Extensions | Viewer |
@@ -70,4 +124,4 @@ after accepting it. Missing-context responses are billed like other AI calls.
70124

71125
## Unsupported previews
72126

73-
There are no dedicated viewers for whole-slide imaging (`.svs`, `.ndpi`), GenBank/NEXUS annotated records, multi-channel OME-TIFF compositing or cross-file DICOM series stacking. Unrecognized files use the text fallback where possible; download binary formats or process them with the agent.
127+
There are no dedicated viewers for whole-slide imaging (`.svs`, `.ndpi`), GenBank/NEXUS annotated records, multi-channel OME-TIFF compositing or cross-file DICOM series stacking. The new data viewers do not cover legacy Excel `.xls`, ODS, Feather v1, FITS table HDUs, MATLAB cells/structs or nested NetCDF groups. NPZ previews accept NumPy header versions 1 and 2. Unrecognized files use the text fallback where possible; download binary formats or process them with the agent.

‎server/src/api/sandbox.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,19 +733,27 @@ export async function registerSandboxRoutes(app: FastifyInstance): Promise<void>
733733
);
734734

735735
// --- generic scientific-file previews (chem/structure/...) via Python helper ---
736-
app.get<{ Querystring: { path: string; kind: string } }>("/sandbox/sci-summary", async (req, reply) => {
736+
app.get<{ Querystring: { path: string; kind: string; key?: string; slice?: string } }>("/sandbox/sci-summary", async (req, reply) => {
737737
try {
738738
if (!sciHelperFor(req.query.kind)) {
739739
reply.code(400);
740740
return { detail: `Unknown kind: ${req.query.kind}` };
741741
}
742+
const { key, slice } = req.query;
743+
if ((key !== undefined && (typeof key !== "string" || key.length > 1024 || key.includes("\0"))) ||
744+
(slice !== undefined && (!/^\d+$/.test(slice) || !Number.isSafeInteger(Number(slice)))) ||
745+
((key !== undefined || slice !== undefined) && !["arrays", "tables"].includes(req.query.kind))) {
746+
reply.code(400);
747+
return { detail: "Invalid scientific preview selection" };
748+
}
742749
const target = safePath(req.query.path);
743750
if (!fs.existsSync(target) || !fs.statSync(target).isFile()) {
744751
reply.code(404);
745752
return { detail: "File not found" };
746753
}
747754
const res = await requestPreview(reply, {
748755
projectId: currentProjectId(), target, script: sciHelperFor(req.query.kind)!.script, command: "summarize",
756+
params: key !== undefined || slice !== undefined ? [key ?? "", slice ?? "0"] : undefined,
749757
});
750758
reply.header("Cache-Control", "private, no-cache");
751759
if (res.timedOut) {

‎server/src/api/sci-helpers.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ import path from "node:path";
1616
import { execFile } from "node:child_process";
1717
import { HELPERS_DIR, helperPython } from "../helpers-env.ts";
1818

19-
export type SciKind = "chem" | "structure" | "massspec" | "arrays" | "imaging";
19+
export type SciKind = "chem" | "structure" | "massspec" | "arrays" | "imaging" | "tables";
2020

2121
const KIND_TO_SCRIPT: Record<string, string> = {
2222
chem: "chem_helper.py",
2323
structure: "structure_helper.py",
2424
massspec: "massspec_helper.py",
2525
arrays: "arrays_helper.py",
2626
imaging: "imaging_helper.py",
27+
tables: "tables_helper.py",
2728
};
2829

2930
/** Wall-clock ceiling for a single preview helper invocation. */
@@ -79,6 +80,7 @@ export interface HelperResult {
7980

8081
/** Absolute helper script path for a known kind, or null if the kind is unrecognized. */
8182
export function sciHelperFor(kind: string): { script: string } | null {
83+
if (!Object.hasOwn(KIND_TO_SCRIPT, kind)) return null;
8284
const file = KIND_TO_SCRIPT[kind];
8385
if (!file) return null;
8486
return { script: path.join(HELPERS_DIR, file) };

‎server/src/api/sci-previews.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ async function version(request: PreviewRequest): Promise<string> {
3939
return JSON.stringify(await Promise.all([
4040
fileVersion(request.target),
4141
fileVersion(request.script),
42+
// A live SQLite database may have newer committed data in its WAL.
43+
...(/\.(db|sqlite3?)$/i.test(request.target)
44+
? [fileVersion(`${request.target}-wal`).catch(() => "missing-wal")] : []),
4245
...[helperPython(), path.join(HELPERS_DIR, "pyproject.toml"), path.join(HELPERS_DIR, ".venv", "pyvenv.cfg"), path.join(HELPERS_DIR, "uv.lock")]
4346
.map((file) => fileVersion(file).catch(() => "missing")),
4447
]));
@@ -52,7 +55,7 @@ export async function getPreview(request: PreviewRequest, signal?: AbortSignal):
5255
try {
5356
const output = dir ? path.join(dir, "image") : "";
5457
const params = request.params ?? [];
55-
const args = request.command === "summarize" ? ["summarize", request.target]
58+
const args = request.command === "summarize" ? ["summarize", request.target, ...params]
5659
: request.command === "render" ? ["render", request.target, params[0] ?? "0", output, params[1] ?? "-"]
5760
: ["embedding", request.target, params[0], params[1] ?? "-", request.cacheDir!, output];
5861
const result = await runHelperScript(request.script, args, undefined, workSignal);

0 commit comments

Comments
 (0)