summaryrefslogtreecommitdiff
path: root/htroot/yacychat.html
diff options
context:
space:
mode:
Diffstat (limited to 'htroot/yacychat.html')
-rw-r--r--htroot/yacychat.html103
1 files changed, 71 insertions, 32 deletions
diff --git a/htroot/yacychat.html b/htroot/yacychat.html
index cd56fa9a8..50d77f6e3 100644
--- a/htroot/yacychat.html
+++ b/htroot/yacychat.html
@@ -214,7 +214,7 @@
border-radius: 999px;
background: #eef2f7;
overflow: hidden;
- width: 380px;
+ width: 500px;
max-width: 100%;
height: 22px;
}
@@ -657,12 +657,12 @@
<div class="search-default-control">
<span>Default Dialog Augmentation:</span>
<div class="search-default-options" id="searchDefaultOptions" role="radiogroup" aria-label="Attach search results by default">
- <input type="radio" id="searchDefaultNone" name="searchDefault" value="none"/>
- <label for="searchDefaultNone">no search results, allow attachments</label>
+ <input type="radio" id="searchDefaultNone" name="searchDefault" value="no"/>
+ <label for="searchDefaultNone">no search, allow attachments</label>
<input type="radio" id="searchDefaultLocal" name="searchDefault" value="local" checked="checked"/>
<label for="searchDefaultLocal">use local search</label>
- <input type="radio" id="searchDefaultGlobal" name="searchDefault" value="global" class="is-future" disabled="disabled"/>
- <label for="searchDefaultGlobal" class="is-future">use global search</label>
+ <input type="radio" id="searchDefaultGlobal" name="searchDefault" value="global"/>
+ <label for="searchDefaultGlobal">use global search</label>
<span class="search-default-slider" aria-hidden="true"></span>
</div>
</div>
@@ -723,7 +723,7 @@
const STORAGE_KEY = 'yacychat_recent_pairs';
const SEARCH_DEFAULT_KEY = 'yacychat_search_default';
const SEARCH_DEFAULTS = {
- none: 'none',
+ none: 'no',
local: 'local',
global: 'global'
};
@@ -737,7 +737,7 @@
},
busy: false,
attachment: null,
- searchMode: false,
+ searchMode: SEARCH_DEFAULTS.none,
searchDefault: SEARCH_DEFAULTS.local,
assistantSeen: false,
showSystem: false
@@ -1215,7 +1215,7 @@
function clearAttachment(options = {}) {
const { applyDefault = true } = options;
state.attachment = null;
- state.searchMode = false;
+ state.searchMode = SEARCH_DEFAULTS.none;
dom.fileInput.value = '';
dom.attachmentFilename.textContent = 'Attach PNG/JPG or text (.txt/.md/.tex)';
dom.attachmentRow.classList.remove('has-attachment');
@@ -1227,7 +1227,7 @@
}
function setComposerAttachment(attachment) {
- state.searchMode = false;
+ state.searchMode = SEARCH_DEFAULTS.none;
dom.attachmentRow.classList.remove('search-mode');
state.attachment = attachment ? cloneAttachment(attachment) : null;
if (state.attachment) {
@@ -1244,16 +1244,26 @@
if (isLoading) {
dom.attachmentFilename.textContent = 'Loading attachment...';
} else if (!state.attachment) {
- dom.attachmentFilename.textContent = state.searchMode ? 'Attach Search Results' : 'Attach PNG/JPG or text (.txt/.md/.tex)';
+ dom.attachmentFilename.textContent = state.searchMode !== SEARCH_DEFAULTS.none ? 'Attach Search Results' : 'Attach PNG/JPG or text (.txt/.md/.tex)';
}
}
- function setSearchModeActive(enabled) {
- state.searchMode = !!enabled;
+ function setSearchModeActive(mode) {
+ let nextMode = mode;
+ if (mode === true) {
+ nextMode = state.searchDefault === SEARCH_DEFAULTS.none ? SEARCH_DEFAULTS.local : state.searchDefault;
+ }
+ if (!nextMode || nextMode === false) {
+ nextMode = SEARCH_DEFAULTS.none;
+ }
+ if (nextMode !== SEARCH_DEFAULTS.local && nextMode !== SEARCH_DEFAULTS.global) {
+ nextMode = SEARCH_DEFAULTS.none;
+ }
+ state.searchMode = nextMode;
state.attachment = null;
dom.fileInput.value = '';
dom.attachmentRow.classList.remove('has-attachment');
- if (state.searchMode) {
+ if (state.searchMode !== SEARCH_DEFAULTS.none) {
dom.attachmentRow.classList.add('search-mode');
dom.attachmentFilename.textContent = 'Attach Search Results';
} else {
@@ -1264,16 +1274,16 @@
function applySearchDefaultToComposer() {
if (!dom.attachmentRow || state.attachment) return;
- if (state.searchDefault === SEARCH_DEFAULTS.local) {
- setSearchModeActive(true);
+ if (state.searchDefault !== SEARCH_DEFAULTS.none) {
+ setSearchModeActive(state.searchDefault);
} else {
- setSearchModeActive(false);
+ setSearchModeActive(SEARCH_DEFAULTS.none);
}
}
function updateSearchDefaultSlider() {
if (!dom.searchDefaultOptions) return;
- const inputs = Array.from(dom.searchDefaultOptions.querySelectorAll('input[name="searchDefault"]:not(.is-future)'));
+ const inputs = Array.from(dom.searchDefaultOptions.querySelectorAll('input[name="searchDefault"]'));
const activeIndex = inputs.findIndex(input => input.checked);
const segments = inputs.length || 2;
dom.searchDefaultOptions.style.setProperty('--segments', segments);
@@ -1295,6 +1305,9 @@
} catch (err) {
console.warn('Failed to load search default', err);
}
+ if (stored === 'none') {
+ stored = SEARCH_DEFAULTS.none;
+ }
if (stored && Object.values(SEARCH_DEFAULTS).includes(stored)) {
state.searchDefault = stored;
}
@@ -1448,6 +1461,7 @@
async function streamChat(userMessage, assistantNode, options = {}) {
const { onFirstToken, includeUserInPayload = true, pushUserToState = true, currentUserIndex = -1 } = options;
+ const requestStart = performance.now();
ensureSystemMessage();
const sanitizedMessages = state.messages
.map((msg, index) => {
@@ -1462,12 +1476,14 @@
messages: sanitizedUserMessage ? [...sanitizedMessages, sanitizedUserMessage] : sanitizedMessages,
stream: true
};
+ console.debug('[yacychat] payload search modes', payload.messages.map(m => ({ role: m.role, search: m.search })));
const headers = { 'Content-Type': 'application/json' };
const response = await fetch(state.config.apiHost.replace(/\/$/, '') + '/v1/chat/completions', {
method: 'POST',
headers,
body: JSON.stringify(payload)
});
+ console.debug('[yacychat] request ms', Math.round(performance.now() - requestStart));
if (!response.ok) {
if (response.status === 429) {
throw new Error('Rate limit reached: please wait and try again. The server is protecting itself from overload.');
@@ -1502,11 +1518,12 @@
const searchName = parsed['search-filename'];
const searchBase64 = parsed['search-text-base64'];
if (!searchName || !searchBase64) return;
+ console.debug('[yacychat] received search attachment', { searchName, size: searchBase64.length, ms: Math.round(performance.now() - requestStart) });
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) {
+ if (msg && msg.role === 'user' && msg.search && msg.search !== SEARCH_DEFAULTS.none) {
msg.attachment = {
kind: 'text',
name: searchName,
@@ -1621,6 +1638,7 @@
? false
: (thinkAutoClosed && !thinkAutoCloseFired);
state.messages.push({ role: 'assistant', content: assistantText, thinkOpen: storedThinkOpen });
+ console.debug('[yacychat] response chars', assistantText.length, 'total ms', Math.round(performance.now() - requestStart));
state.assistantSeen = true;
persistConversation();
updateClearChatVisibility();
@@ -1651,13 +1669,17 @@
filename: state.attachment.name
});
}
- return {
- role: 'user',
- content: parts,
- search: !!state.searchMode
- };
+ const message = { role: 'user', content: parts };
+ if (state.searchMode !== SEARCH_DEFAULTS.none) {
+ message.search = state.searchMode;
+ }
+ return message;
}
- return { role: 'user', content: promptText, search: !!state.searchMode };
+ const message = { role: 'user', content: promptText };
+ if (state.searchMode !== SEARCH_DEFAULTS.none) {
+ message.search = state.searchMode;
+ }
+ return message;
}
function stripMessageForApi(message, options = {}) {
@@ -1675,7 +1697,11 @@
}
}
const sanitized = { role: message.role, content };
- if (message.search) sanitized.search = true;
+ if (message.search === true) {
+ sanitized.search = SEARCH_DEFAULTS.local;
+ } else if (message.search === SEARCH_DEFAULTS.local || message.search === SEARCH_DEFAULTS.global) {
+ sanitized.search = message.search;
+ }
return sanitized;
}
@@ -1719,9 +1745,18 @@
function persistConversation() {
try {
+ const messages = state.messages.map(msg => {
+ if (!msg) return msg;
+ const safe = { ...msg };
+ if (safe.attachment) {
+ const { kind, name, mime, size } = safe.attachment;
+ safe.attachment = { kind, name, mime, size };
+ }
+ return safe;
+ });
const payload = {
model: state.config.model,
- messages: state.messages
+ messages
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
} catch (err) {
@@ -2222,12 +2257,15 @@
userMessage.attachment = userAttachment;
}
// add user message to state immediately so edit/trim is available right away
- state.messages.push({
+ const userEntry = {
role: 'user',
content: userMessage.content,
- attachment: userAttachment,
- search: !!state.searchMode
- });
+ attachment: userAttachment
+ };
+ if (state.searchMode !== SEARCH_DEFAULTS.none) {
+ userEntry.search = state.searchMode;
+ }
+ state.messages.push(userEntry);
const userIndex = state.messages.length - 1;
const preview = formatUserPreview(prompt);
state.busy = true;
@@ -2253,11 +2291,12 @@
dom.fileInput.click();
});
dom.searchButton?.addEventListener('click', () => {
- setSearchModeActive(true);
+ const mode = state.searchDefault === SEARCH_DEFAULTS.none ? SEARCH_DEFAULTS.local : state.searchDefault;
+ setSearchModeActive(mode);
});
dom.fileInput.addEventListener('change', handleFileChange);
dom.clearFileButton.addEventListener('click', () => {
- const allowDefault = !(state.searchMode && !state.attachment);
+ const allowDefault = !(state.searchMode !== SEARCH_DEFAULTS.none && !state.attachment);
clearAttachment({ applyDefault: allowDefault });
});
dom.clearChatButton?.addEventListener('click', clearChatHistory);