next: ai part 3

This commit is contained in:
nym21
2026-07-22 16:50:32 +02:00
parent 52e4db5ea6
commit f0871d895a
29 changed files with 3207 additions and 143 deletions
@@ -0,0 +1,32 @@
import { createChart } from "../../../../chart/index.js";
import { units } from "../../../../chart/units.js";
import { colors } from "../../../../utils/colors.js";
import { createMetric } from "../../../tools/metrics/index.js";
/** @typedef {import("../../../storage.js").ChartArtifact} ChartArtifact */
/** @param {ChartArtifact} artifact */
export function createAskChart(artifact) {
const section = document.createElement("section");
const spec = artifact.chart;
section.dataset.askChart = "";
try {
const chart = {
title: spec.title,
unit: units[spec.unit],
defaultType: spec.view,
defaultScale: spec.scale,
series: spec.series.map((item) => ({
label: item.label,
color: colors[item.color],
metric: createMetric(item.path),
})),
};
section.append(createChart(chart, `ask-${artifact.id}`));
} catch {
section.dataset.state = "unavailable";
section.textContent = "Chart unavailable";
}
return section;
}
@@ -0,0 +1,20 @@
main[data-page="ask"] [data-ask-chart] {
margin-top: 1rem;
&[data-state="unavailable"] {
color: var(--gray);
}
figure[data-chart="series"] {
--chart-plot-height: 16rem;
--chart-reserved-ui-height: 5rem;
margin: 0;
}
}
@media (max-width: 48rem) {
main[data-page="ask"] [data-ask-chart] figure[data-chart="series"] {
--chart-plot-height: 13rem;
}
}
+72 -2
View File
@@ -1,4 +1,6 @@
import { renderMarkdown } from "../../markdown.js";
import { formatDuration } from "../../timing.js";
import { createAskChart } from "./chart/index.js";
/** @typedef {"user" | "assistant"} MessageRole */
@@ -15,16 +17,84 @@ export function setMessageContent(content, role, text) {
/**
* @param {MessageRole} role
* @param {string} text
* @param {Object} [metadata]
* @param {import("../../storage.js").StoredArtifact[]} [metadata.artifacts]
* @param {number} [metadata.elapsedMs]
* @param {import("../../timing.js").ResponseStep[]} [metadata.steps]
*/
export function createAskMessage(role, text) {
export function createAskMessage(role, text, metadata = {}) {
const { artifacts = [], elapsedMs, steps = [] } = metadata;
const item = document.createElement("li");
const label = document.createElement("strong");
const content = document.createElement("div");
const responseSteps = role === "assistant"
? document.createElement("ol")
: undefined;
const responseTime = role === "assistant"
? document.createElement("small")
: undefined;
/** @param {import("../../storage.js").StoredArtifact[]} nextArtifacts */
function setArtifacts(nextArtifacts) {
item.querySelectorAll(":scope > [data-ask-chart]").forEach((chart) => {
chart.remove();
});
const charts = nextArtifacts
.filter((artifact) => artifact.type === "chart")
.map(createAskChart);
if (responseSteps) responseSteps.before(...charts);
else item.append(...charts);
}
/** @param {import("../../timing.js").ResponseStep[]} nextSteps */
function setSteps(nextSteps) {
if (!responseSteps) return;
responseSteps.replaceChildren(...nextSteps.map((step) => {
const item = document.createElement("li");
const name = document.createElement("span");
const duration = document.createElement("time");
name.textContent = step.label;
if (step.elapsedMs === undefined) item.dataset.active = "";
else duration.textContent = formatDuration(step.elapsedMs);
item.append(name, duration);
return item;
}));
responseSteps.hidden = !nextSteps.length;
}
/** @param {number | undefined} nextElapsedMs */
function setElapsed(nextElapsedMs) {
if (!responseTime) return;
const visible =
typeof nextElapsedMs === "number" &&
Number.isFinite(nextElapsedMs) &&
nextElapsedMs >= 0;
responseTime.hidden = !visible;
responseTime.textContent = visible
? `${formatDuration(nextElapsedMs)} total`
: "";
}
item.dataset.role = role;
label.append(role === "user" ? "You" : "Assistant");
setMessageContent(content, role, text);
item.append(label, content);
if (responseSteps) {
responseSteps.dataset.responseSteps = "";
item.append(responseSteps);
}
if (responseTime) {
responseTime.dataset.responseTime = "";
responseTime.title = "Response time";
item.append(responseTime);
}
setArtifacts(artifacts);
setSteps(steps);
setElapsed(elapsedMs);
return { item, content };
return { item, content, setArtifacts, setSteps, setElapsed };
}
@@ -107,6 +107,32 @@ main[data-page="ask"] [data-ask-conversation] > ol > li {
color: color-mix(in oklch, var(--white) 72%, transparent);
}
}
> :is(ol[data-response-steps], small[data-response-time]) {
color: color-mix(in oklch, var(--white) 38%, transparent);
font-size: var(--font-size-xs);
font-variant-numeric: tabular-nums;
line-height: var(--line-height-xs);
}
> ol[data-response-steps] {
display: grid;
width: min(100%, 18rem);
gap: 0.1rem;
margin: 0;
padding: 0;
list-style: none;
> li {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 1rem;
&[data-active] > span::after {
content: "…";
}
}
}
}
@media (max-width: 48rem) {