latticesql 2.2.1 → 2.2.3
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/README.md +6 -4
- package/dist/cli.js +646 -146
- package/dist/index.cjs +0 -7
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +0 -7
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -2137,8 +2137,9 @@ Optional extras, each enabled by its own key/binary:
|
|
|
2137
2137
|
- **Voice** — set an OpenAI (Whisper) or ElevenLabs key to dictate into the composer.
|
|
2138
2138
|
- **File ingest** — reference a local file or paste text; it becomes a row in the
|
|
2139
2139
|
native `files` entity with extracted text + (with a Claude key) an
|
|
2140
|
-
LLM-written description and links to related records.
|
|
2141
|
-
|
|
2140
|
+
LLM-written description and links to related records. Documents (PDF, Word,
|
|
2141
|
+
PowerPoint, Excel, OpenDocument, EPUB, RTF) are parsed natively in-process —
|
|
2142
|
+
no external CLI.
|
|
2142
2143
|
|
|
2143
2144
|
Chat threads, files, and secrets are all stored as native Lattice entities.
|
|
2144
2145
|
|
|
@@ -2295,8 +2296,9 @@ the library API is unchanged and fully backwards-compatible.
|
|
|
2295
2296
|
entity. A subscription **Connect** link (PKCE) appears when the `ANTHROPIC_OAUTH_*`
|
|
2296
2297
|
values are set (see [`.env.example`](.env.example)).
|
|
2297
2298
|
- **Drop files / paste text / images / URLs.** Sources become native `files` rows
|
|
2298
|
-
(referenced, not copied) and are extracted —
|
|
2299
|
-
|
|
2299
|
+
(referenced, not copied) and are extracted — documents (PDF / Office /
|
|
2300
|
+
OpenDocument / EPUB / RTF) parsed **natively in-process**, **images via Claude
|
|
2301
|
+
vision**, a pasted **URL crawled** for readable text —
|
|
2300
2302
|
then summarized with **Claude Haiku** and classified against your records, and
|
|
2301
2303
|
**added, enriched, and linked** automatically, **auto-creating the junction table
|
|
2302
2304
|
when none exists** (and a new object when a source fits nothing). All audited and
|
package/dist/cli.js
CHANGED
|
@@ -6279,7 +6279,7 @@ async function checkForUpdate(pkgName, currentVersion) {
|
|
|
6279
6279
|
|
|
6280
6280
|
// src/gui/server.ts
|
|
6281
6281
|
import { createServer } from "http";
|
|
6282
|
-
import { spawn as
|
|
6282
|
+
import { spawn as spawn2 } from "child_process";
|
|
6283
6283
|
import {
|
|
6284
6284
|
existsSync as existsSync21,
|
|
6285
6285
|
mkdirSync as mkdirSync10,
|
|
@@ -7584,20 +7584,13 @@ var css = `
|
|
|
7584
7584
|
.grants-panel .grants-title { font-weight: 600; margin-bottom: 6px; }
|
|
7585
7585
|
.grants-panel .grants-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; cursor: pointer; }
|
|
7586
7586
|
.grants-panel .grants-row input { accent-color: var(--accent); }
|
|
7587
|
-
/*
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
background: rgba(234, 179, 8, 0.12); color: var(--text);
|
|
7594
|
-
border-bottom: 1px solid rgba(234, 179, 8, 0.45);
|
|
7595
|
-
}
|
|
7596
|
-
.deprecation-banner button {
|
|
7597
|
-
margin-left: auto; background: transparent; border: none; cursor: pointer;
|
|
7598
|
-
color: var(--text-muted); font-size: 13px; padding: 2px 6px; border-radius: 4px;
|
|
7587
|
+
/* Reconnect-required notice: a cloud opened via an unsupported direct
|
|
7588
|
+
database connection serves no data until reconnected through a server. */
|
|
7589
|
+
.cloud-reconnect {
|
|
7590
|
+
padding: 10px 16px; font-size: 13px; line-height: 1.4;
|
|
7591
|
+
background: rgba(239, 68, 68, 0.12); color: var(--text);
|
|
7592
|
+
border-bottom: 1px solid rgba(239, 68, 68, 0.5);
|
|
7599
7593
|
}
|
|
7600
|
-
.deprecation-banner button:hover { background: rgba(234, 179, 8, 0.18); }
|
|
7601
7594
|
|
|
7602
7595
|
/* Inline create-row at the bottom of every table */
|
|
7603
7596
|
tr.create-row td { background: var(--surface-2); }
|
|
@@ -9088,26 +9081,19 @@ var appJs = `
|
|
|
9088
9081
|
|
|
9089
9082
|
window.addEventListener('hashchange', renderRoute);
|
|
9090
9083
|
|
|
9091
|
-
//
|
|
9092
|
-
//
|
|
9093
|
-
//
|
|
9094
|
-
function
|
|
9095
|
-
if (sessionStorage.getItem('lattice-direct-banner-dismissed')) return;
|
|
9084
|
+
// 2.2.3: a cloud reached via a raw postgres:// connection is refused (it
|
|
9085
|
+
// can't enforce per-user access). The server serves no cloud data; tell the
|
|
9086
|
+
// operator to reconnect through a user-authenticated server.
|
|
9087
|
+
function initCloudReconnectNotice() {
|
|
9096
9088
|
fetchJson('/api/dbconfig').then(function (d) {
|
|
9097
|
-
if (!d || !d.
|
|
9098
|
-
var
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
var dismiss = document.getElementById('deprecation-banner-dismiss');
|
|
9104
|
-
if (dismiss) dismiss.addEventListener('click', function () {
|
|
9105
|
-
banner.hidden = true;
|
|
9106
|
-
sessionStorage.setItem('lattice-direct-banner-dismissed', '1');
|
|
9107
|
-
});
|
|
9108
|
-
}).catch(function () { /* dbconfig unavailable (e.g. team-cloud server mode) \u2014 no banner */ });
|
|
9089
|
+
if (!d || !d.cloudReconnectRequired) return;
|
|
9090
|
+
var bar = document.getElementById('cloud-reconnect');
|
|
9091
|
+
if (!bar) return;
|
|
9092
|
+
bar.textContent = 'This cloud is connected with a direct database connection, which is no longer supported. Reconnect through a server (sign in as a user) to access it securely.';
|
|
9093
|
+
bar.hidden = false;
|
|
9094
|
+
}).catch(function () { /* dbconfig unavailable (server mode) \u2014 nothing to show */ });
|
|
9109
9095
|
}
|
|
9110
|
-
|
|
9096
|
+
initCloudReconnectNotice();
|
|
9111
9097
|
|
|
9112
9098
|
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
9113
9099
|
// Sidebar
|
|
@@ -11509,9 +11495,11 @@ var appJs = `
|
|
|
11509
11495
|
headers: { 'content-type': 'application/json' },
|
|
11510
11496
|
body: JSON.stringify({ share: !isShared }),
|
|
11511
11497
|
}).then(function () {
|
|
11512
|
-
//
|
|
11513
|
-
//
|
|
11514
|
-
|
|
11498
|
+
// Rebuild the graph (not just the panel) so the node's share-status
|
|
11499
|
+
// colour (gnode-shared/gnode-private) recolours immediately from the
|
|
11500
|
+
// refreshed entities \u2014 otherwise the swatch stayed stale until a
|
|
11501
|
+
// manual reload. The editor re-shows for the same table.
|
|
11502
|
+
return dmRefreshPanel(tableName, true);
|
|
11515
11503
|
}).then(function () {
|
|
11516
11504
|
showToast(isShared ? 'Unshared "' + tableName + '" from workspace' : 'Shared "' + tableName + '" with workspace', {});
|
|
11517
11505
|
}).catch(function (e) { showToast('Share update failed: ' + e.message, {}); });
|
|
@@ -14231,10 +14219,7 @@ var guiAppHtml = `<!doctype html>
|
|
|
14231
14219
|
</svg>
|
|
14232
14220
|
</button>
|
|
14233
14221
|
</header>
|
|
14234
|
-
<div class="
|
|
14235
|
-
<span id="deprecation-banner-text"></span>
|
|
14236
|
-
<button id="deprecation-banner-dismiss" title="Dismiss for this session" aria-label="Dismiss">\u2715</button>
|
|
14237
|
-
</div>
|
|
14222
|
+
<div class="cloud-reconnect" id="cloud-reconnect" hidden></div>
|
|
14238
14223
|
<div class="layout">
|
|
14239
14224
|
<nav class="sidebar">
|
|
14240
14225
|
<label class="sidebar-advanced toggle" title="Advanced mode \u2014 row/table editor instead of the file workspace">
|
|
@@ -15406,19 +15391,12 @@ async function destroyTeamDirect(db) {
|
|
|
15406
15391
|
}
|
|
15407
15392
|
await db.delete("__lattice_team_identity", "singleton");
|
|
15408
15393
|
}
|
|
15409
|
-
var directDeprecationWarned = false;
|
|
15410
15394
|
async function openCloud(cloudUrl) {
|
|
15411
15395
|
if (!isPostgresUrl(cloudUrl)) {
|
|
15412
15396
|
throw new Error(
|
|
15413
15397
|
`direct-ops: cloudUrl must be a postgres:// URL (got ${cloudUrl.slice(0, 12)}\u2026)`
|
|
15414
15398
|
);
|
|
15415
15399
|
}
|
|
15416
|
-
if (!directDeprecationWarned) {
|
|
15417
|
-
directDeprecationWarned = true;
|
|
15418
|
-
console.warn(
|
|
15419
|
-
"[teams] Direct postgres:// team-cloud connection is deprecated and does NOT enforce 2.2 row-level security. Migrate to a hosted Lattice Teams server."
|
|
15420
|
-
);
|
|
15421
|
-
}
|
|
15422
15400
|
const db = new Lattice(cloudUrl);
|
|
15423
15401
|
await db.init();
|
|
15424
15402
|
for (const [table, def] of Object.entries(CLOUD_INTERNAL_TABLE_DEFS)) {
|
|
@@ -15583,7 +15561,7 @@ async function resolveUserIdByEmail(db, email) {
|
|
|
15583
15561
|
function isVisibleInTeam(tableName, ctx) {
|
|
15584
15562
|
if (ctx.shared.has(tableName)) return true;
|
|
15585
15563
|
const owner = ctx.owners.get(tableName);
|
|
15586
|
-
if (owner === void 0) return
|
|
15564
|
+
if (owner === void 0) return ctx.isCreator;
|
|
15587
15565
|
return owner === ctx.myUserId;
|
|
15588
15566
|
}
|
|
15589
15567
|
async function listTeamUsers(db) {
|
|
@@ -19404,9 +19382,9 @@ async function dispatchDbConfigRoute(req, res, ctx) {
|
|
|
19404
19382
|
// exist when the team cloud itself is the active database).
|
|
19405
19383
|
teamId: ctx.teamMembership?.teamId ?? null,
|
|
19406
19384
|
myUserId: ctx.teamMembership?.myUserId ?? null,
|
|
19407
|
-
//
|
|
19408
|
-
// shows
|
|
19409
|
-
|
|
19385
|
+
// 2.2.3: a direct postgres:// cloud connection is refused — the SPA
|
|
19386
|
+
// shows a "reconnect through a server" prompt instead of cloud data.
|
|
19387
|
+
cloudReconnectRequired: ctx.cloudReconnectRequired
|
|
19410
19388
|
});
|
|
19411
19389
|
});
|
|
19412
19390
|
return true;
|
|
@@ -20865,8 +20843,12 @@ async function buildSchemaContext(d) {
|
|
|
20865
20843
|
}
|
|
20866
20844
|
return lines.join("\n");
|
|
20867
20845
|
}
|
|
20868
|
-
function buildSystemPrompt(schema) {
|
|
20869
|
-
|
|
20846
|
+
function buildSystemPrompt(schema, operatorName) {
|
|
20847
|
+
const who = operatorName && operatorName.trim().length > 0 ? `
|
|
20848
|
+
|
|
20849
|
+
# Who you are assisting
|
|
20850
|
+
You are assisting ${operatorName.trim()}. When the user says "me" / "my", they mean ${operatorName.trim()}; never ask the user for their own name.` : "";
|
|
20851
|
+
return `${BASE_SYSTEM_PROMPT}${who}
|
|
20870
20852
|
|
|
20871
20853
|
# Current database
|
|
20872
20854
|
${schema}`;
|
|
@@ -20881,7 +20863,7 @@ async function* runChat(opts) {
|
|
|
20881
20863
|
...opts.history ?? [],
|
|
20882
20864
|
{ role: "user", content: opts.userMessage }
|
|
20883
20865
|
];
|
|
20884
|
-
const system = buildSystemPrompt(await buildSchemaContext(opts.dispatch));
|
|
20866
|
+
const system = buildSystemPrompt(await buildSchemaContext(opts.dispatch), opts.operatorName);
|
|
20885
20867
|
let loop = 0;
|
|
20886
20868
|
try {
|
|
20887
20869
|
for (; loop < MAX_TOOL_LOOPS; loop++) {
|
|
@@ -21492,6 +21474,9 @@ async function dispatchChatRoute(req, res, ctx) {
|
|
|
21492
21474
|
history,
|
|
21493
21475
|
userMessage: message,
|
|
21494
21476
|
temperature,
|
|
21477
|
+
// Give the assistant the operator's name so it addresses them and
|
|
21478
|
+
// resolves "me"/"my" without asking for a name it already has.
|
|
21479
|
+
operatorName: readIdentity().display_name,
|
|
21495
21480
|
// Capture each executed tool call (capped) for cross-turn replay memory.
|
|
21496
21481
|
onToolRecord: (rec) => {
|
|
21497
21482
|
turns[turns.length - 1]?.toolCalls.push(rec);
|
|
@@ -21573,10 +21558,538 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
21573
21558
|
import { basename as basename10, extname as extname2, resolve as resolve8, join as join20 } from "path";
|
|
21574
21559
|
|
|
21575
21560
|
// src/gui/ai/extract.ts
|
|
21576
|
-
import { readFile } from "fs/promises";
|
|
21561
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
21577
21562
|
import { extname, basename as basename7 } from "path";
|
|
21578
|
-
|
|
21563
|
+
|
|
21564
|
+
// src/gui/ai/doc-extractors.ts
|
|
21565
|
+
import { readFile } from "fs/promises";
|
|
21579
21566
|
var MAX_TEXT = 2e5;
|
|
21567
|
+
var MAX_ENTRY_BYTES = 64 * 1024 * 1024;
|
|
21568
|
+
var MAX_TOTAL_BYTES = 256 * 1024 * 1024;
|
|
21569
|
+
var PDF_TIMEOUT_MS = 3e4;
|
|
21570
|
+
var textDecoder = new TextDecoder("utf-8");
|
|
21571
|
+
function decodeUtf8(bytes) {
|
|
21572
|
+
return textDecoder.decode(bytes);
|
|
21573
|
+
}
|
|
21574
|
+
async function loadOptional(specifier) {
|
|
21575
|
+
try {
|
|
21576
|
+
return await import(specifier);
|
|
21577
|
+
} catch {
|
|
21578
|
+
return null;
|
|
21579
|
+
}
|
|
21580
|
+
}
|
|
21581
|
+
function nullIfEmpty(s) {
|
|
21582
|
+
const t = s.trim();
|
|
21583
|
+
return t ? t : null;
|
|
21584
|
+
}
|
|
21585
|
+
function withTimeout(p, ms, label) {
|
|
21586
|
+
let timer;
|
|
21587
|
+
const timeout = new Promise((_, reject) => {
|
|
21588
|
+
timer = setTimeout(() => {
|
|
21589
|
+
reject(new Error(label));
|
|
21590
|
+
}, ms);
|
|
21591
|
+
timer.unref?.();
|
|
21592
|
+
});
|
|
21593
|
+
return Promise.race([
|
|
21594
|
+
p.finally(() => {
|
|
21595
|
+
clearTimeout(timer);
|
|
21596
|
+
}),
|
|
21597
|
+
timeout
|
|
21598
|
+
]);
|
|
21599
|
+
}
|
|
21600
|
+
function decodeXmlEntities(s) {
|
|
21601
|
+
return s.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&#x([0-9a-fA-F]+);/g, (_, h) => safeCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => safeCodePoint(parseInt(d, 10))).replace(/&/g, "&");
|
|
21602
|
+
}
|
|
21603
|
+
function safeCodePoint(n) {
|
|
21604
|
+
if (!Number.isFinite(n) || n < 0 || n > 1114111) return "";
|
|
21605
|
+
try {
|
|
21606
|
+
return String.fromCodePoint(n);
|
|
21607
|
+
} catch {
|
|
21608
|
+
return "";
|
|
21609
|
+
}
|
|
21610
|
+
}
|
|
21611
|
+
function stripTags(s) {
|
|
21612
|
+
let out = "";
|
|
21613
|
+
let i = 0;
|
|
21614
|
+
while (i < s.length) {
|
|
21615
|
+
const lt = s.indexOf("<", i);
|
|
21616
|
+
if (lt < 0) {
|
|
21617
|
+
out += s.slice(i);
|
|
21618
|
+
break;
|
|
21619
|
+
}
|
|
21620
|
+
out += s.slice(i, lt);
|
|
21621
|
+
const gt = s.indexOf(">", lt + 1);
|
|
21622
|
+
if (gt < 0) break;
|
|
21623
|
+
i = gt + 1;
|
|
21624
|
+
}
|
|
21625
|
+
return out;
|
|
21626
|
+
}
|
|
21627
|
+
function isNameBoundary(code) {
|
|
21628
|
+
return code === 32 || // space
|
|
21629
|
+
code === 9 || // tab
|
|
21630
|
+
code === 10 || // \n
|
|
21631
|
+
code === 13 || // \r
|
|
21632
|
+
code === 62 || // >
|
|
21633
|
+
code === 47 || // /
|
|
21634
|
+
Number.isNaN(code);
|
|
21635
|
+
}
|
|
21636
|
+
function eachElement(xml, tag, cb) {
|
|
21637
|
+
const open = "<" + tag;
|
|
21638
|
+
const close = "</" + tag + ">";
|
|
21639
|
+
let i = 0;
|
|
21640
|
+
while (i < xml.length) {
|
|
21641
|
+
const s = xml.indexOf(open, i);
|
|
21642
|
+
if (s < 0) break;
|
|
21643
|
+
const ne = s + open.length;
|
|
21644
|
+
if (!isNameBoundary(xml.charCodeAt(ne))) {
|
|
21645
|
+
i = ne;
|
|
21646
|
+
continue;
|
|
21647
|
+
}
|
|
21648
|
+
const gt = xml.indexOf(">", ne);
|
|
21649
|
+
if (gt < 0) break;
|
|
21650
|
+
const selfClose = xml.charCodeAt(gt - 1) === 47;
|
|
21651
|
+
const attrs = xml.slice(ne, selfClose ? gt - 1 : gt);
|
|
21652
|
+
if (selfClose) {
|
|
21653
|
+
cb(attrs, "", s);
|
|
21654
|
+
i = gt + 1;
|
|
21655
|
+
continue;
|
|
21656
|
+
}
|
|
21657
|
+
const e = xml.indexOf(close, gt + 1);
|
|
21658
|
+
if (e < 0) break;
|
|
21659
|
+
cb(attrs, xml.slice(gt + 1, e), s);
|
|
21660
|
+
i = e + close.length;
|
|
21661
|
+
}
|
|
21662
|
+
}
|
|
21663
|
+
function stripElement(xml, tag) {
|
|
21664
|
+
const open = "<" + tag;
|
|
21665
|
+
const close = "</" + tag + ">";
|
|
21666
|
+
let out = "";
|
|
21667
|
+
let i = 0;
|
|
21668
|
+
while (i < xml.length) {
|
|
21669
|
+
const s = xml.indexOf(open, i);
|
|
21670
|
+
if (s < 0) {
|
|
21671
|
+
out += xml.slice(i);
|
|
21672
|
+
break;
|
|
21673
|
+
}
|
|
21674
|
+
const ne = s + open.length;
|
|
21675
|
+
if (!isNameBoundary(xml.charCodeAt(ne))) {
|
|
21676
|
+
out += xml.slice(i, ne);
|
|
21677
|
+
i = ne;
|
|
21678
|
+
continue;
|
|
21679
|
+
}
|
|
21680
|
+
const gt = xml.indexOf(">", ne);
|
|
21681
|
+
if (gt < 0) {
|
|
21682
|
+
out += xml.slice(i);
|
|
21683
|
+
break;
|
|
21684
|
+
}
|
|
21685
|
+
out += xml.slice(i, s);
|
|
21686
|
+
if (xml.charCodeAt(gt - 1) === 47) {
|
|
21687
|
+
i = gt + 1;
|
|
21688
|
+
continue;
|
|
21689
|
+
}
|
|
21690
|
+
const e = xml.indexOf(close, gt + 1);
|
|
21691
|
+
if (e < 0) break;
|
|
21692
|
+
i = e + close.length;
|
|
21693
|
+
}
|
|
21694
|
+
return out;
|
|
21695
|
+
}
|
|
21696
|
+
function concatTagText(xml, tag) {
|
|
21697
|
+
let out = "";
|
|
21698
|
+
eachElement(xml, tag, (_, inner) => {
|
|
21699
|
+
out += decodeXmlEntities(stripTags(inner));
|
|
21700
|
+
});
|
|
21701
|
+
return out;
|
|
21702
|
+
}
|
|
21703
|
+
function firstTagText(xml, tag) {
|
|
21704
|
+
let found = "";
|
|
21705
|
+
let done = false;
|
|
21706
|
+
eachElement(xml, tag, (_, inner) => {
|
|
21707
|
+
if (done) return;
|
|
21708
|
+
found = inner;
|
|
21709
|
+
done = true;
|
|
21710
|
+
});
|
|
21711
|
+
return found;
|
|
21712
|
+
}
|
|
21713
|
+
function stripHtml(html) {
|
|
21714
|
+
const noScript = stripElement(stripElement(html, "script"), "style");
|
|
21715
|
+
const text = decodeXmlEntities(stripTags(noScript));
|
|
21716
|
+
return text.replace(/[ \t\f\r]+/g, " ").replace(/ *\n */g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
21717
|
+
}
|
|
21718
|
+
async function unzip(path2) {
|
|
21719
|
+
const fflate = await loadOptional("fflate");
|
|
21720
|
+
if (!fflate || typeof fflate.unzipSync !== "function") return null;
|
|
21721
|
+
try {
|
|
21722
|
+
const buf = await readFile(path2);
|
|
21723
|
+
let total = 0;
|
|
21724
|
+
return fflate.unzipSync(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength), {
|
|
21725
|
+
filter: (file) => {
|
|
21726
|
+
const size = file.originalSize || 0;
|
|
21727
|
+
if (size > MAX_ENTRY_BYTES) throw new Error("zip entry exceeds size cap");
|
|
21728
|
+
total += size;
|
|
21729
|
+
if (total > MAX_TOTAL_BYTES) throw new Error("zip total exceeds size cap");
|
|
21730
|
+
return true;
|
|
21731
|
+
}
|
|
21732
|
+
});
|
|
21733
|
+
} catch {
|
|
21734
|
+
return null;
|
|
21735
|
+
}
|
|
21736
|
+
}
|
|
21737
|
+
async function extractDocx(path2) {
|
|
21738
|
+
const mod = await loadOptional("mammoth");
|
|
21739
|
+
const lib = mod?.default ?? mod;
|
|
21740
|
+
if (!lib || typeof lib.extractRawText !== "function") return null;
|
|
21741
|
+
try {
|
|
21742
|
+
const { value } = await lib.extractRawText({ path: path2 });
|
|
21743
|
+
return nullIfEmpty(value);
|
|
21744
|
+
} catch {
|
|
21745
|
+
return null;
|
|
21746
|
+
}
|
|
21747
|
+
}
|
|
21748
|
+
async function extractDoc(path2) {
|
|
21749
|
+
const mod = await loadOptional(
|
|
21750
|
+
"word-extractor"
|
|
21751
|
+
);
|
|
21752
|
+
const Ctor = mod && "default" in mod ? mod.default : mod;
|
|
21753
|
+
if (typeof Ctor !== "function") return null;
|
|
21754
|
+
try {
|
|
21755
|
+
const doc = await new Ctor().extract(path2);
|
|
21756
|
+
return nullIfEmpty(doc.getBody());
|
|
21757
|
+
} catch {
|
|
21758
|
+
return null;
|
|
21759
|
+
}
|
|
21760
|
+
}
|
|
21761
|
+
async function extractPdf(path2) {
|
|
21762
|
+
const unpdf = await loadOptional("unpdf");
|
|
21763
|
+
if (!unpdf || typeof unpdf.getDocumentProxy !== "function") return null;
|
|
21764
|
+
try {
|
|
21765
|
+
const buf = await readFile(path2);
|
|
21766
|
+
const data = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
21767
|
+
const text = await withTimeout(
|
|
21768
|
+
(async () => {
|
|
21769
|
+
const pdf = await unpdf.getDocumentProxy(data);
|
|
21770
|
+
const out = await unpdf.extractText(pdf, { mergePages: true });
|
|
21771
|
+
return out.text;
|
|
21772
|
+
})(),
|
|
21773
|
+
PDF_TIMEOUT_MS,
|
|
21774
|
+
"pdf extract timeout"
|
|
21775
|
+
);
|
|
21776
|
+
return nullIfEmpty(text);
|
|
21777
|
+
} catch {
|
|
21778
|
+
return null;
|
|
21779
|
+
}
|
|
21780
|
+
}
|
|
21781
|
+
function partNumber(name) {
|
|
21782
|
+
const m = /(\d+)\.xml$/.exec(name);
|
|
21783
|
+
return m?.[1] ? parseInt(m[1], 10) : 0;
|
|
21784
|
+
}
|
|
21785
|
+
function slideText(xml) {
|
|
21786
|
+
const paras = [];
|
|
21787
|
+
eachElement(xml, "a:p", (_, inner) => {
|
|
21788
|
+
const runs = concatTagText(inner, "a:t");
|
|
21789
|
+
if (runs.trim()) paras.push(runs);
|
|
21790
|
+
});
|
|
21791
|
+
if (paras.length === 0) {
|
|
21792
|
+
const runs = concatTagText(xml, "a:t");
|
|
21793
|
+
if (runs.trim()) paras.push(runs);
|
|
21794
|
+
}
|
|
21795
|
+
return paras.join("\n");
|
|
21796
|
+
}
|
|
21797
|
+
async function extractPptx(path2) {
|
|
21798
|
+
const entries = await unzip(path2);
|
|
21799
|
+
if (!entries) return null;
|
|
21800
|
+
const slides = Object.keys(entries).filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n)).sort((a, b) => partNumber(a) - partNumber(b));
|
|
21801
|
+
if (slides.length === 0) return null;
|
|
21802
|
+
const parts = [];
|
|
21803
|
+
let total = 0;
|
|
21804
|
+
for (const n of slides) {
|
|
21805
|
+
if (total >= MAX_TEXT) break;
|
|
21806
|
+
const bytes = entries[n];
|
|
21807
|
+
if (!bytes) continue;
|
|
21808
|
+
const text = slideText(decodeUtf8(bytes)).replace(/[ \t]+/g, " ").trim();
|
|
21809
|
+
if (text) {
|
|
21810
|
+
parts.push(text);
|
|
21811
|
+
total += text.length + 2;
|
|
21812
|
+
}
|
|
21813
|
+
}
|
|
21814
|
+
return nullIfEmpty(parts.join("\n\n"));
|
|
21815
|
+
}
|
|
21816
|
+
async function extractXlsx(path2) {
|
|
21817
|
+
const entries = await unzip(path2);
|
|
21818
|
+
if (!entries) return null;
|
|
21819
|
+
const shared = [];
|
|
21820
|
+
const ssBytes = entries["xl/sharedStrings.xml"];
|
|
21821
|
+
if (ssBytes) {
|
|
21822
|
+
eachElement(decodeUtf8(ssBytes), "si", (_, inner) => {
|
|
21823
|
+
shared.push(concatTagText(stripElement(inner, "rPh"), "t"));
|
|
21824
|
+
});
|
|
21825
|
+
}
|
|
21826
|
+
const sheetNames = Object.keys(entries).filter((n) => /^xl\/worksheets\/sheet\d+\.xml$/.test(n)).sort((a, b) => partNumber(a) - partNumber(b));
|
|
21827
|
+
const rowsOut = [];
|
|
21828
|
+
let total = 0;
|
|
21829
|
+
for (const n of sheetNames) {
|
|
21830
|
+
if (total >= MAX_TEXT) break;
|
|
21831
|
+
const bytes = entries[n];
|
|
21832
|
+
if (!bytes) continue;
|
|
21833
|
+
eachElement(decodeUtf8(bytes), "row", (_, rowInner) => {
|
|
21834
|
+
if (total >= MAX_TEXT) return;
|
|
21835
|
+
const cells = [];
|
|
21836
|
+
eachElement(rowInner, "c", (attrs, body) => {
|
|
21837
|
+
const type = /\bt="([^"]+)"/.exec(attrs)?.[1];
|
|
21838
|
+
let val = "";
|
|
21839
|
+
if (type === "s") {
|
|
21840
|
+
const idx = parseInt(firstTagText(body, "v"), 10);
|
|
21841
|
+
val = Number.isInteger(idx) ? shared[idx] ?? "" : "";
|
|
21842
|
+
} else if (type === "inlineStr") {
|
|
21843
|
+
val = concatTagText(body, "t");
|
|
21844
|
+
} else {
|
|
21845
|
+
val = decodeXmlEntities(firstTagText(body, "v"));
|
|
21846
|
+
}
|
|
21847
|
+
if (val) cells.push(val);
|
|
21848
|
+
});
|
|
21849
|
+
if (cells.length) {
|
|
21850
|
+
const line = cells.join(" ");
|
|
21851
|
+
rowsOut.push(line);
|
|
21852
|
+
total += line.length + 1;
|
|
21853
|
+
}
|
|
21854
|
+
});
|
|
21855
|
+
}
|
|
21856
|
+
return nullIfEmpty(rowsOut.join("\n"));
|
|
21857
|
+
}
|
|
21858
|
+
function odfWhitespace(s) {
|
|
21859
|
+
return s.replace(/<text:tab\b[^>]{0,400}\/?>/g, " ").replace(/<text:line-break\b[^>]{0,400}\/?>/g, "\n").replace(
|
|
21860
|
+
/<text:s\b[^>]{0,400}\btext:c="(\d+)"[^>]{0,400}\/?>/g,
|
|
21861
|
+
(_, c) => " ".repeat(Math.min(parseInt(c, 10) || 1, 100))
|
|
21862
|
+
).replace(/<text:s\b[^>]{0,400}\/?>/g, " ");
|
|
21863
|
+
}
|
|
21864
|
+
function odfParagraph(inner) {
|
|
21865
|
+
return decodeXmlEntities(stripTags(odfWhitespace(inner))).trim();
|
|
21866
|
+
}
|
|
21867
|
+
async function extractOdfText(path2) {
|
|
21868
|
+
const entries = await unzip(path2);
|
|
21869
|
+
if (!entries) return null;
|
|
21870
|
+
const contentBytes = entries["content.xml"];
|
|
21871
|
+
if (!contentBytes) return null;
|
|
21872
|
+
const xml = decodeUtf8(contentBytes);
|
|
21873
|
+
const items = [];
|
|
21874
|
+
const collect = (_, inner, start) => {
|
|
21875
|
+
const line = odfParagraph(inner);
|
|
21876
|
+
if (line) items.push([start, line]);
|
|
21877
|
+
};
|
|
21878
|
+
eachElement(xml, "text:p", collect);
|
|
21879
|
+
eachElement(xml, "text:h", collect);
|
|
21880
|
+
items.sort((a, b) => a[0] - b[0]);
|
|
21881
|
+
const lines = [];
|
|
21882
|
+
let total = 0;
|
|
21883
|
+
for (const [, line] of items) {
|
|
21884
|
+
if (total >= MAX_TEXT) break;
|
|
21885
|
+
lines.push(line);
|
|
21886
|
+
total += line.length + 1;
|
|
21887
|
+
}
|
|
21888
|
+
return nullIfEmpty(lines.join("\n"));
|
|
21889
|
+
}
|
|
21890
|
+
async function extractOds(path2) {
|
|
21891
|
+
const entries = await unzip(path2);
|
|
21892
|
+
if (!entries) return null;
|
|
21893
|
+
const contentBytes = entries["content.xml"];
|
|
21894
|
+
if (!contentBytes) return null;
|
|
21895
|
+
const xml = decodeUtf8(contentBytes);
|
|
21896
|
+
const rows = [];
|
|
21897
|
+
let total = 0;
|
|
21898
|
+
eachElement(xml, "table:table-row", (_, rowInner) => {
|
|
21899
|
+
if (total >= MAX_TEXT) return;
|
|
21900
|
+
const cells = [];
|
|
21901
|
+
eachElement(rowInner, "table:table-cell", (attrs, body) => {
|
|
21902
|
+
const parts = [];
|
|
21903
|
+
eachElement(body, "text:p", (__, p) => {
|
|
21904
|
+
const t = odfParagraph(p);
|
|
21905
|
+
if (t) parts.push(t);
|
|
21906
|
+
});
|
|
21907
|
+
let val = parts.join(" ").trim();
|
|
21908
|
+
if (!val) {
|
|
21909
|
+
const ov = /\boffice:(?:value|date-value|time-value|string-value|boolean-value)="([^"]*)"/.exec(
|
|
21910
|
+
attrs
|
|
21911
|
+
)?.[1];
|
|
21912
|
+
if (ov) val = decodeXmlEntities(ov);
|
|
21913
|
+
}
|
|
21914
|
+
if (val) cells.push(val);
|
|
21915
|
+
});
|
|
21916
|
+
if (cells.length) {
|
|
21917
|
+
const line = cells.join(" ");
|
|
21918
|
+
rows.push(line);
|
|
21919
|
+
total += line.length + 1;
|
|
21920
|
+
}
|
|
21921
|
+
});
|
|
21922
|
+
return nullIfEmpty(rows.join("\n"));
|
|
21923
|
+
}
|
|
21924
|
+
function normalizeZipPath(p) {
|
|
21925
|
+
const parts = [];
|
|
21926
|
+
for (const seg of p.split("/")) {
|
|
21927
|
+
if (seg === "" || seg === ".") continue;
|
|
21928
|
+
if (seg === "..") parts.pop();
|
|
21929
|
+
else parts.push(seg);
|
|
21930
|
+
}
|
|
21931
|
+
return parts.join("/");
|
|
21932
|
+
}
|
|
21933
|
+
function resolveHref(baseDir, href) {
|
|
21934
|
+
let h = href.split("#")[0]?.split("?")[0] ?? "";
|
|
21935
|
+
try {
|
|
21936
|
+
h = decodeURIComponent(h);
|
|
21937
|
+
} catch {
|
|
21938
|
+
}
|
|
21939
|
+
return normalizeZipPath(baseDir + h);
|
|
21940
|
+
}
|
|
21941
|
+
async function extractEpub(path2) {
|
|
21942
|
+
const entries = await unzip(path2);
|
|
21943
|
+
if (!entries) return null;
|
|
21944
|
+
let order = [];
|
|
21945
|
+
const container = entries["META-INF/container.xml"];
|
|
21946
|
+
const opfPath = container ? /full-path="([^"]+)"/.exec(decodeUtf8(container))?.[1] : void 0;
|
|
21947
|
+
if (opfPath && entries[opfPath]) {
|
|
21948
|
+
const opf = decodeUtf8(entries[opfPath]);
|
|
21949
|
+
const manifest = {};
|
|
21950
|
+
eachElement(opf, "item", (attrs) => {
|
|
21951
|
+
const id = /\bid="([^"]+)"/.exec(attrs)?.[1];
|
|
21952
|
+
const href = /\bhref="([^"]+)"/.exec(attrs)?.[1];
|
|
21953
|
+
if (id && href) manifest[id] = href;
|
|
21954
|
+
});
|
|
21955
|
+
const baseDir = opfPath.includes("/") ? opfPath.slice(0, opfPath.lastIndexOf("/") + 1) : "";
|
|
21956
|
+
eachElement(opf, "itemref", (attrs) => {
|
|
21957
|
+
const idref = /\bidref="([^"]+)"/.exec(attrs)?.[1];
|
|
21958
|
+
const href = idref ? manifest[idref] : void 0;
|
|
21959
|
+
if (href) order.push(resolveHref(baseDir, href));
|
|
21960
|
+
});
|
|
21961
|
+
}
|
|
21962
|
+
if (order.length === 0) {
|
|
21963
|
+
order = Object.keys(entries).filter((n) => /\.x?html?$/i.test(n)).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
|
|
21964
|
+
}
|
|
21965
|
+
const parts = [];
|
|
21966
|
+
let total = 0;
|
|
21967
|
+
for (const n of order) {
|
|
21968
|
+
if (total >= MAX_TEXT) break;
|
|
21969
|
+
const bytes = entries[n];
|
|
21970
|
+
if (!bytes) continue;
|
|
21971
|
+
const body = stripHtml(decodeUtf8(bytes));
|
|
21972
|
+
if (body) {
|
|
21973
|
+
parts.push(body);
|
|
21974
|
+
total += body.length + 2;
|
|
21975
|
+
}
|
|
21976
|
+
}
|
|
21977
|
+
return nullIfEmpty(parts.join("\n\n"));
|
|
21978
|
+
}
|
|
21979
|
+
var RTF_IGNORED_DESTINATIONS = /^\\(?:\*|(?:fonttbl|colortbl|stylesheet|info|pict|themedata|colorschememapping|latentstyles|datastore|listtable|listoverridetable|rsidtbl|generator|operator|xmlnstbl|wgrffmtfilter|mmathPr)(?![a-zA-Z]))/;
|
|
21980
|
+
function stripRtfDestinations(s) {
|
|
21981
|
+
const kept = [];
|
|
21982
|
+
let keepFrom = 0;
|
|
21983
|
+
let i = 0;
|
|
21984
|
+
while (i < s.length) {
|
|
21985
|
+
if (s[i] === "{" && RTF_IGNORED_DESTINATIONS.test(s.slice(i + 1, i + 40))) {
|
|
21986
|
+
const pre = s.slice(keepFrom, i);
|
|
21987
|
+
kept.push(pre);
|
|
21988
|
+
if (/\\[a-zA-Z]+$/.test(pre.slice(-40))) kept.push(" ");
|
|
21989
|
+
let depth = 1;
|
|
21990
|
+
let j = i + 1;
|
|
21991
|
+
for (; j < s.length && depth > 0; j++) {
|
|
21992
|
+
const ch = s[j];
|
|
21993
|
+
if (ch === "\\")
|
|
21994
|
+
j++;
|
|
21995
|
+
else if (ch === "{") depth++;
|
|
21996
|
+
else if (ch === "}") depth--;
|
|
21997
|
+
}
|
|
21998
|
+
i = j;
|
|
21999
|
+
keepFrom = i;
|
|
22000
|
+
} else {
|
|
22001
|
+
i++;
|
|
22002
|
+
}
|
|
22003
|
+
}
|
|
22004
|
+
kept.push(s.slice(keepFrom));
|
|
22005
|
+
return kept.join("");
|
|
22006
|
+
}
|
|
22007
|
+
var CP1252_HIGH = {
|
|
22008
|
+
128: 8364,
|
|
22009
|
+
130: 8218,
|
|
22010
|
+
131: 402,
|
|
22011
|
+
132: 8222,
|
|
22012
|
+
133: 8230,
|
|
22013
|
+
134: 8224,
|
|
22014
|
+
135: 8225,
|
|
22015
|
+
136: 710,
|
|
22016
|
+
137: 8240,
|
|
22017
|
+
138: 352,
|
|
22018
|
+
139: 8249,
|
|
22019
|
+
140: 338,
|
|
22020
|
+
142: 381,
|
|
22021
|
+
145: 8216,
|
|
22022
|
+
146: 8217,
|
|
22023
|
+
147: 8220,
|
|
22024
|
+
148: 8221,
|
|
22025
|
+
149: 8226,
|
|
22026
|
+
150: 8211,
|
|
22027
|
+
151: 8212,
|
|
22028
|
+
152: 732,
|
|
22029
|
+
153: 8482,
|
|
22030
|
+
154: 353,
|
|
22031
|
+
155: 8250,
|
|
22032
|
+
156: 339,
|
|
22033
|
+
158: 382,
|
|
22034
|
+
159: 376
|
|
22035
|
+
};
|
|
22036
|
+
function cp1252Char(byte) {
|
|
22037
|
+
if (byte >= 128 && byte <= 159) {
|
|
22038
|
+
const cp = CP1252_HIGH[byte];
|
|
22039
|
+
return cp ? safeCodePoint(cp) : "";
|
|
22040
|
+
}
|
|
22041
|
+
return safeCodePoint(byte);
|
|
22042
|
+
}
|
|
22043
|
+
function rtfToText(rtf) {
|
|
22044
|
+
let s = stripRtfDestinations(rtf);
|
|
22045
|
+
s = s.replace(/\\'([0-9a-fA-F]{2})/g, (_, h) => cp1252Char(parseInt(h, 16)));
|
|
22046
|
+
s = s.replace(/\\u(-?\d+)\s?\??/g, (_, d) => {
|
|
22047
|
+
let n = parseInt(d, 10);
|
|
22048
|
+
if (n < 0) n += 65536;
|
|
22049
|
+
return safeCodePoint(n);
|
|
22050
|
+
});
|
|
22051
|
+
s = s.replace(/\\par[d]?\b/g, "\n").replace(/\\line\b/g, "\n").replace(/\\sect\b/g, "\n").replace(/\\page\b/g, "\n").replace(/\\tab\b/g, " ");
|
|
22052
|
+
s = s.replace(/\\[a-zA-Z]+-?\d*\s?/g, "").replace(/\\[^a-zA-Z]/g, "");
|
|
22053
|
+
s = s.replace(/[{}]/g, "");
|
|
22054
|
+
return s.replace(/[ \t]+/g, (m) => m.includes(" ") ? " " : " ").replace(/[ \t]\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
22055
|
+
}
|
|
22056
|
+
async function extractRtf(path2) {
|
|
22057
|
+
try {
|
|
22058
|
+
const raw = await readFile(path2, "latin1");
|
|
22059
|
+
if (!raw.startsWith("{\\rtf")) return null;
|
|
22060
|
+
return nullIfEmpty(rtfToText(raw));
|
|
22061
|
+
} catch {
|
|
22062
|
+
return null;
|
|
22063
|
+
}
|
|
22064
|
+
}
|
|
22065
|
+
async function extractDocument(path2, ext) {
|
|
22066
|
+
switch (ext) {
|
|
22067
|
+
case ".docx":
|
|
22068
|
+
return extractDocx(path2);
|
|
22069
|
+
case ".doc":
|
|
22070
|
+
return extractDoc(path2);
|
|
22071
|
+
case ".pdf":
|
|
22072
|
+
return extractPdf(path2);
|
|
22073
|
+
case ".pptx":
|
|
22074
|
+
return extractPptx(path2);
|
|
22075
|
+
case ".xlsx":
|
|
22076
|
+
return extractXlsx(path2);
|
|
22077
|
+
case ".odt":
|
|
22078
|
+
case ".odp":
|
|
22079
|
+
return extractOdfText(path2);
|
|
22080
|
+
case ".ods":
|
|
22081
|
+
return extractOds(path2);
|
|
22082
|
+
case ".epub":
|
|
22083
|
+
return extractEpub(path2);
|
|
22084
|
+
case ".rtf":
|
|
22085
|
+
return extractRtf(path2);
|
|
22086
|
+
default:
|
|
22087
|
+
return null;
|
|
22088
|
+
}
|
|
22089
|
+
}
|
|
22090
|
+
|
|
22091
|
+
// src/gui/ai/extract.ts
|
|
22092
|
+
var MAX_TEXT2 = 2e5;
|
|
21580
22093
|
var CODE_LANGS = {
|
|
21581
22094
|
".ts": "typescript",
|
|
21582
22095
|
".tsx": "typescript",
|
|
@@ -21624,81 +22137,25 @@ var TEXT_EXT = /* @__PURE__ */ new Set([
|
|
|
21624
22137
|
".htm"
|
|
21625
22138
|
]);
|
|
21626
22139
|
var TEXT_MIME = /^(text\/|application\/(json|xml|xhtml\+xml|x-yaml|yaml|toml))/;
|
|
21627
|
-
var MARKITDOWN_EXT = /* @__PURE__ */ new Set([
|
|
21628
|
-
".pdf",
|
|
21629
|
-
".docx",
|
|
21630
|
-
".doc",
|
|
21631
|
-
".pptx",
|
|
21632
|
-
".ppt",
|
|
21633
|
-
".xlsx",
|
|
21634
|
-
".xls",
|
|
21635
|
-
".epub",
|
|
21636
|
-
".rtf",
|
|
21637
|
-
".odt",
|
|
21638
|
-
".ods",
|
|
21639
|
-
".odp"
|
|
21640
|
-
]);
|
|
21641
|
-
var MARKITDOWN_TIMEOUT_MS = 12e4;
|
|
21642
|
-
var MARKITDOWN_MAX_BYTES = 5e7;
|
|
21643
|
-
function runMarkitdown(path2) {
|
|
21644
|
-
return new Promise((resolve12) => {
|
|
21645
|
-
const bin = process.env.MARKITDOWN_BIN ?? "markitdown";
|
|
21646
|
-
let child;
|
|
21647
|
-
try {
|
|
21648
|
-
child = spawn2(bin, [path2], { stdio: ["ignore", "pipe", "ignore"] });
|
|
21649
|
-
} catch {
|
|
21650
|
-
resolve12(null);
|
|
21651
|
-
return;
|
|
21652
|
-
}
|
|
21653
|
-
let out = "";
|
|
21654
|
-
let bytes = 0;
|
|
21655
|
-
let settled = false;
|
|
21656
|
-
const finish = (v) => {
|
|
21657
|
-
if (settled) return;
|
|
21658
|
-
settled = true;
|
|
21659
|
-
clearTimeout(timer);
|
|
21660
|
-
resolve12(v);
|
|
21661
|
-
};
|
|
21662
|
-
const timer = setTimeout(() => {
|
|
21663
|
-
child.kill();
|
|
21664
|
-
finish(null);
|
|
21665
|
-
}, MARKITDOWN_TIMEOUT_MS);
|
|
21666
|
-
child.stdout.on("data", (c) => {
|
|
21667
|
-
bytes += c.length;
|
|
21668
|
-
if (bytes > MARKITDOWN_MAX_BYTES) {
|
|
21669
|
-
child.kill();
|
|
21670
|
-
finish(null);
|
|
21671
|
-
} else {
|
|
21672
|
-
out += c.toString("utf8");
|
|
21673
|
-
}
|
|
21674
|
-
});
|
|
21675
|
-
child.on("error", () => {
|
|
21676
|
-
finish(null);
|
|
21677
|
-
});
|
|
21678
|
-
child.on("close", (code) => {
|
|
21679
|
-
finish(code === 0 && out.trim() ? out.trim() : null);
|
|
21680
|
-
});
|
|
21681
|
-
});
|
|
21682
|
-
}
|
|
21683
22140
|
function languageOf(name) {
|
|
21684
22141
|
return CODE_LANGS[extname(name).toLowerCase()] ?? null;
|
|
21685
22142
|
}
|
|
21686
22143
|
function truncate(s) {
|
|
21687
|
-
return s.length >
|
|
22144
|
+
return s.length > MAX_TEXT2 ? s.slice(0, MAX_TEXT2) : s;
|
|
21688
22145
|
}
|
|
21689
22146
|
async function parseFile(path2, mimeHint, originalName) {
|
|
21690
22147
|
const name = originalName ?? basename7(path2);
|
|
21691
22148
|
const ext = extname(name).toLowerCase();
|
|
21692
22149
|
const lang = languageOf(name);
|
|
21693
22150
|
if (lang) {
|
|
21694
|
-
return { text: truncate(await
|
|
22151
|
+
return { text: truncate(await readFile2(path2, "utf8")), language: lang };
|
|
21695
22152
|
}
|
|
21696
22153
|
if (mimeHint && TEXT_MIME.test(mimeHint) || TEXT_EXT.has(ext)) {
|
|
21697
|
-
return { text: truncate(await
|
|
22154
|
+
return { text: truncate(await readFile2(path2, "utf8")) };
|
|
21698
22155
|
}
|
|
21699
|
-
|
|
21700
|
-
|
|
21701
|
-
|
|
22156
|
+
const doc = await extractDocument(path2, ext);
|
|
22157
|
+
if (doc != null) {
|
|
22158
|
+
return { text: truncate(doc) };
|
|
21702
22159
|
}
|
|
21703
22160
|
return { text: "", skip: true };
|
|
21704
22161
|
}
|
|
@@ -21712,7 +22169,7 @@ function describe(text, mime, name) {
|
|
|
21712
22169
|
|
|
21713
22170
|
// src/ai/vision.ts
|
|
21714
22171
|
import { createRequire as createRequire5 } from "module";
|
|
21715
|
-
import { readFile as
|
|
22172
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
21716
22173
|
var DEFAULT_PROMPT = "Describe this image for a knowledge base in 2-4 factual sentences: what it shows, any visible text, and notable details. No preamble.";
|
|
21717
22174
|
var MAX_DIM = 1568;
|
|
21718
22175
|
async function describeImage(auth, path2, opts = {}) {
|
|
@@ -21728,7 +22185,7 @@ async function describeImage(auth, path2, opts = {}) {
|
|
|
21728
22185
|
}
|
|
21729
22186
|
var DEFAULT_PDF_PROMPT = "Read this document for a knowledge base. First transcribe its readable text, then add a 2-4 sentence factual summary of what it is and its key details. It may be a scanned/image-only PDF \u2014 read the text from the page images. No preamble.";
|
|
21730
22187
|
async function describePdf(auth, path2, opts = {}) {
|
|
21731
|
-
const buf = await
|
|
22188
|
+
const buf = await readFile3(path2);
|
|
21732
22189
|
const maxBytes = opts.maxBytes ?? 3e7;
|
|
21733
22190
|
if (buf.length > maxBytes) {
|
|
21734
22191
|
throw new Error(
|
|
@@ -22073,6 +22530,14 @@ async function attachBlob(srcPath, latticeRoot) {
|
|
|
22073
22530
|
}
|
|
22074
22531
|
|
|
22075
22532
|
// src/gui/ingest-routes.ts
|
|
22533
|
+
function fileSlug(name, id) {
|
|
22534
|
+
const base = slugify(name.replace(/\.[^./\\]+$/, "")) || "file";
|
|
22535
|
+
return `${base}-${id.slice(0, 8)}`;
|
|
22536
|
+
}
|
|
22537
|
+
function fileIdentity(displayName, id) {
|
|
22538
|
+
const label = displayName.trim() || "file";
|
|
22539
|
+
return { slug: fileSlug(displayName, id), name: label, title: label };
|
|
22540
|
+
}
|
|
22076
22541
|
var MIME_BY_EXT = {
|
|
22077
22542
|
".pdf": "application/pdf",
|
|
22078
22543
|
".png": "image/png",
|
|
@@ -22377,7 +22842,8 @@ function looksLikeUrl(s) {
|
|
|
22377
22842
|
const t = s.trim();
|
|
22378
22843
|
return /^https?:\/\/\S+$/i.test(t) && !/\s/.test(t);
|
|
22379
22844
|
}
|
|
22380
|
-
|
|
22845
|
+
var MAX_INGEST_BYTES = 5e7;
|
|
22846
|
+
function readBuffer2(req, maxBytes = MAX_INGEST_BYTES) {
|
|
22381
22847
|
return new Promise((resolve_, reject) => {
|
|
22382
22848
|
const chunks = [];
|
|
22383
22849
|
let size = 0;
|
|
@@ -22440,8 +22906,10 @@ async function dispatchIngestRoute(req, res, ctx) {
|
|
|
22440
22906
|
} finally {
|
|
22441
22907
|
await rm(tmp, { force: true }).catch(() => void 0);
|
|
22442
22908
|
}
|
|
22909
|
+
const fileId = crypto.randomUUID();
|
|
22443
22910
|
const { id: id2 } = await createRow(mctx, "files", {
|
|
22444
|
-
id:
|
|
22911
|
+
id: fileId,
|
|
22912
|
+
...fileIdentity(name2, fileId),
|
|
22445
22913
|
original_name: name2,
|
|
22446
22914
|
mime: mime2,
|
|
22447
22915
|
size_bytes: buf.length,
|
|
@@ -22492,8 +22960,10 @@ async function dispatchIngestRoute(req, res, ctx) {
|
|
|
22492
22960
|
console.warn("[ingest] url crawl failed:", e.message);
|
|
22493
22961
|
}
|
|
22494
22962
|
}
|
|
22963
|
+
const textFileId = crypto.randomUUID();
|
|
22495
22964
|
const { id: id2 } = await createRow(mctx, "files", {
|
|
22496
|
-
id:
|
|
22965
|
+
id: textFileId,
|
|
22966
|
+
...fileIdentity(title, textFileId),
|
|
22497
22967
|
original_name: title,
|
|
22498
22968
|
mime: mime2,
|
|
22499
22969
|
size_bytes: Buffer.byteLength(content, "utf8"),
|
|
@@ -22525,10 +22995,16 @@ async function dispatchIngestRoute(req, res, ctx) {
|
|
|
22525
22995
|
sendJson5(res, { error: `file not found: ${abs}` }, 400);
|
|
22526
22996
|
return true;
|
|
22527
22997
|
}
|
|
22998
|
+
if (size > MAX_INGEST_BYTES) {
|
|
22999
|
+
sendJson5(res, { error: "file too large" }, 413);
|
|
23000
|
+
return true;
|
|
23001
|
+
}
|
|
22528
23002
|
const name = basename10(abs);
|
|
22529
23003
|
const mime = mimeFor(name);
|
|
23004
|
+
const localFileId = crypto.randomUUID();
|
|
22530
23005
|
const { id } = await createRow(mctx, "files", {
|
|
22531
|
-
id:
|
|
23006
|
+
id: localFileId,
|
|
23007
|
+
...fileIdentity(name, localFileId),
|
|
22532
23008
|
path: abs,
|
|
22533
23009
|
original_name: name,
|
|
22534
23010
|
mime,
|
|
@@ -22638,7 +23114,7 @@ function sendText(res, body, status = 200, contentType = "text/plain; charset=ut
|
|
|
22638
23114
|
function openUrl(url) {
|
|
22639
23115
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
22640
23116
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
22641
|
-
const child =
|
|
23117
|
+
const child = spawn2(command, args, { stdio: "ignore", detached: true });
|
|
22642
23118
|
child.unref();
|
|
22643
23119
|
}
|
|
22644
23120
|
function listen(server, port, host) {
|
|
@@ -22888,7 +23364,7 @@ function resolveOutputDirForConfig(configPath) {
|
|
|
22888
23364
|
}
|
|
22889
23365
|
return resolve9(base, "context");
|
|
22890
23366
|
}
|
|
22891
|
-
async function openConfig(configPath, outputDir, autoRender = false) {
|
|
23367
|
+
async function openConfig(configPath, outputDir, autoRender = false, teamCloud = false) {
|
|
22892
23368
|
const parsed = parseConfigFile(configPath);
|
|
22893
23369
|
if (!/^postgres(ql)?:\/\//i.test(parsed.dbPath) && !parsed.dbPath.startsWith("file:") && parsed.dbPath !== ":memory:") {
|
|
22894
23370
|
mkdirSync10(dirname11(parsed.dbPath), { recursive: true });
|
|
@@ -23017,6 +23493,7 @@ async function openConfig(configPath, outputDir, autoRender = false) {
|
|
|
23017
23493
|
}
|
|
23018
23494
|
}
|
|
23019
23495
|
let teamContext = null;
|
|
23496
|
+
let cloudReconnectRequired = false;
|
|
23020
23497
|
if (db.getDialect() === "postgres") {
|
|
23021
23498
|
let teamEnabled = false;
|
|
23022
23499
|
try {
|
|
@@ -23024,7 +23501,14 @@ async function openConfig(configPath, outputDir, autoRender = false) {
|
|
|
23024
23501
|
} catch {
|
|
23025
23502
|
teamEnabled = false;
|
|
23026
23503
|
}
|
|
23027
|
-
|
|
23504
|
+
const directGuiPostgres = !teamCloud && isPostgresUrl(parsed.dbPath);
|
|
23505
|
+
if (directGuiPostgres) {
|
|
23506
|
+
if (teamEnabled) {
|
|
23507
|
+
cloudReconnectRequired = true;
|
|
23508
|
+
teamEnabled = false;
|
|
23509
|
+
validTables.clear();
|
|
23510
|
+
}
|
|
23511
|
+
} else if (!teamEnabled) {
|
|
23028
23512
|
try {
|
|
23029
23513
|
const rawDb = parseDocument3(readFileSync15(configPath, "utf8")).get("db");
|
|
23030
23514
|
const dbLine = typeof rawDb === "string" ? rawDb.trim() : "";
|
|
@@ -23065,15 +23549,8 @@ async function openConfig(configPath, outputDir, autoRender = false) {
|
|
|
23065
23549
|
if (!isVisibleInTeam(name, teamContext)) validTables.delete(name);
|
|
23066
23550
|
}
|
|
23067
23551
|
}
|
|
23068
|
-
let directTeamConnection = false;
|
|
23069
|
-
try {
|
|
23070
|
-
const conns = await teamsClient.listConnections();
|
|
23071
|
-
directTeamConnection = conns.some((c) => isPostgresUrl(c.cloud_url));
|
|
23072
|
-
} catch (e) {
|
|
23073
|
-
console.warn("[openConfig] could not check for direct team connections:", e.message);
|
|
23074
|
-
}
|
|
23075
23552
|
let realtime = null;
|
|
23076
|
-
if (db.getDialect() === "postgres") {
|
|
23553
|
+
if (db.getDialect() === "postgres" && !cloudReconnectRequired) {
|
|
23077
23554
|
try {
|
|
23078
23555
|
realtime = new RealtimeBroker(parsed.dbPath);
|
|
23079
23556
|
await realtime.start();
|
|
@@ -23112,7 +23589,8 @@ async function openConfig(configPath, outputDir, autoRender = false) {
|
|
|
23112
23589
|
teamsClient,
|
|
23113
23590
|
validTables,
|
|
23114
23591
|
teamContext,
|
|
23115
|
-
|
|
23592
|
+
teamCloud,
|
|
23593
|
+
cloudReconnectRequired,
|
|
23116
23594
|
junctionTables,
|
|
23117
23595
|
entityContextByTable,
|
|
23118
23596
|
manifest,
|
|
@@ -23207,7 +23685,7 @@ async function disposeActive(active) {
|
|
|
23207
23685
|
async function reopenSameConfig(active, autoRender) {
|
|
23208
23686
|
const feed = active.feed;
|
|
23209
23687
|
await disposeActive(active);
|
|
23210
|
-
const next = await openConfig(active.configPath, active.outputDir, autoRender);
|
|
23688
|
+
const next = await openConfig(active.configPath, active.outputDir, autoRender, active.teamCloud);
|
|
23211
23689
|
next.feed = feed;
|
|
23212
23690
|
return next;
|
|
23213
23691
|
}
|
|
@@ -23342,7 +23820,7 @@ async function applySchemaConfig(active, entry, direction, autoRender) {
|
|
|
23342
23820
|
for (const sql of ddl) await execSql(active.db, sql);
|
|
23343
23821
|
saveConfigDoc(active.configPath, doc);
|
|
23344
23822
|
await disposeActive(active);
|
|
23345
|
-
return openConfig(active.configPath, active.outputDir, autoRender);
|
|
23823
|
+
return openConfig(active.configPath, active.outputDir, autoRender, active.teamCloud);
|
|
23346
23824
|
}
|
|
23347
23825
|
function schemaReverseSummary(verb, entry) {
|
|
23348
23826
|
const what = entry.operation.replace("schema.", "").replace(/_/g, " ");
|
|
@@ -23356,7 +23834,7 @@ async function startGuiServer(options) {
|
|
|
23356
23834
|
const teamCloud = options.teamCloud ?? false;
|
|
23357
23835
|
const autoRender = options.autoRender ?? false;
|
|
23358
23836
|
const sessionId = crypto.randomUUID();
|
|
23359
|
-
let active = await openConfig(configPath, outputDir, autoRender);
|
|
23837
|
+
let active = await openConfig(configPath, outputDir, autoRender, teamCloud);
|
|
23360
23838
|
const latticeRoot = findLatticeRoot(dirname11(configPath));
|
|
23361
23839
|
let currentWorkspaceId = null;
|
|
23362
23840
|
if (latticeRoot) {
|
|
@@ -24546,7 +25024,7 @@ data: ${JSON.stringify(data)}
|
|
|
24546
25024
|
const paths = resolveWorkspacePaths(latticeRoot, ws);
|
|
24547
25025
|
let next;
|
|
24548
25026
|
try {
|
|
24549
|
-
next = await openConfig(paths.configPath, paths.contextDir, autoRender);
|
|
25027
|
+
next = await openConfig(paths.configPath, paths.contextDir, autoRender, teamCloud);
|
|
24550
25028
|
} catch (e) {
|
|
24551
25029
|
const err = e;
|
|
24552
25030
|
sendJson(
|
|
@@ -24588,7 +25066,12 @@ data: ${JSON.stringify(data)}
|
|
|
24588
25066
|
const newPaths = resolveWorkspacePaths(latticeRoot, created);
|
|
24589
25067
|
let newActive;
|
|
24590
25068
|
try {
|
|
24591
|
-
newActive = await openConfig(
|
|
25069
|
+
newActive = await openConfig(
|
|
25070
|
+
newPaths.configPath,
|
|
25071
|
+
newPaths.contextDir,
|
|
25072
|
+
autoRender,
|
|
25073
|
+
teamCloud
|
|
25074
|
+
);
|
|
24592
25075
|
} catch (e) {
|
|
24593
25076
|
sendJson(
|
|
24594
25077
|
res,
|
|
@@ -24647,7 +25130,12 @@ data: ${JSON.stringify(data)}
|
|
|
24647
25130
|
const fbPaths = resolveWorkspacePaths(latticeRoot, fallback);
|
|
24648
25131
|
let next;
|
|
24649
25132
|
try {
|
|
24650
|
-
next = await openConfig(
|
|
25133
|
+
next = await openConfig(
|
|
25134
|
+
fbPaths.configPath,
|
|
25135
|
+
fbPaths.contextDir,
|
|
25136
|
+
autoRender,
|
|
25137
|
+
teamCloud
|
|
25138
|
+
);
|
|
24651
25139
|
} catch (e) {
|
|
24652
25140
|
const err = e;
|
|
24653
25141
|
const codePrefix = err.code ? `[${err.code}] ` : "";
|
|
@@ -24739,7 +25227,12 @@ data: ${JSON.stringify(data)}
|
|
|
24739
25227
|
}
|
|
24740
25228
|
let next;
|
|
24741
25229
|
try {
|
|
24742
|
-
next = await openConfig(
|
|
25230
|
+
next = await openConfig(
|
|
25231
|
+
newPath,
|
|
25232
|
+
resolveOutputDirForConfig(newPath),
|
|
25233
|
+
autoRender,
|
|
25234
|
+
teamCloud
|
|
25235
|
+
);
|
|
24743
25236
|
} catch (e) {
|
|
24744
25237
|
const err = e;
|
|
24745
25238
|
console.error(`[dbconfig.switch] openConfig(${newPath}) failed:`, err);
|
|
@@ -24766,7 +25259,8 @@ data: ${JSON.stringify(data)}
|
|
|
24766
25259
|
const next = await openConfig(
|
|
24767
25260
|
newConfigPath,
|
|
24768
25261
|
resolveOutputDirForConfig(newConfigPath),
|
|
24769
|
-
autoRender
|
|
25262
|
+
autoRender,
|
|
25263
|
+
teamCloud
|
|
24770
25264
|
);
|
|
24771
25265
|
await disposeActive(active);
|
|
24772
25266
|
active = next;
|
|
@@ -24808,7 +25302,8 @@ data: ${JSON.stringify(data)}
|
|
|
24808
25302
|
next = await openConfig(
|
|
24809
25303
|
fallback.path,
|
|
24810
25304
|
resolveOutputDirForConfig(fallback.path),
|
|
24811
|
-
autoRender
|
|
25305
|
+
autoRender,
|
|
25306
|
+
teamCloud
|
|
24812
25307
|
);
|
|
24813
25308
|
} catch (e) {
|
|
24814
25309
|
const err = e;
|
|
@@ -25208,9 +25703,14 @@ data: ${JSON.stringify(data)}
|
|
|
25208
25703
|
teamId: active.teamContext.teamId,
|
|
25209
25704
|
myUserId: active.teamContext.myUserId
|
|
25210
25705
|
} : null,
|
|
25211
|
-
|
|
25706
|
+
cloudReconnectRequired: active.cloudReconnectRequired,
|
|
25212
25707
|
swap: async () => {
|
|
25213
|
-
const next = await openConfig(
|
|
25708
|
+
const next = await openConfig(
|
|
25709
|
+
active.configPath,
|
|
25710
|
+
active.outputDir,
|
|
25711
|
+
autoRender,
|
|
25712
|
+
active.teamCloud
|
|
25713
|
+
);
|
|
25214
25714
|
await disposeActive(active);
|
|
25215
25715
|
active = next;
|
|
25216
25716
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -8096,19 +8096,12 @@ async function destroyTeamDirect(db) {
|
|
|
8096
8096
|
}
|
|
8097
8097
|
await db.delete("__lattice_team_identity", "singleton");
|
|
8098
8098
|
}
|
|
8099
|
-
var directDeprecationWarned = false;
|
|
8100
8099
|
async function openCloud(cloudUrl) {
|
|
8101
8100
|
if (!isPostgresUrl(cloudUrl)) {
|
|
8102
8101
|
throw new Error(
|
|
8103
8102
|
`direct-ops: cloudUrl must be a postgres:// URL (got ${cloudUrl.slice(0, 12)}\u2026)`
|
|
8104
8103
|
);
|
|
8105
8104
|
}
|
|
8106
|
-
if (!directDeprecationWarned) {
|
|
8107
|
-
directDeprecationWarned = true;
|
|
8108
|
-
console.warn(
|
|
8109
|
-
"[teams] Direct postgres:// team-cloud connection is deprecated and does NOT enforce 2.2 row-level security. Migrate to a hosted Lattice Teams server."
|
|
8110
|
-
);
|
|
8111
|
-
}
|
|
8112
8105
|
const db = new Lattice(cloudUrl);
|
|
8113
8106
|
await db.init();
|
|
8114
8107
|
for (const [table, def] of Object.entries(CLOUD_INTERNAL_TABLE_DEFS)) {
|
package/dist/index.d.cts
CHANGED
|
@@ -4509,8 +4509,8 @@ interface PdfOptions {
|
|
|
4509
4509
|
}
|
|
4510
4510
|
/**
|
|
4511
4511
|
* Read a PDF with Claude's native document support — works on text PDFs AND
|
|
4512
|
-
* scanned/image-only PDFs (no text layer),
|
|
4513
|
-
* AI-gated; the model call is injectable for tests.
|
|
4512
|
+
* scanned/image-only PDFs (no text layer), where in-process text extraction
|
|
4513
|
+
* finds nothing. AI-gated; the model call is injectable for tests.
|
|
4514
4514
|
*/
|
|
4515
4515
|
declare function describePdf(auth: ClaudeAuth, path: string, opts?: PdfOptions): Promise<string>;
|
|
4516
4516
|
|
package/dist/index.d.ts
CHANGED
|
@@ -4509,8 +4509,8 @@ interface PdfOptions {
|
|
|
4509
4509
|
}
|
|
4510
4510
|
/**
|
|
4511
4511
|
* Read a PDF with Claude's native document support — works on text PDFs AND
|
|
4512
|
-
* scanned/image-only PDFs (no text layer),
|
|
4513
|
-
* AI-gated; the model call is injectable for tests.
|
|
4512
|
+
* scanned/image-only PDFs (no text layer), where in-process text extraction
|
|
4513
|
+
* finds nothing. AI-gated; the model call is injectable for tests.
|
|
4514
4514
|
*/
|
|
4515
4515
|
declare function describePdf(auth: ClaudeAuth, path: string, opts?: PdfOptions): Promise<string>;
|
|
4516
4516
|
|
package/dist/index.js
CHANGED
|
@@ -7962,19 +7962,12 @@ async function destroyTeamDirect(db) {
|
|
|
7962
7962
|
}
|
|
7963
7963
|
await db.delete("__lattice_team_identity", "singleton");
|
|
7964
7964
|
}
|
|
7965
|
-
var directDeprecationWarned = false;
|
|
7966
7965
|
async function openCloud(cloudUrl) {
|
|
7967
7966
|
if (!isPostgresUrl(cloudUrl)) {
|
|
7968
7967
|
throw new Error(
|
|
7969
7968
|
`direct-ops: cloudUrl must be a postgres:// URL (got ${cloudUrl.slice(0, 12)}\u2026)`
|
|
7970
7969
|
);
|
|
7971
7970
|
}
|
|
7972
|
-
if (!directDeprecationWarned) {
|
|
7973
|
-
directDeprecationWarned = true;
|
|
7974
|
-
console.warn(
|
|
7975
|
-
"[teams] Direct postgres:// team-cloud connection is deprecated and does NOT enforce 2.2 row-level security. Migrate to a hosted Lattice Teams server."
|
|
7976
|
-
);
|
|
7977
|
-
}
|
|
7978
7971
|
const db = new Lattice(cloudUrl);
|
|
7979
7972
|
await db.init();
|
|
7980
7973
|
for (const [table, def] of Object.entries(CLOUD_INTERNAL_TABLE_DEFS)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "latticesql",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"description": "Persistent structured memory for AI agent systems — pluggable SQLite or Postgres backend, LLM context bridge",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -57,10 +57,14 @@
|
|
|
57
57
|
},
|
|
58
58
|
"optionalDependencies": {
|
|
59
59
|
"@anthropic-ai/sdk": "^0.71.2",
|
|
60
|
+
"fflate": "^0.8.3",
|
|
60
61
|
"file-type": "^19.6.0",
|
|
62
|
+
"mammoth": "^1.12.0",
|
|
61
63
|
"pg": "^8.11.0",
|
|
62
64
|
"playwright": "^1.48.0",
|
|
63
|
-
"sharp": "^0.33.5"
|
|
65
|
+
"sharp": "^0.33.5",
|
|
66
|
+
"unpdf": "^1.6.2",
|
|
67
|
+
"word-extractor": "^1.0.4"
|
|
64
68
|
},
|
|
65
69
|
"devDependencies": {
|
|
66
70
|
"@anthropic-ai/sdk": "^0.71.0",
|