Show player-facing processing feedback
Build and publish frontend / publish (push) Successful in 47s
Build and publish frontend / publish (push) Successful in 47s
This commit is contained in:
+48
-8
@@ -116,7 +116,7 @@
|
||||
<footer id="version-footer">Frontend <span id="frontend-version">__FRONTEND_VERSION__</span> · Statik <span id="static-version">hämtar version…</span> · API <span id="api-version">hämtar version…</span></footer>
|
||||
<script src="/piper-runtime.js?v=__FRONTEND_VERSION__"></script>
|
||||
<script src="/voice.js?v=__FRONTEND_VERSION__"></script>
|
||||
<script src="/static/wllama-gemma3-v1/local-ai.js"></script>
|
||||
<script src="/static/wllama-gemma3-v2/local-ai.js"></script>
|
||||
<script>
|
||||
const auth = document.querySelector('#auth');
|
||||
const play = document.querySelector('#play');
|
||||
@@ -174,6 +174,24 @@
|
||||
}
|
||||
return line;
|
||||
}
|
||||
let activityStatusLine = null;
|
||||
function setActivityStatus(message) {
|
||||
if (!message) return;
|
||||
if (!activityStatusLine?.isConnected) {
|
||||
activityStatusLine = document.createElement('li');
|
||||
activityStatusLine.dataset.role = 'system';
|
||||
activityStatusLine.dataset.activityStatus = 'true';
|
||||
activityStatusLine.setAttribute('role', 'status');
|
||||
activityStatusLine.setAttribute('aria-live', 'polite');
|
||||
transcript.append(activityStatusLine);
|
||||
}
|
||||
activityStatusLine.textContent = `Berättelsen: ${message}`;
|
||||
activityStatusLine.scrollIntoView({block: 'end'});
|
||||
}
|
||||
function clearActivityStatus() {
|
||||
activityStatusLine?.remove();
|
||||
activityStatusLine = null;
|
||||
}
|
||||
function biographyContextText(context) {
|
||||
if (!context || typeof context !== 'object') return '';
|
||||
const details = [];
|
||||
@@ -210,13 +228,17 @@
|
||||
}
|
||||
window.addEventListener('local-ai-status', event => {
|
||||
if (!currentAccount || currentAccount.is_admin || play.hidden) return;
|
||||
let statusLine = transcript.querySelector('li[data-local-ai-status]');
|
||||
if (!statusLine) {
|
||||
const detail = typeof event.detail === 'string' ? {text: event.detail} : event.detail;
|
||||
statusLine = appendLine(detail.type === 'terms-required' ? 'assistant' : 'system', detail.text || '', {speaker: 'Lokal AI', speak: detail.type === 'terms-required'});
|
||||
statusLine.dataset.localAiStatus = 'true';
|
||||
}
|
||||
const detail = typeof event.detail === 'string' ? {text: event.detail} : event.detail;
|
||||
const statusAttribute = detail.type === 'activity' ? 'data-local-ai-activity' : 'data-local-ai-status';
|
||||
let statusLine = transcript.querySelector(`li[${statusAttribute}]`);
|
||||
if (!statusLine) {
|
||||
statusLine = appendLine(detail.type === 'terms-required' ? 'assistant' : 'system', detail.text || '', {
|
||||
speaker: 'Lokal AI', speak: detail.type === 'terms-required',
|
||||
});
|
||||
statusLine.setAttribute(statusAttribute, 'true');
|
||||
statusLine.setAttribute('role', 'status');
|
||||
statusLine.setAttribute('aria-live', 'polite');
|
||||
}
|
||||
statusLine.replaceChildren(document.createTextNode(`Lokal AI: ${detail.text || ''}`));
|
||||
if (detail.type === 'terms-required') {
|
||||
statusLine.append(document.createTextNode(' Läs dokumenten: '));
|
||||
@@ -670,15 +692,17 @@
|
||||
stage = 'world-start';
|
||||
messageField.disabled = true;
|
||||
appendLine('user', message);
|
||||
appendLine('assistant', 'Jag tar fram ett förslag på startplats och startläge…');
|
||||
setActivityStatus('Berättaren ber AI ta fram ett förslag på startvärld och en plats som passar din biografi.');
|
||||
try {
|
||||
const proposal = await request('/api/world/draft', {
|
||||
method: 'POST', headers: {'content-type': 'application/json'},
|
||||
body: JSON.stringify({input: message, current_draft: worldDraft}),
|
||||
});
|
||||
worldDraft = proposal.draft;
|
||||
clearActivityStatus();
|
||||
appendLine('assistant', `${proposal.message}\n\nVärld: ${worldDraft.name}\nStartplats: ${worldDraft.location_name}. ${worldDraft.location_description}\nStartbelopp: ${worldDraft.starting_balance_sek} kronor. ${worldDraft.offer_name} kostar ${worldDraft.offer_price_sek} kronor.\n\nSkriv ändringar eller “godkänn”.`);
|
||||
} catch (error) {
|
||||
clearActivityStatus();
|
||||
appendLine('assistant', `Förslaget kunde inte tas fram: ${error.message}. Skriv “försök igen” när du vill försöka på nytt.`);
|
||||
}
|
||||
setStage('world-draft');
|
||||
@@ -692,6 +716,7 @@
|
||||
}
|
||||
appendLine('user', message);
|
||||
messageField.disabled = true;
|
||||
setActivityStatus('Sparar det godkända världsförslaget och skapar din startposition.');
|
||||
try {
|
||||
gameState = await request('/api/world/start', {
|
||||
method: 'POST', headers: {'content-type': 'application/json'}, body: JSON.stringify(worldDraft),
|
||||
@@ -699,8 +724,10 @@
|
||||
worldDraft = null;
|
||||
await loadSavedDialogue();
|
||||
setStage('game');
|
||||
clearActivityStatus();
|
||||
appendLine('assistant', 'Du kan fråga om platsen, dina saker eller din ekonomi, vänta, handla, prata med någon som finns här eller skriva vart du vill gå.');
|
||||
} catch (error) {
|
||||
clearActivityStatus();
|
||||
appendLine('assistant', `Världsförslaget kunde inte sparas: ${error.message}. Förslaget finns kvar så att du kan försöka igen.`);
|
||||
}
|
||||
messageField.disabled = false;
|
||||
@@ -924,6 +951,7 @@
|
||||
}
|
||||
appendLine('user', message);
|
||||
messageField.disabled = true;
|
||||
setActivityStatus('Berättaren ber AI skapa eller uppdatera ett biografiförslag utifrån det du skrev.');
|
||||
try {
|
||||
const proposal = await request('/api/character/draft', {
|
||||
method: 'POST',
|
||||
@@ -931,8 +959,10 @@
|
||||
body: JSON.stringify({input: message, current_draft: characterDraft}),
|
||||
});
|
||||
characterDraft = proposal.draft;
|
||||
clearActivityStatus();
|
||||
appendLine('assistant', `${proposal.message}\n\nFörslag: ${characterDraft.name}, ${characterDraft.age} år.\n${characterDraft.biography}${biographyContextText(characterDraft.life_context)}\n\nSkriv ändringar eller “godkänn”.`);
|
||||
} catch (error) {
|
||||
clearActivityStatus();
|
||||
appendLine('assistant', `Jag kunde inte ta fram ett biografiförslag: ${error.message}. Försök igen när tjänsten svarar.`);
|
||||
}
|
||||
messageField.disabled = false;
|
||||
@@ -977,6 +1007,11 @@
|
||||
if (actionMode === 'normal') pendingDialogue = {message, idempotencyKey};
|
||||
appendLine('user', message, actionMode === 'dry_run' ? {speaker: 'Torrkörning'} : actionMode === 'redo' ? {speaker: 'Gör om'} : {});
|
||||
messageField.disabled = true;
|
||||
setActivityStatus(actionMode === 'dry_run'
|
||||
? 'Torrkör handlingen utan att spara eller göra framsteg.'
|
||||
: actionMode === 'redo'
|
||||
? 'Gör om den senaste handlingen med aktuella regler.'
|
||||
: 'Berättaren arbetar med din avsikt. Den avgör om en plats, scen eller person behöver etableras och fortsätter sedan berättelsen.');
|
||||
try {
|
||||
let clientAssistance = null;
|
||||
if (actionMode === 'normal' && currentAccount && !currentAccount.is_admin && window.localGameAI) {
|
||||
@@ -1017,7 +1052,11 @@
|
||||
if (refreshed.world) gameState = refreshed;
|
||||
} catch (_) { /* The committed dialogue stays successful if this optional view refresh fails. */ }
|
||||
}
|
||||
clearActivityStatus();
|
||||
const speakingNpc = result.status !== 'dry_run' && ['speak', 'meet'].includes(result.intent) ? gameState.world.npc : null;
|
||||
for (const change of result.world_changes || []) {
|
||||
appendLine('system', change, {speaker: 'Världsändring', speak: false});
|
||||
}
|
||||
for (const ruleName of result.debug_rules_created || []) {
|
||||
appendLine('assistant', ruleName, {speaker: 'Nya regler', speak: false});
|
||||
}
|
||||
@@ -1031,6 +1070,7 @@
|
||||
}
|
||||
refreshActionTools();
|
||||
} catch (error) {
|
||||
clearActivityStatus();
|
||||
messageField.value = message;
|
||||
try {
|
||||
const history = [...transcript.querySelectorAll('li')].map(line => line.textContent).filter(Boolean);
|
||||
|
||||
Reference in New Issue
Block a user