fix shop research

This commit is contained in:
team 1
2026-04-27 15:57:38 +02:00
parent ed139d577b
commit 75c376c7a8
8 changed files with 700 additions and 26 deletions

View File

@@ -5,6 +5,7 @@ document.addEventListener('DOMContentLoaded', () => {
const abortBtn = document.getElementById('abort');
const clearBtn = document.getElementById('clear');
const aiCloudEl = document.getElementById('ai-cloud');
const LAST_TURN_STORAGE_KEY = 'retriex:lastCompletedTurn';
const state = {
abortRequested: false,
@@ -15,6 +16,8 @@ document.addEventListener('DOMContentLoaded', () => {
eventSource: null,
completeStream: null,
failStream: null,
lastCompletedUserPrompt: '',
lastCompletedAssistantText: '',
};
marked.setOptions({breaks: true});
@@ -23,6 +26,163 @@ document.addEventListener('DOMContentLoaded', () => {
return DOMPurify.sanitize(marked.parse(text));
}
function normalizeContextHintText(value) {
return String(value || '')
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.replace(/[\t ]+/g, ' ')
.replace(/\n{3,}/g, '\n\n')
.trim();
}
function tokenizeClientMetaGuardText(value) {
return normalizeContextHintText(value)
.toLowerCase()
.replace(/[-/_]/g, ' ')
.replace(/[^\p{L}\p{N}]+/gu, ' ')
.trim()
.split(/\s+/u)
.filter(Boolean);
}
function isClientMetaOnlyShopPrompt(value) {
const tokens = tokenizeClientMetaGuardText(value);
if (!tokens.length) {
return true;
}
const metaTerms = new Set([
'shop', 'shopsuche', 'suche', 'suchen', 'such', 'finde', 'find',
'zeige', 'zeig', 'bitte', 'mal', 'im', 'in', 'nach', 'den', 'die',
'das', 'der', 'dem',
]);
return tokens.every((token) => metaTerms.has(token));
}
function isNoConcreteShopResponse(value) {
return normalizeContextHintText(value)
.toLowerCase()
.includes('keine konkrete shop-suchanfrage erkannt');
}
function rememberCompletedTurn(userPrompt, assistantText) {
const normalizedPrompt = normalizeContextHintText(userPrompt);
const normalizedAssistantText = normalizeContextHintText(assistantText);
if (!normalizedPrompt) {
return;
}
if (isClientMetaOnlyShopPrompt(normalizedPrompt) && isNoConcreteShopResponse(normalizedAssistantText)) {
return;
}
state.lastCompletedUserPrompt = normalizedPrompt.slice(0, 800);
state.lastCompletedAssistantText = normalizedAssistantText.slice(0, 3000);
try {
window.sessionStorage?.setItem(LAST_TURN_STORAGE_KEY, JSON.stringify({
userPrompt: state.lastCompletedUserPrompt,
assistantText: state.lastCompletedAssistantText,
rememberedAt: Date.now(),
}));
} catch (err) {
console.debug('Could not persist last completed turn:', err);
}
}
function loadStoredCompletedTurn() {
try {
const raw = window.sessionStorage?.getItem(LAST_TURN_STORAGE_KEY) || '';
if (!raw) {
return null;
}
const data = JSON.parse(raw);
const userPrompt = normalizeContextHintText(data?.userPrompt || '');
const assistantText = normalizeContextHintText(data?.assistantText || '');
if (!userPrompt) {
return null;
}
return {
userPrompt: userPrompt.slice(0, 800),
assistantText: assistantText.slice(0, 3000),
};
} catch (err) {
console.debug('Could not read last completed turn:', err);
return null;
}
}
function extractLatestVisibleCompletedTurn() {
if (!chatEl) {
return null;
}
const messages = Array.from(chatEl.querySelectorAll('.message'));
let pendingUserPrompt = '';
let latestTurn = null;
messages.forEach((message) => {
const bubble = message.querySelector('.bubble');
const text = normalizeContextHintText(bubble?.innerText || bubble?.textContent || '');
if (!text) {
return;
}
if (message.classList.contains('user')) {
pendingUserPrompt = text;
return;
}
if (!message.classList.contains('assistant') || !pendingUserPrompt) {
return;
}
if (bubble?.classList.contains('loader')) {
return;
}
if (isClientMetaOnlyShopPrompt(pendingUserPrompt) && isNoConcreteShopResponse(text)) {
pendingUserPrompt = '';
return;
}
latestTurn = {
userPrompt: pendingUserPrompt,
assistantText: text,
};
pendingUserPrompt = '';
});
return latestTurn;
}
function buildClientContextHint() {
const visibleTurn = extractLatestVisibleCompletedTurn();
const storedTurn = loadStoredCompletedTurn();
const userPrompt = visibleTurn?.userPrompt || state.lastCompletedUserPrompt || storedTurn?.userPrompt || '';
const assistantText = visibleTurn?.assistantText || state.lastCompletedAssistantText || storedTurn?.assistantText || '';
if (!userPrompt) {
return '';
}
const lines = [`Question: ${userPrompt.slice(0, 800)}`];
if (assistantText) {
lines.push(assistantText.slice(0, 3000));
}
return normalizeContextHintText(lines.join('\n')).slice(0, 4000);
}
function scrollChatToBottom() {
if (!chatEl) {
return;
@@ -368,10 +528,20 @@ document.addEventListener('DOMContentLoaded', () => {
}
const messages = await res.json();
let latestLoadedUserPrompt = '';
messages.forEach((message) => {
const bubble = addMessage(message.role);
renderBubbleContent(bubble, message.text);
if (message.role === 'user') {
latestLoadedUserPrompt = normalizeContextHintText(message.text);
return;
}
if (message.role === 'assistant' && latestLoadedUserPrompt) {
rememberCompletedTurn(latestLoadedUserPrompt, message.text);
}
});
enhanceChatLinks(chatEl);
@@ -396,6 +566,8 @@ document.addEventListener('DOMContentLoaded', () => {
return;
}
const contextHint = buildClientContextHint();
addMessage('user', renderMarkdown(prompt));
promptEl.value = '';
@@ -449,6 +621,7 @@ document.addEventListener('DOMContentLoaded', () => {
body: JSON.stringify({
prompt,
fullContext: false,
contextHint,
}),
signal: state.abortController.signal,
});
@@ -466,6 +639,7 @@ document.addEventListener('DOMContentLoaded', () => {
await new Promise((resolve, reject) => {
let finished = false;
let lastSseEventId = 0;
const source = new EventSource(`/ask-sse/${encodeURIComponent(jobId)}`);
state.eventSource = source;
@@ -521,12 +695,23 @@ document.addEventListener('DOMContentLoaded', () => {
return;
}
const numericEventId = Number.parseInt(event.lastEventId || '', 10);
if (Number.isFinite(numericEventId) && numericEventId > 0) {
if (numericEventId <= lastSseEventId) {
return;
}
lastSseEventId = numericEventId;
}
appendChunk(event.data);
};
source.addEventListener('done', () => {
if (!state.abortRequested) {
finalizeStream(bubble, raw);
rememberCompletedTurn(prompt, raw);
}
complete();
@@ -609,6 +794,13 @@ document.addEventListener('DOMContentLoaded', () => {
console.error('History delete failed:', err);
}
state.lastCompletedUserPrompt = '';
state.lastCompletedAssistantText = '';
try {
window.sessionStorage?.removeItem(LAST_TURN_STORAGE_KEY);
} catch (err) {
console.debug('Could not clear last completed turn:', err);
}
chatEl.innerHTML = '';
addMessage('assistant', '<em>History cleared.</em>');
});