/** @typedef {{title: string, url: string, snippet: string}} WebSource */ /** @typedef {{message: string, model: string, chatId: number, webSearch?: boolean, reasoning?: boolean}} ChatRequest */ /** @typedef {{role: string, content: string, thinking?: string, sources?: WebSource[]}} ChatMessage */ export function createWebSearchToggle(button) { let enabled = false; const reset = () => { enabled = false; render(); }; const render = () => { button.setAttribute("aria-pressed", String(enabled)); button.title = enabled ? "Web search enabled" : "Search the web"; button.setAttribute("aria-label", button.title); }; button.addEventListener("click", () => { enabled = !enabled; render(); }); render(); return {get enabled() { return enabled; }, reset}; } /** @param {HTMLElement} container @param {WebSource[] | undefined} sources */ export function appendSources(container, sources) { if (!Array.isArray(sources) || !sources.length) return; const section = document.createElement("section"); section.className = "msg-sources"; section.setAttribute("aria-label", "Sources"); const heading = document.createElement("div"); heading.className = "msg-sources-heading"; heading.textContent = "Sources"; section.appendChild(heading); sources.slice(0, 5).forEach((source, index) => { let url; try { url = new URL(source.url); } catch { return; } if (!["https:", "http:"].includes(url.protocol) || url.username || url.password) return; const link = document.createElement("a"); link.href = url.href; link.target = "_blank"; link.rel = "noopener noreferrer"; link.className = "msg-source"; const title = document.createElement("span"); title.textContent = `[${index + 1}] ${source.title}`; title.title = source.title; const domain = document.createElement("small"); domain.textContent = url.hostname; link.append(title, domain); section.appendChild(link); }); if (section.childElementCount > 1) container.appendChild(section); }