feat(advice): LLM brewing advice on every observation (work in progress)

Stores a model's advice for each recorded reading, in its own field, so the
brewer's own comment is never overwritten.

Fire and forget: the advice is generated in a background thread, so
recording a reading never blocks on the model.  A failure is recorded as a
visible note saying the advice could not be generated, because a silent
failure is indistinguishable from "still thinking".

The model is `glm-5.3:cloud` on the Ollama instance at 192.168.178.45:11434.
Several models on that instance are retired and fail at call time while
still appearing in the model list, so the default is one that was probed.

The system prompt tells the model never to call a reading impossible: a
gravity below 1.000 is always a mis-entry, but the rest of the run may be a
genuine long fermentation and still worth understanding.

150 tests.
This commit is contained in:
2026-11-09 16:03:29 +10:00
parent 41cf2cbb8e
commit ab3050194d
9 changed files with 799 additions and 4 deletions
+62
View File
@@ -689,3 +689,65 @@ table.data .comment { color: var(--text-muted); font-size: .84rem; }
.reading-list__sg { font-family: var(--mono); font-weight: 650; flex: 0 0 4rem; }
.reading-list__body { flex: 1; min-width: 0; }
.reading-list__comment { color: var(--text-muted); font-size: .84rem; }
/* ------------------------------------------------------------- advice */
/* The LLM's advice. It sits next to the brewer's own comment, never
replaces it, and is visually distinct so the two are never confused. */
.advice {
display: block;
margin-top: .35rem;
padding: .5rem .65rem;
border-left: 2px solid var(--amber);
border-radius: 0 4px 4px 0;
background: var(--amber-dim);
color: var(--text);
font-size: .8125rem;
line-height: 1.5;
}
.advice__label {
display: block;
margin-bottom: .15rem;
font-size: .625rem;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--amber-text);
}
/* Advice that could not be generated - it says why. */
.advice--failed {
border-left-color: var(--danger);
background: var(--danger-dim);
color: var(--text-muted);
}
.advice--failed .advice__label {
color: var(--danger);
}
.advice-cell {
max-width: 32rem;
}
/* In the table the advice is clamped to a few lines - the full text is
on the run detail page, where there is room to read it. */
.advice-cell .advice {
margin-top: 0;
font-size: .75rem;
display: -webkit-box;
-webkit-line-clamp: 4;
line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* The clamped text opens up on hover, so the whole thing is readable
without leaving the page. */
.advice-cell:hover .advice,
.advice-cell:focus-within .advice {
-webkit-line-clamp: unset;
line-clamp: unset;
overflow: visible;
}