graph context: a separate marker message before the first user turn

The map picks no longer glue into the opening prompt as seed text
(owner, 2026-10-04 late). POST /api/chats accepts graphContext
{propositions}; the route resolves the picks against the live graph,
derives the reasoning chains among them (chainsForSelection →
formatChainLine), and writes a graph-context custom marker as
message[0] — before the learner's first user message, in either mode.

- Core: newGraphContextMessage (model-facing content = the
  contextualization framing + proposition lines + chain lines;
  details = the structured pick); toLlmMessages flattens it to a user
  message like every marker, so the model reads it for free.
- View: projects to a user-side data-graph-context part.
- UI: GraphContextCard — minimal, muted, dashed, collapsed by default
  (counts only); expand reveals name — statement rows and the chain
  lines. Same compact-expandable pattern as the practice cards.
- Home: sends graphContext with the creation whenever picks exist;
  the prompt param carries the learner's own words only.
This commit is contained in:
amosblomqvist
2026-10-04 23:07:13 +03:00
parent 87f8e1e6f4
commit 90893ae236
8 changed files with 327 additions and 13 deletions
+11
View File
@@ -290,6 +290,17 @@ function applyResultTo(part: LhViewPart | undefined, result: ToolResultMessage):
/** A marker's projected message (handoff/steers) or splice (plan block). */
function projectMarker(marker: LhMarkerMessage, out: LhViewMessage[]): void {
switch (marker.customType) {
case "graph-context": {
// The session's map context (owner, 2026-10-04): user-side —
// a minimal collapsible card ABOVE the first user message;
// never a text bubble (scaffolding, like the thinking card).
out.push({
id: marker.id,
role: "user",
parts: [{ type: "data-graph-context", id: `gc_${marker.id}`, data: marker.details }],
});
return;
}
case "probe-handoff": {
out.push({
id: marker.id,