prosty-rag / www /chatbot.js
jglowa's picture
Update www/chatbot.js
6201fd4 verified
// Copyright 2024 Mozilla Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const API_KEY = "your-api-key-here";
const DEFAULT_SYSTEM_PROMPT =
"A chat between a curious human and an artificial " +
"intelligence assistant. The assistant gives helpful, " +
"detailed, and polite answers to the human's questions.";
const DEFAULT_FLAGZ = {
"model": null,
"prompt": null,
"nologo": false,
"no_display_prompt": false,
"frequency_penalty": 0,
"presence_penalty": 0,
"temperature": 0.8,
"top_p": 0.95,
"seed": null,
"is_base_model": true,
"completion_mode": false,
};
const chatMessages = document.getElementById("chat-messages");
const chatInput = document.getElementById("chat-input");
const sendButton = document.getElementById("send-button");
const stopButton = document.getElementById("stop-button");
const settingsButton = document.getElementById("settings-button");
const settingsModal = document.getElementById("settings-modal");
const closeSettings = document.getElementById("close-settings");
const redoButton = document.getElementById('redo-button');
const chatInterface = document.getElementById("chat-interface");
const completionsInterface = document.getElementById("completions-interface");
const completionsInput = document.getElementById("completions-input");
const completeButton = document.getElementById("complete-button");
const completionsSettingsButton = document.getElementById("completions-settings-button");
const completionsStopButton = document.getElementById("completions-stop-button");
const uploadButton = document.getElementById("upload-button");
const fileUpload = document.getElementById("file-upload");
let abortController = null;
let disableAutoScroll = false;
let streamingMessageContent = [];
let originalLength = 0;
let uploadedFiles = [];
let chatHistory = [];
let flagz = null;
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).substring(2);
}
function wrapMessageElement(messageElement, role) {
const wrapper = document.createElement("div");
wrapper.appendChild(messageElement);
if (role == "assistant") {
const controlContainer = wrapper.appendChild(document.createElement("div"));
controlContainer.appendChild(createCopyButton(() => messageElement.textContent, () => messageElement.innerHTML));
controlContainer.appendChild(infoButton(wrapper));
controlContainer.classList.add("message-controls");
}
wrapper.classList.add("message-wrapper", role);
return wrapper;
}
function infoButton(container, stats) {
let button = container?.querySelector("#stats");
let statsElement = container?.querySelector("#info-container");
if (!button) {
button = document.createElement("button");
button.id = "stats";
button.innerText = "i";
button.style.fontFamily = "monospace";
statsElement = document.createElement("div");
statsElement.id = "info-container";
statsElement.className = "hidden";
container.append(statsElement);
button.addEventListener("click", () => {
const show = !button.classList.contains("toggled");
statsElement.classList.toggle("hidden", !show);
button.classList.toggle("toggled", show);
if (show)
requestAnimationFrame(() => scrollIntoViewIfNeeded(statsElement, container.parentElement));
});
}
button.style.display = stats ? "" : "none";
if (stats) {
const parts = [];
const promptDurationMs = stats.firstContentTime - stats.startTime;
const responseDurationMs = stats.endTime - stats.firstContentTime;
if (promptDurationMs > 0 && stats.promptTokenCount > 0) {
const tokensPerSecond = (stats.promptTokenCount / (promptDurationMs / 1000)).toFixed(2);
const durationString = promptDurationMs >= 1000 ? `${(promptDurationMs / 1000).toFixed(2)}s` : `${promptDurationMs}ms`;
parts.push(`Processed ${stats.promptTokenCount} input tokens in ${durationString} (${tokensPerSecond} tokens/s)`);
}
if (responseDurationMs > 0 && stats.reponseTokenCount > 0) {
const tokensPerSecond = (stats.reponseTokenCount / (responseDurationMs / 1000)).toFixed(2);
const durationString = responseDurationMs >= 1000 ? `${(responseDurationMs / 1000).toFixed(2)}s` : `${promptDurationMs}ms`;
parts.push(`Generated ${stats.reponseTokenCount} tokens in ${durationString} (${tokensPerSecond} tokens/s)`)
} else {
parts.push("Incomplete");
}
button.title = parts.join("\n");
statsElement.innerHTML = "";
parts.forEach(part => statsElement.appendChild(wrapInSpan(part + " ")));
}
return button;
}
function scrollIntoViewIfNeeded(elem, container) {
let rectElem = elem.getBoundingClientRect(), rectContainer = container.getBoundingClientRect();
if (rectElem.bottom > rectContainer.bottom) elem.scrollIntoView(false);
if (rectElem.top < rectContainer.top) elem.scrollIntoView();
}
function wrapInSpan(innerText) {
const span = document.createElement("span");
span.innerText = innerText;
return span;
}
function createMessageElement(content) {
const messageDiv = document.createElement("div");
messageDiv.classList.add("message");
let hdom = new HighlightDom(messageDiv);
const high = new RenderMarkdown(hdom);
high.feed(content);
high.flush();
return messageDiv;
}
function scrollToBottom() {
if (!disableAutoScroll) {
document.getElementById("bottom").scrollIntoView({ behavior: "instant" });
chatMessages.scrollTop = chatMessages.scrollHeight;
}
}
function onChatInput() {
chatInput.style.height = "auto"; // computes scrollHeight
chatInput.style.height = Math.min(chatInput.scrollHeight, 200) + "px";
}
function cleanupAfterMessage() {
chatMessages.scrollTop = chatMessages.scrollHeight;
chatInput.disabled = false;
sendButton.style.display = "inline-block";
stopButton.style.display = "none";
abortController = null;
if (!disableAutoScroll) {
scrollToBottom();
chatInput.focus();
}
disableAutoScroll = false;
}
function onWheel(e) {
if (e.deltaY == undefined || e.deltaY < 0)
disableAutoScroll = true;
}
async function handleChatStream(response, stats) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let currentMessageElement = null;
let currentMessageWrapper = null;
let messageAppended = false;
let finishReason = null;
let hdom = null;
let high = null;
streamingMessageContent = [];
const prefillStatus = document.getElementById('prefill-status');
const progressBar = prefillStatus.querySelector('.progress-bar');
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
// process all complete lines
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i].trim();
if (line.startsWith("data: ")) {
const data = line.slice(6);
if (data === "[DONE]") {
prefillStatus.style.display = "none";
continue;
}
try {
const parsed = JSON.parse(data);
const content = parsed.choices[0]?.delta?.content || "";
finishReason = parsed.choices[0]?.finish_reason;
// handle prefill progress
if (parsed.x_prefill_progress !== undefined) {
prefillStatus.style.display = "flex";
progressBar.style.width = `${parsed.x_prefill_progress * 100}%`;
} else {
if (content && !stats.firstContentTime) {
// Finished parsing the prompt
stats.firstContentTime = Date.now();
prefillStatus.style.display = "none";
}
}
if (content && !messageAppended) {
currentMessageElement = createMessageElement("");
currentMessageWrapper = wrapMessageElement(currentMessageElement, "assistant");
chatMessages.appendChild(currentMessageWrapper);
hdom = new HighlightDom(currentMessageElement);
high = new RenderMarkdown(hdom);
messageAppended = true;
}
if (messageAppended && content) {
streamingMessageContent.push(content);
high.feed(content);
scrollToBottom();
}
if (parsed.usage) {
stats.endTime = Date.now()
stats.promptTokenCount = parsed.usage.prompt_tokens
stats.reponseTokenCount = parsed.usage.completion_tokens
}
} catch (e) {
console.error("Error parsing JSON:", e);
}
}
}
// keep the last incomplete line in the buffer
buffer = lines[lines.length - 1];
}
} catch (error) {
if (error.name !== "AbortError") {
console.error("Error reading stream:", error);
}
} finally {
if (messageAppended) {
stats.firstContentTime = stats.firstContentTime ?? Date.now();
stats.endTime = stats.endTime ?? Date.now();
infoButton(currentMessageWrapper, stats);
high.flush();
// we don't supply max_tokens, so "length" can
// only mean that we ran out of context window
if (finishReason === "length") {
let img = document.createElement("IMG");
img.className = "ooc";
img.src = "ooc.svg";
img.alt = "🚫";
img.title = "Message truncated due to running out of context window. Consider tuning --ctx-size and/or --reserve-tokens";
img.width = 16;
img.height = 16;
hdom.lastElement.appendChild(img);
}
}
prefillStatus.style.display = "none";
cleanupAfterMessage();
}
}
function stopMessage() {
if (abortController) {
abortController.abort();
cleanupAfterMessage();
}
}
function fixUploads(str) {
str = uploadedFiles.reduce(
(text, [from, to]) => text.replaceAll(from, to),
str);
return str;
}
async function sendMessage() {
const message = fixUploads(chatInput.value.trim());
if (!message)
return;
// disable input while processing
chatInput.value = "";
chatInput.disabled = true;
onChatInput();
disableAutoScroll = false;
sendButton.style.display = "none";
stopButton.style.display = "inline-block";
stopButton.focus();
abortController = new AbortController();
// add user message to chat
const userMessageElement = createMessageElement(message);
chatMessages.appendChild(wrapMessageElement(userMessageElement, "user"));
scrollToBottom();
// Prosty RAG
try {
const res = await fetch("http://localhost:8081/?q=" + encodeURIComponent(message), {signal: abortController.signal});
if (res.ok) {
const context = await res.text();
if (context) {
chatHistory[0].content = chatHistory[0].content.replace(/\n\nKontekst:\n\n[\s\S]*$/g, '') + '\n\nKontekst:\n\n' + context.replace(/^\d+ \d\.\d+ /gm, '');
}
} else {
console.error("sendMessage() failed due to embedfile server error", response);
chatMessages.appendChild(wrapMessageElement(createMessageElement(
`Embedfile server replied with error code ${response.status} ${response.statusText}`),
"system"));
cleanupAfterMessage();
}
} catch (error) {
if (error.name !== "AbortError") {
console.error("sendMessage() failed due to unexpected exception", error);
chatMessages.appendChild(wrapMessageElement(createMessageElement(
"There was an error processing your request."),
"system"));
}
cleanupAfterMessage();
}
// update chat history
chatHistory.push({ role: "user", content: message });
const settings = loadSettings();
try {
const stats = {
startTime: Date.now(), // Timestamp when the request started
firstContentTime: null, // Timestamp when the first content was received
endTime: null, // Timestamp when the response was fully received
promptTokenCount: 0, // Number of tokens in the prompt
reponseTokenCount: 0 // Number of tokens in the response
};
const response = await fetch("/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
model: flagz.model || "gpt-3.5-turbo",
messages: chatHistory,
temperature: settings.temperature,
top_p: settings.top_p,
presence_penalty: settings.presence_penalty,
frequency_penalty: settings.frequency_penalty,
stream: true,
stream_options: {
include_usage: true
}
}),
signal: abortController.signal
});
if (response.ok) {
await handleChatStream(response, stats);
const lastMessage = streamingMessageContent.join("");
if (lastMessage)
chatHistory.push({ role: "assistant", content: lastMessage });
} else {
console.error("sendMessage() failed due to server error", response);
chatMessages.appendChild(wrapMessageElement(createMessageElement(
`Server replied with error code ${response.status} ${response.statusText}`),
"system"));
cleanupAfterMessage();
}
} catch (error) {
if (error.name !== "AbortError") {
console.error("sendMessage() failed due to unexpected exception", error);
chatMessages.appendChild(wrapMessageElement(createMessageElement(
"There was an error processing your request."),
"system"));
}
cleanupAfterMessage();
}
}
function onDragBegin(e) {
e.preventDefault();
e.stopPropagation();
chatInput.classList.add("drag-over");
}
function onDragEnd(e) {
e.preventDefault();
e.stopPropagation();
chatInput.classList.remove("drag-over");
}
function onDrop(e) {
const files = e.dataTransfer.files;
[...files].forEach(onFile);
}
function onPaste(e) {
const items = e.clipboardData.items;
for (let item of items) {
if (item.type.startsWith("image/")) {
e.preventDefault();
onFile(item.getAsFile());
return;
}
}
}
// fixes image data uri
// - convert to jpg if it's not jpg/png/gif
// - reduce quality and/or downscale if too big
async function fixImageDataUri(dataUri, maxLength = 1024 * 1024) {
const mimeMatch = dataUri.match(/^data:([^;,]+)/);
if (!mimeMatch)
throw new Error("bad image data uri");
const mimeType = mimeMatch[1].toLowerCase();
const supported = ["image/jpeg", "image/jpg", "image/png", "image/gif"];
if (supported.includes(mimeType))
if (dataUri.length <= maxLength)
return dataUri;
const lossless = ["image/png", "image/gif"];
const quality = lossless.includes(mimeType) ? 0.92 : 0.8;
function createScaledCanvas(img, scale) {
const canvas = document.createElement("canvas");
canvas.width = Math.floor(img.width * scale);
canvas.height = Math.floor(img.height * scale);
const ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = "high";
ctx.fillStyle = "white"; // in case of transparency
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
return canvas;
}
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = async () => {
let scale = 1.0;
let attempts = 0;
const maxAttempts = 5;
const initialCanvas = createScaledCanvas(img, scale);
let result = initialCanvas.toDataURL("image/jpeg", quality);
while (result.length > maxLength && attempts < maxAttempts) {
attempts++;
scale *= 0.7071;
const scaledCanvas = createScaledCanvas(img, scale);
result = scaledCanvas.toDataURL("image/jpeg", quality);
result.length = result.length;
}
if (result.length <= maxLength) {
resolve(result);
} else {
reject(new Error(`Could not reduce image to ${(maxLength/1024).toFixed(2)}kb after ${maxAttempts} attempts`));
}
};
img.onerror = () => {
reject(new Error('Failed to load image from data URI'));
};
img.src = dataUri;
});
}
async function onFile(file) {
const reader = new FileReader();
if (file.type.toLowerCase().startsWith('image/')) {
reader.onloadend = async function() {
const description = file.name;
const realDataUri = await fixImageDataUri(reader.result);
const fakeDataUri = 'data:,placeholder/' + generateId();
uploadedFiles.push([fakeDataUri, realDataUri]);
insertText(chatInput, `![${description}](${fakeDataUri})`);
};
reader.readAsDataURL(file);
} else if (file.type.toLowerCase().startsWith('text/')) {
reader.onloadend = function() {
const content = reader.result;
insertText(chatInput, `\`\`\`\n${content}\n\`\`\``);
};
reader.readAsText(file);
} else {
alert('Only image and text files are supported');
return;
}
}
function checkSurroundingNewlines(text, pos) {
const beforeCaret = text.slice(0, pos);
const afterCaret = text.slice(pos);
const precedingNewlines = beforeCaret.match(/\n*$/)[0].length;
const followingNewlines = afterCaret.match(/^\n*/)[0].length;
return { precedingNewlines, followingNewlines };
}
function insertText(elem, text) {
const pos = elem.selectionStart;
const isCodeBlock = text.includes('```');
if (isCodeBlock) {
const { precedingNewlines, followingNewlines } = checkSurroundingNewlines(elem.value, pos);
const needsLeadingNewlines = pos > 0 && precedingNewlines < 2 ? '\n'.repeat(2 - precedingNewlines) : '';
const needsTrailingNewlines = pos < elem.value.length && followingNewlines < 2 ? '\n'.repeat(2 - followingNewlines) : '';
text = needsLeadingNewlines + text + needsTrailingNewlines;
}
elem.value = elem.value.slice(0, pos) + text + elem.value.slice(pos);
const newPos = pos + text.length;
elem.setSelectionRange(newPos, newPos);
elem.focus();
elem.dispatchEvent(new Event("input"));
}
function onKeyDown(e) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}
async function fetchFlagz() {
try {
const response = await fetch("/flagz");
return await response.json();
} catch (error) {
console.error("Could not fetch /flagz so using defaults", error);
return DEFAULT_FLAGZ;
}
}
function getSystemPrompt() {
let defaultPrompt = flagz.prompt;
if (!defaultPrompt)
defaultPrompt = DEFAULT_SYSTEM_PROMPT;
let promptsText = localStorage.getItem("v1.prompts");
if (!promptsText)
return defaultPrompt;
let prompts = JSON.parse(promptsText);
let prompt = prompts[defaultPrompt];
if (!prompt)
return defaultPrompt;
return prompt;
}
function updateModelInfo() {
if (flagz.model) {
const modelName = flagz.model;
document.title = `${modelName} - llamafile`;
document.getElementById("model").textContent = modelName;
document.getElementById("model-completions").textContent = modelName;
}
if (!flagz.nologo) {
document.querySelectorAll(".logo").forEach(logo => logo.style.display = "inline-block");
}
}
function startChat(history) {
chatHistory = history;
chatMessages.innerHTML = "";
for (let i = 0; i < chatHistory.length; i++) {
if (flagz.no_display_prompt && chatHistory[i].role == "system")
continue;
chatMessages.appendChild(wrapMessageElement(createMessageElement(chatHistory[i].content),
chatHistory[i].role));
}
scrollToBottom();
}
function loadSettings() {
const stored = localStorage.getItem('v1.modelSettings');
if (stored) {
return JSON.parse(stored);
}
return {
temperature: flagz.temperature,
top_p: flagz.top_p,
presence_penalty: flagz.presence_penalty,
frequency_penalty: flagz.frequency_penalty,
};
}
function saveSettings(settings) {
localStorage.setItem('v1.modelSettings', JSON.stringify(settings));
}
function formatDoubleWithPlus(x) {
return (x >= 0 ? "+" : "") + x.toFixed(2);
}
function updateSettingsDisplay(settings) {
document.getElementById("temp-value").textContent = settings.temperature ? settings.temperature.toFixed(2) : "0.00 (deterministic)";
document.getElementById("top-p-value").textContent = settings.top_p.toFixed(2);
document.getElementById("presence-value").textContent = formatDoubleWithPlus(settings.presence_penalty);
document.getElementById("frequency-value").textContent = formatDoubleWithPlus(settings.frequency_penalty);
document.getElementById("temperature").value = settings.temperature;
document.getElementById("top-p").value = settings.top_p;
document.getElementById("presence-penalty").value = settings.presence_penalty;
document.getElementById("frequency-penalty").value = settings.frequency_penalty;
// Handle top-p disabling - using a more reliable selector
const topPSettingItem = document.querySelector('.setting-item:has(#top-p)');
if (settings.temperature === 0) {
topPSettingItem.classList.add('disabled');
} else {
topPSettingItem.classList.remove('disabled');
}
// Update top-p description with percentage
const topPDescription = topPSettingItem.querySelector('.setting-description');
if (settings.top_p >= 1) {
topPDescription.textContent = "Disabled. All tokens will be considered by the sampler.";
} else if (settings.top_p > .5) {
const percentage = Math.round((1 - settings.top_p) * 100);
topPDescription.textContent = `The bottom ${percentage}% tokens will be ignored by the sampler.`;
} else {
const percentage = Math.round(settings.top_p * 100);
topPDescription.textContent = `Only the top ${percentage}% tokens will be considered by the sampler.`;
}
}
function setupSettings() {
settingsButton.addEventListener("click", () => {
settingsModal.style.display = "flex";
updateSettingsDisplay(loadSettings());
});
closeSettings.addEventListener("click", () => {
settingsModal.style.display = "none";
});
["temperature", "top-p", "presence-penalty", "frequency-penalty"].forEach(id => {
const element = document.getElementById(id);
element.addEventListener("input", (e) => {
const settings = loadSettings();
const value = parseFloat(e.target.value);
const key = id.replace(/-/g, '_');
settings[key] = value;
saveSettings(settings);
updateSettingsDisplay(settings);
});
});
settingsModal.addEventListener("mousedown", (e) => {
if (e.target === settingsModal) {
settingsModal.style.display = "none";
}
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
settingsModal.style.display = "none";
}
});
}
function setupCompletions() {
completeButton.addEventListener("click", sendCompletion);
completionsStopButton.addEventListener("click", stopCompletion);
completionsSettingsButton.addEventListener("click", () => {
settingsModal.style.display = "flex";
updateSettingsDisplay(loadSettings());
});
completionsInput.addEventListener("keydown", (e) => {
if (e.key === "Enter" && !e.shiftKey && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
sendCompletion();
}
});
}
function stopCompletion() {
if (abortController) {
abortController.abort();
cleanupAfterCompletion();
}
}
function cleanupAfterCompletion() {
completeButton.style.display = "inline-block";
completionsStopButton.style.display = "none";
completeButton.disabled = false;
abortController = null;
// select newly added text and restore focus
const textArea = completionsInput;
textArea.focus();
textArea.selectionStart = originalLength || 0;
textArea.selectionEnd = textArea.value.length;
}
async function sendCompletion() {
const text = completionsInput.value;
completeButton.style.display = "none";
completionsStopButton.style.display = "inline-block";
completeButton.disabled = true;
abortController = new AbortController();
originalLength = text.length;
completionsStopButton.focus();
const settings = loadSettings();
try {
const response = await fetch("/v1/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
model: flagz.model || "gpt-3.5-turbo",
prompt: text,
temperature: settings.temperature,
top_p: settings.top_p,
presence_penalty: settings.presence_penalty,
frequency_penalty: settings.frequency_penalty,
stream: true
}),
signal: abortController.signal
});
if (response.ok) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
try {
while (true) {
const { done, value } = await reader.read();
if (done)
break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i].trim();
if (line.startsWith("data: ")) {
const data = line.slice(6);
if (data === "[DONE]")
continue;
try {
const parsed = JSON.parse(data);
const content = parsed.choices[0]?.text || "";
completionsInput.value += content;
completionsInput.scrollTop = completionsInput.scrollHeight;
} catch (e) {
console.error("Error parsing JSON:", e);
}
}
}
buffer = lines[lines.length - 1];
}
} catch (error) {
if (error.name !== "AbortError") {
console.error("Error reading stream:", error);
}
}
} else {
console.error("Completion failed:", response.statusText);
}
} catch (error) {
if (error.name !== "AbortError") {
console.error("Completion error:", error);
}
} finally {
cleanupAfterCompletion();
}
}
function removeLastDirectChild(element) {
if (element.lastElementChild) {
element.removeChild(element.lastElementChild);
}
}
function onRedo() {
if (!chatHistory.length)
return;
removeLastDirectChild(chatMessages);
let msg = chatHistory.pop();
if (msg.role === "assistant") {
removeLastDirectChild(chatMessages);
msg = chatHistory.pop();
}
chatInput.value = msg.content;
chatInput.focus();
chatInput.dispatchEvent(new Event("input")); // adjust textarea height
}
function setupMenu() {
const triggers = document.querySelectorAll('.menu-trigger');
const menus = document.querySelectorAll('.menu');
const chatModeSwitch = document.getElementById('chat-mode-switch');
const completionsModeSwitch = document.getElementById('completions-mode-switch');
if (flagz.is_base_model) {
completionsModeSwitch.classList.add('disabled');
completionsModeSwitch.title = "Chatbot mode isn't possible because this is a base model that hasn't had instruction fine tuning; try passing --chat-template chatml or llama2 if this is really an instruct model.";
completionsModeSwitch.disabled = true;
}
triggers.forEach(trigger => {
trigger.addEventListener('click', (e) => {
e.stopPropagation();
const menu = trigger.nextElementSibling;
menus.forEach(m => {
if (m !== menu)
m.classList.remove('show');
});
menu.classList.toggle('show');
});
});
document.addEventListener('click', () => {
menus.forEach(menu => menu.classList.remove('show'));
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape')
menus.forEach(menu => menu.classList.remove('show'));
});
chatModeSwitch.addEventListener('click', () => {
flagz.completion_mode = true;
setupCompletionsMode();
menus.forEach(menu => menu.classList.remove('show'));
});
completionsModeSwitch.addEventListener('click', () => {
if (!flagz.is_base_model) {
flagz.completion_mode = false;
setupChatCompletionsMode();
menus.forEach(menu => menu.classList.remove('show'));
}
});
}
function setupChatCompletionsMode() {
chatInterface.style.display = "flex";
completionsInterface.style.display = "none";
startChat([{ role: "system", content: getSystemPrompt() }]);
chatInput.focus();
}
function setupCompletionsMode() {
chatInterface.style.display = "none";
completionsInterface.style.display = "flex";
completionsInput.focus();
}
function onUploadButtonClick() {
fileUpload.click();
}
function onFileUploadChange(e) {
if (e.target.files[0]) {
onFile(e.target.files[0]);
e.target.value = '';
}
}
async function chatbot() {
flagz = await fetchFlagz();
updateModelInfo();
setupSettings();
setupCompletions();
setupMenu();
if (flagz.is_base_model || flagz.completion_mode) {
setupCompletionsMode();
} else {
setupChatCompletionsMode();
}
sendButton.addEventListener("click", sendMessage);
stopButton.addEventListener("click", stopMessage);
redoButton.addEventListener("click", onRedo);
chatInput.addEventListener("input", onChatInput);
chatInput.addEventListener("keydown", onKeyDown);
chatMessages.addEventListener("touchmove", onWheel);
document.addEventListener("wheel", onWheel);
document.addEventListener("dragenter", onDragBegin);
document.addEventListener("dragover", onDragBegin);
document.addEventListener("dragleave", onDragEnd);
document.addEventListener("drop", onDragEnd);
document.addEventListener("drop", onDrop);
document.addEventListener("paste", onPaste);
uploadButton.addEventListener("click", onUploadButtonClick);
fileUpload.addEventListener("change", onFileUploadChange);
}
chatbot();