Add English home and chat pages, and keep the client unnamed on the CV
Build & Deploy / deploy (push) Successful in 2m46s
Build & Deploy / deploy (push) Successful in 2m46s
Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
---
|
||||
// Selve chatten til "Spørg mit CV" (/chat og /en/chat). Teksterne følger sidens sprog;
|
||||
// svarene kommer fra Claude via chat-proxyen bag /api/chat.
|
||||
const { lang = 'da' } = Astro.props as { lang?: 'da' | 'en' };
|
||||
const t = lang === 'en' ? {
|
||||
placeholder: 'Ask about experience, incident response, Peppol…',
|
||||
disclaimer: 'Answers are written by Claude (Anthropic) from my public CV.. check important details with me directly.',
|
||||
thinking: '…thinking', offline: 'The chat is offline right now.. try again later.',
|
||||
unreachable: 'Could not reach the chat.. try again in a moment.',
|
||||
} : {
|
||||
placeholder: 'Spørg om erfaring, Peppol, homelab…',
|
||||
disclaimer: 'Svarene skrives af Claude (Anthropic) ud fra mit offentlige CV.. tjek vigtige detaljer med mig direkte.',
|
||||
thinking: '…tænker', offline: 'Chatten er offline lige nu.. prøv senere.',
|
||||
unreachable: 'Kunne ikke nå chatten.. prøv igen om lidt.',
|
||||
};
|
||||
---
|
||||
<div id="chat-log" class="mt-8 space-y-3" aria-live="polite"></div>
|
||||
|
||||
<form id="chat-form" class="mt-6 flex gap-3" data-thinking={t.thinking} data-offline={t.offline} data-unreachable={t.unreachable}>
|
||||
<input id="chat-input" type="text" required maxlength="500" autocomplete="off"
|
||||
placeholder={t.placeholder}
|
||||
class="flex-1 rounded-xl border border-border bg-surface/60 px-4 py-2.5 outline-none focus:border-accent" />
|
||||
<button type="submit" class="rounded-xl bg-accent px-5 py-2.5 font-medium text-bg transition-transform hover:-translate-y-0.5">Send</button>
|
||||
</form>
|
||||
<p class="mt-3 text-xs text-muted/80">{t.disclaimer}</p>
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
var form = document.getElementById('chat-form');
|
||||
if (!form || form.__wired) return; form.__wired = true;
|
||||
var history = [];
|
||||
function esc(s) {
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
// Escape FØRST, linkificér så: rene URL'er og e-mails bliver klikbare, intet HTML slipper igennem.
|
||||
function linkify(text) {
|
||||
var html = esc(text);
|
||||
html = html.replace(/\b(https?:\/\/[^\s<]+[^\s<.,!?:;)])/g,
|
||||
'<a href="$1" target="_blank" rel="noopener" class="text-accent underline">$1</a>');
|
||||
html = html.replace(/\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b/g,
|
||||
'<a href="mailto:$1" class="text-accent underline">$1</a>');
|
||||
return html;
|
||||
}
|
||||
// Track spørgsmålet i Matomo FRA browseren, så det knyttes til den besøgendes
|
||||
// egen visitor-profil (cookie/visitor-ID, IP, browser) i stedet for en server-IP.
|
||||
function track(action, q) {
|
||||
try { (window._paq = window._paq || []).push(['trackEvent', 'Chat', action, q]); } catch (_) {}
|
||||
}
|
||||
function bubble(role, text) {
|
||||
var log = document.getElementById('chat-log');
|
||||
var d = document.createElement('div');
|
||||
d.className = 'rounded-2xl border border-border p-4 text-sm whitespace-pre-wrap ' +
|
||||
(role === 'user' ? 'bg-surface-2 ml-8' : 'bg-surface/60 mr-8');
|
||||
if (role === 'user') d.textContent = text; else d.innerHTML = linkify(text);
|
||||
log.appendChild(d);
|
||||
d.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
||||
return d;
|
||||
}
|
||||
form.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
var input = document.getElementById('chat-input');
|
||||
var q = input.value.trim(); if (!q) return;
|
||||
input.value = '';
|
||||
bubble('user', q);
|
||||
history.push({ role: 'user', content: q });
|
||||
var wait = bubble('assistant', form.dataset.thinking);
|
||||
var btn = form.querySelector('button'); btn.disabled = true;
|
||||
try {
|
||||
var r = await fetch('/api/chat', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ messages: history.slice(-10) }),
|
||||
});
|
||||
var data = await r.json();
|
||||
if (!r.ok) { wait.textContent = data.error || form.dataset.offline; track('afvist', q); return; }
|
||||
wait.innerHTML = linkify(data.reply);
|
||||
history.push({ role: 'assistant', content: data.reply });
|
||||
track('besvaret', q);
|
||||
} catch (_) {
|
||||
wait.textContent = form.dataset.unreachable;
|
||||
track('fejl', q);
|
||||
} finally { btn.disabled = false; input.focus(); }
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user