@polycode-projects/the-mechanical-code-talker 3.0.2 → 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/bin/tmct.mjs +24 -1
- package/package.json +1 -1
- package/src/domain/hash.mjs +6 -1
- package/src/domain/router/goal-reasoner.mjs +6 -2
- package/src/services/chat.mjs +151 -66
- package/src/services/ledger-viz.mjs +56 -7
- 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 +141 -141
- package/src/surfaces/web/research-browser-entry.mjs +20 -1
|
@@ -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 };
|