summaryrefslogtreecommitdiff
path: root/htroot
diff options
context:
space:
mode:
authorMichael Peter Christen <mc@yacy.net>2025-11-30 21:33:00 +0100
committerMichael Peter Christen <mc@yacy.net>2025-11-30 21:33:00 +0100
commita5ed11a5dc994526bc29be2d74f68e94a4e775c1 (patch)
tree47d3a91f4b739b3fc93e809af83f49cd2435e9ef /htroot
parentb36e72a4d70c57efd584522fd000d9dc90b48188 (diff)
added automatic serch result feedback to chat attachment
Diffstat (limited to 'htroot')
-rw-r--r--htroot/yacychat.html133
1 files changed, 85 insertions, 48 deletions
diff --git a/htroot/yacychat.html b/htroot/yacychat.html
index 778589d63..120efb8bc 100644
--- a/htroot/yacychat.html
+++ b/htroot/yacychat.html
@@ -381,7 +381,7 @@
padding: 8px;
border-radius: 4px;
font-family: 'Roboto Mono', 'Menlo', 'Consolas', monospace;
- font-size: 0.95rem;
+ font-size: 1rem;
white-space: pre-wrap;
}
@@ -1113,11 +1113,38 @@
let assistantText = '';
let sawFirstDelta = false;
let focusShown = false;
+ let searchApplied = false;
if (typeof onFirstToken === 'function' && !focusShown) {
focusShown = true;
onFirstToken();
}
let autoScroll = true;
+ let pending = '';
+
+ const applySearchAttachment = parsed => {
+ if (searchApplied) return;
+ if (!parsed) return;
+ const searchName = parsed['search-filename'];
+ const searchBase64 = parsed['search-text-base64'];
+ if (!searchName || !searchBase64) return;
+ const dataUrl = `data:text/markdown;base64,${searchBase64}`;
+ const textContent = base64ToUtf8(searchBase64);
+ for (let i = state.messages.length - 1; i >= 0; i--) {
+ const msg = state.messages[i];
+ if (msg && msg.role === 'user' && msg.search) {
+ msg.attachment = {
+ kind: 'text',
+ name: searchName,
+ dataUrl,
+ mime: 'text/markdown',
+ textContent
+ };
+ delete msg.search;
+ searchApplied = true;
+ break;
+ }
+ }
+ };
const performAutoScroll = () => {
if (!autoScroll) return;
@@ -1129,37 +1156,40 @@
};
const parseChunk = chunk => {
- const segments = chunk
- .split(/\n+/)
- .flatMap(line => line.split(/(?=data:)/))
- .map(line => line.trim())
- .filter(Boolean);
- for (const line of segments) {
- if (line === 'data: [DONE]' || line === '[DONE]') {
- return true;
- }
- const clean = line.replace(/^data:\s*/i, '');
- if (!clean) continue;
- try {
- const parsed = JSON.parse(clean);
- const delta = parsed?.choices?.[0]?.delta?.content;
- if (delta) {
- if (!sawFirstDelta) {
- sawFirstDelta = true;
- applyMessageContent(assistantNode, 'assistant', '');
- if (!focusShown && typeof onFirstToken === 'function') {
- focusShown = true;
- onFirstToken();
+ pending += chunk;
+ const lines = pending.split(/\r?\n/);
+ pending = lines.pop() || '';
+ for (const raw of lines) {
+ const fragments = raw.split(/(?=data:\s*)/).map(f => f.trim()).filter(Boolean);
+ for (const line of fragments) {
+ if (line === 'data: [DONE]' || line === '[DONE]') {
+ return true;
+ }
+ if (!line.startsWith('data:')) continue;
+ const clean = line.replace(/^data:\s*/i, '');
+ if (!clean) continue;
+ try {
+ const parsed = JSON.parse(clean);
+ applySearchAttachment(parsed);
+ const delta = parsed?.choices?.[0]?.delta?.content;
+ if (delta) {
+ if (!sawFirstDelta) {
+ sawFirstDelta = true;
+ applyMessageContent(assistantNode, 'assistant', '');
+ if (!focusShown && typeof onFirstToken === 'function') {
+ focusShown = true;
+ onFirstToken();
+ }
}
+ assistantText += delta;
+ applyMessageContent(assistantNode, 'assistant', assistantText);
+ performAutoScroll();
}
- assistantText += delta;
- applyMessageContent(assistantNode, 'assistant', assistantText);
- performAutoScroll();
- }
} catch (err) {
console.warn('Failed to parse line', line, err);
}
}
+ }
return false;
};
let done = false;
@@ -1173,6 +1203,9 @@
}
done = parseChunk(chunk);
}
+ if (!done && pending.trim()) {
+ parseChunk('\n');
+ }
if (!focusShown && typeof onFirstToken === 'function') {
focusShown = true;
onFirstToken();
@@ -1185,6 +1218,7 @@
persistConversation();
updateClearChatVisibility();
updateSystemToggleButton();
+ renderConversation({ skipScroll: true });
}
function resizeComposer() {
@@ -1247,20 +1281,6 @@
return '';
}
- function collectRecentPairs() {
- const pairs = [];
- let pendingUser = null;
- for (const msg of state.messages) {
- if (msg.role === 'user') {
- pendingUser = messageText(msg.content);
- } else if (msg.role === 'assistant' && pendingUser !== null) {
- pairs.push({ user: pendingUser, assistant: messageText(msg.content) });
- pendingUser = null;
- }
- }
- return pairs.slice(-3);
- }
-
function renderConversation(options = {}) {
const { skipScroll = false } = options;
closeAttachmentPopover();
@@ -1280,8 +1300,11 @@
function persistConversation() {
try {
- const pairs = collectRecentPairs();
- localStorage.setItem(STORAGE_KEY, JSON.stringify(pairs));
+ const payload = {
+ model: state.config.model,
+ messages: state.messages
+ };
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
} catch (err) {
console.warn('Failed to persist conversation', err);
}
@@ -1294,13 +1317,19 @@
} catch (err) {
console.warn('Failed to parse stored conversation', err);
}
- if (!Array.isArray(stored) || !stored.length) return;
- for (const pair of stored) {
- if (pair?.user) {
- state.messages.push({ role: 'user', content: pair.user });
+ if (Array.isArray(stored) && stored.length) {
+ for (const pair of stored) {
+ if (pair?.user) {
+ state.messages.push({ role: 'user', content: pair.user });
+ }
+ if (pair?.assistant) {
+ state.messages.push({ role: 'assistant', content: pair.assistant });
+ }
}
- if (pair?.assistant) {
- state.messages.push({ role: 'assistant', content: pair.assistant });
+ } else if (stored && Array.isArray(stored.messages)) {
+ state.messages = stored.messages.map(msg => ({ ...msg }));
+ if (stored.model) {
+ state.config.model = stored.model;
}
}
ensureSystemMessage();
@@ -1359,6 +1388,14 @@
return `data:${safeMime};base64,${encoded}`;
}
+ function base64ToUtf8(base64) {
+ try {
+ return decodeURIComponent(escape(atob(base64)));
+ } catch (err) {
+ return '';
+ }
+ }
+
function getFileExtension(name) {
if (!name) return '';
const dot = name.lastIndexOf('.');