harden information getter services and optimize user msg
This commit is contained in:
@@ -45,6 +45,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractAssistantContextText(bubble) {
|
||||
if (!bubble) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const clone = bubble.cloneNode(true);
|
||||
clone.querySelectorAll('.think, .retriex-meta-card, .retriex-alert').forEach((element) => {
|
||||
element.remove();
|
||||
});
|
||||
|
||||
return normalizeContextHintText(clone.innerText || clone.textContent || '');
|
||||
}
|
||||
|
||||
function isClientMetaOnlyShopPrompt(value) {
|
||||
const tokens = tokenizeClientMetaGuardText(value);
|
||||
|
||||
@@ -62,9 +75,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
function isNoConcreteShopResponse(value) {
|
||||
return normalizeContextHintText(value)
|
||||
.toLowerCase()
|
||||
.includes('keine konkrete shop-suchanfrage erkannt');
|
||||
const normalized = normalizeContextHintText(value).toLowerCase();
|
||||
|
||||
return normalized.includes('keine konkrete shop-suchanfrage erkannt')
|
||||
|| normalized.includes('shop-suche noch nicht belastbar auflösen')
|
||||
|| normalized.includes('shop-suche noch nicht belastbar aufloesen');
|
||||
}
|
||||
|
||||
function rememberCompletedTurn(userPrompt, assistantText) {
|
||||
@@ -130,7 +145,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
messages.forEach((message) => {
|
||||
const bubble = message.querySelector('.bubble');
|
||||
const text = normalizeContextHintText(bubble?.innerText || bubble?.textContent || '');
|
||||
const text = message.classList.contains('assistant')
|
||||
? extractAssistantContextText(bubble)
|
||||
: normalizeContextHintText(bubble?.innerText || bubble?.textContent || '');
|
||||
|
||||
if (!text) {
|
||||
return;
|
||||
@@ -220,12 +237,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
function addLoader() {
|
||||
return addMessage('assistant', 'AI is thinking…', 'loader');
|
||||
return addMessage('assistant', 'Antwort wird vorbereitet…', 'loader');
|
||||
}
|
||||
|
||||
function hasMeaningfulChildContent(element) {
|
||||
return element.querySelector(
|
||||
'img, table, pre, code, ul, ol, h1, h2, h3, h4, h5, h6, a, hr, .badge'
|
||||
'img, table, pre, code, ul, ol, h1, h2, h3, h4, h5, h6, a, hr, .badge, .retriex-meta-card, .retriex-alert, .retriex-action-chip'
|
||||
) !== null;
|
||||
}
|
||||
|
||||
@@ -414,24 +431,50 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
removeThinkSpansOnly(container);
|
||||
}
|
||||
|
||||
function copyElementAttributes(target, source) {
|
||||
Array.from(target.attributes).forEach((attribute) => {
|
||||
target.removeAttribute(attribute.name);
|
||||
});
|
||||
|
||||
Array.from(source.attributes).forEach((attribute) => {
|
||||
target.setAttribute(attribute.name, attribute.value);
|
||||
});
|
||||
}
|
||||
|
||||
function deduplicateRetriexMetaCards(container) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cards = Array.from(container.querySelectorAll('.retriex-meta-card[data-retriex-meta-id]'));
|
||||
const latestCardById = new Map();
|
||||
|
||||
cards.forEach((card) => {
|
||||
latestCardById.set(card.getAttribute('data-retriex-meta-id'), card);
|
||||
});
|
||||
const cardsById = new Map();
|
||||
|
||||
cards.forEach((card) => {
|
||||
const cardId = card.getAttribute('data-retriex-meta-id');
|
||||
|
||||
if (latestCardById.get(cardId) !== card) {
|
||||
card.remove();
|
||||
if (!cardId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cardsById.has(cardId)) {
|
||||
cardsById.set(cardId, []);
|
||||
}
|
||||
|
||||
cardsById.get(cardId).push(card);
|
||||
});
|
||||
|
||||
cardsById.forEach((group) => {
|
||||
if (group.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstCard = group[0];
|
||||
const latestCard = group[group.length - 1];
|
||||
|
||||
firstCard.innerHTML = latestCard.innerHTML;
|
||||
copyElementAttributes(firstCard, latestCard);
|
||||
|
||||
group.slice(1).forEach((card) => card.remove());
|
||||
});
|
||||
|
||||
cleanupEmptyBlocks(container);
|
||||
@@ -515,11 +558,45 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
};
|
||||
}
|
||||
|
||||
function finalizeStream(bubble, raw) {
|
||||
function finalizeRetriexRunMetaCards(container, options = {}) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isError = options.state === 'error';
|
||||
const titleText = isError ? 'Antwort wurde unterbrochen' : 'Abgeschlossen';
|
||||
const statusText = isError ? 'Status: unterbrochen' : 'Status: abgeschlossen';
|
||||
const emptySourceText = isError ? 'nicht vollständig geprüft' : 'keine belastbare Datenbasis';
|
||||
|
||||
container.querySelectorAll('.retriex-run-meta[data-retriex-meta-id="run-status"]').forEach((card) => {
|
||||
card.setAttribute('data-retriex-meta-state', isError ? 'error' : 'completed');
|
||||
|
||||
const title = card.querySelector('.retriex-meta-card__title');
|
||||
|
||||
if (title) {
|
||||
title.textContent = titleText;
|
||||
}
|
||||
|
||||
const statusPill = card.querySelector('.retriex-meta-pill--status');
|
||||
|
||||
if (statusPill) {
|
||||
statusPill.textContent = statusText;
|
||||
}
|
||||
|
||||
const emptySource = card.querySelector('.retriex-source-overview__empty');
|
||||
|
||||
if (emptySource && emptySource.textContent.trim() === 'wird geprüft') {
|
||||
emptySource.textContent = emptySourceText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function finalizeStream(bubble, raw, options = {}) {
|
||||
clearScheduledRender();
|
||||
bubble.innerHTML = renderMarkdown(raw);
|
||||
removeThinkSpansOnly(bubble);
|
||||
deduplicateRetriexMetaCards(bubble);
|
||||
finalizeRetriexRunMetaCards(bubble, options);
|
||||
bubble.classList.remove('loader');
|
||||
enhanceChatLinks(bubble);
|
||||
scrollChatToBottom();
|
||||
@@ -575,6 +652,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
}
|
||||
|
||||
chatEl?.addEventListener('click', (event) => {
|
||||
const actionButton = event.target?.closest?.('.retriex-action-chip[data-retriex-action-prompt]');
|
||||
|
||||
if (!actionButton || state.isStreaming) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actionPrompt = normalizeContextHintText(actionButton.getAttribute('data-retriex-action-prompt') || '');
|
||||
|
||||
if (!actionPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
promptEl.value = actionPrompt;
|
||||
promptEl.focus();
|
||||
sendBtn.click();
|
||||
});
|
||||
|
||||
loadHistory();
|
||||
|
||||
promptEl.addEventListener('keydown', (event) => {
|
||||
@@ -631,7 +726,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const formattedMessage = `<em>${safeMessage}</em>`;
|
||||
raw += raw.trim() === '' ? formattedMessage : `\n\n${formattedMessage}`;
|
||||
finalizeStream(bubble, raw);
|
||||
finalizeStream(bubble, raw, {state: 'error'});
|
||||
};
|
||||
|
||||
const finishEventStream = () => {
|
||||
@@ -781,7 +876,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (raw.trim() !== '') {
|
||||
const formattedMessage = `<em>${userMessage}</em>`;
|
||||
raw += raw.trim() === '' ? formattedMessage : `\n\n${formattedMessage}`;
|
||||
renderBubbleContent(bubble, raw);
|
||||
finalizeStream(bubble, raw, {state: 'error'});
|
||||
} else {
|
||||
bubble.innerHTML = `<em>${userMessage}</em>`;
|
||||
enhanceChatLinks(bubble);
|
||||
|
||||
@@ -565,3 +565,164 @@ span.think {
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
|
||||
.retriex-run-meta[data-retriex-meta-state="completed"] {
|
||||
border-color: rgba(25, 135, 84, 0.36);
|
||||
}
|
||||
|
||||
.retriex-meta-pill--confidence {
|
||||
background: rgba(255, 193, 7, 0.14);
|
||||
color: #ffe8a1;
|
||||
}
|
||||
|
||||
.retriex-meta-pill--status {
|
||||
background: rgba(25, 135, 84, 0.15);
|
||||
color: #b7f5cf;
|
||||
}
|
||||
|
||||
.retriex-source-overview {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
margin-top: 0.15rem;
|
||||
}
|
||||
|
||||
.retriex-source-overview > span,
|
||||
.retriex-product-results__summary {
|
||||
color: rgba(248, 249, 250, 0.72);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.retriex-source-overview__badges,
|
||||
.retriex-action-chip-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.retriex-source-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
padding: 0.16rem 0.55rem;
|
||||
background: rgba(13, 202, 240, 0.14);
|
||||
color: #b6effb;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.retriex-source-overview__empty {
|
||||
color: rgba(248, 249, 250, 0.58);
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.retriex-product-results__summary {
|
||||
margin-bottom: 0.65rem;
|
||||
}
|
||||
|
||||
.retriex-meta-query--compact {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.retriex-product-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 0.65rem;
|
||||
}
|
||||
|
||||
.retriex-product-card {
|
||||
display: grid;
|
||||
gap: 0.55rem;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.045);
|
||||
}
|
||||
|
||||
.retriex-product-card__title {
|
||||
color: #f8f9fa;
|
||||
font-weight: 800;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.retriex-product-card__title a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.retriex-product-card__title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.retriex-product-card__facts {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.retriex-product-card__facts div {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(88px, 0.8fr) 1.2fr;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.retriex-product-card__facts dt {
|
||||
color: rgba(248, 249, 250, 0.58);
|
||||
font-size: 0.76rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.retriex-product-card__facts dd {
|
||||
color: rgba(248, 249, 250, 0.9);
|
||||
font-size: 0.8rem;
|
||||
margin: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.retriex-product-card__relevance {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
padding-top: 0.5rem;
|
||||
color: rgba(248, 249, 250, 0.78);
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.retriex-product-card__relevance span {
|
||||
display: block;
|
||||
color: #86b7fe;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.18rem;
|
||||
}
|
||||
|
||||
.retriex-action-chip {
|
||||
border: 1px solid rgba(134, 183, 254, 0.32);
|
||||
border-radius: 999px;
|
||||
padding: 0.34rem 0.72rem;
|
||||
background: rgba(134, 183, 254, 0.11);
|
||||
color: #d8e8ff;
|
||||
font-size: 0.83rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.retriex-action-chip:hover,
|
||||
.retriex-action-chip:focus-visible {
|
||||
border-color: rgba(134, 183, 254, 0.68);
|
||||
background: rgba(134, 183, 254, 0.2);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.retriex-product-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.retriex-product-card__facts div {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user