@polycode-projects/the-mechanical-code-talker 3.0.1 → 3.0.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 +11 -0
- package/bin/tmct.mjs +93 -1
- package/data/templates/constructions/digest-sentence-structures.toml +97 -0
- package/package.json +7 -1
- package/src/adapters/corpus/digest-bank.mjs +62 -0
- package/src/adapters/toml-config.mjs +5 -0
- package/src/domain/ask.mjs +53 -4
- package/src/domain/cli-verbs.mjs +11 -0
- package/src/domain/digest/article.mjs +106 -0
- package/src/domain/digest/compose.mjs +103 -0
- package/src/domain/digest/config.json +13 -0
- package/src/domain/digest/index.mjs +38 -0
- package/src/domain/digest/select.mjs +233 -0
- package/src/domain/digest/store-stats.mjs +77 -0
- package/src/domain/digest/structures.mjs +100 -0
- package/src/domain/digest/words.mjs +40 -0
- package/src/domain/discourse.mjs +0 -0
- package/src/domain/hash.mjs +6 -1
- package/src/domain/router/goal-reasoner.mjs +6 -2
- package/src/services/chat-session.mjs +6 -1
- package/src/services/chat.mjs +317 -53
- package/src/services/ledger-viz.mjs +63 -3
- package/src/services/research-viz.mjs +106 -6
- package/src/surfaces/web/digest-client.mjs +73 -0
- package/src/surfaces/web/ledger-browser-entry.mjs +2 -1
- package/src/surfaces/web/memory-ask-browser-entry.mjs +6 -1
- package/src/surfaces/web/memory-ask-browser.bundle.js +144 -139
- package/src/surfaces/web/research-browser-entry.mjs +20 -1
|
@@ -504,8 +504,8 @@ function sparkCaptionHtml(stats) {
|
|
|
504
504
|
* different questions (did this render even offer the reference; did the
|
|
505
505
|
* browser actually manage to load it), and both must hold for the live
|
|
506
506
|
* path to run. */
|
|
507
|
-
export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, worthALook, payload, meta, memoryAskBundle, stats, ledgerBundleAvailable = false } = {}) {
|
|
508
|
-
const ledgerJson = embedJson({ rows: rows || [], terms: terms || [], edges: edges || [], focus: focus || null, contradictions: contradictions || [], worthALook: worthALook || null, meta: meta || { shown: 0, total: 0, truncated: false } });
|
|
507
|
+
export function renderLedgerHtml({ rows, terms, edges, focus, contradictions, worthALook, payload, meta, memoryAskBundle, stats, ledgerBundleAvailable = false, focusDigest = null, digestStructures = [] } = {}) {
|
|
508
|
+
const ledgerJson = embedJson({ rows: rows || [], terms: terms || [], edges: edges || [], focus: focus || null, contradictions: contradictions || [], worthALook: worthALook || null, meta: meta || { shown: 0, total: 0, truncated: false }, focusDigest: focusDigest || null, digestStructures: Array.isArray(digestStructures) ? digestStructures : [] });
|
|
509
509
|
const payloadJson = embedJson(payload || { individuals: [], objectProperties: [] });
|
|
510
510
|
const shown = meta?.shown ?? (rows || []).length;
|
|
511
511
|
const title = `tmct ledger — ${shown} fact${shown === 1 ? "" : "s"}${focus ? ` (focus: ${escapeHtml(focus)})` : ""}`;
|
|
@@ -641,6 +641,15 @@ ${THEME_TOKENS_CSS}
|
|
|
641
641
|
.focuscard { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: .8rem 1rem; margin-bottom: 1rem; }
|
|
642
642
|
.focuscard .term { font-size: 1.35rem; font-weight: 700; }
|
|
643
643
|
.focuscard .klass, .focuscard .stats { font-family: ${MONO_STACK}; font-size: .72rem; color: var(--muted); margin-top: .3rem; font-variant-numeric: tabular-nums; }
|
|
644
|
+
.focuscard .focusdigest { margin-top: .7rem; font-size: .92rem; line-height: 1.5; }
|
|
645
|
+
.focuscard .focusdigest p { margin: 0 0 .4rem; }
|
|
646
|
+
.focuscard .focusdigest .dgsrc { font-family: ${MONO_STACK}; font-size: .7rem; color: var(--muted); margin-top: .2rem; }
|
|
647
|
+
.focuscard .focusdigest .dgfacts { margin-top: .5rem; }
|
|
648
|
+
.focuscard .focusdigest .dgfacts > summary { font-family: ${MONO_STACK}; font-size: .7rem; color: var(--corpus); cursor: pointer; }
|
|
649
|
+
.focuscard .focusdigest .dgfacts[open] > summary { margin-bottom: .35rem; }
|
|
650
|
+
.focuscard .focusdigest .dgfactlist { border-top: 1px solid var(--line); }
|
|
651
|
+
.focuscard .focusdigest .dgfact { font-size: .72rem; padding: .22rem 0; border-bottom: 1px solid var(--line); word-break: break-word; }
|
|
652
|
+
.focuscard .focusdigest .dgfact:last-child { border-bottom: none; }
|
|
644
653
|
.group { margin: 1.1rem 0; }
|
|
645
654
|
.group h3 { font-family: ${MONO_STACK}; font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); font-weight: 400; margin: 0 0 .35rem; display: flex; align-items: baseline; gap: .5rem; }
|
|
646
655
|
.group h3::after { content: ""; flex: 1; border-top: 1px solid var(--line); transform: translateY(-.2em); }
|
|
@@ -805,6 +814,30 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
805
814
|
let trail = focus ? [{ term: focus, label: null }] : [];
|
|
806
815
|
const sel = { prov: new Set(), fam: new Set(), rec: new Set() };
|
|
807
816
|
|
|
817
|
+
// The store the client-side digest reads. Defaults to the page's embedded
|
|
818
|
+
// PAYLOAD (a static viz page never changes it); once a live dock session
|
|
819
|
+
// exists it points at that session's store, which teach/research grow in
|
|
820
|
+
// place, so a digest of a just-taught term reads the fresh facts.
|
|
821
|
+
let getLivePayload = () => PAYLOAD;
|
|
822
|
+
// Whichever loaded bundle carries the browser digest helper — the demo
|
|
823
|
+
// ledger's own live engine (tmctLedger) or the committed memory-ask engine
|
|
824
|
+
// the CLI viz page inlines (tmctMemoryAsk). Null before either loads, and the
|
|
825
|
+
// focus card then keeps whatever server-computed digest it shipped.
|
|
826
|
+
const digestHelper = () =>
|
|
827
|
+
(typeof tmctLedger !== "undefined" && tmctLedger && tmctLedger.digestTermFromPayloadBrowser)
|
|
828
|
+
|| (typeof tmctMemoryAsk !== "undefined" && tmctMemoryAsk && tmctMemoryAsk.digestTermFromPayloadBrowser)
|
|
829
|
+
|| null;
|
|
830
|
+
// The digest for one term, computed live in the browser from the embedded
|
|
831
|
+
// structure table, or null when no structures were embedded, no engine has
|
|
832
|
+
// loaded, or the term holds nothing to compose.
|
|
833
|
+
function clientDigest(term) {
|
|
834
|
+
const structures = LEDGER.digestStructures;
|
|
835
|
+
const helper = digestHelper();
|
|
836
|
+
if (!term || !helper || !structures || !structures.length) return null;
|
|
837
|
+
try { return helper(getLivePayload(), term, structures, { budget: 8 }); }
|
|
838
|
+
catch { return null; }
|
|
839
|
+
}
|
|
840
|
+
|
|
808
841
|
const recOf = (r) => {
|
|
809
842
|
const t = Date.parse(r.createdAt);
|
|
810
843
|
if (!Number.isFinite(t)) return "older";
|
|
@@ -860,10 +893,28 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
860
893
|
const srcs = new Set(all.map((r) => r.src.split(" | ")[0]));
|
|
861
894
|
const dates = all.map((r) => r.createdAt).filter(Boolean).sort();
|
|
862
895
|
const klass = all.find((r) => r.s === focus && r.family === "is-a");
|
|
896
|
+
// The digest paragraph recomputes client-side on every focus, from the
|
|
897
|
+
// embedded structure table over the current store — so a refocus reads the
|
|
898
|
+
// new term back rather than losing the card. The server-computed focus
|
|
899
|
+
// digest is the fallback for the initial focus alone, kept for a page whose
|
|
900
|
+
// engine bundle never loads (its client digest would come back null).
|
|
901
|
+
const dg = clientDigest(focus)
|
|
902
|
+
|| ((focus === LEDGER.focus && LEDGER.focusDigest && LEDGER.focusDigest.paragraphs && LEDGER.focusDigest.paragraphs.length) ? LEDGER.focusDigest : null);
|
|
903
|
+
const dgFacts = (dg && Array.isArray(dg.facts)) ? dg.facts : [];
|
|
904
|
+
const factsEscapeHtml = dgFacts.length
|
|
905
|
+
? '<details class="dgfacts"><summary>show the facts (' + dgFacts.length + ')</summary><div class="dgfactlist">' +
|
|
906
|
+
dgFacts.map((f) => '<div class="dgfact"><span class="mono">' + esc(String(f.subject || "")) + " " + esc(String(f.predicate || "")) + " " + esc(String(f.object || "")) + "</span></div>").join("") +
|
|
907
|
+
"</div></details>"
|
|
908
|
+
: "";
|
|
909
|
+
const digestHtml = dg
|
|
910
|
+
? '<div class="focusdigest">' + dg.paragraphs.map((p) => "<p>" + esc(p) + "</p>").join("") +
|
|
911
|
+
(dg.sources && dg.sources.length ? '<p class="dgsrc">(sources: ' + esc(dg.sources.join("; ")) + ")</p>" : "") +
|
|
912
|
+
factsEscapeHtml + "</div>"
|
|
913
|
+
: "";
|
|
863
914
|
let html = '<div class="focuscard"><div class="term">' + esc(focus) + "</div>" +
|
|
864
915
|
'<div class="klass">' + (klass ? esc(klass.phrase + " " + klass.o) : "no class recorded") + "</div>" +
|
|
865
916
|
'<div class="stats">' + all.length + " facts · " + srcs.size + " source" + (srcs.size === 1 ? "" : "s") +
|
|
866
|
-
(dates.length ? " · first " + esc(dates[0].slice(0, 10)) + " · last " + esc(dates[dates.length - 1].slice(0, 10)) : "") + "</div
|
|
917
|
+
(dates.length ? " · first " + esc(dates[0].slice(0, 10)) + " · last " + esc(dates[dates.length - 1].slice(0, 10)) : "") + "</div>" + digestHtml + "</div>";
|
|
867
918
|
const bracketed = new Set();
|
|
868
919
|
for (const fam of FAMS) {
|
|
869
920
|
const rows = mine.filter((r) => r.family === fam && !bracketed.has(r.id));
|
|
@@ -998,6 +1049,11 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
998
1049
|
rows: freshData.rows, terms: freshData.terms, edges: freshData.edges,
|
|
999
1050
|
focus: freshData.focus, contradictions: freshData.contradictions,
|
|
1000
1051
|
worthALook: freshData.worthALook, meta: freshData.meta,
|
|
1052
|
+
// The embedded structure table is build-time data a re-derivation never
|
|
1053
|
+
// recomputes; carry it across so a digest of the just-taught term still
|
|
1054
|
+
// has a table to compose from. The stale server focusDigest is dropped —
|
|
1055
|
+
// the client recompute over the grown store supersedes it.
|
|
1056
|
+
digestStructures: LEDGER.digestStructures,
|
|
1001
1057
|
};
|
|
1002
1058
|
rebuildIndexes();
|
|
1003
1059
|
el("dash").outerHTML = dashboardHtml(freshData.stats, { fresh: true });
|
|
@@ -1069,6 +1125,10 @@ ${ledgerBundleAvailable ? `<script src="./ledger-browser.bundle.js"></script>` :
|
|
|
1069
1125
|
if (session) return session;
|
|
1070
1126
|
await tryLoadWink();
|
|
1071
1127
|
session = await tmctLedger.createLedgerSession({ seedPayload: PAYLOAD });
|
|
1128
|
+
// From here the live store is the source of truth for the digest, so a
|
|
1129
|
+
// digest of a term taught this session reads its fresh facts — the store
|
|
1130
|
+
// grows in place, so this one closure stays current.
|
|
1131
|
+
getLivePayload = () => session.memoryDir.payload;
|
|
1072
1132
|
return session;
|
|
1073
1133
|
}
|
|
1074
1134
|
|
|
@@ -73,9 +73,14 @@ export function loadProgressLine(parts) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/** The self-contained research page. Pure — the same output for the same
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
|
|
76
|
+
* input every time; every piece of state is computed live in the browser once
|
|
77
|
+
* the sibling research bundle loads. `digestStructures` are the pre-parsed
|
|
78
|
+
* [[structure]] rows of the digest sentence-structure bank, embedded so the
|
|
79
|
+
* page can digest a term client-side over its grown store (no TOML parser in
|
|
80
|
+
* the browser); an empty list leaves the digest panel degrading to an honest
|
|
81
|
+
* "no digest available" the same way the node stub does. */
|
|
82
|
+
export function renderResearchHtml({ title = DEFAULT_TITLE, digestStructures = [] } = {}) {
|
|
83
|
+
const digestStructuresJson = JSON.stringify(Array.isArray(digestStructures) ? digestStructures : []);
|
|
79
84
|
return `<!doctype html>
|
|
80
85
|
<html lang="en">
|
|
81
86
|
<head>
|
|
@@ -148,8 +153,26 @@ ${THEME_TOKENS_CSS}
|
|
|
148
153
|
|
|
149
154
|
.chips { display: flex; flex-wrap: wrap; gap: .4rem; }
|
|
150
155
|
.chip { font-family: ${MONO_STACK}; font-size: .74rem; border: 1px solid var(--line); border-radius: 99px; padding: .2rem .6rem; background: var(--bg); }
|
|
156
|
+
.chip.tapchip { cursor: pointer; }
|
|
157
|
+
.chip.tapchip:hover { border-color: var(--ink); }
|
|
151
158
|
.chip .deg { color: var(--muted); margin-left: .35rem; }
|
|
152
159
|
|
|
160
|
+
/* the term digest: a narrative card, its sources, and the flat fact list one
|
|
161
|
+
click away behind "show the facts". */
|
|
162
|
+
.digestpanel .hint { font-size: .78rem; color: var(--muted); margin: 0 0 .55rem; }
|
|
163
|
+
.digestout { min-height: 1.4rem; }
|
|
164
|
+
.digestout .empty { color: var(--muted); font-size: .82rem; margin: .2rem 0; }
|
|
165
|
+
.digestout .miss { color: var(--muted); font-size: .88rem; border: 1px dashed var(--line); border-radius: 8px; padding: .55rem .7rem; }
|
|
166
|
+
.dgcard { border: 1px solid var(--line); border-radius: 8px; padding: .7rem .85rem .8rem; background: var(--bg); }
|
|
167
|
+
.dgterm { font-family: ${MONO_STACK}; font-size: .72rem; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); margin-bottom: .4rem; }
|
|
168
|
+
.dgcard p { margin: 0 0 .5rem; font-size: .95rem; }
|
|
169
|
+
.dgcard p:last-of-type { margin-bottom: 0; }
|
|
170
|
+
.dgsrc { font-family: ${MONO_STACK}; font-size: .7rem; color: var(--muted); word-break: break-word; }
|
|
171
|
+
.dgfacts { margin-top: .6rem; }
|
|
172
|
+
.dgfacts > summary { font-family: ${MONO_STACK}; font-size: .72rem; color: var(--corpus); cursor: pointer; list-style: revert; }
|
|
173
|
+
.dgfacts[open] > summary { margin-bottom: .35rem; }
|
|
174
|
+
.dgfacts .factlist { border-top: 1px solid var(--line); }
|
|
175
|
+
|
|
153
176
|
/* ask, scoped by source */
|
|
154
177
|
.askRow { display: flex; gap: .5rem; margin: .2rem 0 .7rem; }
|
|
155
178
|
.askRow input { flex: 1; min-width: 0; font-family: ${SERIF_STACK}; font-size: .92rem; background: var(--bg); color: var(--ink); border: 1px solid var(--line); border-radius: 8px; padding: .45rem .7rem; }
|
|
@@ -247,6 +270,16 @@ ${THEME_TOKENS_CSS}
|
|
|
247
270
|
</div>
|
|
248
271
|
</div>
|
|
249
272
|
|
|
273
|
+
<h2 class="band">read a term back</h2>
|
|
274
|
+
<div class="panel digestpanel">
|
|
275
|
+
<p class="hint">Pick a term the graph knows and it reads back a short narrative — the facts it holds, composed into sentences with the sources named, deterministic and never a guess. The full fact list stays one click away behind “show the facts”.</p>
|
|
276
|
+
<div class="askRow">
|
|
277
|
+
<input id="digestInput" type="text" autocomplete="off" spellcheck="false" placeholder="a term the graph knows, e.g. dog" aria-label="A term to read back as a digest" disabled>
|
|
278
|
+
<button type="button" class="btn primary" id="digestGo" disabled>digest</button>
|
|
279
|
+
</div>
|
|
280
|
+
<div id="digestOut" class="digestout" aria-live="polite"><p class="empty">Ask for a digest, or click a term under “best-connected terms” above.</p></div>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
250
283
|
<h2 class="band">ask the graph</h2>
|
|
251
284
|
<div class="cols">
|
|
252
285
|
<div class="panel">
|
|
@@ -283,6 +316,10 @@ ${THEME_TOKENS_CSS}
|
|
|
283
316
|
const createTicker = ${createTicker.toString()};
|
|
284
317
|
const prefersReducedMotion = ${prefersReducedMotion.toString()};
|
|
285
318
|
const el = (id) => document.getElementById(id);
|
|
319
|
+
// The digest sentence-structure bank, pre-parsed at build time — the browser
|
|
320
|
+
// has no TOML parser, so the page carries the table the client-side digest
|
|
321
|
+
// composes from.
|
|
322
|
+
const DIGEST_STRUCTURES = ${digestStructuresJson};
|
|
286
323
|
|
|
287
324
|
if ("serviceWorker" in navigator) navigator.serviceWorker.register("./tmct-sw.js").catch(() => {});
|
|
288
325
|
|
|
@@ -332,7 +369,7 @@ ${THEME_TOKENS_CSS}
|
|
|
332
369
|
try { return structuredClone(seedPayload); } catch { return JSON.parse(JSON.stringify(seedPayload)); }
|
|
333
370
|
};
|
|
334
371
|
function newSession() {
|
|
335
|
-
return window.tmctResearch.createResearchSession({ seedPayload: cloneSeed(), vocabSeeded: Boolean(seedPayload) });
|
|
372
|
+
return window.tmctResearch.createResearchSession({ seedPayload: cloneSeed(), vocabSeeded: Boolean(seedPayload), digestStructures: DIGEST_STRUCTURES });
|
|
336
373
|
}
|
|
337
374
|
|
|
338
375
|
// The curated reference pack provider, same fetch seam chat.html registers,
|
|
@@ -377,6 +414,8 @@ ${THEME_TOKENS_CSS}
|
|
|
377
414
|
el("ingestGo").disabled = false;
|
|
378
415
|
el("askInput").disabled = false;
|
|
379
416
|
el("askGo").disabled = false;
|
|
417
|
+
el("digestInput").disabled = false;
|
|
418
|
+
el("digestGo").disabled = false;
|
|
380
419
|
el("exportFacts").disabled = false;
|
|
381
420
|
}
|
|
382
421
|
|
|
@@ -427,13 +466,16 @@ ${THEME_TOKENS_CSS}
|
|
|
427
466
|
return;
|
|
428
467
|
}
|
|
429
468
|
for (const hub of hubs) {
|
|
430
|
-
const chip = document.createElement("
|
|
431
|
-
chip.
|
|
469
|
+
const chip = document.createElement("button");
|
|
470
|
+
chip.type = "button";
|
|
471
|
+
chip.className = "chip tapchip";
|
|
472
|
+
chip.setAttribute("aria-label", "read a digest of " + hub.term);
|
|
432
473
|
chip.appendChild(document.createTextNode(hub.term));
|
|
433
474
|
const deg = document.createElement("span");
|
|
434
475
|
deg.className = "deg";
|
|
435
476
|
deg.textContent = hub.degree + (hub.degree === 1 ? " fact" : " facts");
|
|
436
477
|
chip.appendChild(deg);
|
|
478
|
+
chip.addEventListener("click", () => { el("digestInput").value = hub.term; digest(); });
|
|
437
479
|
box.appendChild(chip);
|
|
438
480
|
}
|
|
439
481
|
}
|
|
@@ -544,6 +586,64 @@ ${THEME_TOKENS_CSS}
|
|
|
544
586
|
el("askGo").addEventListener("click", ask);
|
|
545
587
|
el("askInput").addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); ask(); } });
|
|
546
588
|
|
|
589
|
+
// ---- digest a term: the narrative read-back over the grown store ---------
|
|
590
|
+
function renderDigestFact(fact) {
|
|
591
|
+
const row = document.createElement("div");
|
|
592
|
+
row.className = "fact";
|
|
593
|
+
const dot = document.createElement("span"); dot.className = "dot tone-seed";
|
|
594
|
+
const subj = document.createElement("span"); subj.className = "subj"; subj.textContent = String(fact.subject || "");
|
|
595
|
+
const pred = document.createElement("span"); pred.className = "pred"; pred.textContent = String(fact.predicate || "");
|
|
596
|
+
const obj = document.createElement("span"); obj.className = "obj"; obj.textContent = String(fact.object || "");
|
|
597
|
+
row.appendChild(dot); row.appendChild(subj); row.appendChild(pred); row.appendChild(obj);
|
|
598
|
+
return row;
|
|
599
|
+
}
|
|
600
|
+
function renderDigest(term, view) {
|
|
601
|
+
const box = el("digestOut");
|
|
602
|
+
box.textContent = "";
|
|
603
|
+
if (!view) {
|
|
604
|
+
const p = document.createElement("p"); p.className = "miss";
|
|
605
|
+
p.textContent = 'No grounded digest for "' + term + '" — nothing is stored about it, or nothing the digest could compose. It abstains rather than guess.';
|
|
606
|
+
box.appendChild(p);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const card = document.createElement("div"); card.className = "dgcard";
|
|
610
|
+
const head = document.createElement("div"); head.className = "dgterm mono"; head.textContent = view.term || term;
|
|
611
|
+
card.appendChild(head);
|
|
612
|
+
for (const para of view.paragraphs) {
|
|
613
|
+
const p = document.createElement("p"); p.textContent = para; card.appendChild(p);
|
|
614
|
+
}
|
|
615
|
+
if (view.sources && view.sources.length) {
|
|
616
|
+
const src = document.createElement("p"); src.className = "dgsrc";
|
|
617
|
+
src.textContent = "(sources: " + view.sources.join("; ") + ")";
|
|
618
|
+
card.appendChild(src);
|
|
619
|
+
}
|
|
620
|
+
const facts = view.facts || [];
|
|
621
|
+
if (facts.length) {
|
|
622
|
+
const det = document.createElement("details"); det.className = "dgfacts";
|
|
623
|
+
const sum = document.createElement("summary");
|
|
624
|
+
sum.textContent = "show the facts (" + view.factCount + ")";
|
|
625
|
+
det.appendChild(sum);
|
|
626
|
+
const list = document.createElement("div"); list.className = "factlist";
|
|
627
|
+
for (const f of facts) list.appendChild(renderDigestFact(f));
|
|
628
|
+
det.appendChild(list);
|
|
629
|
+
card.appendChild(det);
|
|
630
|
+
}
|
|
631
|
+
box.appendChild(card);
|
|
632
|
+
}
|
|
633
|
+
async function digest() {
|
|
634
|
+
const term = el("digestInput").value.trim();
|
|
635
|
+
if (!term || !session) return;
|
|
636
|
+
const box = el("digestOut");
|
|
637
|
+
box.textContent = "";
|
|
638
|
+
const wait = document.createElement("p"); wait.className = "empty"; wait.textContent = "reading it back…";
|
|
639
|
+
box.appendChild(wait);
|
|
640
|
+
let view = null;
|
|
641
|
+
try { view = await session.digest(term); } catch { view = null; }
|
|
642
|
+
renderDigest(term, view);
|
|
643
|
+
}
|
|
644
|
+
el("digestGo").addEventListener("click", digest);
|
|
645
|
+
el("digestInput").addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); digest(); } });
|
|
646
|
+
|
|
547
647
|
// ---- grow: teach by telling --------------------------------------------
|
|
548
648
|
async function teach() {
|
|
549
649
|
const q = el("teachInput").value.trim();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// digest-client.mjs — the browser-native side of the digest layer's stage-5
|
|
2
|
+
// wiring. The node surfaces (chat, `tmct digest`, the page generators) reach
|
|
3
|
+
// the pure pipeline through adapters/corpus/digest-bank.mjs, which reads the
|
|
4
|
+
// sentence-structure bank off disk with a TOML parser. A browser can't do that,
|
|
5
|
+
// so the bundle scripts stub digest-bank out — and the pages that grow or hold
|
|
6
|
+
// a store IN the browser (research, the ledger dock, the CLI `tmct viz` page)
|
|
7
|
+
// digest from a structure table embedded in the page as JSON at build time.
|
|
8
|
+
//
|
|
9
|
+
// This module is that browser path: it feeds the embedded structures straight
|
|
10
|
+
// into the SAME pure layer (src/domain/digest — buildStructureTable, digestTerm,
|
|
11
|
+
// the store-stats scan) the node seam uses, so the two produce the same article
|
|
12
|
+
// from the same rows. It ships no TOML parser; the page hands it pre-parsed
|
|
13
|
+
// [[structure]] rows.
|
|
14
|
+
|
|
15
|
+
import { buildStructureTable, digestTerm } from "../../domain/digest/index.mjs";
|
|
16
|
+
import { digestStoreStats, chainsForObjects, isaObjectsOf } from "../../domain/digest/store-stats.mjs";
|
|
17
|
+
import { readFactRows } from "../../adapters/memory/core.mjs";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Digest one term end to end from fact rows and a pre-parsed structure table.
|
|
21
|
+
* The browser twin of digestTermFromRows: identical pipeline, but the structure
|
|
22
|
+
* rows arrive as data (embedded JSON) rather than from a filesystem TOML read.
|
|
23
|
+
*
|
|
24
|
+
* `structures` is the array of [[structure]] rows the bank parses to. Returns
|
|
25
|
+
* the term-article shape, or null when no structures were supplied (the page
|
|
26
|
+
* then falls back to its flat fact list, the same degradation the node stub
|
|
27
|
+
* gives).
|
|
28
|
+
*/
|
|
29
|
+
export function digestTermFromRowsBrowser(term, termRows, allRows, structures, opts = {}) {
|
|
30
|
+
const table = buildStructureTable(structures || []);
|
|
31
|
+
if (!table || table.size === 0) return null;
|
|
32
|
+
const rows = termRows || [];
|
|
33
|
+
const store = digestStoreStats(allRows || rows);
|
|
34
|
+
const chains = chainsForObjects(store.subClassEdges, isaObjectsOf(rows));
|
|
35
|
+
return digestTerm(term, rows, store, table, { ...opts, chains });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Digest one term straight from a loadMemory()-shaped payload — the shape every
|
|
40
|
+
* in-browser surface already holds (the ledger's embedded PAYLOAD, the ledger
|
|
41
|
+
* dock's live memoryDir.payload, the research session's store). Scans the
|
|
42
|
+
* payload once for the term's own rows and the whole-store statistics, so a
|
|
43
|
+
* caller never has to run readFactRows itself.
|
|
44
|
+
*
|
|
45
|
+
* Returns the render-ready view (see digestViewFromArticle) or null when the
|
|
46
|
+
* term has no rows, no structures were supplied, or the selector kept nothing.
|
|
47
|
+
*/
|
|
48
|
+
export function digestTermFromPayloadBrowser(payload, term, structures, opts = {}) {
|
|
49
|
+
const rows = readFactRows(payload || { individuals: [], objectProperties: [] });
|
|
50
|
+
const termRows = rows.filter((r) => r.subject === term);
|
|
51
|
+
if (!termRows.length) return null;
|
|
52
|
+
const article = digestTermFromRowsBrowser(term, termRows, rows, structures, opts);
|
|
53
|
+
return digestViewFromArticle(article);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The flat, render-ready view a page's DOM code wants from a term article:
|
|
58
|
+
* the narrative paragraphs, the distinct source strings (the "(sources: …)"
|
|
59
|
+
* line the chat and CLI surfaces render), and the full fact list behind the
|
|
60
|
+
* "show the facts" escape. Null when the article carried no narrative, so the
|
|
61
|
+
* caller falls back to its flat rendering rather than showing an empty card.
|
|
62
|
+
*/
|
|
63
|
+
export function digestViewFromArticle(article) {
|
|
64
|
+
if (!article || !Array.isArray(article.paragraphs) || !article.paragraphs.length) return null;
|
|
65
|
+
const facts = (article.detail && Array.isArray(article.detail.facts)) ? article.detail.facts : [];
|
|
66
|
+
return {
|
|
67
|
+
term: article.term || "",
|
|
68
|
+
paragraphs: article.paragraphs.slice(),
|
|
69
|
+
sources: [...new Set((article.sources || []).map((s) => s.provenance).filter(Boolean))],
|
|
70
|
+
facts,
|
|
71
|
+
factCount: (article.detail && Number.isInteger(article.detail.factCount)) ? article.detail.factCount : facts.length,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -25,6 +25,7 @@ import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
|
25
25
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
26
26
|
import { registerResearchProvider } from "../../adapters/corpus/wikipedia-live.mjs";
|
|
27
27
|
import { computeLedgerDataFromPayload } from "../../services/ledger-viz.mjs";
|
|
28
|
+
import { digestTermFromPayloadBrowser } from "./digest-client.mjs";
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* A browser ledger-dock session over the real turn engine — createChatSession's
|
|
@@ -101,4 +102,4 @@ export async function exportFactsJsonl(memoryDir) {
|
|
|
101
102
|
// splitSentences + exportFactsJsonl carry the dock's paste-and-drop ingest and
|
|
102
103
|
// its JSONL export across the bundle boundary, the same one-serializer posture
|
|
103
104
|
// chat-browser-entry.mjs holds for its own page.
|
|
104
|
-
globalThis.tmctLedger = { createLedgerSession, computeLedgerDataFromPayload, normFactTerm, registerWinkModel, registerResearchProvider, splitSentences: splitSentencesPreservingPaths, exportFactsJsonl };
|
|
105
|
+
globalThis.tmctLedger = { createLedgerSession, computeLedgerDataFromPayload, normFactTerm, registerWinkModel, registerResearchProvider, splitSentences: splitSentencesPreservingPaths, exportFactsJsonl, digestTermFromPayloadBrowser };
|
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
// bypassing the structural-graph parse pipeline this dock has no use for.
|
|
9
9
|
import { factAnswer, factReadBack } from "../../services/chat.mjs";
|
|
10
10
|
import { createInMemoryStore, normFactTerm } from "../../adapters/memory/core.mjs";
|
|
11
|
+
import { digestTermFromPayloadBrowser } from "./digest-client.mjs";
|
|
11
12
|
|
|
12
13
|
// normFactTerm is re-exported too, for the page's client-side term normalization.
|
|
13
14
|
// factReadBack carries the taught-relation chases (grandfather-style questions)
|
|
14
15
|
// that factAnswer's own lanes don't reach.
|
|
15
|
-
|
|
16
|
+
// digestTermFromPayloadBrowser lets the CLI `tmct viz` page read a refocused
|
|
17
|
+
// term back as a digest client-side, from the structure table the page embeds —
|
|
18
|
+
// the same narrative the demo ledger's live bundle produces, over the same
|
|
19
|
+
// static store this page already carries.
|
|
20
|
+
globalThis.tmctMemoryAsk = { factAnswer, factReadBack, createInMemoryStore, normFactTerm, digestTermFromPayloadBrowser };
|