Chat.svelte 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411
  1. <script lang="ts">
  2. import { v4 as uuidv4 } from 'uuid';
  3. import { toast } from 'svelte-sonner';
  4. import mermaid from 'mermaid';
  5. import { PaneGroup, Pane, PaneResizer } from 'paneforge';
  6. import { getContext, onDestroy, onMount, tick } from 'svelte';
  7. const i18n: Writable<i18nType> = getContext('i18n');
  8. import { goto } from '$app/navigation';
  9. import { page } from '$app/stores';
  10. import { get, type Unsubscriber, type Writable } from 'svelte/store';
  11. import type { i18n as i18nType } from 'i18next';
  12. import { WEBUI_BASE_URL } from '$lib/constants';
  13. import {
  14. chatId,
  15. chats,
  16. config,
  17. type Model,
  18. models,
  19. tags as allTags,
  20. settings,
  21. showSidebar,
  22. WEBUI_NAME,
  23. banners,
  24. user,
  25. socket,
  26. showControls,
  27. showCallOverlay,
  28. currentChatPage,
  29. temporaryChatEnabled,
  30. mobile,
  31. showOverview,
  32. chatTitle,
  33. showArtifacts,
  34. tools,
  35. toolServers,
  36. selectedFolder
  37. } from '$lib/stores';
  38. import {
  39. convertMessagesToHistory,
  40. copyToClipboard,
  41. getMessageContentParts,
  42. createMessagesList,
  43. extractSentencesForAudio,
  44. promptTemplate,
  45. splitStream,
  46. sleep,
  47. removeDetails,
  48. getPromptVariables,
  49. processDetails,
  50. removeAllDetails
  51. } from '$lib/utils';
  52. import { generateChatCompletion } from '$lib/apis/ollama';
  53. import {
  54. createNewChat,
  55. getAllTags,
  56. getChatById,
  57. getChatList,
  58. getTagsById,
  59. updateChatById
  60. } from '$lib/apis/chats';
  61. import { generateOpenAIChatCompletion } from '$lib/apis/openai';
  62. import { processWeb, processWebSearch, processYoutubeVideo } from '$lib/apis/retrieval';
  63. import { createOpenAITextStream } from '$lib/apis/streaming';
  64. import { queryMemory } from '$lib/apis/memories';
  65. import { getAndUpdateUserLocation, getUserSettings } from '$lib/apis/users';
  66. import {
  67. chatCompleted,
  68. generateQueries,
  69. chatAction,
  70. generateMoACompletion,
  71. stopTask,
  72. getTaskIdsByChatId
  73. } from '$lib/apis';
  74. import { getTools } from '$lib/apis/tools';
  75. import Banner from '../common/Banner.svelte';
  76. import MessageInput from '$lib/components/chat/MessageInput.svelte';
  77. import Messages from '$lib/components/chat/Messages.svelte';
  78. import Navbar from '$lib/components/chat/Navbar.svelte';
  79. import ChatControls from './ChatControls.svelte';
  80. import EventConfirmDialog from '../common/ConfirmDialog.svelte';
  81. import Placeholder from './Placeholder.svelte';
  82. import NotificationToast from '../NotificationToast.svelte';
  83. import Spinner from '../common/Spinner.svelte';
  84. import { fade } from 'svelte/transition';
  85. import Tooltip from '../common/Tooltip.svelte';
  86. import Sidebar from '../icons/Sidebar.svelte';
  87. export let chatIdProp = '';
  88. let loading = true;
  89. const eventTarget = new EventTarget();
  90. let controlPane;
  91. let controlPaneComponent;
  92. let messageInput;
  93. let autoScroll = true;
  94. let processing = '';
  95. let messagesContainerElement: HTMLDivElement;
  96. let navbarElement;
  97. let showEventConfirmation = false;
  98. let eventConfirmationTitle = '';
  99. let eventConfirmationMessage = '';
  100. let eventConfirmationInput = false;
  101. let eventConfirmationInputPlaceholder = '';
  102. let eventConfirmationInputValue = '';
  103. let eventCallback = null;
  104. let chatIdUnsubscriber: Unsubscriber | undefined;
  105. let selectedModels = [''];
  106. let atSelectedModel: Model | undefined;
  107. let selectedModelIds = [];
  108. $: selectedModelIds = atSelectedModel !== undefined ? [atSelectedModel.id] : selectedModels;
  109. let selectedToolIds = [];
  110. let selectedFilterIds = [];
  111. let imageGenerationEnabled = false;
  112. let webSearchEnabled = false;
  113. let codeInterpreterEnabled = false;
  114. let showCommands = false;
  115. let generating = false;
  116. let generationController = null;
  117. let chat = null;
  118. let tags = [];
  119. let history = {
  120. messages: {},
  121. currentId: null
  122. };
  123. let taskIds = null;
  124. // Chat Input
  125. let prompt = '';
  126. let chatFiles = [];
  127. let files = [];
  128. let params = {};
  129. $: if (chatIdProp) {
  130. navigateHandler();
  131. }
  132. const navigateHandler = async () => {
  133. loading = true;
  134. prompt = '';
  135. messageInput?.setText('');
  136. files = [];
  137. selectedToolIds = [];
  138. selectedFilterIds = [];
  139. webSearchEnabled = false;
  140. imageGenerationEnabled = false;
  141. const storageChatInput = sessionStorage.getItem(
  142. `chat-input${chatIdProp ? `-${chatIdProp}` : ''}`
  143. );
  144. if (chatIdProp && (await loadChat())) {
  145. await tick();
  146. loading = false;
  147. window.setTimeout(() => scrollToBottom(), 0);
  148. await tick();
  149. if (storageChatInput) {
  150. try {
  151. const input = JSON.parse(storageChatInput);
  152. if (!$temporaryChatEnabled) {
  153. messageInput?.setText(input.prompt);
  154. files = input.files;
  155. selectedToolIds = input.selectedToolIds;
  156. selectedFilterIds = input.selectedFilterIds;
  157. webSearchEnabled = input.webSearchEnabled;
  158. imageGenerationEnabled = input.imageGenerationEnabled;
  159. codeInterpreterEnabled = input.codeInterpreterEnabled;
  160. }
  161. } catch (e) {}
  162. }
  163. const chatInput = document.getElementById('chat-input');
  164. chatInput?.focus();
  165. } else {
  166. await goto('/');
  167. }
  168. };
  169. const onSelect = async (e) => {
  170. const { type, data } = e;
  171. if (type === 'prompt') {
  172. // Handle prompt selection
  173. messageInput?.setText(data);
  174. }
  175. };
  176. $: if (selectedModels && chatIdProp !== '') {
  177. saveSessionSelectedModels();
  178. }
  179. const saveSessionSelectedModels = () => {
  180. if (selectedModels.length === 0 || (selectedModels.length === 1 && selectedModels[0] === '')) {
  181. return;
  182. }
  183. sessionStorage.selectedModels = JSON.stringify(selectedModels);
  184. console.log('saveSessionSelectedModels', selectedModels, sessionStorage.selectedModels);
  185. };
  186. let oldSelectedModelIds = [''];
  187. $: if (JSON.stringify(selectedModelIds) !== JSON.stringify(oldSelectedModelIds)) {
  188. onSelectedModelIdsChange();
  189. }
  190. const onSelectedModelIdsChange = () => {
  191. if (oldSelectedModelIds.filter((id) => id).length > 0) {
  192. resetInput();
  193. }
  194. oldSelectedModelIds = selectedModelIds;
  195. };
  196. const resetInput = () => {
  197. console.debug('resetInput');
  198. setToolIds();
  199. selectedFilterIds = [];
  200. webSearchEnabled = false;
  201. imageGenerationEnabled = false;
  202. codeInterpreterEnabled = false;
  203. };
  204. const setToolIds = async () => {
  205. if (!$tools) {
  206. tools.set(await getTools(localStorage.token));
  207. }
  208. if (selectedModels.length !== 1 && !atSelectedModel) {
  209. return;
  210. }
  211. const model = atSelectedModel ?? $models.find((m) => m.id === selectedModels[0]);
  212. if (model && model?.info?.meta?.toolIds) {
  213. selectedToolIds = [
  214. ...new Set(
  215. [...(model?.info?.meta?.toolIds ?? [])].filter((id) => $tools.find((t) => t.id === id))
  216. )
  217. ];
  218. } else {
  219. selectedToolIds = [];
  220. }
  221. };
  222. const showMessage = async (message) => {
  223. await tick();
  224. const _chatId = JSON.parse(JSON.stringify($chatId));
  225. let _messageId = JSON.parse(JSON.stringify(message.id));
  226. let messageChildrenIds = [];
  227. if (_messageId === null) {
  228. messageChildrenIds = Object.keys(history.messages).filter(
  229. (id) => history.messages[id].parentId === null
  230. );
  231. } else {
  232. messageChildrenIds = history.messages[_messageId].childrenIds;
  233. }
  234. while (messageChildrenIds.length !== 0) {
  235. _messageId = messageChildrenIds.at(-1);
  236. messageChildrenIds = history.messages[_messageId].childrenIds;
  237. }
  238. history.currentId = _messageId;
  239. await tick();
  240. await tick();
  241. await tick();
  242. if ($settings?.scrollOnBranchChange ?? true) {
  243. const messageElement = document.getElementById(`message-${message.id}`);
  244. if (messageElement) {
  245. messageElement.scrollIntoView({ behavior: 'smooth' });
  246. }
  247. }
  248. await tick();
  249. saveChatHandler(_chatId, history);
  250. };
  251. const chatEventHandler = async (event, cb) => {
  252. console.log(event);
  253. if (event.chat_id === $chatId) {
  254. await tick();
  255. let message = history.messages[event.message_id];
  256. if (message) {
  257. const type = event?.data?.type ?? null;
  258. const data = event?.data?.data ?? null;
  259. if (type === 'status') {
  260. if (message?.statusHistory) {
  261. message.statusHistory.push(data);
  262. } else {
  263. message.statusHistory = [data];
  264. }
  265. } else if (type === 'chat:completion') {
  266. chatCompletionEventHandler(data, message, event.chat_id);
  267. } else if (type === 'chat:message:delta' || type === 'message') {
  268. message.content += data.content;
  269. } else if (type === 'chat:message' || type === 'replace') {
  270. message.content = data.content;
  271. } else if (type === 'chat:message:files' || type === 'files') {
  272. message.files = data.files;
  273. } else if (type === 'chat:message:follow_ups') {
  274. message.followUps = data.follow_ups;
  275. if (autoScroll) {
  276. scrollToBottom('smooth');
  277. }
  278. } else if (type === 'chat:title') {
  279. chatTitle.set(data);
  280. currentChatPage.set(1);
  281. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  282. } else if (type === 'chat:tags') {
  283. chat = await getChatById(localStorage.token, $chatId);
  284. allTags.set(await getAllTags(localStorage.token));
  285. } else if (type === 'source' || type === 'citation') {
  286. if (data?.type === 'code_execution') {
  287. // Code execution; update existing code execution by ID, or add new one.
  288. if (!message?.code_executions) {
  289. message.code_executions = [];
  290. }
  291. const existingCodeExecutionIndex = message.code_executions.findIndex(
  292. (execution) => execution.id === data.id
  293. );
  294. if (existingCodeExecutionIndex !== -1) {
  295. message.code_executions[existingCodeExecutionIndex] = data;
  296. } else {
  297. message.code_executions.push(data);
  298. }
  299. message.code_executions = message.code_executions;
  300. } else {
  301. // Regular source.
  302. if (message?.sources) {
  303. message.sources.push(data);
  304. } else {
  305. message.sources = [data];
  306. }
  307. }
  308. } else if (type === 'notification') {
  309. const toastType = data?.type ?? 'info';
  310. const toastContent = data?.content ?? '';
  311. if (toastType === 'success') {
  312. toast.success(toastContent);
  313. } else if (toastType === 'error') {
  314. toast.error(toastContent);
  315. } else if (toastType === 'warning') {
  316. toast.warning(toastContent);
  317. } else {
  318. toast.info(toastContent);
  319. }
  320. } else if (type === 'confirmation') {
  321. eventCallback = cb;
  322. eventConfirmationInput = false;
  323. showEventConfirmation = true;
  324. eventConfirmationTitle = data.title;
  325. eventConfirmationMessage = data.message;
  326. } else if (type === 'execute') {
  327. eventCallback = cb;
  328. try {
  329. // Use Function constructor to evaluate code in a safer way
  330. const asyncFunction = new Function(`return (async () => { ${data.code} })()`);
  331. const result = await asyncFunction(); // Await the result of the async function
  332. if (cb) {
  333. cb(result);
  334. }
  335. } catch (error) {
  336. console.error('Error executing code:', error);
  337. }
  338. } else if (type === 'input') {
  339. eventCallback = cb;
  340. eventConfirmationInput = true;
  341. showEventConfirmation = true;
  342. eventConfirmationTitle = data.title;
  343. eventConfirmationMessage = data.message;
  344. eventConfirmationInputPlaceholder = data.placeholder;
  345. eventConfirmationInputValue = data?.value ?? '';
  346. } else {
  347. console.log('Unknown message type', data);
  348. }
  349. history.messages[event.message_id] = message;
  350. }
  351. }
  352. };
  353. const onMessageHandler = async (event: {
  354. origin: string;
  355. data: { type: string; text: string };
  356. }) => {
  357. if (event.origin !== window.origin) {
  358. return;
  359. }
  360. // Replace with your iframe's origin
  361. if (event.data.type === 'input:prompt') {
  362. console.debug(event.data.text);
  363. const inputElement = document.getElementById('chat-input');
  364. if (inputElement) {
  365. messageInput?.setText(event.data.text);
  366. inputElement.focus();
  367. }
  368. }
  369. if (event.data.type === 'action:submit') {
  370. console.debug(event.data.text);
  371. if (prompt !== '') {
  372. await tick();
  373. submitPrompt(prompt);
  374. }
  375. }
  376. if (event.data.type === 'input:prompt:submit') {
  377. console.debug(event.data.text);
  378. if (event.data.text !== '') {
  379. await tick();
  380. submitPrompt(event.data.text);
  381. }
  382. }
  383. };
  384. let pageSubscribe = null;
  385. onMount(async () => {
  386. loading = true;
  387. console.log('mounted');
  388. window.addEventListener('message', onMessageHandler);
  389. $socket?.on('chat-events', chatEventHandler);
  390. pageSubscribe = page.subscribe(async (p) => {
  391. if (p.url.pathname === '/') {
  392. await tick();
  393. initNewChat();
  394. }
  395. });
  396. const storageChatInput = sessionStorage.getItem(
  397. `chat-input${chatIdProp ? `-${chatIdProp}` : ''}`
  398. );
  399. if (!chatIdProp) {
  400. loading = false;
  401. await tick();
  402. }
  403. if (storageChatInput) {
  404. prompt = '';
  405. messageInput?.setText('');
  406. files = [];
  407. selectedToolIds = [];
  408. selectedFilterIds = [];
  409. webSearchEnabled = false;
  410. imageGenerationEnabled = false;
  411. codeInterpreterEnabled = false;
  412. try {
  413. const input = JSON.parse(storageChatInput);
  414. if (!$temporaryChatEnabled) {
  415. messageInput?.setText(input.prompt);
  416. files = input.files;
  417. selectedToolIds = input.selectedToolIds;
  418. selectedFilterIds = input.selectedFilterIds;
  419. webSearchEnabled = input.webSearchEnabled;
  420. imageGenerationEnabled = input.imageGenerationEnabled;
  421. codeInterpreterEnabled = input.codeInterpreterEnabled;
  422. }
  423. } catch (e) {}
  424. }
  425. showControls.subscribe(async (value) => {
  426. if (controlPane && !$mobile) {
  427. try {
  428. if (value) {
  429. controlPaneComponent.openPane();
  430. } else {
  431. controlPane.collapse();
  432. }
  433. } catch (e) {
  434. // ignore
  435. }
  436. }
  437. if (!value) {
  438. showCallOverlay.set(false);
  439. showOverview.set(false);
  440. showArtifacts.set(false);
  441. }
  442. });
  443. const chatInput = document.getElementById('chat-input');
  444. chatInput?.focus();
  445. chats.subscribe(() => {});
  446. });
  447. onDestroy(() => {
  448. pageSubscribe();
  449. chatIdUnsubscriber?.();
  450. window.removeEventListener('message', onMessageHandler);
  451. $socket?.off('chat-events', chatEventHandler);
  452. });
  453. // File upload functions
  454. const uploadGoogleDriveFile = async (fileData) => {
  455. console.log('Starting uploadGoogleDriveFile with:', {
  456. id: fileData.id,
  457. name: fileData.name,
  458. url: fileData.url,
  459. headers: {
  460. Authorization: `Bearer ${token}`
  461. }
  462. });
  463. // Validate input
  464. if (!fileData?.id || !fileData?.name || !fileData?.url || !fileData?.headers?.Authorization) {
  465. throw new Error('Invalid file data provided');
  466. }
  467. const tempItemId = uuidv4();
  468. const fileItem = {
  469. type: 'file',
  470. file: '',
  471. id: null,
  472. url: fileData.url,
  473. name: fileData.name,
  474. collection_name: '',
  475. status: 'uploading',
  476. error: '',
  477. itemId: tempItemId,
  478. size: 0
  479. };
  480. try {
  481. files = [...files, fileItem];
  482. console.log('Processing web file with URL:', fileData.url);
  483. // Configure fetch options with proper headers
  484. const fetchOptions = {
  485. headers: {
  486. Authorization: fileData.headers.Authorization,
  487. Accept: '*/*'
  488. },
  489. method: 'GET'
  490. };
  491. // Attempt to fetch the file
  492. console.log('Fetching file content from Google Drive...');
  493. const fileResponse = await fetch(fileData.url, fetchOptions);
  494. if (!fileResponse.ok) {
  495. const errorText = await fileResponse.text();
  496. throw new Error(`Failed to fetch file (${fileResponse.status}): ${errorText}`);
  497. }
  498. // Get content type from response
  499. const contentType = fileResponse.headers.get('content-type') || 'application/octet-stream';
  500. console.log('Response received with content-type:', contentType);
  501. // Convert response to blob
  502. console.log('Converting response to blob...');
  503. const fileBlob = await fileResponse.blob();
  504. if (fileBlob.size === 0) {
  505. throw new Error('Retrieved file is empty');
  506. }
  507. console.log('Blob created:', {
  508. size: fileBlob.size,
  509. type: fileBlob.type || contentType
  510. });
  511. // Create File object with proper MIME type
  512. const file = new File([fileBlob], fileData.name, {
  513. type: fileBlob.type || contentType
  514. });
  515. console.log('File object created:', {
  516. name: file.name,
  517. size: file.size,
  518. type: file.type
  519. });
  520. if (file.size === 0) {
  521. throw new Error('Created file is empty');
  522. }
  523. // If the file is an audio file, provide the language for STT.
  524. let metadata = null;
  525. if (
  526. (file.type.startsWith('audio/') || file.type.startsWith('video/')) &&
  527. $settings?.audio?.stt?.language
  528. ) {
  529. metadata = {
  530. language: $settings?.audio?.stt?.language
  531. };
  532. }
  533. // Upload file to server
  534. console.log('Uploading file to server...');
  535. const uploadedFile = await uploadFile(localStorage.token, file, metadata);
  536. if (!uploadedFile) {
  537. throw new Error('Server returned null response for file upload');
  538. }
  539. console.log('File uploaded successfully:', uploadedFile);
  540. // Update file item with upload results
  541. fileItem.status = 'uploaded';
  542. fileItem.file = uploadedFile;
  543. fileItem.id = uploadedFile.id;
  544. fileItem.size = file.size;
  545. fileItem.collection_name = uploadedFile?.meta?.collection_name;
  546. fileItem.url = `${WEBUI_API_BASE_URL}/files/${uploadedFile.id}`;
  547. files = files;
  548. toast.success($i18n.t('File uploaded successfully'));
  549. } catch (e) {
  550. console.error('Error uploading file:', e);
  551. files = files.filter((f) => f.itemId !== tempItemId);
  552. toast.error(
  553. $i18n.t('Error uploading file: {{error}}', {
  554. error: e.message || 'Unknown error'
  555. })
  556. );
  557. }
  558. };
  559. const uploadWeb = async (url) => {
  560. console.log(url);
  561. const fileItem = {
  562. type: 'doc',
  563. name: url,
  564. collection_name: '',
  565. status: 'uploading',
  566. url: url,
  567. error: ''
  568. };
  569. try {
  570. files = [...files, fileItem];
  571. const res = await processWeb(localStorage.token, '', url);
  572. if (res) {
  573. fileItem.status = 'uploaded';
  574. fileItem.collection_name = res.collection_name;
  575. fileItem.file = {
  576. ...res.file,
  577. ...fileItem.file
  578. };
  579. files = files;
  580. }
  581. } catch (e) {
  582. // Remove the failed doc from the files array
  583. files = files.filter((f) => f.name !== url);
  584. toast.error(JSON.stringify(e));
  585. }
  586. };
  587. const uploadYoutubeTranscription = async (url) => {
  588. console.log(url);
  589. const fileItem = {
  590. type: 'doc',
  591. name: url,
  592. collection_name: '',
  593. status: 'uploading',
  594. context: 'full',
  595. url: url,
  596. error: ''
  597. };
  598. try {
  599. files = [...files, fileItem];
  600. const res = await processYoutubeVideo(localStorage.token, url);
  601. if (res) {
  602. fileItem.status = 'uploaded';
  603. fileItem.collection_name = res.collection_name;
  604. fileItem.file = {
  605. ...res.file,
  606. ...fileItem.file
  607. };
  608. files = files;
  609. }
  610. } catch (e) {
  611. // Remove the failed doc from the files array
  612. files = files.filter((f) => f.name !== url);
  613. toast.error(`${e}`);
  614. }
  615. };
  616. //////////////////////////
  617. // Web functions
  618. //////////////////////////
  619. const initNewChat = async () => {
  620. if ($user?.role !== 'admin' && $user?.permissions?.chat?.temporary_enforced) {
  621. await temporaryChatEnabled.set(true);
  622. }
  623. const availableModels = $models
  624. .filter((m) => !(m?.info?.meta?.hidden ?? false))
  625. .map((m) => m.id);
  626. if ($page.url.searchParams.get('models') || $page.url.searchParams.get('model')) {
  627. const urlModels = (
  628. $page.url.searchParams.get('models') ||
  629. $page.url.searchParams.get('model') ||
  630. ''
  631. )?.split(',');
  632. if (urlModels.length === 1) {
  633. const m = $models.find((m) => m.id === urlModels[0]);
  634. if (!m) {
  635. const modelSelectorButton = document.getElementById('model-selector-0-button');
  636. if (modelSelectorButton) {
  637. modelSelectorButton.click();
  638. await tick();
  639. const modelSelectorInput = document.getElementById('model-search-input');
  640. if (modelSelectorInput) {
  641. modelSelectorInput.focus();
  642. modelSelectorInput.value = urlModels[0];
  643. modelSelectorInput.dispatchEvent(new Event('input'));
  644. }
  645. }
  646. } else {
  647. selectedModels = urlModels;
  648. }
  649. } else {
  650. selectedModels = urlModels;
  651. }
  652. selectedModels = selectedModels.filter((modelId) =>
  653. $models.map((m) => m.id).includes(modelId)
  654. );
  655. } else {
  656. if (sessionStorage.selectedModels) {
  657. selectedModels = JSON.parse(sessionStorage.selectedModels);
  658. sessionStorage.removeItem('selectedModels');
  659. } else {
  660. if ($settings?.models) {
  661. selectedModels = $settings?.models;
  662. } else if ($config?.default_models) {
  663. console.log($config?.default_models.split(',') ?? '');
  664. selectedModels = $config?.default_models.split(',');
  665. }
  666. }
  667. selectedModels = selectedModels.filter((modelId) => availableModels.includes(modelId));
  668. }
  669. if (selectedModels.length === 0 || (selectedModels.length === 1 && selectedModels[0] === '')) {
  670. if (availableModels.length > 0) {
  671. selectedModels = [availableModels?.at(0) ?? ''];
  672. } else {
  673. selectedModels = [''];
  674. }
  675. }
  676. await showControls.set(false);
  677. await showCallOverlay.set(false);
  678. await showOverview.set(false);
  679. await showArtifacts.set(false);
  680. if ($page.url.pathname.includes('/c/')) {
  681. window.history.replaceState(history.state, '', `/`);
  682. }
  683. autoScroll = true;
  684. resetInput();
  685. await chatId.set('');
  686. await chatTitle.set('');
  687. history = {
  688. messages: {},
  689. currentId: null
  690. };
  691. chatFiles = [];
  692. params = {};
  693. if ($page.url.searchParams.get('youtube')) {
  694. uploadYoutubeTranscription(
  695. `https://www.youtube.com/watch?v=${$page.url.searchParams.get('youtube')}`
  696. );
  697. }
  698. if ($page.url.searchParams.get('load-url')) {
  699. await uploadWeb($page.url.searchParams.get('load-url'));
  700. }
  701. if ($page.url.searchParams.get('web-search') === 'true') {
  702. webSearchEnabled = true;
  703. }
  704. if ($page.url.searchParams.get('image-generation') === 'true') {
  705. imageGenerationEnabled = true;
  706. }
  707. if ($page.url.searchParams.get('code-interpreter') === 'true') {
  708. codeInterpreterEnabled = true;
  709. }
  710. if ($page.url.searchParams.get('tools')) {
  711. selectedToolIds = $page.url.searchParams
  712. .get('tools')
  713. ?.split(',')
  714. .map((id) => id.trim())
  715. .filter((id) => id);
  716. } else if ($page.url.searchParams.get('tool-ids')) {
  717. selectedToolIds = $page.url.searchParams
  718. .get('tool-ids')
  719. ?.split(',')
  720. .map((id) => id.trim())
  721. .filter((id) => id);
  722. }
  723. if ($page.url.searchParams.get('call') === 'true') {
  724. showCallOverlay.set(true);
  725. showControls.set(true);
  726. }
  727. if ($page.url.searchParams.get('q')) {
  728. const q = $page.url.searchParams.get('q') ?? '';
  729. messageInput?.setText(q);
  730. if (q) {
  731. if (($page.url.searchParams.get('submit') ?? 'true') === 'true') {
  732. await tick();
  733. submitPrompt(q);
  734. }
  735. }
  736. }
  737. selectedModels = selectedModels.map((modelId) =>
  738. $models.map((m) => m.id).includes(modelId) ? modelId : ''
  739. );
  740. const userSettings = await getUserSettings(localStorage.token);
  741. if (userSettings) {
  742. settings.set(userSettings.ui);
  743. } else {
  744. settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
  745. }
  746. const chatInput = document.getElementById('chat-input');
  747. setTimeout(() => chatInput?.focus(), 0);
  748. };
  749. const loadChat = async () => {
  750. chatId.set(chatIdProp);
  751. if ($temporaryChatEnabled) {
  752. temporaryChatEnabled.set(false);
  753. }
  754. chat = await getChatById(localStorage.token, $chatId).catch(async (error) => {
  755. await goto('/');
  756. return null;
  757. });
  758. if (chat) {
  759. tags = await getTagsById(localStorage.token, $chatId).catch(async (error) => {
  760. return [];
  761. });
  762. const chatContent = chat.chat;
  763. if (chatContent) {
  764. console.log(chatContent);
  765. selectedModels =
  766. (chatContent?.models ?? undefined) !== undefined
  767. ? chatContent.models
  768. : [chatContent.models ?? ''];
  769. if (!($user?.role === 'admin' || ($user?.permissions?.chat?.multiple_models ?? true))) {
  770. selectedModels = selectedModels.length > 0 ? [selectedModels[0]] : [''];
  771. }
  772. oldSelectedModelIds = selectedModels;
  773. history =
  774. (chatContent?.history ?? undefined) !== undefined
  775. ? chatContent.history
  776. : convertMessagesToHistory(chatContent.messages);
  777. chatTitle.set(chatContent.title);
  778. const userSettings = await getUserSettings(localStorage.token);
  779. if (userSettings) {
  780. await settings.set(userSettings.ui);
  781. } else {
  782. await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
  783. }
  784. params = chatContent?.params ?? {};
  785. chatFiles = chatContent?.files ?? [];
  786. autoScroll = true;
  787. await tick();
  788. if (history.currentId) {
  789. for (const message of Object.values(history.messages)) {
  790. if (message.role === 'assistant') {
  791. message.done = true;
  792. }
  793. }
  794. }
  795. const taskRes = await getTaskIdsByChatId(localStorage.token, $chatId).catch((error) => {
  796. return null;
  797. });
  798. if (taskRes) {
  799. taskIds = taskRes.task_ids;
  800. }
  801. await tick();
  802. return true;
  803. } else {
  804. return null;
  805. }
  806. }
  807. };
  808. const scrollToBottom = async (behavior = 'auto') => {
  809. await tick();
  810. if (messagesContainerElement) {
  811. messagesContainerElement.scrollTo({
  812. top: messagesContainerElement.scrollHeight,
  813. behavior
  814. });
  815. }
  816. };
  817. const chatCompletedHandler = async (chatId, modelId, responseMessageId, messages) => {
  818. const res = await chatCompleted(localStorage.token, {
  819. model: modelId,
  820. messages: messages.map((m) => ({
  821. id: m.id,
  822. role: m.role,
  823. content: m.content,
  824. info: m.info ? m.info : undefined,
  825. timestamp: m.timestamp,
  826. ...(m.usage ? { usage: m.usage } : {}),
  827. ...(m.sources ? { sources: m.sources } : {})
  828. })),
  829. filter_ids: selectedFilterIds.length > 0 ? selectedFilterIds : undefined,
  830. model_item: $models.find((m) => m.id === modelId),
  831. chat_id: chatId,
  832. session_id: $socket?.id,
  833. id: responseMessageId
  834. }).catch((error) => {
  835. toast.error(`${error}`);
  836. messages.at(-1).error = { content: error };
  837. return null;
  838. });
  839. if (res !== null && res.messages) {
  840. // Update chat history with the new messages
  841. for (const message of res.messages) {
  842. if (message?.id) {
  843. // Add null check for message and message.id
  844. history.messages[message.id] = {
  845. ...history.messages[message.id],
  846. ...(history.messages[message.id].content !== message.content
  847. ? { originalContent: history.messages[message.id].content }
  848. : {}),
  849. ...message
  850. };
  851. }
  852. }
  853. }
  854. await tick();
  855. if ($chatId == chatId) {
  856. if (!$temporaryChatEnabled) {
  857. chat = await updateChatById(localStorage.token, chatId, {
  858. models: selectedModels,
  859. messages: messages,
  860. history: history,
  861. params: params,
  862. files: chatFiles
  863. });
  864. currentChatPage.set(1);
  865. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  866. }
  867. }
  868. taskIds = null;
  869. };
  870. const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => {
  871. const messages = createMessagesList(history, responseMessageId);
  872. const res = await chatAction(localStorage.token, actionId, {
  873. model: modelId,
  874. messages: messages.map((m) => ({
  875. id: m.id,
  876. role: m.role,
  877. content: m.content,
  878. info: m.info ? m.info : undefined,
  879. timestamp: m.timestamp,
  880. ...(m.sources ? { sources: m.sources } : {})
  881. })),
  882. ...(event ? { event: event } : {}),
  883. model_item: $models.find((m) => m.id === modelId),
  884. chat_id: chatId,
  885. session_id: $socket?.id,
  886. id: responseMessageId
  887. }).catch((error) => {
  888. toast.error(`${error}`);
  889. messages.at(-1).error = { content: error };
  890. return null;
  891. });
  892. if (res !== null && res.messages) {
  893. // Update chat history with the new messages
  894. for (const message of res.messages) {
  895. history.messages[message.id] = {
  896. ...history.messages[message.id],
  897. ...(history.messages[message.id].content !== message.content
  898. ? { originalContent: history.messages[message.id].content }
  899. : {}),
  900. ...message
  901. };
  902. }
  903. }
  904. if ($chatId == chatId) {
  905. if (!$temporaryChatEnabled) {
  906. chat = await updateChatById(localStorage.token, chatId, {
  907. models: selectedModels,
  908. messages: messages,
  909. history: history,
  910. params: params,
  911. files: chatFiles
  912. });
  913. currentChatPage.set(1);
  914. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  915. }
  916. }
  917. };
  918. const getChatEventEmitter = async (modelId: string, chatId: string = '') => {
  919. return setInterval(() => {
  920. $socket?.emit('usage', {
  921. action: 'chat',
  922. model: modelId,
  923. chat_id: chatId
  924. });
  925. }, 1000);
  926. };
  927. const createMessagePair = async (userPrompt) => {
  928. messageInput?.setText('');
  929. if (selectedModels.length === 0) {
  930. toast.error($i18n.t('Model not selected'));
  931. } else {
  932. const modelId = selectedModels[0];
  933. const model = $models.filter((m) => m.id === modelId).at(0);
  934. const messages = createMessagesList(history, history.currentId);
  935. const parentMessage = messages.length !== 0 ? messages.at(-1) : null;
  936. const userMessageId = uuidv4();
  937. const responseMessageId = uuidv4();
  938. const userMessage = {
  939. id: userMessageId,
  940. parentId: parentMessage ? parentMessage.id : null,
  941. childrenIds: [responseMessageId],
  942. role: 'user',
  943. content: userPrompt ? userPrompt : `[PROMPT] ${userMessageId}`,
  944. timestamp: Math.floor(Date.now() / 1000)
  945. };
  946. const responseMessage = {
  947. id: responseMessageId,
  948. parentId: userMessageId,
  949. childrenIds: [],
  950. role: 'assistant',
  951. content: `[RESPONSE] ${responseMessageId}`,
  952. done: true,
  953. model: modelId,
  954. modelName: model.name ?? model.id,
  955. modelIdx: 0,
  956. timestamp: Math.floor(Date.now() / 1000)
  957. };
  958. if (parentMessage) {
  959. parentMessage.childrenIds.push(userMessageId);
  960. history.messages[parentMessage.id] = parentMessage;
  961. }
  962. history.messages[userMessageId] = userMessage;
  963. history.messages[responseMessageId] = responseMessage;
  964. history.currentId = responseMessageId;
  965. await tick();
  966. if (autoScroll) {
  967. scrollToBottom();
  968. }
  969. if (messages.length === 0) {
  970. await initChatHandler(history);
  971. } else {
  972. await saveChatHandler($chatId, history);
  973. }
  974. }
  975. };
  976. const addMessages = async ({ modelId, parentId, messages }) => {
  977. const model = $models.filter((m) => m.id === modelId).at(0);
  978. let parentMessage = history.messages[parentId];
  979. let currentParentId = parentMessage ? parentMessage.id : null;
  980. for (const message of messages) {
  981. let messageId = uuidv4();
  982. if (message.role === 'user') {
  983. const userMessage = {
  984. id: messageId,
  985. parentId: currentParentId,
  986. childrenIds: [],
  987. timestamp: Math.floor(Date.now() / 1000),
  988. ...message
  989. };
  990. if (parentMessage) {
  991. parentMessage.childrenIds.push(messageId);
  992. history.messages[parentMessage.id] = parentMessage;
  993. }
  994. history.messages[messageId] = userMessage;
  995. parentMessage = userMessage;
  996. currentParentId = messageId;
  997. } else {
  998. const responseMessage = {
  999. id: messageId,
  1000. parentId: currentParentId,
  1001. childrenIds: [],
  1002. done: true,
  1003. model: model.id,
  1004. modelName: model.name ?? model.id,
  1005. modelIdx: 0,
  1006. timestamp: Math.floor(Date.now() / 1000),
  1007. ...message
  1008. };
  1009. if (parentMessage) {
  1010. parentMessage.childrenIds.push(messageId);
  1011. history.messages[parentMessage.id] = parentMessage;
  1012. }
  1013. history.messages[messageId] = responseMessage;
  1014. parentMessage = responseMessage;
  1015. currentParentId = messageId;
  1016. }
  1017. }
  1018. history.currentId = currentParentId;
  1019. await tick();
  1020. if (autoScroll) {
  1021. scrollToBottom();
  1022. }
  1023. if (messages.length === 0) {
  1024. await initChatHandler(history);
  1025. } else {
  1026. await saveChatHandler($chatId, history);
  1027. }
  1028. };
  1029. const chatCompletionEventHandler = async (data, message, chatId) => {
  1030. const { id, done, choices, content, sources, selected_model_id, error, usage } = data;
  1031. if (error) {
  1032. await handleOpenAIError(error, message);
  1033. }
  1034. if (sources && !message?.sources) {
  1035. message.sources = sources;
  1036. }
  1037. if (choices) {
  1038. if (choices[0]?.message?.content) {
  1039. // Non-stream response
  1040. message.content += choices[0]?.message?.content;
  1041. } else {
  1042. // Stream response
  1043. let value = choices[0]?.delta?.content ?? '';
  1044. if (message.content == '' && value == '\n') {
  1045. console.log('Empty response');
  1046. } else {
  1047. message.content += value;
  1048. if (navigator.vibrate && ($settings?.hapticFeedback ?? false)) {
  1049. navigator.vibrate(5);
  1050. }
  1051. // Emit chat event for TTS
  1052. const messageContentParts = getMessageContentParts(
  1053. removeAllDetails(message.content),
  1054. $config?.audio?.tts?.split_on ?? 'punctuation'
  1055. );
  1056. messageContentParts.pop();
  1057. // dispatch only last sentence and make sure it hasn't been dispatched before
  1058. if (
  1059. messageContentParts.length > 0 &&
  1060. messageContentParts[messageContentParts.length - 1] !== message.lastSentence
  1061. ) {
  1062. message.lastSentence = messageContentParts[messageContentParts.length - 1];
  1063. eventTarget.dispatchEvent(
  1064. new CustomEvent('chat', {
  1065. detail: {
  1066. id: message.id,
  1067. content: messageContentParts[messageContentParts.length - 1]
  1068. }
  1069. })
  1070. );
  1071. }
  1072. }
  1073. }
  1074. }
  1075. if (content) {
  1076. // REALTIME_CHAT_SAVE is disabled
  1077. message.content = content;
  1078. if (navigator.vibrate && ($settings?.hapticFeedback ?? false)) {
  1079. navigator.vibrate(5);
  1080. }
  1081. // Emit chat event for TTS
  1082. const messageContentParts = getMessageContentParts(
  1083. removeAllDetails(message.content),
  1084. $config?.audio?.tts?.split_on ?? 'punctuation'
  1085. );
  1086. messageContentParts.pop();
  1087. // dispatch only last sentence and make sure it hasn't been dispatched before
  1088. if (
  1089. messageContentParts.length > 0 &&
  1090. messageContentParts[messageContentParts.length - 1] !== message.lastSentence
  1091. ) {
  1092. message.lastSentence = messageContentParts[messageContentParts.length - 1];
  1093. eventTarget.dispatchEvent(
  1094. new CustomEvent('chat', {
  1095. detail: {
  1096. id: message.id,
  1097. content: messageContentParts[messageContentParts.length - 1]
  1098. }
  1099. })
  1100. );
  1101. }
  1102. }
  1103. if (selected_model_id) {
  1104. message.selectedModelId = selected_model_id;
  1105. message.arena = true;
  1106. }
  1107. if (usage) {
  1108. message.usage = usage;
  1109. }
  1110. history.messages[message.id] = message;
  1111. if (done) {
  1112. message.done = true;
  1113. if ($settings.responseAutoCopy) {
  1114. copyToClipboard(message.content);
  1115. }
  1116. if ($settings.responseAutoPlayback && !$showCallOverlay) {
  1117. await tick();
  1118. document.getElementById(`speak-button-${message.id}`)?.click();
  1119. }
  1120. // Emit chat event for TTS
  1121. let lastMessageContentPart =
  1122. getMessageContentParts(
  1123. removeAllDetails(message.content),
  1124. $config?.audio?.tts?.split_on ?? 'punctuation'
  1125. )?.at(-1) ?? '';
  1126. if (lastMessageContentPart) {
  1127. eventTarget.dispatchEvent(
  1128. new CustomEvent('chat', {
  1129. detail: { id: message.id, content: lastMessageContentPart }
  1130. })
  1131. );
  1132. }
  1133. eventTarget.dispatchEvent(
  1134. new CustomEvent('chat:finish', {
  1135. detail: {
  1136. id: message.id,
  1137. content: message.content
  1138. }
  1139. })
  1140. );
  1141. history.messages[message.id] = message;
  1142. await tick();
  1143. if (autoScroll) {
  1144. scrollToBottom();
  1145. }
  1146. await chatCompletedHandler(
  1147. chatId,
  1148. message.model,
  1149. message.id,
  1150. createMessagesList(history, message.id)
  1151. );
  1152. }
  1153. console.log(data);
  1154. await tick();
  1155. if (autoScroll) {
  1156. scrollToBottom();
  1157. }
  1158. };
  1159. //////////////////////////
  1160. // Chat functions
  1161. //////////////////////////
  1162. const submitPrompt = async (userPrompt, { _raw = false } = {}) => {
  1163. console.log('submitPrompt', userPrompt, $chatId);
  1164. const messages = createMessagesList(history, history.currentId);
  1165. const _selectedModels = selectedModels.map((modelId) =>
  1166. $models.map((m) => m.id).includes(modelId) ? modelId : ''
  1167. );
  1168. if (JSON.stringify(selectedModels) !== JSON.stringify(_selectedModels)) {
  1169. selectedModels = _selectedModels;
  1170. }
  1171. if (userPrompt === '' && files.length === 0) {
  1172. toast.error($i18n.t('Please enter a prompt'));
  1173. return;
  1174. }
  1175. if (selectedModels.includes('')) {
  1176. toast.error($i18n.t('Model not selected'));
  1177. return;
  1178. }
  1179. if (messages.length != 0 && messages.at(-1).done != true) {
  1180. // Response not done
  1181. return;
  1182. }
  1183. if (messages.length != 0 && messages.at(-1).error && !messages.at(-1).content) {
  1184. // Error in response
  1185. toast.error($i18n.t(`Oops! There was an error in the previous response.`));
  1186. return;
  1187. }
  1188. if (
  1189. files.length > 0 &&
  1190. files.filter((file) => file.type !== 'image' && file.status === 'uploading').length > 0
  1191. ) {
  1192. toast.error(
  1193. $i18n.t(`Oops! There are files still uploading. Please wait for the upload to complete.`)
  1194. );
  1195. return;
  1196. }
  1197. if (
  1198. ($config?.file?.max_count ?? null) !== null &&
  1199. files.length + chatFiles.length > $config?.file?.max_count
  1200. ) {
  1201. toast.error(
  1202. $i18n.t(`You can only chat with a maximum of {{maxCount}} file(s) at a time.`, {
  1203. maxCount: $config?.file?.max_count
  1204. })
  1205. );
  1206. return;
  1207. }
  1208. messageInput?.setText('');
  1209. // Reset chat input textarea
  1210. if (!($settings?.richTextInput ?? true)) {
  1211. const chatInputElement = document.getElementById('chat-input');
  1212. if (chatInputElement) {
  1213. await tick();
  1214. chatInputElement.style.height = '';
  1215. }
  1216. }
  1217. const _files = JSON.parse(JSON.stringify(files));
  1218. chatFiles.push(..._files.filter((item) => ['doc', 'file', 'collection'].includes(item.type)));
  1219. chatFiles = chatFiles.filter(
  1220. // Remove duplicates
  1221. (item, index, array) =>
  1222. array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
  1223. );
  1224. files = [];
  1225. messageInput?.setText('');
  1226. // Create user message
  1227. let userMessageId = uuidv4();
  1228. let userMessage = {
  1229. id: userMessageId,
  1230. parentId: messages.length !== 0 ? messages.at(-1).id : null,
  1231. childrenIds: [],
  1232. role: 'user',
  1233. content: userPrompt,
  1234. files: _files.length > 0 ? _files : undefined,
  1235. timestamp: Math.floor(Date.now() / 1000), // Unix epoch
  1236. models: selectedModels
  1237. };
  1238. // Add message to history and Set currentId to messageId
  1239. history.messages[userMessageId] = userMessage;
  1240. history.currentId = userMessageId;
  1241. // Append messageId to childrenIds of parent message
  1242. if (messages.length !== 0) {
  1243. history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
  1244. }
  1245. // focus on chat input
  1246. const chatInput = document.getElementById('chat-input');
  1247. chatInput?.focus();
  1248. saveSessionSelectedModels();
  1249. await sendMessage(history, userMessageId, { newChat: true });
  1250. };
  1251. const sendMessage = async (
  1252. _history,
  1253. parentId: string,
  1254. {
  1255. messages = null,
  1256. modelId = null,
  1257. modelIdx = null,
  1258. newChat = false
  1259. }: {
  1260. messages?: any[] | null;
  1261. modelId?: string | null;
  1262. modelIdx?: number | null;
  1263. newChat?: boolean;
  1264. } = {}
  1265. ) => {
  1266. if (autoScroll) {
  1267. scrollToBottom();
  1268. }
  1269. let _chatId = JSON.parse(JSON.stringify($chatId));
  1270. _history = JSON.parse(JSON.stringify(_history));
  1271. const responseMessageIds: Record<PropertyKey, string> = {};
  1272. // If modelId is provided, use it, else use selected model
  1273. let selectedModelIds = modelId
  1274. ? [modelId]
  1275. : atSelectedModel !== undefined
  1276. ? [atSelectedModel.id]
  1277. : selectedModels;
  1278. // Create response messages for each selected model
  1279. for (const [_modelIdx, modelId] of selectedModelIds.entries()) {
  1280. const model = $models.filter((m) => m.id === modelId).at(0);
  1281. if (model) {
  1282. let responseMessageId = uuidv4();
  1283. let responseMessage = {
  1284. parentId: parentId,
  1285. id: responseMessageId,
  1286. childrenIds: [],
  1287. role: 'assistant',
  1288. content: '',
  1289. model: model.id,
  1290. modelName: model.name ?? model.id,
  1291. modelIdx: modelIdx ? modelIdx : _modelIdx,
  1292. timestamp: Math.floor(Date.now() / 1000) // Unix epoch
  1293. };
  1294. // Add message to history and Set currentId to messageId
  1295. history.messages[responseMessageId] = responseMessage;
  1296. history.currentId = responseMessageId;
  1297. // Append messageId to childrenIds of parent message
  1298. if (parentId !== null && history.messages[parentId]) {
  1299. // Add null check before accessing childrenIds
  1300. history.messages[parentId].childrenIds = [
  1301. ...history.messages[parentId].childrenIds,
  1302. responseMessageId
  1303. ];
  1304. }
  1305. responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`] = responseMessageId;
  1306. }
  1307. }
  1308. history = history;
  1309. // Create new chat if newChat is true and first user message
  1310. if (newChat && _history.messages[_history.currentId].parentId === null) {
  1311. _chatId = await initChatHandler(_history);
  1312. }
  1313. await tick();
  1314. _history = JSON.parse(JSON.stringify(history));
  1315. // Save chat after all messages have been created
  1316. await saveChatHandler(_chatId, _history);
  1317. await Promise.all(
  1318. selectedModelIds.map(async (modelId, _modelIdx) => {
  1319. console.log('modelId', modelId);
  1320. const model = $models.filter((m) => m.id === modelId).at(0);
  1321. if (model) {
  1322. // If there are image files, check if model is vision capable
  1323. const hasImages = createMessagesList(_history, parentId).some((message) =>
  1324. message.files?.some((file) => file.type === 'image')
  1325. );
  1326. if (hasImages && !(model.info?.meta?.capabilities?.vision ?? true)) {
  1327. toast.error(
  1328. $i18n.t('Model {{modelName}} is not vision capable', {
  1329. modelName: model.name ?? model.id
  1330. })
  1331. );
  1332. }
  1333. let responseMessageId =
  1334. responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`];
  1335. const chatEventEmitter = await getChatEventEmitter(model.id, _chatId);
  1336. scrollToBottom();
  1337. await sendMessageSocket(
  1338. model,
  1339. messages && messages.length > 0
  1340. ? messages
  1341. : createMessagesList(_history, responseMessageId),
  1342. _history,
  1343. responseMessageId,
  1344. _chatId
  1345. );
  1346. if (chatEventEmitter) clearInterval(chatEventEmitter);
  1347. } else {
  1348. toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
  1349. }
  1350. })
  1351. );
  1352. currentChatPage.set(1);
  1353. chats.set(await getChatList(localStorage.token, $currentChatPage));
  1354. };
  1355. const sendMessageSocket = async (model, _messages, _history, responseMessageId, _chatId) => {
  1356. const responseMessage = _history.messages[responseMessageId];
  1357. const userMessage = _history.messages[responseMessage.parentId];
  1358. const chatMessageFiles = _messages
  1359. .filter((message) => message.files)
  1360. .flatMap((message) => message.files);
  1361. // Filter chatFiles to only include files that are in the chatMessageFiles
  1362. chatFiles = chatFiles.filter((item) => {
  1363. const fileExists = chatMessageFiles.some((messageFile) => messageFile.id === item.id);
  1364. return fileExists;
  1365. });
  1366. let files = JSON.parse(JSON.stringify(chatFiles));
  1367. files.push(
  1368. ...(userMessage?.files ?? []).filter((item) =>
  1369. ['doc', 'text', 'file', 'note', 'collection'].includes(item.type)
  1370. )
  1371. );
  1372. // Remove duplicates
  1373. files = files.filter(
  1374. (item, index, array) =>
  1375. array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
  1376. );
  1377. scrollToBottom();
  1378. eventTarget.dispatchEvent(
  1379. new CustomEvent('chat:start', {
  1380. detail: {
  1381. id: responseMessageId
  1382. }
  1383. })
  1384. );
  1385. await tick();
  1386. const stream =
  1387. model?.info?.params?.stream_response ??
  1388. $settings?.params?.stream_response ??
  1389. params?.stream_response ??
  1390. true;
  1391. let messages = [
  1392. params?.system || $settings.system
  1393. ? {
  1394. role: 'system',
  1395. content: `${promptTemplate(
  1396. params?.system ?? $settings?.system ?? '',
  1397. $user?.name,
  1398. $settings?.userLocation
  1399. ? await getAndUpdateUserLocation(localStorage.token).catch((err) => {
  1400. console.error(err);
  1401. return undefined;
  1402. })
  1403. : undefined
  1404. )}`
  1405. }
  1406. : undefined,
  1407. ..._messages.map((message) => ({
  1408. ...message,
  1409. content: processDetails(message.content)
  1410. }))
  1411. ].filter((message) => message);
  1412. messages = messages
  1413. .map((message, idx, arr) => ({
  1414. role: message.role,
  1415. ...((message.files?.filter((file) => file.type === 'image').length > 0 ?? false) &&
  1416. message.role === 'user'
  1417. ? {
  1418. content: [
  1419. {
  1420. type: 'text',
  1421. text: message?.merged?.content ?? message.content
  1422. },
  1423. ...message.files
  1424. .filter((file) => file.type === 'image')
  1425. .map((file) => ({
  1426. type: 'image_url',
  1427. image_url: {
  1428. url: file.url
  1429. }
  1430. }))
  1431. ]
  1432. }
  1433. : {
  1434. content: message?.merged?.content ?? message.content
  1435. })
  1436. }))
  1437. .filter((message) => message?.role === 'user' || message?.content?.trim());
  1438. const res = await generateOpenAIChatCompletion(
  1439. localStorage.token,
  1440. {
  1441. stream: stream,
  1442. model: model.id,
  1443. messages: messages,
  1444. params: {
  1445. ...$settings?.params,
  1446. ...params,
  1447. stop:
  1448. (params?.stop ?? $settings?.params?.stop ?? undefined)
  1449. ? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map(
  1450. (str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
  1451. )
  1452. : undefined
  1453. },
  1454. files: (files?.length ?? 0) > 0 ? files : undefined,
  1455. filter_ids: selectedFilterIds.length > 0 ? selectedFilterIds : undefined,
  1456. tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
  1457. tool_servers: $toolServers,
  1458. features: {
  1459. image_generation:
  1460. $config?.features?.enable_image_generation &&
  1461. ($user?.role === 'admin' || $user?.permissions?.features?.image_generation)
  1462. ? imageGenerationEnabled
  1463. : false,
  1464. code_interpreter:
  1465. $config?.features?.enable_code_interpreter &&
  1466. ($user?.role === 'admin' || $user?.permissions?.features?.code_interpreter)
  1467. ? codeInterpreterEnabled
  1468. : false,
  1469. web_search:
  1470. $config?.features?.enable_web_search &&
  1471. ($user?.role === 'admin' || $user?.permissions?.features?.web_search)
  1472. ? webSearchEnabled || ($settings?.webSearch ?? false) === 'always'
  1473. : false,
  1474. memory: $settings?.memory ?? false
  1475. },
  1476. variables: {
  1477. ...getPromptVariables(
  1478. $user?.name,
  1479. $settings?.userLocation
  1480. ? await getAndUpdateUserLocation(localStorage.token).catch((err) => {
  1481. console.error(err);
  1482. return undefined;
  1483. })
  1484. : undefined
  1485. )
  1486. },
  1487. model_item: $models.find((m) => m.id === model.id),
  1488. session_id: $socket?.id,
  1489. chat_id: $chatId,
  1490. id: responseMessageId,
  1491. background_tasks: {
  1492. ...(!$temporaryChatEnabled &&
  1493. (messages.length == 1 ||
  1494. (messages.length == 2 &&
  1495. messages.at(0)?.role === 'system' &&
  1496. messages.at(1)?.role === 'user')) &&
  1497. (selectedModels[0] === model.id || atSelectedModel !== undefined)
  1498. ? {
  1499. title_generation: $settings?.title?.auto ?? true,
  1500. tags_generation: $settings?.autoTags ?? true
  1501. }
  1502. : {}),
  1503. follow_up_generation: $settings?.autoFollowUps ?? true
  1504. },
  1505. ...(stream && (model.info?.meta?.capabilities?.usage ?? false)
  1506. ? {
  1507. stream_options: {
  1508. include_usage: true
  1509. }
  1510. }
  1511. : {})
  1512. },
  1513. `${WEBUI_BASE_URL}/api`
  1514. ).catch(async (error) => {
  1515. console.log(error);
  1516. let errorMessage = error;
  1517. if (error?.error?.message) {
  1518. errorMessage = error.error.message;
  1519. } else if (error?.message) {
  1520. errorMessage = error.message;
  1521. }
  1522. if (typeof errorMessage === 'object') {
  1523. errorMessage = $i18n.t(`Uh-oh! There was an issue with the response.`);
  1524. }
  1525. toast.error(`${errorMessage}`);
  1526. responseMessage.error = {
  1527. content: error
  1528. };
  1529. responseMessage.done = true;
  1530. history.messages[responseMessageId] = responseMessage;
  1531. history.currentId = responseMessageId;
  1532. return null;
  1533. });
  1534. if (res) {
  1535. if (res.error) {
  1536. await handleOpenAIError(res.error, responseMessage);
  1537. } else {
  1538. if (taskIds) {
  1539. taskIds.push(res.task_id);
  1540. } else {
  1541. taskIds = [res.task_id];
  1542. }
  1543. }
  1544. }
  1545. await tick();
  1546. scrollToBottom();
  1547. };
  1548. const handleOpenAIError = async (error, responseMessage) => {
  1549. let errorMessage = '';
  1550. let innerError;
  1551. if (error) {
  1552. innerError = error;
  1553. }
  1554. console.error(innerError);
  1555. if ('detail' in innerError) {
  1556. // FastAPI error
  1557. toast.error(innerError.detail);
  1558. errorMessage = innerError.detail;
  1559. } else if ('error' in innerError) {
  1560. // OpenAI error
  1561. if ('message' in innerError.error) {
  1562. toast.error(innerError.error.message);
  1563. errorMessage = innerError.error.message;
  1564. } else {
  1565. toast.error(innerError.error);
  1566. errorMessage = innerError.error;
  1567. }
  1568. } else if ('message' in innerError) {
  1569. // OpenAI error
  1570. toast.error(innerError.message);
  1571. errorMessage = innerError.message;
  1572. }
  1573. responseMessage.error = {
  1574. content: $i18n.t(`Uh-oh! There was an issue with the response.`) + '\n' + errorMessage
  1575. };
  1576. responseMessage.done = true;
  1577. if (responseMessage.statusHistory) {
  1578. responseMessage.statusHistory = responseMessage.statusHistory.filter(
  1579. (status) => status.action !== 'knowledge_search'
  1580. );
  1581. }
  1582. history.messages[responseMessage.id] = responseMessage;
  1583. };
  1584. const stopResponse = async () => {
  1585. if (taskIds) {
  1586. for (const taskId of taskIds) {
  1587. const res = await stopTask(localStorage.token, taskId).catch((error) => {
  1588. toast.error(`${error}`);
  1589. return null;
  1590. });
  1591. }
  1592. taskIds = null;
  1593. const responseMessage = history.messages[history.currentId];
  1594. // Set all response messages to done
  1595. for (const messageId of history.messages[responseMessage.parentId].childrenIds) {
  1596. history.messages[messageId].done = true;
  1597. }
  1598. history.messages[history.currentId] = responseMessage;
  1599. if (autoScroll) {
  1600. scrollToBottom();
  1601. }
  1602. }
  1603. if (generating) {
  1604. generating = false;
  1605. generationController?.abort();
  1606. generationController = null;
  1607. }
  1608. };
  1609. const submitMessage = async (parentId, prompt) => {
  1610. let userPrompt = prompt;
  1611. let userMessageId = uuidv4();
  1612. let userMessage = {
  1613. id: userMessageId,
  1614. parentId: parentId,
  1615. childrenIds: [],
  1616. role: 'user',
  1617. content: userPrompt,
  1618. models: selectedModels,
  1619. timestamp: Math.floor(Date.now() / 1000) // Unix epoch
  1620. };
  1621. if (parentId !== null) {
  1622. history.messages[parentId].childrenIds = [
  1623. ...history.messages[parentId].childrenIds,
  1624. userMessageId
  1625. ];
  1626. }
  1627. history.messages[userMessageId] = userMessage;
  1628. history.currentId = userMessageId;
  1629. await tick();
  1630. if (autoScroll) {
  1631. scrollToBottom();
  1632. }
  1633. await sendMessage(history, userMessageId);
  1634. };
  1635. const regenerateResponse = async (message, suggestionPrompt = null) => {
  1636. console.log('regenerateResponse');
  1637. if (history.currentId) {
  1638. let userMessage = history.messages[message.parentId];
  1639. if (autoScroll) {
  1640. scrollToBottom();
  1641. }
  1642. await sendMessage(history, userMessage.id, {
  1643. ...(suggestionPrompt
  1644. ? {
  1645. messages: [
  1646. ...createMessagesList(history, message.id),
  1647. {
  1648. role: 'user',
  1649. content: suggestionPrompt
  1650. }
  1651. ]
  1652. }
  1653. : {}),
  1654. ...((userMessage?.models ?? [...selectedModels]).length > 1
  1655. ? {
  1656. // If multiple models are selected, use the model from the message
  1657. modelId: message.model,
  1658. modelIdx: message.modelIdx
  1659. }
  1660. : {})
  1661. });
  1662. }
  1663. };
  1664. const continueResponse = async () => {
  1665. console.log('continueResponse');
  1666. const _chatId = JSON.parse(JSON.stringify($chatId));
  1667. if (history.currentId && history.messages[history.currentId].done == true) {
  1668. const responseMessage = history.messages[history.currentId];
  1669. responseMessage.done = false;
  1670. await tick();
  1671. const model = $models
  1672. .filter((m) => m.id === (responseMessage?.selectedModelId ?? responseMessage.model))
  1673. .at(0);
  1674. if (model) {
  1675. await sendMessageSocket(
  1676. model,
  1677. createMessagesList(history, responseMessage.id),
  1678. history,
  1679. responseMessage.id,
  1680. _chatId
  1681. );
  1682. }
  1683. }
  1684. };
  1685. const mergeResponses = async (messageId, responses, _chatId) => {
  1686. console.log('mergeResponses', messageId, responses);
  1687. const message = history.messages[messageId];
  1688. const mergedResponse = {
  1689. status: true,
  1690. content: ''
  1691. };
  1692. message.merged = mergedResponse;
  1693. history.messages[messageId] = message;
  1694. try {
  1695. generating = true;
  1696. const [res, controller] = await generateMoACompletion(
  1697. localStorage.token,
  1698. message.model,
  1699. history.messages[message.parentId].content,
  1700. responses
  1701. );
  1702. if (res && res.ok && res.body && generating) {
  1703. generationController = controller;
  1704. const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
  1705. for await (const update of textStream) {
  1706. const { value, done, sources, error, usage } = update;
  1707. if (error || done) {
  1708. generating = false;
  1709. generationController = null;
  1710. break;
  1711. }
  1712. if (mergedResponse.content == '' && value == '\n') {
  1713. continue;
  1714. } else {
  1715. mergedResponse.content += value;
  1716. history.messages[messageId] = message;
  1717. }
  1718. if (autoScroll) {
  1719. scrollToBottom();
  1720. }
  1721. }
  1722. await saveChatHandler(_chatId, history);
  1723. } else {
  1724. console.error(res);
  1725. }
  1726. } catch (e) {
  1727. console.error(e);
  1728. }
  1729. };
  1730. const initChatHandler = async (history) => {
  1731. let _chatId = $chatId;
  1732. if (!$temporaryChatEnabled) {
  1733. chat = await createNewChat(
  1734. localStorage.token,
  1735. {
  1736. id: _chatId,
  1737. title: $i18n.t('New Chat'),
  1738. models: selectedModels,
  1739. system: $settings.system ?? undefined,
  1740. params: params,
  1741. history: history,
  1742. messages: createMessagesList(history, history.currentId),
  1743. tags: [],
  1744. timestamp: Date.now()
  1745. },
  1746. $selectedFolder?.id
  1747. );
  1748. _chatId = chat.id;
  1749. await chatId.set(_chatId);
  1750. window.history.replaceState(history.state, '', `/c/${_chatId}`);
  1751. await tick();
  1752. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  1753. currentChatPage.set(1);
  1754. selectedFolder.set(null);
  1755. } else {
  1756. _chatId = 'local';
  1757. await chatId.set('local');
  1758. }
  1759. await tick();
  1760. return _chatId;
  1761. };
  1762. const saveChatHandler = async (_chatId, history) => {
  1763. if ($chatId == _chatId) {
  1764. if (!$temporaryChatEnabled) {
  1765. chat = await updateChatById(localStorage.token, _chatId, {
  1766. models: selectedModels,
  1767. history: history,
  1768. messages: createMessagesList(history, history.currentId),
  1769. params: params,
  1770. files: chatFiles
  1771. });
  1772. currentChatPage.set(1);
  1773. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  1774. }
  1775. }
  1776. };
  1777. const MAX_DRAFT_LENGTH = 5000;
  1778. let saveDraftTimeout = null;
  1779. const saveDraft = async (draft, chatId = null) => {
  1780. if (saveDraftTimeout) {
  1781. clearTimeout(saveDraftTimeout);
  1782. }
  1783. if (draft.prompt !== null && draft.prompt.length < MAX_DRAFT_LENGTH) {
  1784. saveDraftTimeout = setTimeout(async () => {
  1785. await sessionStorage.setItem(
  1786. `chat-input${chatId ? `-${chatId}` : ''}`,
  1787. JSON.stringify(draft)
  1788. );
  1789. }, 500);
  1790. } else {
  1791. sessionStorage.removeItem(`chat-input${chatId ? `-${chatId}` : ''}`);
  1792. }
  1793. };
  1794. const clearDraft = async (chatId = null) => {
  1795. if (saveDraftTimeout) {
  1796. clearTimeout(saveDraftTimeout);
  1797. }
  1798. await sessionStorage.removeItem(`chat-input${chatId ? `-${chatId}` : ''}`);
  1799. };
  1800. </script>
  1801. <svelte:head>
  1802. <title>
  1803. {$chatTitle
  1804. ? `${$chatTitle.length > 30 ? `${$chatTitle.slice(0, 30)}...` : $chatTitle} • ${$WEBUI_NAME}`
  1805. : `${$WEBUI_NAME}`}
  1806. </title>
  1807. </svelte:head>
  1808. <audio id="audioElement" src="" style="display: none;" />
  1809. <EventConfirmDialog
  1810. bind:show={showEventConfirmation}
  1811. title={eventConfirmationTitle}
  1812. message={eventConfirmationMessage}
  1813. input={eventConfirmationInput}
  1814. inputPlaceholder={eventConfirmationInputPlaceholder}
  1815. inputValue={eventConfirmationInputValue}
  1816. on:confirm={(e) => {
  1817. if (e.detail) {
  1818. eventCallback(e.detail);
  1819. } else {
  1820. eventCallback(true);
  1821. }
  1822. }}
  1823. on:cancel={() => {
  1824. eventCallback(false);
  1825. }}
  1826. />
  1827. <div
  1828. class="h-screen max-h-[100dvh] transition-width duration-200 ease-in-out {$showSidebar
  1829. ? ' md:max-w-[calc(100%-260px)]'
  1830. : ' '} w-full max-w-full flex flex-col"
  1831. id="chat-container"
  1832. >
  1833. {#if !loading}
  1834. <div in:fade={{ duration: 50 }} class="w-full h-full flex flex-col">
  1835. {#if $settings?.backgroundImageUrl ?? $config?.license_metadata?.background_image_url ?? null}
  1836. <div
  1837. class="absolute {$showSidebar
  1838. ? 'md:max-w-[calc(100%-260px)] md:translate-x-[260px]'
  1839. : ''} top-0 left-0 w-full h-full bg-cover bg-center bg-no-repeat"
  1840. style="background-image: url({$settings?.backgroundImageUrl ??
  1841. $config?.license_metadata?.background_image_url}) "
  1842. />
  1843. <div
  1844. class="absolute top-0 left-0 w-full h-full bg-linear-to-t from-white to-white/85 dark:from-gray-900 dark:to-gray-900/90 z-0"
  1845. />
  1846. {/if}
  1847. <PaneGroup direction="horizontal" class="w-full h-full">
  1848. <Pane defaultSize={50} class="h-full flex relative max-w-full flex-col">
  1849. <Navbar
  1850. bind:this={navbarElement}
  1851. chat={{
  1852. id: $chatId,
  1853. chat: {
  1854. title: $chatTitle,
  1855. models: selectedModels,
  1856. system: $settings.system ?? undefined,
  1857. params: params,
  1858. history: history,
  1859. timestamp: Date.now()
  1860. }
  1861. }}
  1862. {history}
  1863. title={$chatTitle}
  1864. bind:selectedModels
  1865. shareEnabled={!!history.currentId}
  1866. {initNewChat}
  1867. showBanners={!showCommands}
  1868. onSaveTempChat={async () => {
  1869. try {
  1870. if (!history?.currentId || !Object.keys(history.messages).length) {
  1871. toast.error($i18n.t('No conversation to save'));
  1872. return;
  1873. }
  1874. const messages = createMessagesList(history, history.currentId);
  1875. const title =
  1876. messages.find((m) => m.role === 'user')?.content ?? $i18n.t('New Chat');
  1877. const savedChat = await createNewChat(
  1878. localStorage.token,
  1879. {
  1880. id: uuidv4(),
  1881. title: title.length > 50 ? `${title.slice(0, 50)}...` : title,
  1882. models: selectedModels,
  1883. history: history,
  1884. messages: messages,
  1885. timestamp: Date.now()
  1886. },
  1887. null
  1888. );
  1889. if (savedChat) {
  1890. temporaryChatEnabled.set(false);
  1891. chatId.set(savedChat.id);
  1892. chats.set(await getChatList(localStorage.token, $currentChatPage));
  1893. await goto(`/c/${savedChat.id}`);
  1894. toast.success($i18n.t('Conversation saved successfully'));
  1895. }
  1896. } catch (error) {
  1897. console.error('Error saving conversation:', error);
  1898. toast.error($i18n.t('Failed to save conversation'));
  1899. }
  1900. }}
  1901. />
  1902. <div class="flex flex-col flex-auto z-10 w-full @container overflow-auto">
  1903. {#if ($settings?.landingPageMode === 'chat' && !$selectedFolder) || createMessagesList(history, history.currentId).length > 0}
  1904. <div
  1905. class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full z-10 scrollbar-hidden"
  1906. id="messages-container"
  1907. bind:this={messagesContainerElement}
  1908. on:scroll={(e) => {
  1909. autoScroll =
  1910. messagesContainerElement.scrollHeight - messagesContainerElement.scrollTop <=
  1911. messagesContainerElement.clientHeight + 5;
  1912. }}
  1913. >
  1914. <div class=" h-full w-full flex flex-col">
  1915. <Messages
  1916. chatId={$chatId}
  1917. bind:history
  1918. bind:autoScroll
  1919. bind:prompt
  1920. setInputText={(text) => {
  1921. messageInput?.setText(text);
  1922. }}
  1923. {selectedModels}
  1924. {atSelectedModel}
  1925. {sendMessage}
  1926. {showMessage}
  1927. {submitMessage}
  1928. {continueResponse}
  1929. {regenerateResponse}
  1930. {mergeResponses}
  1931. {chatActionHandler}
  1932. {addMessages}
  1933. topPadding={true}
  1934. bottomPadding={files.length > 0}
  1935. {onSelect}
  1936. />
  1937. </div>
  1938. </div>
  1939. <div class=" pb-2">
  1940. <MessageInput
  1941. bind:this={messageInput}
  1942. {history}
  1943. {taskIds}
  1944. {selectedModels}
  1945. bind:files
  1946. bind:prompt
  1947. bind:autoScroll
  1948. bind:selectedToolIds
  1949. bind:selectedFilterIds
  1950. bind:imageGenerationEnabled
  1951. bind:codeInterpreterEnabled
  1952. bind:webSearchEnabled
  1953. bind:atSelectedModel
  1954. bind:showCommands
  1955. toolServers={$toolServers}
  1956. {generating}
  1957. {stopResponse}
  1958. {createMessagePair}
  1959. onChange={(data) => {
  1960. if (!$temporaryChatEnabled) {
  1961. saveDraft(data, $chatId);
  1962. }
  1963. }}
  1964. on:upload={async (e) => {
  1965. const { type, data } = e.detail;
  1966. if (type === 'web') {
  1967. await uploadWeb(data);
  1968. } else if (type === 'youtube') {
  1969. await uploadYoutubeTranscription(data);
  1970. } else if (type === 'google-drive') {
  1971. await uploadGoogleDriveFile(data);
  1972. }
  1973. }}
  1974. on:submit={async (e) => {
  1975. clearDraft();
  1976. if (e.detail || files.length > 0) {
  1977. await tick();
  1978. submitPrompt(
  1979. ($settings?.richTextInput ?? true)
  1980. ? e.detail.replaceAll('\n\n', '\n')
  1981. : e.detail
  1982. );
  1983. }
  1984. }}
  1985. />
  1986. <div
  1987. class="absolute bottom-1 text-xs text-gray-500 text-center line-clamp-1 right-0 left-0"
  1988. >
  1989. <!-- {$i18n.t('LLMs can make mistakes. Verify important information.')} -->
  1990. </div>
  1991. </div>
  1992. {:else}
  1993. <div class="flex items-center h-full">
  1994. <Placeholder
  1995. {history}
  1996. {selectedModels}
  1997. bind:messageInput
  1998. bind:files
  1999. bind:prompt
  2000. bind:autoScroll
  2001. bind:selectedToolIds
  2002. bind:selectedFilterIds
  2003. bind:imageGenerationEnabled
  2004. bind:codeInterpreterEnabled
  2005. bind:webSearchEnabled
  2006. bind:atSelectedModel
  2007. bind:showCommands
  2008. toolServers={$toolServers}
  2009. {stopResponse}
  2010. {createMessagePair}
  2011. {onSelect}
  2012. onChange={(data) => {
  2013. if (!$temporaryChatEnabled) {
  2014. saveDraft(data);
  2015. }
  2016. }}
  2017. on:upload={async (e) => {
  2018. const { type, data } = e.detail;
  2019. if (type === 'web') {
  2020. await uploadWeb(data);
  2021. } else if (type === 'youtube') {
  2022. await uploadYoutubeTranscription(data);
  2023. }
  2024. }}
  2025. on:submit={async (e) => {
  2026. clearDraft();
  2027. if (e.detail || files.length > 0) {
  2028. await tick();
  2029. submitPrompt(
  2030. ($settings?.richTextInput ?? true)
  2031. ? e.detail.replaceAll('\n\n', '\n')
  2032. : e.detail
  2033. );
  2034. }
  2035. }}
  2036. />
  2037. </div>
  2038. {/if}
  2039. </div>
  2040. </Pane>
  2041. <ChatControls
  2042. bind:this={controlPaneComponent}
  2043. bind:history
  2044. bind:chatFiles
  2045. bind:params
  2046. bind:files
  2047. bind:pane={controlPane}
  2048. chatId={$chatId}
  2049. modelId={selectedModelIds?.at(0) ?? null}
  2050. models={selectedModelIds.reduce((a, e, i, arr) => {
  2051. const model = $models.find((m) => m.id === e);
  2052. if (model) {
  2053. return [...a, model];
  2054. }
  2055. return a;
  2056. }, [])}
  2057. {submitPrompt}
  2058. {stopResponse}
  2059. {showMessage}
  2060. {eventTarget}
  2061. />
  2062. </PaneGroup>
  2063. </div>
  2064. {:else if loading}
  2065. <div class=" flex items-center justify-center h-full w-full">
  2066. <div class="m-auto">
  2067. <Spinner className="size-5" />
  2068. </div>
  2069. </div>
  2070. {/if}
  2071. </div>