Intervieweren når de forberedte spørgsmål, følger op højst N gange pr. spørgsmål (indstilles pr. emne), trækker tilbage til emnet og afrunder. 'Spring over' i UI. Spørgsmålet oplæses med ElevenLabs-tags ([warmly], [chuckles] …) som ikke vises i teksten; oplæsningen gemmes som <id>-sp.mp3 ved siden af svaret og følger med i zip-eksporten, så spørgsmål + svar kan klippes ind i en podcast. Claude-Session: https://claude.ai/code/session_01JmhJG6JnhXfzNiKv8f2vHu
86 lines
7.4 KiB
HTML
86 lines
7.4 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Admin — erindringer{% endblock %}
|
|
{% block body %}
|
|
<p><a href="/admin" class="wt-muted">← Admin</a></p>
|
|
<h1>Erindringer</h1>
|
|
<p class="wt-lead">Familien fortæller minder til en AI-interviewer, der stiller ét spørgsmål ad gangen og følger op. Svarene gemmes som lyd og tekst pr. person og kan hentes som zip til podcasten. Indloggede i familiegruppen har altid "Dagbog" på <a href="/erindringer">/erindringer</a>; gæster uden login får et hemmeligt link, som du laver her.</p>
|
|
{% if saved %}<div class="wt-flash">{{ saved }}</div>{% endif %}
|
|
{% if not har_claude or not har_eleven %}
|
|
<div class="wt-flash">⚠️ {% if not har_claude %}ANTHROPIC_API_KEY mangler i /etc/portal.env: intervieweren falder tilbage til projektets startspørgsmål.{% endif %}
|
|
{% if not har_eleven %}ELEVEN_API_KEY mangler: ingen oplæsning og ingen transskription (svar gemmes kun som lyd).{% endif %}</div>
|
|
{% endif %}
|
|
|
|
{% if ny %}
|
|
{% for p in personer if p.pid == ny %}
|
|
<div class="wt-card" style="padding:1rem 1.2rem;margin:1rem 0;border-color:var(--color-accent)">
|
|
<p style="margin:0 0 .5rem"><strong>Link til {{ p.navn }}</strong>, send det privat (Messenger, sms). Linket er personligt og kan lukkes igen.</p>
|
|
<div style="display:flex;gap:.5rem;flex-wrap:wrap">
|
|
<input class="wt-input" readonly id="ny-link" value="{{ base }}/erindringer/g/{{ p.noegle }}/" style="flex:1 1 22rem;font-family:ui-monospace,monospace;font-size:.85rem" onclick="this.select()">
|
|
<button class="wt-btn" type="button" onclick="var i=document.getElementById('ny-link');i.select();navigator.clipboard&&navigator.clipboard.writeText(i.value);this.textContent='Kopieret'">Kopiér</button>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
<h2 style="font-size:1.15rem;margin-top:1.6rem">Personer</h2>
|
|
<table class="wt-table">
|
|
<thead><tr><th>Navn</th><th>Adgang</th><th>Emner</th><th>Svar</th><th>Sidst</th><th></th></tr></thead>
|
|
<tbody>
|
|
{% for p in personer %}
|
|
<tr>
|
|
<td><a href="/admin/erindringer/p/{{ p.pid }}">{{ p.navn }}</a></td>
|
|
<td>{% if p.bruger %}login ({{ p.bruger }}){% elif p.lukket %}<span class="wt-tag muted">gæstelink lukket</span>{% else %}gæstelink{% endif %}</td>
|
|
<td>{{ p.projekter|join(', ') }}</td>
|
|
<td>{{ p.antal }}</td>
|
|
<td>{{ (p.sidst or p.oprettet or '')[:10] }}</td>
|
|
<td><a class="wt-btn" href="/admin/erindringer/p/{{ p.pid }}">Åbn</a></td>
|
|
</tr>
|
|
{% else %}
|
|
<tr><td colspan="6" class="wt-muted">Ingen endnu. Indloggede dukker op, når de åbner /erindringer første gang.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
<h2 style="font-size:1.15rem;margin-top:2rem">Nyt gæstelink (uden login)</h2>
|
|
<form method="post" action="/admin/erindringer/gaest" class="wt-card" style="padding:1rem 1.2rem;max-width:48rem;margin:0">
|
|
<label>Navn (som intervieweren bruger)<br><input class="wt-input" name="navn" required maxlength="80" style="width:100%"></label>
|
|
<p style="margin:.8rem 0 .3rem">Emner, personen må fortælle om:</p>
|
|
{% for pr in projekter %}
|
|
<label style="display:block;margin:.2rem 0"><input type="checkbox" name="projekter" value="{{ pr.slug }}" {% if pr.slug != 'dagbog' %}checked{% endif %}> {{ pr.titel }} <span class="wt-muted">({{ pr.slug }})</span></label>
|
|
{% endfor %}
|
|
<p style="margin:.8rem 0 0"><button class="wt-btn wt-btn--primary" type="submit">Lav link</button></p>
|
|
</form>
|
|
|
|
<h2 style="font-size:1.15rem;margin-top:2rem">Emner (projekter)</h2>
|
|
<p class="wt-muted" style="margin:.3rem 0 .8rem">Baggrunden gives til intervieweren, så spørgsmålene bliver konkrete (navne, steder, årstal, hvad vi allerede ved). De forberedte spørgsmål er samtalens rygrad: intervieweren ({{ model }}) stiller dem alle, følger op på det konkrete og pudsige i svarene (højst det antal, du sætter), trækker venligt tilbage til emnet, hvis det stikker af, og afrunder, når listen er tømt. Personen kan springe et spørgsmål over og holde pause når som helst. Spørgsmålets oplæsning gemmes sammen med svaret, så spørgsmål og svar kan klippes ind i en podcast. "dagbog" er indbygget og kan ikke slettes, men teksten kan rettes.</p>
|
|
{% for pr in projekter %}
|
|
<details class="wt-card" style="padding:.8rem 1.2rem;max-width:48rem;margin:.5rem 0">
|
|
<summary style="cursor:pointer"><strong>{{ pr.titel }}</strong> <span class="wt-muted">({{ pr.slug }}) · {{ pr.start|length }} startspørgsmål</span></summary>
|
|
<form method="post" action="/admin/erindringer/projekt" style="margin:.8rem 0 0">
|
|
<input type="hidden" name="slug" value="{{ pr.slug }}">
|
|
<label>Titel<br><input class="wt-input" name="titel" value="{{ pr.titel }}" required style="width:100%"></label>
|
|
<label style="display:block;margin-top:.6rem">Beskrivelse (vises til personen)<br><textarea class="wt-input" name="beskrivelse" rows="2" style="width:100%">{{ pr.beskrivelse }}</textarea></label>
|
|
<label style="display:block;margin-top:.6rem">Baggrund til intervieweren (vises ikke til personen)<br><textarea class="wt-input" name="kontekst" rows="8" style="width:100%">{{ pr.kontekst }}</textarea></label>
|
|
<label style="display:block;margin-top:.6rem">Forberedte spørgsmål, ét pr. linje (intervieweren når dem alle, i den rækkefølge der passer, og afrunder derefter)<br><textarea class="wt-input" name="start" rows="5" style="width:100%">{{ pr.start|join('\n') }}</textarea></label>
|
|
<label style="display:block;margin-top:.6rem">Højst så mange opfølgende spørgsmål pr. forberedt spørgsmål (0-6)<br><input class="wt-input" name="opfoelg" type="number" min="0" max="6" value="{{ pr.opfoelg if pr.opfoelg is not none else 2 }}" style="width:6rem"></label>
|
|
<p style="margin:.8rem 0 0;display:flex;gap:.5rem;flex-wrap:wrap">
|
|
<button class="wt-btn wt-btn--primary" type="submit">Gem</button>
|
|
{% if pr.slug != 'dagbog' %}<button class="wt-btn wt-btn--danger" type="submit" name="handling" value="slet" onclick="return confirm('Slet emnet {{ pr.slug }}? Svarene bliver liggende hos personerne.')">Slet emne</button>{% endif %}
|
|
</p>
|
|
</form>
|
|
</details>
|
|
{% endfor %}
|
|
<details class="wt-card" style="padding:.8rem 1.2rem;max-width:48rem;margin:.5rem 0">
|
|
<summary style="cursor:pointer"><strong>Nyt emne</strong></summary>
|
|
<form method="post" action="/admin/erindringer/projekt" style="margin:.8rem 0 0">
|
|
<label>Kort navn (a-z, 0-9, bindestreg)<br><input class="wt-input" name="slug" required pattern="[a-z0-9][a-z0-9-]{0,40}" style="width:100%" placeholder="fx ep10-foraeldrene"></label>
|
|
<label style="display:block;margin-top:.6rem">Titel<br><input class="wt-input" name="titel" required style="width:100%"></label>
|
|
<label style="display:block;margin-top:.6rem">Beskrivelse (vises til personen)<br><textarea class="wt-input" name="beskrivelse" rows="2" style="width:100%"></textarea></label>
|
|
<label style="display:block;margin-top:.6rem">Baggrund til intervieweren<br><textarea class="wt-input" name="kontekst" rows="8" style="width:100%"></textarea></label>
|
|
<label style="display:block;margin-top:.6rem">Forberedte spørgsmål, ét pr. linje<br><textarea class="wt-input" name="start" rows="5" style="width:100%"></textarea></label>
|
|
<label style="display:block;margin-top:.6rem">Højst så mange opfølgende spørgsmål pr. forberedt spørgsmål (0-6)<br><input class="wt-input" name="opfoelg" type="number" min="0" max="6" value="2" style="width:6rem"></label>
|
|
<p style="margin:.8rem 0 0"><button class="wt-btn wt-btn--primary" type="submit">Opret</button></p>
|
|
</form>
|
|
</details>
|
|
{% endblock %}
|