jglowa commited on
Commit
076036d
·
verified ·
1 Parent(s): 20ae7d6

Upload 7 files

Browse files
Files changed (8) hide show
  1. .args +13 -0
  2. .gitattributes +1 -0
  3. baza/wikipedia.txt +12 -0
  4. bge-m3.embedfile +3 -0
  5. build.cmd +14 -0
  6. indeksator.cmd +43 -0
  7. prosty-rag.cmd +24 -0
  8. www/chatbot.js +865 -0
.args ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --server
2
+ --v2
3
+ -m
4
+ "Prosty RAG - Bielik-4.5B-v3.0-Instruct.gguf"
5
+ -ngl
6
+ 999
7
+ -c
8
+ 0
9
+ --prompt
10
+ "Jesteś pomocnym asystentem."
11
+ --no-display-prompt
12
+ --nologo
13
+ ...
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ bge-m3.embedfile filter=lfs diff=lfs merge=lfs -text
baza/wikipedia.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Generowanie wspomagane wyszukiwaniem (ang. retrieval-augmented generation, RAG) – technika umożliwiająca modelom GenAI wyszukiwanie i włączanie nowych, zewnętrznych informacji. RAG modyfikuje zapytania do dużego modelu językowego (LLM) tak, aby odpowiadał on na pytania użytkowników z uwzględnieniem dodatkowych, dołączonych danych. Umożliwia to LLM-om korzystanie z informacji specyficznych dla danej dziedziny i/lub zaktualizowanych.
2
+
3
+ Oprócz zwiększenia jakości zwracanych odpowiedzi, RAG pozwala również LLM-om na uwzględnianie w generowanych treściach odniesień do źródeł, co umożliwia użytkownikom weryfikację informacji poprzez analizę cytowanych dokumentów lub oryginalnych źródeł. Dzięki temu możliwe jest uzyskanie większej wiarygodności i transparentność, ponieważ użytkownicy mogą sprawdzać pobrane treści, aby upewnić się co do ich dokładności, trafności i pochodzenia.
4
+
5
+ Termin RAG został po raz pierwszy wprowadzony w 2020 r. przez Meta. Istnieją biblioteki umożliwiające wdrożenie technik RAG wraz z ich specyficznymi funkcjonalnościami.
6
+
7
+ RAG składa się z czterech elementów:
8
+
9
+ 1. Indexing (pol. indeksowanie) – dodatkowe dane dostarczane do systemu są osadzane i zapisywane w bazie danych.
10
+ 2. Retrieval (pol. pobieranie) – pobieranie najbardziej odpowiednich dokumentów dla danego zapytania.
11
+ 3. Augmentation (pol. uzupełnianie) – przekazanie pobranych informacji do LLMa za pomocą inżynierii podpowiedzi.
12
+ 4. Generation (pol. generowanie) – LLM zwraca odpowiedź w oparciu o wzbogacone dane i zapytanie użytkownika. Niektóre modele mogą korzystać z innych technik wzbogacania odpowiedzi.
bge-m3.embedfile ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e61dc06706d888983b888d634be950ae7ef3eac6a04414665a28ae669517ed0e
3
+ size 512373844
build.cmd ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :; # Prosty RAG v0.1 - Jerzy Głowacki na licencji Apache 2.0
2
+ :; # *NIX:
3
+ :; [ ! -f "Prosty RAG - Bielik-4.5B-v3.0-Instruct.gguf" ] && echo Pobieranie Bielik-4.5B-v3.0-Instruct.gguf... && curl -Lo "Prosty RAG - Bielik-4.5B-v3.0-Instruct.gguf" https://huggingface.co/gaianet/Bielik-4.5B-v3.0-Instruct-GGUF/resolve/main/Bielik-4.5B-v3.0-Instruct-Q6_K.gguf?download=true && echo Gotowe!
4
+ :; [ ! -f zipalign ] && echo Pobieranie zipalign... && curl -Lo zipalign https://github.com/Mozilla-Ocho/llamafile/releases/download/0.9.3/zipalign-0.9.3 && echo Gotowe!
5
+ :; [ ! -f llamafile ] && echo Pobieranie llamafile... && curl -Lo llamafile https://github.com/Mozilla-Ocho/llamafile/releases/download/0.9.3/llamafile-0.9.3 && echo Gotowe!
6
+ :; chmod +x zipalign llamafile
7
+ :; cp llamafile prosty-rag.llamafile
8
+ :; echo Budowanie... && ./zipalign -j0 prosty-rag.llamafile "Prosty RAG - Bielik-4.5B-v3.0-Instruct.gguf" && ./zipalign prosty-rag.llamafile .args www/chatbot.js && echo Gotowe! Uruchom ./prosty-rag.cmd, zadaj pytanie i otwórz http://localhost:8080 w przeglądarce internetowej.; exit $?
9
+ :; # Windows:
10
+ @if not exist "Prosty RAG - Bielik-4.5B-v3.0-Instruct.gguf" echo Pobieranie Bielik-4.5B-v3.0-Instruct.gguf... && curl -Lo "Prosty RAG - Bielik-4.5B-v3.0-Instruct.gguf" https://huggingface.co/gaianet/Bielik-4.5B-v3.0-Instruct-GGUF/resolve/main/Bielik-4.5B-v3.0-Instruct-Q6_K.gguf?download=true && echo Gotowe!
11
+ @if not exist zipalign.exe echo Pobieranie zipalign.exe... && curl -Lo zipalign.exe https://github.com/Mozilla-Ocho/llamafile/releases/download/0.9.3/zipalign-0.9.3 && echo Gotowe!
12
+ @if not exist llamafile.exe echo Pobieranie llamafile.exe... && curl -Lo llamafile.exe https://github.com/Mozilla-Ocho/llamafile/releases/download/0.9.3/llamafile-0.9.3 && echo Gotowe!
13
+ @copy /b /y llamafile.exe prosty-rag.llamafile
14
+ @echo Budowanie... && zipalign -j0 prosty-rag.llamafile "Prosty RAG - Bielik-4.5B-v3.0-Instruct.gguf" && zipalign prosty-rag.llamafile .args www/chatbot.js && echo Gotowe! Uruchom prosty-rag.cmd, zadaj pytanie i otwórz http://localhost:8080 w przeglądarce internetowej.
indeksator.cmd ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :; # Indeksator Prosty RAG v0.1 - Jerzy Głowacki na licencji Apache 2.0
2
+ @echo off
3
+ setlocal enabledelayedexpansion
4
+ set embedfile=bge-m3.embedfile
5
+ set inputDir=baza
6
+ set chunksFile=chunks.txt
7
+ set dbFile=prosty-rag.db
8
+ set chunkWords=200
9
+ set overlapWords=10
10
+ for /l %%i in (1,1,%overlapWords%) do set buf[%%i]=
11
+ break>%chunksFile%
12
+ if not exist %inputDir% echo Pobieranie przykładowego pliku %inputDir%\wikipedia.txt... && curl --create-dirs -Lo %inputDir%\wikipedia.txt https://huggingface.co/jglowa/prosty-rag/resolve/main/baza/wikipedia.txt?download=true && echo Gotowe!
13
+ if not exist %embedfile% echo Pobieranie %embedfile%... && curl -Lo %embedfile% https://huggingface.co/jglowa/prosty-rag/resolve/main/bge-m3.embedfile?download=true && echo Gotowe!
14
+ if not exist pdftotext.exe echo Pobieranie pdftotext.exe... && curl -LO https://dl.xpdfreader.com/xpdf-tools-win-4.05.zip && tar --strip-components 2 -xf xpdf-tools-win-4.05.zip xpdf-tools-win-4.05/bin64/pdftotext.exe && del xpdf-tools-win-4.05.zip && echo Gotowe!
15
+ echo Indeksowanie plików PDF/TXT/MD w folderze %inputDir%...
16
+ for %%F in ("%inputDir%\*.pdf") do if not exist "%%~dpnF.txt" echo Konwertowanie %%~nxF... && pdftotext -nopgbrk -enc UTF-8 "%%~F"
17
+ for %%F in ("%inputDir%\*.txt" "%inputDir%\*.md") do (
18
+ echo Przetwarzanie %%~nxF...
19
+ (
20
+ set wordCount=0
21
+ set /p =%%~nxF: <nul
22
+ for /f "usebackq delims=" %%L in ("%%F") do (
23
+ for %%W in (%%L) do (
24
+ set /p =%%W <nul
25
+ for /l %%i in (2,1,%overlapWords%) do (set /a j=%%i-1 && set buf[!j!]=!buf[%%i]!)
26
+ set "buf[%overlapWords%]=%%W"
27
+ set /a wordCount+=1
28
+ if !wordCount! geq !chunkWords! (
29
+ echo.
30
+ set /p =%%~nxF: <nul
31
+ for /l %%i in (1,1,%overlapWords%) do set /p =!buf[%%i]! <nul
32
+ set /a wordCount=0
33
+ )
34
+ )
35
+ )
36
+ echo.
37
+ )>>%chunksFile%
38
+ )
39
+ echo Osadzanie plików...
40
+ if exist %dbFile% del %dbFile%
41
+ %embedfile% import %chunksFile% %dbFile% && echo Gotowe! Po każdej zmianie w folderze %inputDir% należy uruchomić ponownie indeksator.
42
+ del %chunksFile%
43
+ endlocal
prosty-rag.cmd ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :; # Prosty RAG v0.1 - Jerzy Głowacki na licencji Apache 2.0
2
+ @echo off
3
+ setlocal enabledelayedexpansion
4
+ set llamafile=prosty-rag.llamafile
5
+ set embedfile=bge-m3.embedfile
6
+ set dbFile=prosty-rag.db
7
+ set topK=5
8
+ if not exist %llamafile% echo Pobieranie %llamafile%... && curl -Lo %llamafile% https://huggingface.co/jglowa/prosty-rag/resolve/main/prosty-rag.llamafile?download=true && echo Gotowe!
9
+ if not exist %embedfile% echo Pobieranie %embedfile%... && curl -Lo %embedfile% https://huggingface.co/jglowa/prosty-rag/resolve/main/bge-m3.embedfile?download=true && echo Gotowe!
10
+ if not exist indeksator.cmd echo Pobieranie indeksator.cmd... && curl -Lo indeksator.cmd https://huggingface.co/jglowa/prosty-rag/resolve/main/indeksator.cmd?download=true && echo Gotowe!
11
+ if not exist %dbFile% call indeksator.cmd
12
+ set /p query=Wpisz pytanie:
13
+ if "%query%"=="" goto :eof
14
+ set "prompt=Jesteś pomocnym asystentem. Odpowiedz na pytanie użytkownika korzystając z poniższego kontekstu, zawierającego wybrane fragmenty plików. Na końcu podaj odwołanie do pliku źródła. Nie zmyślaj.\n\n Kontekst:\n"
15
+ set line=0
16
+ echo Wyszukiwanie w bazie danych...
17
+ for /f "tokens=2*" %%A in ('%embedfile% search %dbFile% "%query%"') do (
18
+ set /a line+=1
19
+ if !line! leq %topK% (set "prompt=!prompt!\n %%B") else (goto next)
20
+ )
21
+ :next
22
+ start cmd /u /c "echo Uruchamianie czatu w przeglądarce... && timeout 5 /nobreak && start http://localhost:8080?q=%query: =+%"
23
+ echo Ładowanie modelu...
24
+ %llamafile% --server --v2 --prompt "%prompt%" --flash-attn 1 %*
www/chatbot.js ADDED
@@ -0,0 +1,865 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Copyright 2024 Mozilla Foundation
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ const API_KEY = "your-api-key-here";
16
+
17
+ const DEFAULT_SYSTEM_PROMPT =
18
+ "A chat between a curious human and an artificial " +
19
+ "intelligence assistant. The assistant gives helpful, " +
20
+ "detailed, and polite answers to the human's questions.";
21
+
22
+ const DEFAULT_FLAGZ = {
23
+ "model": null,
24
+ "prompt": null,
25
+ "nologo": false,
26
+ "no_display_prompt": false,
27
+ "frequency_penalty": 0,
28
+ "presence_penalty": 0,
29
+ "temperature": 0.8,
30
+ "top_p": 0.95,
31
+ "seed": null,
32
+ "is_base_model": true,
33
+ "completion_mode": false,
34
+ };
35
+
36
+ const chatMessages = document.getElementById("chat-messages");
37
+ const chatInput = document.getElementById("chat-input");
38
+ const sendButton = document.getElementById("send-button");
39
+ const stopButton = document.getElementById("stop-button");
40
+ const settingsButton = document.getElementById("settings-button");
41
+ const settingsModal = document.getElementById("settings-modal");
42
+ const closeSettings = document.getElementById("close-settings");
43
+ const redoButton = document.getElementById('redo-button');
44
+ const chatInterface = document.getElementById("chat-interface");
45
+ const completionsInterface = document.getElementById("completions-interface");
46
+ const completionsInput = document.getElementById("completions-input");
47
+ const completeButton = document.getElementById("complete-button");
48
+ const completionsSettingsButton = document.getElementById("completions-settings-button");
49
+ const completionsStopButton = document.getElementById("completions-stop-button");
50
+ const uploadButton = document.getElementById("upload-button");
51
+ const fileUpload = document.getElementById("file-upload");
52
+
53
+ let abortController = null;
54
+ let disableAutoScroll = false;
55
+ let streamingMessageContent = [];
56
+ let originalLength = 0;
57
+ let uploadedFiles = [];
58
+ let chatHistory = [];
59
+ let flagz = null;
60
+
61
+ function generateId() {
62
+ return Date.now().toString(36) + Math.random().toString(36).substring(2);
63
+ }
64
+
65
+ function wrapMessageElement(messageElement, role) {
66
+ const wrapper = document.createElement("div");
67
+ wrapper.appendChild(messageElement);
68
+ if (role == "assistant") {
69
+ const controlContainer = wrapper.appendChild(document.createElement("div"));
70
+ controlContainer.appendChild(createCopyButton(() => messageElement.textContent, () => messageElement.innerHTML));
71
+ controlContainer.appendChild(infoButton(wrapper));
72
+ controlContainer.classList.add("message-controls");
73
+ }
74
+ wrapper.classList.add("message-wrapper", role);
75
+ return wrapper;
76
+ }
77
+
78
+ function infoButton(container, stats) {
79
+ let button = container?.querySelector("#stats");
80
+ let statsElement = container?.querySelector("#info-container");
81
+ if (!button) {
82
+ button = document.createElement("button");
83
+ button.id = "stats";
84
+ button.innerText = "i";
85
+ button.style.fontFamily = "monospace";
86
+
87
+ statsElement = document.createElement("div");
88
+ statsElement.id = "info-container";
89
+ statsElement.className = "hidden";
90
+ container.append(statsElement);
91
+ button.addEventListener("click", () => {
92
+ const show = !button.classList.contains("toggled");
93
+ statsElement.classList.toggle("hidden", !show);
94
+ button.classList.toggle("toggled", show);
95
+ if (show)
96
+ requestAnimationFrame(() => scrollIntoViewIfNeeded(statsElement, container.parentElement));
97
+ });
98
+ }
99
+ button.style.display = stats ? "" : "none";
100
+ if (stats) {
101
+ const parts = [];
102
+ const promptDurationMs = stats.firstContentTime - stats.startTime;
103
+ const responseDurationMs = stats.endTime - stats.firstContentTime;
104
+ if (promptDurationMs > 0 && stats.promptTokenCount > 0) {
105
+ const tokensPerSecond = (stats.promptTokenCount / (promptDurationMs / 1000)).toFixed(2);
106
+ const durationString = promptDurationMs >= 1000 ? `${(promptDurationMs / 1000).toFixed(2)}s` : `${promptDurationMs}ms`;
107
+ parts.push(`Processed ${stats.promptTokenCount} input tokens in ${durationString} (${tokensPerSecond} tokens/s)`);
108
+ }
109
+ if (responseDurationMs > 0 && stats.reponseTokenCount > 0) {
110
+ const tokensPerSecond = (stats.reponseTokenCount / (responseDurationMs / 1000)).toFixed(2);
111
+ const durationString = responseDurationMs >= 1000 ? `${(responseDurationMs / 1000).toFixed(2)}s` : `${promptDurationMs}ms`;
112
+ parts.push(`Generated ${stats.reponseTokenCount} tokens in ${durationString} (${tokensPerSecond} tokens/s)`)
113
+ } else {
114
+ parts.push("Incomplete");
115
+ }
116
+ button.title = parts.join("\n");
117
+ statsElement.innerHTML = "";
118
+ parts.forEach(part => statsElement.appendChild(wrapInSpan(part + " ")));
119
+ }
120
+ return button;
121
+ }
122
+
123
+ function scrollIntoViewIfNeeded(elem, container) {
124
+ let rectElem = elem.getBoundingClientRect(), rectContainer = container.getBoundingClientRect();
125
+ if (rectElem.bottom > rectContainer.bottom) elem.scrollIntoView(false);
126
+ if (rectElem.top < rectContainer.top) elem.scrollIntoView();
127
+ }
128
+
129
+ function wrapInSpan(innerText) {
130
+ const span = document.createElement("span");
131
+ span.innerText = innerText;
132
+ return span;
133
+ }
134
+
135
+ function createMessageElement(content) {
136
+ const messageDiv = document.createElement("div");
137
+ messageDiv.classList.add("message");
138
+ let hdom = new HighlightDom(messageDiv);
139
+ const high = new RenderMarkdown(hdom);
140
+ high.feed(content);
141
+ high.flush();
142
+ return messageDiv;
143
+ }
144
+
145
+ function scrollToBottom() {
146
+ if (!disableAutoScroll) {
147
+ document.getElementById("bottom").scrollIntoView({ behavior: "instant" });
148
+ chatMessages.scrollTop = chatMessages.scrollHeight;
149
+ }
150
+ }
151
+
152
+ function onChatInput() {
153
+ chatInput.style.height = "auto"; // computes scrollHeight
154
+ chatInput.style.height = Math.min(chatInput.scrollHeight, 200) + "px";
155
+ }
156
+
157
+ function cleanupAfterMessage() {
158
+ chatMessages.scrollTop = chatMessages.scrollHeight;
159
+ chatInput.disabled = false;
160
+ sendButton.style.display = "inline-block";
161
+ stopButton.style.display = "none";
162
+ abortController = null;
163
+ if (!disableAutoScroll) {
164
+ scrollToBottom();
165
+ chatInput.focus();
166
+ }
167
+ disableAutoScroll = false;
168
+ }
169
+
170
+ function onWheel(e) {
171
+ if (e.deltaY == undefined || e.deltaY < 0)
172
+ disableAutoScroll = true;
173
+ }
174
+
175
+ async function handleChatStream(response, stats) {
176
+ const reader = response.body.getReader();
177
+ const decoder = new TextDecoder();
178
+ let buffer = "";
179
+ let currentMessageElement = null;
180
+ let currentMessageWrapper = null;
181
+ let messageAppended = false;
182
+ let finishReason = null;
183
+ let hdom = null;
184
+ let high = null;
185
+ streamingMessageContent = [];
186
+ const prefillStatus = document.getElementById('prefill-status');
187
+ const progressBar = prefillStatus.querySelector('.progress-bar');
188
+
189
+ try {
190
+ while (true) {
191
+ const { done, value } = await reader.read();
192
+ if (done) break;
193
+
194
+ buffer += decoder.decode(value, { stream: true });
195
+ const lines = buffer.split("\n");
196
+
197
+ // process all complete lines
198
+ for (let i = 0; i < lines.length - 1; i++) {
199
+ const line = lines[i].trim();
200
+ if (line.startsWith("data: ")) {
201
+ const data = line.slice(6);
202
+ if (data === "[DONE]") {
203
+ prefillStatus.style.display = "none";
204
+ continue;
205
+ }
206
+ try {
207
+ const parsed = JSON.parse(data);
208
+ const content = parsed.choices[0]?.delta?.content || "";
209
+ finishReason = parsed.choices[0]?.finish_reason;
210
+
211
+ // handle prefill progress
212
+ if (parsed.x_prefill_progress !== undefined) {
213
+ prefillStatus.style.display = "flex";
214
+ progressBar.style.width = `${parsed.x_prefill_progress * 100}%`;
215
+ } else {
216
+ if (content && !stats.firstContentTime) {
217
+ // Finished parsing the prompt
218
+ stats.firstContentTime = Date.now();
219
+ prefillStatus.style.display = "none";
220
+ }
221
+ }
222
+
223
+ if (content && !messageAppended) {
224
+ currentMessageElement = createMessageElement("");
225
+ currentMessageWrapper = wrapMessageElement(currentMessageElement, "assistant");
226
+ chatMessages.appendChild(currentMessageWrapper);
227
+ hdom = new HighlightDom(currentMessageElement);
228
+ high = new RenderMarkdown(hdom);
229
+ messageAppended = true;
230
+ }
231
+
232
+ if (messageAppended && content) {
233
+ streamingMessageContent.push(content);
234
+ high.feed(content);
235
+ scrollToBottom();
236
+ }
237
+ if (parsed.usage) {
238
+ stats.endTime = Date.now()
239
+ stats.promptTokenCount = parsed.usage.prompt_tokens
240
+ stats.reponseTokenCount = parsed.usage.completion_tokens
241
+ }
242
+ } catch (e) {
243
+ console.error("Error parsing JSON:", e);
244
+ }
245
+ }
246
+ }
247
+
248
+ // keep the last incomplete line in the buffer
249
+ buffer = lines[lines.length - 1];
250
+ }
251
+ } catch (error) {
252
+ if (error.name !== "AbortError") {
253
+ console.error("Error reading stream:", error);
254
+ }
255
+ } finally {
256
+ if (messageAppended) {
257
+ stats.firstContentTime = stats.firstContentTime ?? Date.now();
258
+ stats.endTime = stats.endTime ?? Date.now();
259
+ infoButton(currentMessageWrapper, stats);
260
+ high.flush();
261
+ // we don't supply max_tokens, so "length" can
262
+ // only mean that we ran out of context window
263
+ if (finishReason === "length") {
264
+ let img = document.createElement("IMG");
265
+ img.className = "ooc";
266
+ img.src = "ooc.svg";
267
+ img.alt = "🚫";
268
+ img.title = "Message truncated due to running out of context window. Consider tuning --ctx-size and/or --reserve-tokens";
269
+ img.width = 16;
270
+ img.height = 16;
271
+ hdom.lastElement.appendChild(img);
272
+ }
273
+ }
274
+ prefillStatus.style.display = "none";
275
+ cleanupAfterMessage();
276
+ }
277
+ }
278
+
279
+ function stopMessage() {
280
+ if (abortController) {
281
+ abortController.abort();
282
+ cleanupAfterMessage();
283
+ }
284
+ }
285
+
286
+ function fixUploads(str) {
287
+ str = uploadedFiles.reduce(
288
+ (text, [from, to]) => text.replaceAll(from, to),
289
+ str);
290
+ return str;
291
+ }
292
+
293
+ async function sendMessage() {
294
+ const message = fixUploads(chatInput.value.trim());
295
+ if (!message)
296
+ return;
297
+
298
+ // disable input while processing
299
+ chatInput.value = "";
300
+ chatInput.disabled = true;
301
+ onChatInput();
302
+ disableAutoScroll = false;
303
+ sendButton.style.display = "none";
304
+ stopButton.style.display = "inline-block";
305
+ stopButton.focus();
306
+ abortController = new AbortController();
307
+
308
+ // add user message to chat
309
+ const userMessageElement = createMessageElement(message);
310
+ chatMessages.appendChild(wrapMessageElement(userMessageElement, "user"));
311
+ scrollToBottom();
312
+
313
+ // update chat history
314
+ chatHistory.push({ role: "user", content: message });
315
+
316
+ const settings = loadSettings();
317
+ try {
318
+ const stats = {
319
+ startTime: Date.now(), // Timestamp when the request started
320
+ firstContentTime: null, // Timestamp when the first content was received
321
+ endTime: null, // Timestamp when the response was fully received
322
+ promptTokenCount: 0, // Number of tokens in the prompt
323
+ reponseTokenCount: 0 // Number of tokens in the response
324
+ };
325
+ const response = await fetch("/v1/chat/completions", {
326
+ method: "POST",
327
+ headers: {
328
+ "Content-Type": "application/json",
329
+ "Authorization": `Bearer ${API_KEY}`
330
+ },
331
+ body: JSON.stringify({
332
+ model: flagz.model || "gpt-3.5-turbo",
333
+ messages: chatHistory,
334
+ temperature: settings.temperature,
335
+ top_p: settings.top_p,
336
+ presence_penalty: settings.presence_penalty,
337
+ frequency_penalty: settings.frequency_penalty,
338
+ stream: true,
339
+ stream_options: {
340
+ include_usage: true
341
+ }
342
+ }),
343
+ signal: abortController.signal
344
+ });
345
+ if (response.ok) {
346
+ await handleChatStream(response, stats);
347
+ const lastMessage = streamingMessageContent.join("");
348
+ if (lastMessage)
349
+ chatHistory.push({ role: "assistant", content: lastMessage });
350
+ } else {
351
+ console.error("sendMessage() failed due to server error", response);
352
+ chatMessages.appendChild(wrapMessageElement(createMessageElement(
353
+ `Server replied with error code ${response.status} ${response.statusText}`),
354
+ "system"));
355
+ cleanupAfterMessage();
356
+ }
357
+ } catch (error) {
358
+ if (error.name !== "AbortError") {
359
+ console.error("sendMessage() failed due to unexpected exception", error);
360
+ chatMessages.appendChild(wrapMessageElement(createMessageElement(
361
+ "There was an error processing your request."),
362
+ "system"));
363
+ }
364
+ cleanupAfterMessage();
365
+ }
366
+ }
367
+
368
+ function onDragBegin(e) {
369
+ e.preventDefault();
370
+ e.stopPropagation();
371
+ chatInput.classList.add("drag-over");
372
+ }
373
+
374
+ function onDragEnd(e) {
375
+ e.preventDefault();
376
+ e.stopPropagation();
377
+ chatInput.classList.remove("drag-over");
378
+ }
379
+
380
+ function onDrop(e) {
381
+ const files = e.dataTransfer.files;
382
+ [...files].forEach(onFile);
383
+ }
384
+
385
+ function onPaste(e) {
386
+ const items = e.clipboardData.items;
387
+ for (let item of items) {
388
+ if (item.type.startsWith("image/")) {
389
+ e.preventDefault();
390
+ onFile(item.getAsFile());
391
+ return;
392
+ }
393
+ }
394
+ }
395
+
396
+ // fixes image data uri
397
+ // - convert to jpg if it's not jpg/png/gif
398
+ // - reduce quality and/or downscale if too big
399
+ async function fixImageDataUri(dataUri, maxLength = 1024 * 1024) {
400
+ const mimeMatch = dataUri.match(/^data:([^;,]+)/);
401
+ if (!mimeMatch)
402
+ throw new Error("bad image data uri");
403
+ const mimeType = mimeMatch[1].toLowerCase();
404
+ const supported = ["image/jpeg", "image/jpg", "image/png", "image/gif"];
405
+ if (supported.includes(mimeType))
406
+ if (dataUri.length <= maxLength)
407
+ return dataUri;
408
+ const lossless = ["image/png", "image/gif"];
409
+ const quality = lossless.includes(mimeType) ? 0.92 : 0.8;
410
+ function createScaledCanvas(img, scale) {
411
+ const canvas = document.createElement("canvas");
412
+ canvas.width = Math.floor(img.width * scale);
413
+ canvas.height = Math.floor(img.height * scale);
414
+ const ctx = canvas.getContext("2d");
415
+ ctx.imageSmoothingEnabled = true;
416
+ ctx.imageSmoothingQuality = "high";
417
+ ctx.fillStyle = "white"; // in case of transparency
418
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
419
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
420
+ return canvas;
421
+ }
422
+ return new Promise((resolve, reject) => {
423
+ const img = new Image();
424
+ img.onload = async () => {
425
+ let scale = 1.0;
426
+ let attempts = 0;
427
+ const maxAttempts = 5;
428
+ const initialCanvas = createScaledCanvas(img, scale);
429
+ let result = initialCanvas.toDataURL("image/jpeg", quality);
430
+ while (result.length > maxLength && attempts < maxAttempts) {
431
+ attempts++;
432
+ scale *= 0.7071;
433
+ const scaledCanvas = createScaledCanvas(img, scale);
434
+ result = scaledCanvas.toDataURL("image/jpeg", quality);
435
+ result.length = result.length;
436
+ }
437
+ if (result.length <= maxLength) {
438
+ resolve(result);
439
+ } else {
440
+ reject(new Error(`Could not reduce image to ${(maxLength/1024).toFixed(2)}kb after ${maxAttempts} attempts`));
441
+ }
442
+ };
443
+ img.onerror = () => {
444
+ reject(new Error('Failed to load image from data URI'));
445
+ };
446
+ img.src = dataUri;
447
+ });
448
+ }
449
+
450
+ async function onFile(file) {
451
+ const reader = new FileReader();
452
+ if (file.type.toLowerCase().startsWith('image/')) {
453
+ reader.onloadend = async function() {
454
+ const description = file.name;
455
+ const realDataUri = await fixImageDataUri(reader.result);
456
+ const fakeDataUri = 'data:,placeholder/' + generateId();
457
+ uploadedFiles.push([fakeDataUri, realDataUri]);
458
+ insertText(chatInput, `![${description}](${fakeDataUri})`);
459
+ };
460
+ reader.readAsDataURL(file);
461
+ } else if (file.type.toLowerCase().startsWith('text/')) {
462
+ reader.onloadend = function() {
463
+ const content = reader.result;
464
+ insertText(chatInput, `\`\`\`\n${content}\n\`\`\``);
465
+ };
466
+ reader.readAsText(file);
467
+ } else {
468
+ alert('Only image and text files are supported');
469
+ return;
470
+ }
471
+ }
472
+
473
+ function checkSurroundingNewlines(text, pos) {
474
+ const beforeCaret = text.slice(0, pos);
475
+ const afterCaret = text.slice(pos);
476
+ const precedingNewlines = beforeCaret.match(/\n*$/)[0].length;
477
+ const followingNewlines = afterCaret.match(/^\n*/)[0].length;
478
+ return { precedingNewlines, followingNewlines };
479
+ }
480
+
481
+ function insertText(elem, text) {
482
+ const pos = elem.selectionStart;
483
+ const isCodeBlock = text.includes('```');
484
+
485
+ if (isCodeBlock) {
486
+ const { precedingNewlines, followingNewlines } = checkSurroundingNewlines(elem.value, pos);
487
+ const needsLeadingNewlines = pos > 0 && precedingNewlines < 2 ? '\n'.repeat(2 - precedingNewlines) : '';
488
+ const needsTrailingNewlines = pos < elem.value.length && followingNewlines < 2 ? '\n'.repeat(2 - followingNewlines) : '';
489
+ text = needsLeadingNewlines + text + needsTrailingNewlines;
490
+ }
491
+
492
+ elem.value = elem.value.slice(0, pos) + text + elem.value.slice(pos);
493
+ const newPos = pos + text.length;
494
+ elem.setSelectionRange(newPos, newPos);
495
+ elem.focus();
496
+ elem.dispatchEvent(new Event("input"));
497
+ }
498
+
499
+ function onKeyDown(e) {
500
+ if (e.key === "Enter" && !e.shiftKey) {
501
+ e.preventDefault();
502
+ sendMessage();
503
+ }
504
+ }
505
+
506
+ async function fetchFlagz() {
507
+ try {
508
+ const response = await fetch("/flagz");
509
+ return await response.json();
510
+ } catch (error) {
511
+ console.error("Could not fetch /flagz so using defaults", error);
512
+ return DEFAULT_FLAGZ;
513
+ }
514
+ }
515
+
516
+ function getSystemPrompt() {
517
+ let defaultPrompt = flagz.prompt;
518
+ if (!defaultPrompt)
519
+ defaultPrompt = DEFAULT_SYSTEM_PROMPT;
520
+ let promptsText = localStorage.getItem("v1.prompts");
521
+ if (!promptsText)
522
+ return defaultPrompt;
523
+ let prompts = JSON.parse(promptsText);
524
+ let prompt = prompts[defaultPrompt];
525
+ if (!prompt)
526
+ return defaultPrompt;
527
+ return prompt;
528
+ }
529
+
530
+ function updateModelInfo() {
531
+ if (flagz.model) {
532
+ const modelName = flagz.model;
533
+ document.title = `${modelName} - llamafile`;
534
+ document.getElementById("model").textContent = modelName;
535
+ document.getElementById("model-completions").textContent = modelName;
536
+ }
537
+ if (!flagz.nologo) {
538
+ document.querySelectorAll(".logo").forEach(logo => logo.style.display = "inline-block");
539
+ }
540
+ }
541
+
542
+ function startChat(history) {
543
+ chatHistory = history;
544
+ chatMessages.innerHTML = "";
545
+ for (let i = 0; i < chatHistory.length; i++) {
546
+ if (flagz.no_display_prompt && chatHistory[i].role == "system")
547
+ continue;
548
+ chatMessages.appendChild(wrapMessageElement(createMessageElement(chatHistory[i].content),
549
+ chatHistory[i].role));
550
+ }
551
+ scrollToBottom();
552
+ }
553
+
554
+ function loadSettings() {
555
+ const stored = localStorage.getItem('v1.modelSettings');
556
+ if (stored) {
557
+ return JSON.parse(stored);
558
+ }
559
+ return {
560
+ temperature: flagz.temperature,
561
+ top_p: flagz.top_p,
562
+ presence_penalty: flagz.presence_penalty,
563
+ frequency_penalty: flagz.frequency_penalty,
564
+ };
565
+ }
566
+
567
+ function saveSettings(settings) {
568
+ localStorage.setItem('v1.modelSettings', JSON.stringify(settings));
569
+ }
570
+
571
+ function formatDoubleWithPlus(x) {
572
+ return (x >= 0 ? "+" : "") + x.toFixed(2);
573
+ }
574
+
575
+ function updateSettingsDisplay(settings) {
576
+ document.getElementById("temp-value").textContent = settings.temperature ? settings.temperature.toFixed(2) : "0.00 (deterministic)";
577
+ document.getElementById("top-p-value").textContent = settings.top_p.toFixed(2);
578
+ document.getElementById("presence-value").textContent = formatDoubleWithPlus(settings.presence_penalty);
579
+ document.getElementById("frequency-value").textContent = formatDoubleWithPlus(settings.frequency_penalty);
580
+ document.getElementById("temperature").value = settings.temperature;
581
+ document.getElementById("top-p").value = settings.top_p;
582
+ document.getElementById("presence-penalty").value = settings.presence_penalty;
583
+ document.getElementById("frequency-penalty").value = settings.frequency_penalty;
584
+
585
+ // Handle top-p disabling - using a more reliable selector
586
+ const topPSettingItem = document.querySelector('.setting-item:has(#top-p)');
587
+ if (settings.temperature === 0) {
588
+ topPSettingItem.classList.add('disabled');
589
+ } else {
590
+ topPSettingItem.classList.remove('disabled');
591
+ }
592
+
593
+ // Update top-p description with percentage
594
+ const topPDescription = topPSettingItem.querySelector('.setting-description');
595
+ if (settings.top_p >= 1) {
596
+ topPDescription.textContent = "Disabled. All tokens will be considered by the sampler.";
597
+ } else if (settings.top_p > .5) {
598
+ const percentage = Math.round((1 - settings.top_p) * 100);
599
+ topPDescription.textContent = `The bottom ${percentage}% tokens will be ignored by the sampler.`;
600
+ } else {
601
+ const percentage = Math.round(settings.top_p * 100);
602
+ topPDescription.textContent = `Only the top ${percentage}% tokens will be considered by the sampler.`;
603
+ }
604
+ }
605
+
606
+ function setupSettings() {
607
+ settingsButton.addEventListener("click", () => {
608
+ settingsModal.style.display = "flex";
609
+ updateSettingsDisplay(loadSettings());
610
+ });
611
+ closeSettings.addEventListener("click", () => {
612
+ settingsModal.style.display = "none";
613
+ });
614
+ ["temperature", "top-p", "presence-penalty", "frequency-penalty"].forEach(id => {
615
+ const element = document.getElementById(id);
616
+ element.addEventListener("input", (e) => {
617
+ const settings = loadSettings();
618
+ const value = parseFloat(e.target.value);
619
+ const key = id.replace(/-/g, '_');
620
+ settings[key] = value;
621
+ saveSettings(settings);
622
+ updateSettingsDisplay(settings);
623
+ });
624
+ });
625
+ settingsModal.addEventListener("mousedown", (e) => {
626
+ if (e.target === settingsModal) {
627
+ settingsModal.style.display = "none";
628
+ }
629
+ });
630
+ document.addEventListener("keydown", (e) => {
631
+ if (e.key === "Escape") {
632
+ settingsModal.style.display = "none";
633
+ }
634
+ });
635
+ }
636
+
637
+ function setupCompletions() {
638
+ completeButton.addEventListener("click", sendCompletion);
639
+ completionsStopButton.addEventListener("click", stopCompletion);
640
+ completionsSettingsButton.addEventListener("click", () => {
641
+ settingsModal.style.display = "flex";
642
+ updateSettingsDisplay(loadSettings());
643
+ });
644
+ completionsInput.addEventListener("keydown", (e) => {
645
+ if (e.key === "Enter" && !e.shiftKey && (e.ctrlKey || e.metaKey)) {
646
+ e.preventDefault();
647
+ sendCompletion();
648
+ }
649
+ });
650
+ }
651
+
652
+ function stopCompletion() {
653
+ if (abortController) {
654
+ abortController.abort();
655
+ cleanupAfterCompletion();
656
+ }
657
+ }
658
+
659
+ function cleanupAfterCompletion() {
660
+ completeButton.style.display = "inline-block";
661
+ completionsStopButton.style.display = "none";
662
+ completeButton.disabled = false;
663
+ abortController = null;
664
+
665
+ // select newly added text and restore focus
666
+ const textArea = completionsInput;
667
+ textArea.focus();
668
+ textArea.selectionStart = originalLength || 0;
669
+ textArea.selectionEnd = textArea.value.length;
670
+ }
671
+
672
+ async function sendCompletion() {
673
+ const text = completionsInput.value;
674
+ completeButton.style.display = "none";
675
+ completionsStopButton.style.display = "inline-block";
676
+ completeButton.disabled = true;
677
+ abortController = new AbortController();
678
+ originalLength = text.length;
679
+ completionsStopButton.focus();
680
+ const settings = loadSettings();
681
+ try {
682
+ const response = await fetch("/v1/completions", {
683
+ method: "POST",
684
+ headers: {
685
+ "Content-Type": "application/json",
686
+ "Authorization": `Bearer ${API_KEY}`
687
+ },
688
+ body: JSON.stringify({
689
+ model: flagz.model || "gpt-3.5-turbo",
690
+ prompt: text,
691
+ temperature: settings.temperature,
692
+ top_p: settings.top_p,
693
+ presence_penalty: settings.presence_penalty,
694
+ frequency_penalty: settings.frequency_penalty,
695
+ stream: true
696
+ }),
697
+ signal: abortController.signal
698
+ });
699
+ if (response.ok) {
700
+ const reader = response.body.getReader();
701
+ const decoder = new TextDecoder();
702
+ let buffer = "";
703
+ try {
704
+ while (true) {
705
+ const { done, value } = await reader.read();
706
+ if (done)
707
+ break;
708
+ buffer += decoder.decode(value, { stream: true });
709
+ const lines = buffer.split("\n");
710
+ for (let i = 0; i < lines.length - 1; i++) {
711
+ const line = lines[i].trim();
712
+ if (line.startsWith("data: ")) {
713
+ const data = line.slice(6);
714
+ if (data === "[DONE]")
715
+ continue;
716
+ try {
717
+ const parsed = JSON.parse(data);
718
+ const content = parsed.choices[0]?.text || "";
719
+ completionsInput.value += content;
720
+ completionsInput.scrollTop = completionsInput.scrollHeight;
721
+ } catch (e) {
722
+ console.error("Error parsing JSON:", e);
723
+ }
724
+ }
725
+ }
726
+ buffer = lines[lines.length - 1];
727
+ }
728
+ } catch (error) {
729
+ if (error.name !== "AbortError") {
730
+ console.error("Error reading stream:", error);
731
+ }
732
+ }
733
+ } else {
734
+ console.error("Completion failed:", response.statusText);
735
+ }
736
+ } catch (error) {
737
+ if (error.name !== "AbortError") {
738
+ console.error("Completion error:", error);
739
+ }
740
+ } finally {
741
+ cleanupAfterCompletion();
742
+ }
743
+ }
744
+
745
+ function removeLastDirectChild(element) {
746
+ if (element.lastElementChild) {
747
+ element.removeChild(element.lastElementChild);
748
+ }
749
+ }
750
+
751
+ function onRedo() {
752
+ if (!chatHistory.length)
753
+ return;
754
+ removeLastDirectChild(chatMessages);
755
+ let msg = chatHistory.pop();
756
+ if (msg.role === "assistant") {
757
+ removeLastDirectChild(chatMessages);
758
+ msg = chatHistory.pop();
759
+ }
760
+ chatInput.value = msg.content;
761
+ chatInput.focus();
762
+ chatInput.dispatchEvent(new Event("input")); // adjust textarea height
763
+ }
764
+
765
+ function setupMenu() {
766
+ const triggers = document.querySelectorAll('.menu-trigger');
767
+ const menus = document.querySelectorAll('.menu');
768
+ const chatModeSwitch = document.getElementById('chat-mode-switch');
769
+ const completionsModeSwitch = document.getElementById('completions-mode-switch');
770
+ if (flagz.is_base_model) {
771
+ completionsModeSwitch.classList.add('disabled');
772
+ 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.";
773
+ completionsModeSwitch.disabled = true;
774
+ }
775
+ triggers.forEach(trigger => {
776
+ trigger.addEventListener('click', (e) => {
777
+ e.stopPropagation();
778
+ const menu = trigger.nextElementSibling;
779
+ menus.forEach(m => {
780
+ if (m !== menu)
781
+ m.classList.remove('show');
782
+ });
783
+ menu.classList.toggle('show');
784
+ });
785
+ });
786
+ document.addEventListener('click', () => {
787
+ menus.forEach(menu => menu.classList.remove('show'));
788
+ });
789
+ document.addEventListener('keydown', (e) => {
790
+ if (e.key === 'Escape')
791
+ menus.forEach(menu => menu.classList.remove('show'));
792
+ });
793
+ chatModeSwitch.addEventListener('click', () => {
794
+ flagz.completion_mode = true;
795
+ setupCompletionsMode();
796
+ menus.forEach(menu => menu.classList.remove('show'));
797
+ });
798
+ completionsModeSwitch.addEventListener('click', () => {
799
+ if (!flagz.is_base_model) {
800
+ flagz.completion_mode = false;
801
+ setupChatCompletionsMode();
802
+ menus.forEach(menu => menu.classList.remove('show'));
803
+ }
804
+ });
805
+ }
806
+
807
+ function setupChatCompletionsMode() {
808
+ chatInterface.style.display = "flex";
809
+ completionsInterface.style.display = "none";
810
+ startChat([{ role: "system", content: getSystemPrompt() }]);
811
+ chatInput.focus();
812
+ }
813
+
814
+ function setupCompletionsMode() {
815
+ chatInterface.style.display = "none";
816
+ completionsInterface.style.display = "flex";
817
+ completionsInput.focus();
818
+ }
819
+
820
+ function onUploadButtonClick() {
821
+ fileUpload.click();
822
+ }
823
+
824
+ function onFileUploadChange(e) {
825
+ if (e.target.files[0]) {
826
+ onFile(e.target.files[0]);
827
+ e.target.value = '';
828
+ }
829
+ }
830
+
831
+ async function chatbot() {
832
+ flagz = await fetchFlagz();
833
+ updateModelInfo();
834
+ setupSettings();
835
+ setupCompletions();
836
+ setupMenu();
837
+ if (flagz.is_base_model || flagz.completion_mode) {
838
+ setupCompletionsMode();
839
+ } else {
840
+ setupChatCompletionsMode();
841
+ }
842
+ sendButton.addEventListener("click", sendMessage);
843
+ stopButton.addEventListener("click", stopMessage);
844
+ redoButton.addEventListener("click", onRedo);
845
+ chatInput.addEventListener("input", onChatInput);
846
+ chatInput.addEventListener("keydown", onKeyDown);
847
+ chatMessages.addEventListener("touchmove", onWheel);
848
+ document.addEventListener("wheel", onWheel);
849
+ document.addEventListener("dragenter", onDragBegin);
850
+ document.addEventListener("dragover", onDragBegin);
851
+ document.addEventListener("dragleave", onDragEnd);
852
+ document.addEventListener("drop", onDragEnd);
853
+ document.addEventListener("drop", onDrop);
854
+ document.addEventListener("paste", onPaste);
855
+ uploadButton.addEventListener("click", onUploadButtonClick);
856
+ fileUpload.addEventListener("change", onFileUploadChange);
857
+ //Prosty RAG
858
+ const q = new URLSearchParams(location.search).get('q');
859
+ if (q) {
860
+ insertText(chatInput, q);
861
+ sendMessage();
862
+ }
863
+ }
864
+
865
+ chatbot();