This repository contains the static Swedish web client for the AI-driven life simulator. The intended deployment is a small Nginx pod in the `ai-spel` namespace. The existing Rust service remains authoritative for accounts, game state, and all `/api/...` routes.
This repository contains the static Swedish web client for the AI-driven life simulator. The intended deployment is a small Nginx pod in the `ai-spel` namespace. The existing Rust service remains authoritative for accounts, game state, and all `/api/...` routes.
Players provide a character biography, not a world description. The client asks the backend to create a biography-based starting place. It does not preview a person the player may meet; encounters belong to later world exploration.
After login, character creation and play use one dialogue history and one text input. The player writes a free biography, receives a temporary AI draft, can request revisions, and approves it by writing `godkänn`. The player then receives a proposed start world and place based on the biography. That draft can also be revised in dialogue and is saved only after a second explicit `godkänn`. Drafts stay in the browser until approval and are lost if the page is reloaded first. The backend creates the character and initializes the world only after their respective approvals.
During play the UI has no persistent location, task, inventory, economy, status, or purchase panels. The player asks for information or writes an action in the same input. Rust validates state changes; the AI can only propose a typed intent. A direct request to travel may establish a new place and an encounter when Rust commits that movement.
The separate `/admin` route provides an authenticated, read-only operations view for player search/profile/trace, YAML prompt assets, generated Luau rules with validation results, and the append-only audit log. All privileged data comes from the Rust `/api/admin/...` API; the browser never connects directly to PostgreSQL. Backend endpoints require `accounts.is_admin=true`.
The game is text- and dialogue-based and uses no images. Place descriptions include sound, smell, air/temperature, touch/surface, and spatial-orientation cues. Visible details may also be described in words, but are never required to understand the scene or play. The transcript is screen-reader announced, and Enter submits the input while Shift+Enter inserts a newline; no separate send button is required.
The browser and API stay on the same public origin, `https://ai-spel.k3s.k0.nu`. The ingress routes `/` to this frontend service and routes `/api`, `/version`, and `/health` to the Rust service. That keeps the current server-side session cookie same-origin and avoids browser CORS configuration.
The browser and API stay on the same public origin, `https://ai-spel.k3s.k0.nu`. The ingress routes `/` to this frontend service and routes `/api`, `/version`, and `/health` to the Rust service. That keeps the current server-side session cookie same-origin and avoids browser CORS configuration.
@@ -12,6 +18,6 @@ The browser and API stay on the same public origin, `https://ai-spel.k3s.k0.nu`.
CI needs repository Action secrets named `HARBOR_USERNAME` and `HARBOR_PASSWORD`. The Harbor pull secret `harbor-pull` is already present in the Kubernetes namespace. Argo CD uses the existing `ai-spel` project and Harbor OCI credentials.
CI needs repository Action secrets named `HARBOR_USERNAME` and `HARBOR_PASSWORD`. The Harbor pull secret `harbor-pull` is already present in the Kubernetes namespace. Argo CD uses the existing `ai-spel` project and Harbor OCI credentials.
The first Argo Application is described in `deploy/argocd/application.yaml`. The backend ingress must be switched to the frontend Service before public traffic uses this pod. That cutover is intentionally coordinated with a backend release so the API routes and login session remain on the same host.
The first Argo Application is described in `deploy/argocd/application.yaml`. The backend ingress routes `/api`, `/version`, and health checks to Rust while serving the static client at `/`. Both services share the same origin so the session cookie remains same-origin.
Builds, checks, and runtime verification happen in the pipeline and Kubernetes. Do not run the app, tests, container images, or binaries locally.
Builds, checks, and runtime verification happen in the pipeline and Kubernetes. Do not run the app, tests, container images, or binaries locally.
adminElement('p','Registreringstid visas separat från världstid och commitordning. AI-frågor, svar och världshändelser är korrelerade där korrelations-ID finns.');
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){
appendLine('assistant',`Världsförslaget kunde inte sparas: ${error.message}. Förslaget finns kvar så att du kan försöka igen.`);
document.querySelector('#offer-affordability').textContent=(world.balance_sek??0)<world.offer.price_sek?'Du har inte tillräckligt saldo för det här köpet.':'';
document.querySelector('#buy-offer').setAttribute('aria-label',`Köp ${world.offer.name} för ${world.offer.price_sek} kronor`);
}
}
messageField.disabled=false;
messageField.focus();
}
}
asyncfunctionhandleGameMessage(message){
if(/^logga ut[.!]?$/i.test(message.trim())){
appendLine('user',message);
awaithandleLogout();
return;
}
if(stage==='world-start'){
if(/^försök igen[.!]?$/i.test(message.trim())){
awaitproposeWorld(message);
}else{
appendLine('user',message);
appendLine('assistant','Startplatsen är inte skapad än. Skriv “försök igen” när du vill försöka på nytt.');
event.preventDefault();constdata=newFormData(event.currentTarget);status.textContent='Sparar din karaktär…';
try{awaitrequest('/api/character',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({name:data.get('name'),age:Number(data.get('age')),biography:data.get('biography')})});awaitopenGame();if(gameState?.world)status.textContent='Karaktären har sparats och du har börjat på en ny plats.';}
catch(error){status.textContent=`Kunde inte spara karaktären: ${error.message}`;}
status.textContent='Din samtalspartner funderar på sitt svar…';
try{constresult=awaitrequest('/api/npc/messages',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({message,idempotency_key:crypto.randomUUID()})});field.value='';awaitopenGame();status.textContent='Samtalet har sparats.';}
catch(error){status.textContent=`Samtalet kunde inte skickas: ${error.message}`;}
status.textContent=`Köpet kunde inte genomföras: ${error.message}`;
return;
}
}
try{
awaitopenGame();status.textContent='Köpet är sparat i din värld.';
}catch(error){status.textContent='Köpet är sparat. Världen kunde inte läsas in just nu; ladda om sidan för att visa det.';}
});
});
document.querySelector('#logout').addEventListener('click',async()=>{try{awaitrequest('/api/logout',{method:'POST'});showAuth();status.textContent='Du har loggat ut.';}catch(error){status.textContent=`Kunde inte logga ut: ${error.message}`;}});
ifgrep -Eq 'world/draft|npc_name|Person du kan möta|Beskriv vad du vill börja med' index.html;then
grep -q "request('/api/character'" index.html
echo'The start flow must not ask for a world description or preview a person.' >&2
grep -q 'location = /healthz' nginx/default.conf
if grep -Eq 'id="(new-character|world-setup|shop|buy-offer|balance|inventory|place-heading|npc-heading)"|Platsens sinnesintryck|Ekonomi och ägodelar|Köp på platsen|id="status"|id="logout"' index.html;then
echo'The active player UI must not contain permanent data panels, forms, or action buttons.' >&2
exit1
fi
if sed -n '/<section id="play"/,/^ <\/section>/p' index.html | grep -q '<button';then
echo'The game input must not require a separate send or action button.' >&2
exit1
exit1
fi
fi
grep -q 'location = /healthz' nginx/default.conf
echo'Frontend source checks passed.'
echo'Frontend source checks passed.'
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.