Add storyteller-only admin reading view
Build and publish frontend / publish (push) Successful in 56s
Build and publish frontend / publish (push) Successful in 56s
This commit is contained in:
+25
-2
@@ -28,6 +28,11 @@
|
|||||||
#admin-content { display: grid; gap: 1rem; }
|
#admin-content { display: grid; gap: 1rem; }
|
||||||
#admin-content section, #admin-content article { border: 1px solid currentColor; border-radius: .35rem; padding: .75rem; }
|
#admin-content section, #admin-content article { border: 1px solid currentColor; border-radius: .35rem; padding: .75rem; }
|
||||||
#admin-content article > button { display: block; max-width: 100%; margin-block: .5rem; text-align: start; white-space: normal; }
|
#admin-content article > button { display: block; max-width: 100%; margin-block: .5rem; text-align: start; white-space: normal; }
|
||||||
|
#admin-content article > .admin-story-reply { display: none; }
|
||||||
|
#admin-content .admin-simple-conversation > h2,
|
||||||
|
#admin-content .admin-simple-conversation > p:not(.admin-story-empty),
|
||||||
|
#admin-content .admin-simple-conversation article > :not(.admin-story-reply) { display: none; }
|
||||||
|
#admin-content .admin-simple-conversation article > .admin-story-reply { display: block; margin-block: .85rem; white-space: pre-wrap; }
|
||||||
#admin-content pre { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 32rem; overflow: auto; }
|
#admin-content pre { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 32rem; overflow: auto; }
|
||||||
#admin-toolbar { display: flex; flex-wrap: wrap; gap: .5rem; margin-block: 1rem; }
|
#admin-toolbar { display: flex; flex-wrap: wrap; gap: .5rem; margin-block: 1rem; }
|
||||||
#player-search { display: flex; flex-wrap: wrap; align-items: end; gap: .5rem; }
|
#player-search { display: flex; flex-wrap: wrap; align-items: end; gap: .5rem; }
|
||||||
@@ -168,6 +173,7 @@
|
|||||||
let adminOffset = 0;
|
let adminOffset = 0;
|
||||||
const adminPageSize = 50;
|
const adminPageSize = 50;
|
||||||
let adminTraceOffset = 0;
|
let adminTraceOffset = 0;
|
||||||
|
let adminSimpleConversation = false;
|
||||||
let presenceTimer = null;
|
let presenceTimer = null;
|
||||||
let playerMessagesPrimed = false;
|
let playerMessagesPrimed = false;
|
||||||
const seenPlayerMessageIds = new Set();
|
const seenPlayerMessageIds = new Set();
|
||||||
@@ -652,6 +658,7 @@
|
|||||||
const profile = await request(`/api/admin/players/${encodeURIComponent(accountId)}`);
|
const profile = await request(`/api/admin/players/${encodeURIComponent(accountId)}`);
|
||||||
adminJson(`${profile.username} · profil och biografi`, profile);
|
adminJson(`${profile.username} · profil och biografi`, profile);
|
||||||
adminTraceOffset = 0;
|
adminTraceOffset = 0;
|
||||||
|
adminSimpleConversation = false;
|
||||||
const trace = await request(`/api/admin/players/${encodeURIComponent(accountId)}/trace?limit=200&offset=0`);
|
const trace = await request(`/api/admin/players/${encodeURIComponent(accountId)}/trace?limit=200&offset=0`);
|
||||||
renderAdminConversations(trace, accountId);
|
renderAdminConversations(trace, accountId);
|
||||||
}
|
}
|
||||||
@@ -670,7 +677,21 @@
|
|||||||
const conversations = [...groups.values()].sort((a, b) => String(a.at).localeCompare(String(b.at)));
|
const conversations = [...groups.values()].sort((a, b) => String(a.at).localeCompare(String(b.at)));
|
||||||
adminElement('h2', 'Samtal och beslut', root);
|
adminElement('h2', 'Samtal och beslut', root);
|
||||||
adminElement('p', `Visar ${conversations.length} korrelerade samtalsturer från den här sidan. Välj en berättarreplik eller ett AI-svar för att öppna hela registrerade beslutskedjan. Registreringstid, världstid och commitordning visas var för sig.`, root);
|
adminElement('p', `Visar ${conversations.length} korrelerade samtalsturer från den här sidan. Välj en berättarreplik eller ett AI-svar för att öppna hela registrerade beslutskedjan. Registreringstid, världstid och commitordning visas var för sig.`, root);
|
||||||
if (conversations.length === 0) { adminElement('p', 'Det finns inga sparade dialogturer för kontot ännu.', root); return; }
|
const readingToggle = adminElement('button', adminSimpleConversation ? 'Visa felsökningsspår' : 'Lättläst konversation', root);
|
||||||
|
readingToggle.type = 'button';
|
||||||
|
readingToggle.setAttribute('aria-pressed', String(adminSimpleConversation));
|
||||||
|
readingToggle.addEventListener('click', () => {
|
||||||
|
adminSimpleConversation = !adminSimpleConversation;
|
||||||
|
root.classList.toggle('admin-simple-conversation', adminSimpleConversation);
|
||||||
|
readingToggle.textContent = adminSimpleConversation ? 'Visa felsökningsspår' : 'Lättläst konversation';
|
||||||
|
readingToggle.setAttribute('aria-pressed', String(adminSimpleConversation));
|
||||||
|
});
|
||||||
|
root.classList.toggle('admin-simple-conversation', adminSimpleConversation);
|
||||||
|
if (conversations.length === 0) {
|
||||||
|
const empty = adminElement('p', 'Det finns inga sparade berättarrepliker för kontot ännu.', root);
|
||||||
|
empty.className = 'admin-story-empty';
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (const [index, group] of conversations.entries()) {
|
for (const [index, group] of conversations.entries()) {
|
||||||
group.events.sort((a, b) => (a.commit_sequence ?? 0) - (b.commit_sequence ?? 0));
|
group.events.sort((a, b) => (a.commit_sequence ?? 0) - (b.commit_sequence ?? 0));
|
||||||
group.messages.sort((a, b) => (a.sequence ?? 0) - (b.sequence ?? 0));
|
group.messages.sort((a, b) => (a.sequence ?? 0) - (b.sequence ?? 0));
|
||||||
@@ -683,6 +704,8 @@
|
|||||||
if (payload.player_message) { adminElement('p', `Spelaren: ${payload.player_message}`, card); showedPlayerInput = true; }
|
if (payload.player_message) { adminElement('p', `Spelaren: ${payload.player_message}`, card); showedPlayerInput = true; }
|
||||||
const reply = payload.reply || payload.npc_reply;
|
const reply = payload.reply || payload.npc_reply;
|
||||||
if (reply) {
|
if (reply) {
|
||||||
|
const story = adminElement('p', `Berättelsen: ${reply}`, card);
|
||||||
|
story.className = 'admin-story-reply';
|
||||||
const button = adminElement('button', `Berättaren${payload.npc_reply ? ' / NPC' : ''}: ${reply}`, card);
|
const button = adminElement('button', `Berättaren${payload.npc_reply ? ' / NPC' : ''}: ${reply}`, card);
|
||||||
button.type = 'button';
|
button.type = 'button';
|
||||||
button.setAttribute('aria-expanded', 'false');
|
button.setAttribute('aria-expanded', 'false');
|
||||||
@@ -719,7 +742,7 @@
|
|||||||
}
|
}
|
||||||
const pageHasMore = hasMoreOverride ?? trace.has_more ?? (Math.max(trace.events?.length || 0, trace.ai_interactions?.length || 0, trace.player_messages?.length || 0) >= (trace.limit || 200));
|
const pageHasMore = hasMoreOverride ?? trace.has_more ?? (Math.max(trace.events?.length || 0, trace.ai_interactions?.length || 0, trace.player_messages?.length || 0) >= (trace.limit || 200));
|
||||||
if (pageHasMore) {
|
if (pageHasMore) {
|
||||||
const older = adminElement('button', 'Visa äldre samtalsturer', root);
|
const older = adminElement('button', adminSimpleConversation ? 'Visa äldre berättarrepliker' : 'Visa äldre samtalsturer', root);
|
||||||
older.type = 'button';
|
older.type = 'button';
|
||||||
older.addEventListener('click', async () => {
|
older.addEventListener('click', async () => {
|
||||||
older.disabled = true;
|
older.disabled = true;
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ grep -q 'Välj konto att följa' index.html
|
|||||||
grep -q 'function renderAdminConversations' index.html
|
grep -q 'function renderAdminConversations' index.html
|
||||||
grep -q 'function renderDecisionProvenance' index.html
|
grep -q 'function renderDecisionProvenance' index.html
|
||||||
grep -q 'Visa äldre samtalsturer' index.html
|
grep -q 'Visa äldre samtalsturer' index.html
|
||||||
|
grep -q 'Lättläst konversation' index.html
|
||||||
|
grep -q 'admin-simple-conversation' index.html
|
||||||
grep -q 'aria-expanded' index.html
|
grep -q 'aria-expanded' index.html
|
||||||
grep -q "request('/api/admin/prompts'" index.html
|
grep -q "request('/api/admin/prompts'" index.html
|
||||||
grep -q "request('/api/admin/rules'" index.html
|
grep -q "request('/api/admin/rules'" index.html
|
||||||
|
|||||||
@@ -31,3 +31,11 @@ test('account trace can be paged until all stored turns are loaded', () => {
|
|||||||
assert.match(page, /Visa äldre samtalsturer/);
|
assert.match(page, /Visa äldre samtalsturer/);
|
||||||
assert.match(page, /\?limit=\$\{trace\.limit \|\| 200\}&offset=\$\{adminTraceOffset\}/);
|
assert.match(page, /\?limit=\$\{trace\.limit \|\| 200\}&offset=\$\{adminTraceOffset\}/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('admin can switch to a storyteller-only reading view and return to diagnostics', () => {
|
||||||
|
assert.match(page, /Lättläst konversation/);
|
||||||
|
assert.match(page, /Visa felsökningsspår/);
|
||||||
|
assert.match(page, /root\.classList\.toggle\('admin-simple-conversation', adminSimpleConversation\)/);
|
||||||
|
assert.match(page, /story\.className = 'admin-story-reply'/);
|
||||||
|
assert.match(page, /article > :not\(\.admin-story-reply\)/);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user