Live-Monitoring: Quellen-Namen pro Bullet (Prompt + Frontend-Parser)
Der LATEST_DEVELOPMENTS-Prompt produzierte Bullets ohne Citations — das
Frontend zeigte daher "Keine Quelle". Prompt ergaenzt: jedes Bullet endet mit
{Quellenname1, Quellenname2} (geschweifte Klammern, exakte Schreibweise aus
Quelle:-Zeile). Frontend-Parser extrahiert diese Klammer, matcht Namen
case-insensitive gegen sources_json und erstellt klickbare Pills.
Fallback fuer Legacy-Bullets: Inline-[N]-Citations werden weiterhin erkannt.
Altbestand-Bullets ohne Marker erhalten beim naechsten Refresh Quellen.
Dieser Commit ist enthalten in:
@@ -226,8 +226,9 @@ Extrahiere aus den NEUEN Meldungen konkrete Ereignisse und aktualisiere die List
|
||||
REGELN:
|
||||
- Jedes Bullet = EIN konkretes Ereignis (1-2 Sätze, faktenbasiert). Keine Themen-Zusammenfassungen.
|
||||
- Jedes Bullet beginnt mit dem Zeitstempel der frühesten belegenden Quelle im Format "[DD.MM. HH:MM]".
|
||||
- Wenn mehrere Meldungen dasselbe Ereignis betreffen: EIN Bullet, Zeitstempel = frühester Zeitpunkt.
|
||||
- Bestehende Bullets aus BISHERIGE ENTWICKLUNGEN sinngemäß übernehmen, NICHT umformulieren. Nur entfernen, wenn sie durch neue Meldungen nachweislich überholt sind oder die 8-Bullet-Grenze überschritten wird (dann älteste fallen raus).
|
||||
- Jedes Bullet endet mit den Quellen-Namen in geschweiften Klammern, kommasepariert. Beispiel: "{{Reuters, Rybar}}". Verwende EXAKT die Schreibweise aus der "Quelle:"-Zeile der Meldung (z.B. "Der Tagesspiegel", nicht "Tagesspiegel"). Keine Abkuerzungen, keine Umformulierungen.
|
||||
- Wenn mehrere Meldungen dasselbe Ereignis betreffen: EIN Bullet, Zeitstempel = frühester Zeitpunkt, ALLE belegenden Quellen-Namen in den Klammern.
|
||||
- Bestehende Bullets aus BISHERIGE ENTWICKLUNGEN sinngemäß übernehmen, NICHT umformulieren. Nur entfernen, wenn sie durch neue Meldungen nachweislich überholt sind oder die 8-Bullet-Grenze überschritten wird (dann älteste fallen raus). Wenn einem uebernommenen Bullet die Quellen-Klammer fehlt (Altformat), ergaenze sie anhand der URSPRUENGLICHEN Beleg-Meldung so gut moeglich; ansonsten "{{Unbekannt}}".
|
||||
- Wenn eine Quelle eine erkennbare politische Ausrichtung hat (z.B. pro-russisch, staatsnah, rechtsextrem), im Bullet-Text erwähnen ("laut pro-russischem Telegram-Kanal Rybar...").
|
||||
- Neutral und sachlich — keine Wertungen oder Spekulationen.
|
||||
- KEINE Gedankenstriche (—, –) — stattdessen Kommas, Doppelpunkte oder neue Sätze.
|
||||
@@ -236,8 +237,8 @@ REGELN:
|
||||
- Wenn aus den neuen Meldungen kein neues Ereignis extrahierbar ist: BISHERIGE ENTWICKLUNGEN unverändert zurückgeben.
|
||||
|
||||
OUTPUT-FORMAT (ausschliesslich, keine Anführungszeichen, kein Code-Fence):
|
||||
- [DD.MM. HH:MM] Erster Ereignistext.
|
||||
- [DD.MM. HH:MM] Zweiter Ereignistext.
|
||||
- [DD.MM. HH:MM] Erster Ereignistext. {{Quellenname1}}
|
||||
- [DD.MM. HH:MM] Zweiter Ereignistext. {{Quellenname1, Quellenname2}}
|
||||
..."""
|
||||
|
||||
|
||||
|
||||
@@ -746,8 +746,8 @@ const UI = {
|
||||
|
||||
/**
|
||||
* Rendert "Neueste Entwicklungen" für Live-Monitoring (adhoc).
|
||||
* Erwartet Bullets im Format "- [DD.MM. HH:MM] Text [N][M]".
|
||||
* Erzeugt Karten mit sichtbaren Quellen-Pills + dezentem Zeitstempel.
|
||||
* Erwartet Bullets im Format "- [DD.MM. HH:MM] Text {Quelle1, Quelle2}".
|
||||
* Legacy: Inline-[N]-Citations werden als Fallback ebenfalls erkannt.
|
||||
*/
|
||||
renderLatestDevelopments(text, sourcesJson) {
|
||||
if (!text) return '<span style="color:var(--text-disabled);">Noch keine Entwicklungen erfasst.</span>';
|
||||
@@ -756,14 +756,14 @@ const UI = {
|
||||
|
||||
const bulletLines = text.split("\n").map(l => l.trim()).filter(l => l.startsWith("- "));
|
||||
if (bulletLines.length === 0) {
|
||||
// Fallback: kein Bullet-Format erkannt, alten Render verwenden
|
||||
return this.renderZusammenfassung(text, sourcesJson);
|
||||
}
|
||||
|
||||
const bulletRe = /^-\s*\[(\d{1,2}\.\d{1,2}\.)\s+(\d{1,2}:\d{2})\]\s*(.+?)\s*$/;
|
||||
const citationRe = /\[(\d+[a-z]?)\]/g;
|
||||
const trailingNamesRe = /\s*\{([^{}]+)\}\s*\.?\s*$/;
|
||||
|
||||
const lookupSource = (num) => {
|
||||
const lookupByNum = (num) => {
|
||||
let src = sources.find(s => String(s.nr) === num || Number(s.nr) === Number(num));
|
||||
if (!src && /[a-z]$/.test(num)) {
|
||||
const baseNum = num.replace(/[a-z]$/, '');
|
||||
@@ -772,6 +772,30 @@ const UI = {
|
||||
return src || null;
|
||||
};
|
||||
|
||||
const normalize = (s) => (s || '').toLowerCase().replace(/^(der|die|das)\s+/, '').replace(/\s+/g, ' ').trim();
|
||||
const lookupByName = (name) => {
|
||||
const n = normalize(name);
|
||||
if (!n) return null;
|
||||
let src = sources.find(s => normalize(s.name) === n);
|
||||
if (src) return src;
|
||||
src = sources.find(s => {
|
||||
const sn = normalize(s.name);
|
||||
return sn.includes(n) || n.includes(sn);
|
||||
});
|
||||
return src || null;
|
||||
};
|
||||
|
||||
const buildPill = (src, fallbackName) => {
|
||||
const displayName = src ? (src.name || fallbackName) : fallbackName;
|
||||
const esc = this.escape(displayName);
|
||||
const biasClass = this._classifyBias(displayName);
|
||||
const biasHtml = biasClass ? `<span class="dev-bias dev-bias-${biasClass}">${this._biasLabel(biasClass)}</span>` : '';
|
||||
if (src && src.url) {
|
||||
return `<a href="${this.escape(src.url)}" target="_blank" rel="noopener" class="dev-source-pill" title="${esc}">${esc}${biasHtml}</a>`;
|
||||
}
|
||||
return `<span class="dev-source-pill" title="${esc}">${esc}${biasHtml}</span>`;
|
||||
};
|
||||
|
||||
const cards = bulletLines.map(line => {
|
||||
const m = bulletRe.exec(line);
|
||||
if (!m) {
|
||||
@@ -780,31 +804,46 @@ const UI = {
|
||||
}
|
||||
const date = m[1];
|
||||
const time = m[2];
|
||||
const rawBody = m[3];
|
||||
let rawBody = m[3];
|
||||
|
||||
let pillsHtml = '';
|
||||
|
||||
// Primär: {Name1, Name2} am Bullet-Ende
|
||||
const trailing = trailingNamesRe.exec(rawBody);
|
||||
if (trailing) {
|
||||
rawBody = rawBody.replace(trailingNamesRe, '').trim();
|
||||
const names = trailing[1].split(',').map(s => s.trim()).filter(Boolean);
|
||||
const seen = new Set();
|
||||
pillsHtml = names.map(name => {
|
||||
const key = normalize(name);
|
||||
if (seen.has(key)) return '';
|
||||
seen.add(key);
|
||||
if (/^(unbekannt|unknown|n\/a|keine)$/i.test(name)) return '';
|
||||
const src = lookupByName(name);
|
||||
return buildPill(src, name);
|
||||
}).filter(Boolean).join('');
|
||||
}
|
||||
|
||||
// Fallback: Inline-[N]-Citations (Legacy-Recherche-Format)
|
||||
if (!pillsHtml) {
|
||||
const nums = [];
|
||||
let cm;
|
||||
while ((cm = citationRe.exec(rawBody)) !== null) {
|
||||
if (!nums.includes(cm[1])) nums.push(cm[1]);
|
||||
}
|
||||
citationRe.lastIndex = 0;
|
||||
|
||||
const cleanBody = this.escape(rawBody.replace(citationRe, '').replace(/\s+/g, ' ').trim());
|
||||
|
||||
const pills = nums.map(num => {
|
||||
const src = lookupSource(num);
|
||||
if (!src) return '';
|
||||
const name = this.escape(src.name || `Quelle ${num}`);
|
||||
const biasClass = this._classifyBias(src.name || '');
|
||||
const biasHtml = biasClass ? `<span class="dev-bias dev-bias-${biasClass}">${this._biasLabel(biasClass)}</span>` : '';
|
||||
if (src.url) {
|
||||
return `<a href="${this.escape(src.url)}" target="_blank" rel="noopener" class="dev-source-pill" title="${name}">${name}${biasHtml}</a>`;
|
||||
}
|
||||
return `<span class="dev-source-pill" title="${name}">${name}${biasHtml}</span>`;
|
||||
if (nums.length > 0) {
|
||||
rawBody = rawBody.replace(citationRe, '').replace(/\s+/g, ' ').trim();
|
||||
pillsHtml = nums.map(num => {
|
||||
const src = lookupByNum(num);
|
||||
return src ? buildPill(src, src.name || `Quelle ${num}`) : '';
|
||||
}).filter(Boolean).join('');
|
||||
}
|
||||
}
|
||||
|
||||
const sourcesHtml = pills
|
||||
? `<span class="dev-sources">${pills}</span>`
|
||||
const cleanBody = this.escape(rawBody.trim());
|
||||
const sourcesHtml = pillsHtml
|
||||
? `<span class="dev-sources">${pillsHtml}</span>`
|
||||
: `<span class="dev-sources dev-sources-empty">Keine Quelle</span>`;
|
||||
const timeHtml = `<span class="dev-time" title="${this.escape(date + ' ' + time)}">${this.escape(time)} \u00b7 ${this.escape(date)}</span>`;
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren