Widget:StartseitenSuche: Unterschied zwischen den Versionen
Aus MediaWiki
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
KKeine Bearbeitungszusammenfassung |
||
| (25 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
<includeonly> | <includeonly> | ||
< | <div class="overlay-container"> | ||
. | <div class="overlay-content"> | ||
<div id="chat-widget"> | |||
<div id="chat-messages"></div> | |||
<div id="chat-sources-panel"></div> | |||
<form id="chat-form"> | |||
<input type="text" id="chat-input" placeholder="Stelle mir deine Frage …" autocomplete="off" /> | |||
<button type="submit" id="chat-send">Fragen</button> | |||
</form> | |||
<div id="chat-footer"> | |||
<span id="chat-status"></span> | |||
<button id="chat-new" type="button">Neue Unterhaltung</button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<style> | |||
.overlay-container { | |||
position: relative; | |||
width: 100%; | |||
min-height: 400px; | |||
padding: 24px; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
overflow: hidden; | |||
border-radius: 12px; | |||
background-color: #2f3e46; | |||
background-image: url("/w/images/a/a9/Schloss-Muenster-Philipp-Foelting-CC-BY-SA.jpg"); | |||
background-size: cover; | |||
background-position: center; | |||
background-repeat: no-repeat; | |||
box-sizing: border-box; | |||
} | |||
.overlay-container::before { | |||
content: ""; | |||
position: absolute; | |||
inset: 0; | |||
background: rgba(0, 0, 0, 0.38); | |||
z-index: 1; | |||
} | |||
.overlay-content { | |||
position: relative; | |||
z-index: 2; | |||
width: 100%; | |||
max-width: 860px; | |||
padding: 0px; | |||
border-radius: 12px; | |||
background: rgba(255, 255, 255, 0.92); | |||
backdrop-filter: blur(4px); | |||
-webkit-backdrop-filter: blur(4px); | |||
box-sizing: border-box; | |||
} | |||
#chat-widget { | |||
width: 80%; | |||
margin: 0 auto; | |||
font-family: -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; | |||
color: #212529; | |||
display: flex; | |||
flex-direction: column; | |||
background: transparent; | |||
} | |||
#chat-messages { | |||
min-height: 120px; | |||
max-height: 500px; | |||
overflow-y: auto; | |||
padding: 8px 0; | |||
display: flex; | |||
flex-direction: column; | |||
gap: 12px; | |||
} | |||
#chat-messages:empty::before { | |||
content: "Stelle eine Frage und erhalte Antworten basierend auf dem Münster Wiki und ausgewählten Web-Inhalten für Münster. Beachte: KI-Suche kann Fehler machen. Prüfe deine Ergebnisse sorgfältig. Verwende keine personenbezogenen Daten."; | |||
display: block; | |||
text-align: center; | |||
color: #666; | |||
font-size: 14px; | |||
padding: 20px 16px; | |||
} | |||
.chat-msg { | |||
max-width: 85%; | |||
padding: 10px 14px; | |||
border-radius: 10px; | |||
font-size: 14.5px; | |||
line-height: 1.55; | |||
word-wrap: break-word; | |||
} | |||
.chat-msg-user { | |||
align-self: flex-end; | |||
background: #00308D; | |||
color: #fff; | |||
border-bottom-right-radius: 3px; | |||
} | |||
.chat-msg-assistant { | |||
align-self: flex-start; | |||
background: #f0f2f5; | |||
color: #212529; | |||
border-bottom-left-radius: 3px; | |||
} | |||
.chat-msg-assistant p { | |||
margin: 0 0 8px 0; | |||
} | |||
.chat-msg-assistant p:last-child { | |||
margin-bottom: 0; | |||
} | |||
@keyframes chatDots { | |||
0%, 80%, 100% { opacity: 0.3; } | |||
40% { opacity: 1; } | |||
} | |||
.chat-loading-dots span { | |||
display: inline-block; | |||
width: 7px; | |||
height: 7px; | |||
margin: 0 2px; | |||
background: #999; | |||
border-radius: 50%; | |||
animation: chatDots 1.2s ease-in-out infinite; | |||
} | |||
.chat-loading-dots span:nth-child(2) { animation-delay: 0.15s; } | |||
.chat-loading-dots span:nth-child(3) { animation-delay: 0.3s; } | |||
#chat-sources-panel { | |||
display: none; | |||
} | |||
#chat-sources-panel.visible { | |||
display: block; | |||
margin: 4px 0 8px 0; | |||
} | |||
.chat-sources-toggle { | |||
background: none; | |||
border: 1px solid #ddd; | |||
border-radius: 6px; | |||
padding: 6px 12px; | |||
font-size: 12px; | |||
color: #555; | |||
cursor: pointer; | |||
transition: background 0.15s; | |||
} | |||
.chat-sources-toggle:hover { | |||
background: #f5f5f5; | |||
} | |||
.chat-sources-list { | |||
display: none; | |||
margin-top: 8px; | |||
} | |||
.chat-sources-list.open { | |||
display: block; | |||
} | |||
.chat-source-item { | |||
padding: 10px 12px; | |||
margin-bottom: 6px; | |||
border: 1px solid #e0e0e0; | |||
border-radius: 6px; | |||
background: #fff; | |||
transition: border-color 0.15s; | |||
} | |||
.chat-source-item:hover { | |||
border-color: #b0b8c1; | |||
} | |||
.chat-source-item h5 { | |||
margin: 0 0 3px 0; | |||
font-size: 14px; | |||
font-weight: 600; | |||
} | |||
.chat-source-item h5 a { | |||
color: #00308D; | |||
text-decoration: none; | |||
} | |||
.chat-source-item h5 a:hover { | |||
text-decoration: underline; | |||
} | |||
.chat-source-item .chat-source-snippet { | |||
font-size: 12.5px; | |||
line-height: 1.45; | |||
color: #666; | |||
margin: 0; | |||
} | |||
.chat-source-item .chat-source-meta { | |||
font-size: 11px; | |||
color: #999; | |||
margin-top: 3px; | |||
} | |||
#chat-form { | |||
display: flex; | |||
margin-top: 8px; | |||
} | |||
#chat-input { | |||
flex: 1; | |||
padding: 10px 14px; | |||
font-size: 15px; | |||
border: 1px solid #ccc; | |||
border-right: none; | |||
border-radius: 4px 0 0 4px; | |||
outline: none; | |||
transition: border-color 0.2s; | |||
color: #212529; | |||
background: #fff; | |||
min-width: 0; | |||
} | |||
#chat-input:focus { | |||
border-color: #4CAF50; | |||
} | |||
#chat-input:disabled { | |||
background: #f5f5f5; | |||
color: #999; | |||
} | |||
#chat-send { | |||
padding: 10px 20px; | |||
font-size: 15px; | |||
font-weight: 500; | |||
cursor: pointer; | |||
background: #4CAF50; | |||
color: #fff; | |||
border: none; | |||
border-radius: 0 4px 4px 0; | |||
transition: background 0.2s; | |||
white-space: nowrap; | |||
} | |||
#chat-send:hover { | |||
background: #43A047; | |||
} | |||
#chat-send:disabled { | |||
background: #a5d6a7; | |||
cursor: not-allowed; | |||
} | |||
#chat-footer { | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
gap: 12px; | |||
margin-top: 8px; | |||
min-height: 28px; | |||
} | |||
#chat-status { | |||
font-size: 12px; | |||
color: #666; | |||
} | |||
#chat-new { | |||
display: none; | |||
background: none; | |||
border: 1px solid #ccc; | |||
border-radius: 4px; | |||
padding: 4px 12px; | |||
font-size: 12px; | |||
color: #555; | |||
cursor: pointer; | |||
transition: background 0.15s; | |||
} | |||
#chat-new:hover { | |||
background: #f5f5f5; | |||
} | |||
#chat-new.visible { | |||
display: inline-block; | |||
} | |||
.chat-msg-error { | |||
align-self: center; | |||
background: #fef2f2; | |||
color: #b00; | |||
border: 1px solid #fca5a5; | |||
font-size: 13px; | |||
padding: 8px 14px; | |||
border-radius: 6px; | |||
} | |||
/* Tablet */ | |||
@media (max-width: 900px) { | |||
.overlay-container { | |||
min-height: 340px; | |||
padding: 18px; | |||
border-radius: 10px; | |||
} | |||
.overlay-content { | |||
padding: 16px; | |||
border-radius: 10px; | |||
} | |||
} | |||
/* Smartphone */ | |||
@media (max-width: 600px) { | |||
.overlay-container { | |||
min-height: 280px; | |||
padding: 12px; | |||
align-items: stretch; | |||
border-radius: 8px; | |||
} | |||
.overlay-content { | |||
padding: 14px; | |||
border-radius: 8px; | |||
} | |||
#chat-form { | |||
flex-direction: column; | |||
gap: 8px; | |||
} | |||
#chat-input { | |||
border-right: 1px solid #ccc; | |||
border-radius: 4px; | |||
} | |||
#chat-send { | |||
width: 100%; | |||
border-radius: 4px; | |||
} | |||
.chat-msg { | |||
max-width: 100%; | |||
} | |||
#chat-footer { | |||
flex-direction: column; | |||
align-items: flex-start; | |||
} | |||
} | |||
/* Kleine Smartphones */ | |||
@media (max-width: 400px) { | |||
.overlay-container { | |||
min-height: 240px; | |||
padding: 10px; | padding: 10px; | ||
} | } | ||
. | .overlay-content { | ||
padding: 12px; | |||
padding: | |||
} | } | ||
#chat-messages { | |||
max-height: 420px; | |||
} | } | ||
.chat-msg { | |||
. | font-size: 14px; | ||
padding: 9px 12px; | |||
} | |||
} | } | ||
</style> | |||
<script> | |||
(function() { | |||
var form = document.getElementById('chat-form'); | |||
var input = document.getElementById('chat-input'); | |||
var sendBtn = document.getElementById('chat-send'); | |||
var messagesEl = document.getElementById('chat-messages'); | |||
var sourcesPanel = document.getElementById('chat-sources-panel'); | |||
var statusEl = document.getElementById('chat-status'); | |||
var newBtn = document.getElementById('chat-new'); | |||
var apiHost = 'api.' + location.hostname.split('.').slice(1).join('.'); | |||
var conversationId = null; | |||
var isLoading = false; | |||
function escapeHtml(str) { | |||
var d = document.createElement('div'); | |||
d.textContent = str; | |||
return d.innerHTML; | |||
} | |||
function truncate(str, len) { | |||
if (str.length <= len) return str; | |||
return str.substring(0, len).replace(/\\s+\\S*$/, '') + '\\u2026'; | |||
} | |||
function formatAnswer(text) { | |||
var html = escapeHtml(text); | |||
html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>'); | |||
var paragraphs = html.split(/\\n{2,}/); | |||
if (paragraphs.length > 1) { | |||
html = paragraphs.map(function(p) { return '<p>' + p.replace(/\\n/g, '<br>') + '</p>'; }).join(''); | |||
} else { | |||
html = html.replace(/\\n/g, '<br>'); | |||
} | |||
return html; | |||
} | |||
function addUserMessage(text) { | |||
var div = document.createElement('div'); | |||
div.className = 'chat-msg chat-msg-user'; | |||
div.textContent = text; | |||
messagesEl.appendChild(div); | |||
scrollToBottom(); | |||
} | |||
function addAssistantMessage(text) { | |||
var div = document.createElement('div'); | |||
div.className = 'chat-msg chat-msg-assistant'; | |||
div.innerHTML = formatAnswer(text); | |||
messagesEl.appendChild(div); | |||
scrollToBottom(); | |||
} | |||
function addLoadingIndicator() { | |||
var div = document.createElement('div'); | |||
div.className = 'chat-msg chat-msg-assistant chat-loading-dots'; | |||
div.id = 'chat-loading'; | |||
div.innerHTML = '<span></span><span></span><span></span>'; | |||
messagesEl.appendChild(div); | |||
scrollToBottom(); | |||
} | |||
function removeLoadingIndicator() { | |||
var el = document.getElementById('chat-loading'); | |||
if (el) el.remove(); | |||
} | |||
</ | function addError(msg) { | ||
var div = document.createElement('div'); | |||
div.className = 'chat-msg chat-msg-error'; | |||
div.textContent = msg; | |||
messagesEl.appendChild(div); | |||
scrollToBottom(); | |||
} | |||
function scrollToBottom() { | |||
messagesEl.scrollTop = messagesEl.scrollHeight; | |||
} | |||
function renderSources(sources) { | |||
if (!sources || sources.length === 0) { | |||
sourcesPanel.className = ''; | |||
sourcesPanel.innerHTML = ''; | |||
return; | |||
} | |||
sourcesPanel.className = 'visible'; | |||
var listId = 'src-list-' + Date.now(); | |||
var html = '<button class="chat-sources-toggle" onclick="var l=document.getElementById(\\'' + listId + '\\');l.classList.toggle(\\'open\\');this.textContent=l.classList.contains(\\'open\\')?\\'▲ Quellen verbergen\\':\\'▼ ' + sources.length + ' Quellen anzeigen\\'">' | |||
+ '▼ ' + sources.length + ' Quellen anzeigen</button>'; | |||
html += '<div class="chat-sources-list" id="' + listId + '">'; | |||
sources.forEach(function(s) { | |||
var pct = Math.round((s.similarity_score || 0) * 100); | |||
var wikiUrl = '/wiki/' + encodeURIComponent((s.page_title || '').replace(/ /g, '_')); | |||
html += '<div class="chat-source-item">' | |||
+ '<h5><a href="' + wikiUrl + '">' + escapeHtml(s.page_title || '') + '</a></h5>' | |||
+ '<p class="chat-source-snippet">' + escapeHtml(truncate(s.content_text || '', 150)) + '</p>' | |||
+ '<div class="chat-source-meta">Relevanz: ' + pct + '% · ' + escapeHtml(s.source || '') + '</div>' | |||
+ '</div>'; | |||
}); | |||
html += '</div>'; | |||
sourcesPanel.innerHTML = html; | |||
} | |||
function setLoading(loading) { | |||
isLoading = loading; | |||
input.disabled = loading; | |||
sendBtn.disabled = loading; | |||
if (loading) { | |||
addLoadingIndicator(); | |||
} else { | |||
removeLoadingIndicator(); | |||
} | |||
} | |||
function updateStatus(remaining) { | |||
if (remaining > 0) { | |||
statusEl.textContent = remaining + ' R\\u00fcckfrage' + (remaining !== 1 ? 'n' : '') + ' verbleibend'; | |||
} else if (remaining === 0) { | |||
if (conversationId) { | |||
statusEl.textContent = 'Keine R\\u00fcckfragen mehr m\\u00f6glich'; | |||
input.disabled = true; | |||
sendBtn.disabled = true; | |||
input.placeholder = 'Starte eine neue Unterhaltung'; | |||
} | |||
} | |||
newBtn.className = conversationId ? 'visible' : ''; | |||
} | |||
function resetConversation() { | |||
conversationId = null; | |||
messagesEl.innerHTML = ''; | |||
sourcesPanel.className = ''; | |||
sourcesPanel.innerHTML = ''; | |||
statusEl.textContent = ''; | |||
newBtn.className = ''; | |||
input.disabled = false; | |||
sendBtn.disabled = false; | |||
input.placeholder = 'Stelle eine Frage \\u00fcber M\\u00fcnster \\u2026'; | |||
input.focus(); | |||
} | |||
newBtn.addEventListener('click', resetConversation); | |||
form.addEventListener('submit', function(e) { | |||
e.preventDefault(); | |||
if (isLoading) return; | |||
var message = input.value.trim(); | |||
if (!message) return; | |||
input.value = ''; | |||
addUserMessage(message); | |||
setLoading(true); | |||
var body = { message: message }; | |||
if (conversationId) { | |||
body.conversation_id = conversationId; | |||
} | |||
var url = location.protocol + '//' + apiHost + '/chat'; | |||
fetch(url, { | |||
method: 'POST', | |||
headers: { 'Content-Type': 'application/json' }, | |||
body: JSON.stringify(body) | |||
}) | |||
.then(function(res) { | |||
if (!res.ok) { | |||
return res.json().then(function(data) { | |||
throw new Error(data.detail || 'HTTP ' + res.status); | |||
}); | |||
} | |||
return res.json(); | |||
}) | |||
.then(function(data) { | |||
setLoading(false); | |||
conversationId = data.conversation_id; | |||
addAssistantMessage(data.answer); | |||
renderSources(data.sources); | |||
updateStatus(data.remaining_followups); | |||
input.focus(); | |||
}) | |||
.catch(function(err) { | |||
setLoading(false); | |||
addError('Fehler: ' + err.message); | |||
input.focus(); | |||
}); | |||
}); | |||
})(); | |||
</script> | |||
</includeonly> | </includeonly> | ||