feat: add material asset workspace
This commit is contained in:
@@ -3,6 +3,8 @@ const state = {
|
||||
selectedQuestion: null,
|
||||
lastAnswer: null,
|
||||
progress: null,
|
||||
ontology: null,
|
||||
assetPrompt: null,
|
||||
};
|
||||
|
||||
const els = {
|
||||
@@ -14,6 +16,12 @@ const els = {
|
||||
feedback: document.querySelector("#feedback"),
|
||||
progress: document.querySelector("#progress"),
|
||||
refreshProgress: document.querySelector("#refresh-progress"),
|
||||
materialForm: document.querySelector("#material-form"),
|
||||
assetForm: document.querySelector("#asset-form"),
|
||||
ontology: document.querySelector("#ontology"),
|
||||
assetOutput: document.querySelector("#asset-output"),
|
||||
assetConcept: document.querySelector("#asset-concept"),
|
||||
assetButton: document.querySelector("#asset-button"),
|
||||
status: document.querySelector("#status-line"),
|
||||
error: document.querySelector("#error-line"),
|
||||
title: document.querySelector("#session-title"),
|
||||
@@ -41,6 +49,7 @@ els.sessionForm.addEventListener("submit", async (event) => {
|
||||
state.lastAnswer = null;
|
||||
renderSession();
|
||||
renderFeedback();
|
||||
renderProgress();
|
||||
setStatus(`Session ${session.id} ready`);
|
||||
} catch (error) {
|
||||
showError(error.message);
|
||||
@@ -81,9 +90,54 @@ els.answerForm.addEventListener("submit", async (event) => {
|
||||
}
|
||||
});
|
||||
|
||||
els.materialForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
clearError();
|
||||
setStatus("Ingesting material...");
|
||||
|
||||
try {
|
||||
const result = await request("/api/v1/materials", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
title: value("#material-title"),
|
||||
source_type: value("#material-source"),
|
||||
body: value("#material-body"),
|
||||
}),
|
||||
});
|
||||
state.ontology = result.snapshot;
|
||||
renderOntology();
|
||||
setStatus(`Material ${result.material.id} ingested`);
|
||||
} catch (error) {
|
||||
showError(error.message);
|
||||
setStatus("Content workspace ready");
|
||||
}
|
||||
});
|
||||
|
||||
els.assetForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
clearError();
|
||||
setStatus("Generating prompt candidate...");
|
||||
|
||||
try {
|
||||
const prompt = await request("/api/v1/teaching-assets/prompts", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
concept_id: els.assetConcept.value,
|
||||
asset_type: value("#asset-type"),
|
||||
}),
|
||||
});
|
||||
state.assetPrompt = prompt;
|
||||
renderAssetPrompt();
|
||||
setStatus(`Prompt ${prompt.id} generated`);
|
||||
} catch (error) {
|
||||
showError(error.message);
|
||||
setStatus("Content workspace ready");
|
||||
}
|
||||
});
|
||||
|
||||
function renderSession() {
|
||||
if (!state.session) return;
|
||||
els.title.textContent = `${state.session.target_role} · ${state.session.questions.length} questions`;
|
||||
els.title.textContent = `${state.session.target_role} - ${state.session.questions.length} questions`;
|
||||
els.questions.className = "question-list";
|
||||
els.questions.innerHTML = "";
|
||||
|
||||
@@ -143,16 +197,64 @@ function renderProgress() {
|
||||
</section>
|
||||
<section>
|
||||
<h2>Concept memory</h2>
|
||||
<div>${mastery.map((item) => `<span class="concept-pill">${escapeHTML(item.concept.label)} · ${escapeHTML(item.state)}</span>`).join("")}</div>
|
||||
<div>${mastery.map((item) => `<span class="concept-pill">${escapeHTML(item.concept.label)} - ${escapeHTML(item.state)}</span>`).join("")}</div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Next challenge</h2>
|
||||
<p class="status-line">${escapeHTML(challenge.concept.label)} · ${escapeHTML(challenge.ladder_level)}</p>
|
||||
<p class="status-line">${escapeHTML(challenge.concept.label)} - ${escapeHTML(challenge.ladder_level)}</p>
|
||||
<p>${escapeHTML(challenge.question)}</p>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderOntology() {
|
||||
if (!state.ontology) {
|
||||
els.ontology.className = "ontology-view empty-state";
|
||||
els.ontology.textContent = "Ingest material to inspect ontology candidates.";
|
||||
return;
|
||||
}
|
||||
|
||||
const concepts = state.ontology.concepts || [];
|
||||
els.ontology.className = "ontology-view";
|
||||
els.ontology.innerHTML = `
|
||||
<div class="summary-strip">
|
||||
<span class="summary-chip">${concepts.length} concepts</span>
|
||||
<span class="summary-chip">${(state.ontology.edges || []).length} edges</span>
|
||||
<span class="summary-chip">${(state.ontology.gaps || []).length} gaps</span>
|
||||
</div>
|
||||
<section>
|
||||
<h2>Candidate concepts</h2>
|
||||
<div>${concepts.map((item) => `<span class="concept-pill">${escapeHTML(item.concept.label)} - ${escapeHTML(item.review_state)}</span>`).join("") || "No candidates yet."}</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
els.assetConcept.innerHTML = concepts
|
||||
.map((item) => `<option value="${escapeHTML(item.concept.id)}">${escapeHTML(item.concept.label)}</option>`)
|
||||
.join("");
|
||||
els.assetConcept.disabled = concepts.length === 0;
|
||||
els.assetButton.disabled = concepts.length === 0;
|
||||
}
|
||||
|
||||
function renderAssetPrompt() {
|
||||
if (!state.assetPrompt) {
|
||||
els.assetOutput.className = "ontology-view empty-state";
|
||||
els.assetOutput.textContent = "Generate a prompt to inspect model key, review state, and evidence.";
|
||||
return;
|
||||
}
|
||||
|
||||
const prompt = state.assetPrompt;
|
||||
els.assetOutput.className = "ontology-view";
|
||||
els.assetOutput.innerHTML = `
|
||||
<div class="summary-strip">
|
||||
<span class="summary-chip">${escapeHTML(prompt.model_key)}</span>
|
||||
<span class="summary-chip">${escapeHTML(prompt.review_state)}</span>
|
||||
<span class="summary-chip">verify model id: ${prompt.requires_model_id_verification ? "yes" : "no"}</span>
|
||||
</div>
|
||||
<pre class="prompt-text">${escapeHTML(prompt.prompt)}</pre>
|
||||
${evidenceBlock(prompt.source_evidence)}
|
||||
`;
|
||||
}
|
||||
|
||||
function renderFeedback() {
|
||||
if (!state.lastAnswer) {
|
||||
els.feedback.className = "feedback empty-state";
|
||||
|
||||
Reference in New Issue
Block a user