fix shop research
This commit is contained in:
@@ -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>');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user