Chat.svelte 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741
  1. <script lang="ts">
  2. import { v4 as uuidv4 } from 'uuid';
  3. import { toast } from 'svelte-sonner';
  4. import mermaid from 'mermaid';
  5. import { getContext, onMount, tick } from 'svelte';
  6. import { goto } from '$app/navigation';
  7. import { page } from '$app/stores';
  8. import type { Writable } from 'svelte/store';
  9. import type { i18n as i18nType } from 'i18next';
  10. import { OLLAMA_API_BASE_URL, OPENAI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
  11. import {
  12. chatId,
  13. chats,
  14. config,
  15. type Model,
  16. models,
  17. settings,
  18. showSidebar,
  19. tags as _tags,
  20. WEBUI_NAME,
  21. banners,
  22. user,
  23. socket,
  24. showCallOverlay,
  25. tools,
  26. currentChatPage,
  27. temporaryChatEnabled
  28. } from '$lib/stores';
  29. import {
  30. convertMessagesToHistory,
  31. copyToClipboard,
  32. extractSentencesForAudio,
  33. getUserPosition,
  34. promptTemplate,
  35. splitStream
  36. } from '$lib/utils';
  37. import { generateChatCompletion } from '$lib/apis/ollama';
  38. import {
  39. addTagById,
  40. createNewChat,
  41. deleteTagById,
  42. getAllChatTags,
  43. getChatById,
  44. getChatList,
  45. getTagsById,
  46. updateChatById
  47. } from '$lib/apis/chats';
  48. import { generateOpenAIChatCompletion } from '$lib/apis/openai';
  49. import { runWebSearch } from '$lib/apis/rag';
  50. import { createOpenAITextStream } from '$lib/apis/streaming';
  51. import { queryMemory } from '$lib/apis/memories';
  52. import { getAndUpdateUserLocation, getUserSettings } from '$lib/apis/users';
  53. import {
  54. chatCompleted,
  55. generateTitle,
  56. generateSearchQuery,
  57. chatAction,
  58. generateMoACompletion
  59. } from '$lib/apis';
  60. import Banner from '../common/Banner.svelte';
  61. import MessageInput from '$lib/components/chat/MessageInput.svelte';
  62. import Messages from '$lib/components/chat/Messages.svelte';
  63. import Navbar from '$lib/components/layout/Navbar.svelte';
  64. import CallOverlay from './MessageInput/CallOverlay.svelte';
  65. import { error } from '@sveltejs/kit';
  66. import ChatControls from './ChatControls.svelte';
  67. import EventConfirmDialog from '../common/ConfirmDialog.svelte';
  68. const i18n: Writable<i18nType> = getContext('i18n');
  69. export let chatIdProp = '';
  70. let loaded = false;
  71. const eventTarget = new EventTarget();
  72. let showControls = false;
  73. let stopResponseFlag = false;
  74. let autoScroll = true;
  75. let processing = '';
  76. let messagesContainerElement: HTMLDivElement;
  77. let showEventConfirmation = false;
  78. let eventConfirmationTitle = '';
  79. let eventConfirmationMessage = '';
  80. let eventConfirmationInput = false;
  81. let eventConfirmationInputPlaceholder = '';
  82. let eventConfirmationInputValue = '';
  83. let eventCallback = null;
  84. let showModelSelector = true;
  85. let selectedModels = [''];
  86. let atSelectedModel: Model | undefined;
  87. let selectedModelIds = [];
  88. $: selectedModelIds = atSelectedModel !== undefined ? [atSelectedModel.id] : selectedModels;
  89. let selectedToolIds = [];
  90. let webSearchEnabled = false;
  91. let chat = null;
  92. let tags = [];
  93. let title = '';
  94. let prompt = '';
  95. let chatFiles = [];
  96. let files = [];
  97. let messages = [];
  98. let history = {
  99. messages: {},
  100. currentId: null
  101. };
  102. let params = {};
  103. $: if (history.currentId !== null) {
  104. let _messages = [];
  105. let currentMessage = history.messages[history.currentId];
  106. while (currentMessage !== null) {
  107. _messages.unshift({ ...currentMessage });
  108. currentMessage =
  109. currentMessage.parentId !== null ? history.messages[currentMessage.parentId] : null;
  110. }
  111. messages = _messages;
  112. } else {
  113. messages = [];
  114. }
  115. $: if (chatIdProp) {
  116. (async () => {
  117. console.log(chatIdProp);
  118. if (chatIdProp && (await loadChat())) {
  119. await tick();
  120. loaded = true;
  121. window.setTimeout(() => scrollToBottom(), 0);
  122. const chatInput = document.getElementById('chat-textarea');
  123. chatInput?.focus();
  124. } else {
  125. await goto('/');
  126. }
  127. })();
  128. }
  129. const chatEventHandler = async (event, cb) => {
  130. if (event.chat_id === $chatId) {
  131. await tick();
  132. console.log(event);
  133. let message = history.messages[event.message_id];
  134. const type = event?.data?.type ?? null;
  135. const data = event?.data?.data ?? null;
  136. if (type === 'status') {
  137. if (message?.statusHistory) {
  138. message.statusHistory.push(data);
  139. } else {
  140. message.statusHistory = [data];
  141. }
  142. } else if (type === 'citation') {
  143. if (message?.citations) {
  144. message.citations.push(data);
  145. } else {
  146. message.citations = [data];
  147. }
  148. } else if (type === 'message') {
  149. message.content += data.content;
  150. } else if (type === 'replace') {
  151. message.content = data.content;
  152. } else if (type === 'action') {
  153. if (data.action === 'continue') {
  154. const continueButton = document.getElementById('continue-response-button');
  155. if (continueButton) {
  156. continueButton.click();
  157. }
  158. }
  159. } else if (type === 'confirmation') {
  160. eventCallback = cb;
  161. eventConfirmationInput = false;
  162. showEventConfirmation = true;
  163. eventConfirmationTitle = data.title;
  164. eventConfirmationMessage = data.message;
  165. } else if (type === 'input') {
  166. eventCallback = cb;
  167. eventConfirmationInput = true;
  168. showEventConfirmation = true;
  169. eventConfirmationTitle = data.title;
  170. eventConfirmationMessage = data.message;
  171. eventConfirmationInputPlaceholder = data.placeholder;
  172. eventConfirmationInputValue = data?.value ?? '';
  173. } else {
  174. console.log('Unknown message type', data);
  175. }
  176. messages = messages;
  177. }
  178. };
  179. onMount(async () => {
  180. const onMessageHandler = async (event) => {
  181. if (event.origin === window.origin) {
  182. // Replace with your iframe's origin
  183. console.log('Message received from iframe:', event.data);
  184. if (event.data.type === 'input:prompt') {
  185. console.log(event.data.text);
  186. const inputElement = document.getElementById('chat-textarea');
  187. if (inputElement) {
  188. prompt = event.data.text;
  189. inputElement.focus();
  190. }
  191. }
  192. if (event.data.type === 'action:submit') {
  193. console.log(event.data.text);
  194. if (prompt !== '') {
  195. await tick();
  196. submitPrompt(prompt);
  197. }
  198. }
  199. if (event.data.type === 'input:prompt:submit') {
  200. console.log(event.data.text);
  201. if (prompt !== '') {
  202. await tick();
  203. submitPrompt(event.data.text);
  204. }
  205. }
  206. }
  207. };
  208. window.addEventListener('message', onMessageHandler);
  209. $socket.on('chat-events', chatEventHandler);
  210. if (!$chatId) {
  211. chatId.subscribe(async (value) => {
  212. if (!value) {
  213. await initNewChat();
  214. }
  215. });
  216. } else {
  217. if ($temporaryChatEnabled) {
  218. await goto('/');
  219. }
  220. }
  221. return () => {
  222. window.removeEventListener('message', onMessageHandler);
  223. $socket.off('chat-events');
  224. };
  225. });
  226. //////////////////////////
  227. // Web functions
  228. //////////////////////////
  229. const initNewChat = async () => {
  230. if ($page.url.pathname.includes('/c/')) {
  231. window.history.replaceState(history.state, '', `/`);
  232. }
  233. await chatId.set('');
  234. autoScroll = true;
  235. title = '';
  236. messages = [];
  237. history = {
  238. messages: {},
  239. currentId: null
  240. };
  241. chatFiles = [];
  242. params = {};
  243. if ($page.url.searchParams.get('models')) {
  244. selectedModels = $page.url.searchParams.get('models')?.split(',');
  245. } else if ($settings?.models) {
  246. selectedModels = $settings?.models;
  247. } else if ($config?.default_models) {
  248. console.log($config?.default_models.split(',') ?? '');
  249. selectedModels = $config?.default_models.split(',');
  250. } else {
  251. selectedModels = [''];
  252. }
  253. if ($page.url.searchParams.get('q')) {
  254. prompt = $page.url.searchParams.get('q') ?? '';
  255. selectedToolIds = ($page.url.searchParams.get('tool_ids') ?? '')
  256. .split(',')
  257. .map((id) => id.trim())
  258. .filter((id) => id);
  259. if (prompt) {
  260. await tick();
  261. submitPrompt(prompt);
  262. }
  263. }
  264. selectedModels = selectedModels.map((modelId) =>
  265. $models.map((m) => m.id).includes(modelId) ? modelId : ''
  266. );
  267. const userSettings = await getUserSettings(localStorage.token);
  268. if (userSettings) {
  269. settings.set(userSettings.ui);
  270. } else {
  271. settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
  272. }
  273. const chatInput = document.getElementById('chat-textarea');
  274. setTimeout(() => chatInput?.focus(), 0);
  275. };
  276. const loadChat = async () => {
  277. chatId.set(chatIdProp);
  278. chat = await getChatById(localStorage.token, $chatId).catch(async (error) => {
  279. await goto('/');
  280. return null;
  281. });
  282. if (chat) {
  283. tags = await getTags();
  284. const chatContent = chat.chat;
  285. if (chatContent) {
  286. console.log(chatContent);
  287. selectedModels =
  288. (chatContent?.models ?? undefined) !== undefined
  289. ? chatContent.models
  290. : [chatContent.models ?? ''];
  291. history =
  292. (chatContent?.history ?? undefined) !== undefined
  293. ? chatContent.history
  294. : convertMessagesToHistory(chatContent.messages);
  295. title = chatContent.title;
  296. const userSettings = await getUserSettings(localStorage.token);
  297. if (userSettings) {
  298. await settings.set(userSettings.ui);
  299. } else {
  300. await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
  301. }
  302. params = chatContent?.params ?? {};
  303. chatFiles = chatContent?.files ?? [];
  304. autoScroll = true;
  305. await tick();
  306. if (messages.length > 0) {
  307. history.messages[messages.at(-1).id].done = true;
  308. }
  309. await tick();
  310. return true;
  311. } else {
  312. return null;
  313. }
  314. }
  315. };
  316. const scrollToBottom = async () => {
  317. await tick();
  318. if (messagesContainerElement) {
  319. messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight;
  320. }
  321. };
  322. const createMessagesList = (responseMessageId) => {
  323. const message = history.messages[responseMessageId];
  324. if (message.parentId) {
  325. return [...createMessagesList(message.parentId), message];
  326. } else {
  327. return [message];
  328. }
  329. };
  330. const chatCompletedHandler = async (chatId, modelId, responseMessageId, messages) => {
  331. await mermaid.run({
  332. querySelector: '.mermaid'
  333. });
  334. const res = await chatCompleted(localStorage.token, {
  335. model: modelId,
  336. messages: messages.map((m) => ({
  337. id: m.id,
  338. role: m.role,
  339. content: m.content,
  340. info: m.info ? m.info : undefined,
  341. timestamp: m.timestamp
  342. })),
  343. chat_id: chatId,
  344. session_id: $socket?.id,
  345. id: responseMessageId
  346. }).catch((error) => {
  347. toast.error(error);
  348. messages.at(-1).error = { content: error };
  349. return null;
  350. });
  351. if (res !== null) {
  352. // Update chat history with the new messages
  353. for (const message of res.messages) {
  354. history.messages[message.id] = {
  355. ...history.messages[message.id],
  356. ...(history.messages[message.id].content !== message.content
  357. ? { originalContent: history.messages[message.id].content }
  358. : {}),
  359. ...message
  360. };
  361. }
  362. }
  363. if ($chatId == chatId) {
  364. if (!$temporaryChatEnabled) {
  365. chat = await updateChatById(localStorage.token, chatId, {
  366. models: selectedModels,
  367. messages: messages,
  368. history: history,
  369. params: params,
  370. files: chatFiles
  371. });
  372. currentChatPage.set(1);
  373. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  374. }
  375. }
  376. };
  377. const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => {
  378. const res = await chatAction(localStorage.token, actionId, {
  379. model: modelId,
  380. messages: messages.map((m) => ({
  381. id: m.id,
  382. role: m.role,
  383. content: m.content,
  384. info: m.info ? m.info : undefined,
  385. timestamp: m.timestamp
  386. })),
  387. ...(event ? { event: event } : {}),
  388. chat_id: chatId,
  389. session_id: $socket?.id,
  390. id: responseMessageId
  391. }).catch((error) => {
  392. toast.error(error);
  393. messages.at(-1).error = { content: error };
  394. return null;
  395. });
  396. if (res !== null) {
  397. // Update chat history with the new messages
  398. for (const message of res.messages) {
  399. history.messages[message.id] = {
  400. ...history.messages[message.id],
  401. ...(history.messages[message.id].content !== message.content
  402. ? { originalContent: history.messages[message.id].content }
  403. : {}),
  404. ...message
  405. };
  406. }
  407. }
  408. if ($chatId == chatId) {
  409. if (!$temporaryChatEnabled) {
  410. chat = await updateChatById(localStorage.token, chatId, {
  411. models: selectedModels,
  412. messages: messages,
  413. history: history,
  414. params: params,
  415. files: chatFiles
  416. });
  417. currentChatPage.set(1);
  418. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  419. }
  420. }
  421. };
  422. const getChatEventEmitter = async (modelId: string, chatId: string = '') => {
  423. return setInterval(() => {
  424. $socket?.emit('usage', {
  425. action: 'chat',
  426. model: modelId,
  427. chat_id: chatId
  428. });
  429. }, 1000);
  430. };
  431. //////////////////////////
  432. // Chat functions
  433. //////////////////////////
  434. const submitPrompt = async (userPrompt, { _raw = false } = {}) => {
  435. let _responses = [];
  436. console.log('submitPrompt', $chatId);
  437. selectedModels = selectedModels.map((modelId) =>
  438. $models.map((m) => m.id).includes(modelId) ? modelId : ''
  439. );
  440. if (selectedModels.includes('')) {
  441. toast.error($i18n.t('Model not selected'));
  442. } else if (messages.length != 0 && messages.at(-1).done != true) {
  443. // Response not done
  444. console.log('wait');
  445. } else if (messages.length != 0 && messages.at(-1).error) {
  446. // Error in response
  447. toast.error(
  448. $i18n.t(
  449. `Oops! There was an error in the previous response. Please try again or contact admin.`
  450. )
  451. );
  452. } else if (
  453. files.length > 0 &&
  454. files.filter((file) => file.type !== 'image' && file.status !== 'processed').length > 0
  455. ) {
  456. // Upload not done
  457. toast.error(
  458. $i18n.t(
  459. `Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.`
  460. )
  461. );
  462. } else {
  463. // Reset chat input textarea
  464. const chatTextAreaElement = document.getElementById('chat-textarea');
  465. if (chatTextAreaElement) {
  466. chatTextAreaElement.value = '';
  467. chatTextAreaElement.style.height = '';
  468. }
  469. const _files = JSON.parse(JSON.stringify(files));
  470. chatFiles.push(..._files.filter((item) => ['doc', 'file', 'collection'].includes(item.type)));
  471. chatFiles = chatFiles.filter(
  472. // Remove duplicates
  473. (item, index, array) =>
  474. array.findIndex((i) => JSON.stringify(i) === JSON.stringify(item)) === index
  475. );
  476. files = [];
  477. prompt = '';
  478. // Create user message
  479. let userMessageId = uuidv4();
  480. let userMessage = {
  481. id: userMessageId,
  482. parentId: messages.length !== 0 ? messages.at(-1).id : null,
  483. childrenIds: [],
  484. role: 'user',
  485. content: userPrompt,
  486. files: _files.length > 0 ? _files : undefined,
  487. timestamp: Math.floor(Date.now() / 1000), // Unix epoch
  488. models: selectedModels
  489. };
  490. // Add message to history and Set currentId to messageId
  491. history.messages[userMessageId] = userMessage;
  492. history.currentId = userMessageId;
  493. // Append messageId to childrenIds of parent message
  494. if (messages.length !== 0) {
  495. history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
  496. }
  497. // Wait until history/message have been updated
  498. await tick();
  499. _responses = await sendPrompt(userPrompt, userMessageId, { newChat: true });
  500. }
  501. return _responses;
  502. };
  503. const sendPrompt = async (
  504. prompt,
  505. parentId,
  506. { modelId = null, modelIdx = null, newChat = false } = {}
  507. ) => {
  508. let _responses = [];
  509. // If modelId is provided, use it, else use selected model
  510. let selectedModelIds = modelId
  511. ? [modelId]
  512. : atSelectedModel !== undefined
  513. ? [atSelectedModel.id]
  514. : selectedModels;
  515. // Create response messages for each selected model
  516. const responseMessageIds = {};
  517. for (const [_modelIdx, modelId] of selectedModelIds.entries()) {
  518. const model = $models.filter((m) => m.id === modelId).at(0);
  519. if (model) {
  520. let responseMessageId = uuidv4();
  521. let responseMessage = {
  522. parentId: parentId,
  523. id: responseMessageId,
  524. childrenIds: [],
  525. role: 'assistant',
  526. content: '',
  527. model: model.id,
  528. modelName: model.name ?? model.id,
  529. modelIdx: modelIdx ? modelIdx : _modelIdx,
  530. userContext: null,
  531. timestamp: Math.floor(Date.now() / 1000) // Unix epoch
  532. };
  533. // Add message to history and Set currentId to messageId
  534. history.messages[responseMessageId] = responseMessage;
  535. history.currentId = responseMessageId;
  536. // Append messageId to childrenIds of parent message
  537. if (parentId !== null) {
  538. history.messages[parentId].childrenIds = [
  539. ...history.messages[parentId].childrenIds,
  540. responseMessageId
  541. ];
  542. }
  543. responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`] = responseMessageId;
  544. }
  545. }
  546. await tick();
  547. // Create new chat if only one message in messages
  548. if (newChat && messages.length == 2) {
  549. if (!$temporaryChatEnabled) {
  550. chat = await createNewChat(localStorage.token, {
  551. id: $chatId,
  552. title: $i18n.t('New Chat'),
  553. models: selectedModels,
  554. system: $settings.system ?? undefined,
  555. params: params,
  556. messages: messages,
  557. history: history,
  558. tags: [],
  559. timestamp: Date.now()
  560. });
  561. currentChatPage.set(1);
  562. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  563. await chatId.set(chat.id);
  564. } else {
  565. await chatId.set('local');
  566. }
  567. await tick();
  568. }
  569. const _chatId = JSON.parse(JSON.stringify($chatId));
  570. await Promise.all(
  571. selectedModelIds.map(async (modelId, _modelIdx) => {
  572. console.log('modelId', modelId);
  573. const model = $models.filter((m) => m.id === modelId).at(0);
  574. if (model) {
  575. // If there are image files, check if model is vision capable
  576. const hasImages = messages.some((message) =>
  577. message.files?.some((file) => file.type === 'image')
  578. );
  579. if (hasImages && !(model.info?.meta?.capabilities?.vision ?? true)) {
  580. toast.error(
  581. $i18n.t('Model {{modelName}} is not vision capable', {
  582. modelName: model.name ?? model.id
  583. })
  584. );
  585. }
  586. let responseMessageId =
  587. responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`];
  588. let responseMessage = history.messages[responseMessageId];
  589. let userContext = null;
  590. if ($settings?.memory ?? false) {
  591. if (userContext === null) {
  592. const res = await queryMemory(localStorage.token, prompt).catch((error) => {
  593. toast.error(error);
  594. return null;
  595. });
  596. if (res) {
  597. if (res.documents[0].length > 0) {
  598. userContext = res.documents[0].reduce((acc, doc, index) => {
  599. const createdAtTimestamp = res.metadatas[0][index].created_at;
  600. const createdAtDate = new Date(createdAtTimestamp * 1000)
  601. .toISOString()
  602. .split('T')[0];
  603. return `${acc}${index + 1}. [${createdAtDate}]. ${doc}\n`;
  604. }, '');
  605. }
  606. console.log(userContext);
  607. }
  608. }
  609. }
  610. responseMessage.userContext = userContext;
  611. const chatEventEmitter = await getChatEventEmitter(model.id, _chatId);
  612. if (webSearchEnabled) {
  613. await getWebSearchResults(model.id, parentId, responseMessageId);
  614. }
  615. let _response = null;
  616. if (model?.owned_by === 'openai') {
  617. _response = await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
  618. } else if (model) {
  619. _response = await sendPromptOllama(model, prompt, responseMessageId, _chatId);
  620. }
  621. _responses.push(_response);
  622. if (chatEventEmitter) clearInterval(chatEventEmitter);
  623. } else {
  624. toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
  625. }
  626. })
  627. );
  628. currentChatPage.set(1);
  629. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  630. return _responses;
  631. };
  632. const sendPromptOllama = async (model, userPrompt, responseMessageId, _chatId) => {
  633. let _response = null;
  634. const responseMessage = history.messages[responseMessageId];
  635. const userMessage = history.messages[responseMessage.parentId];
  636. // Wait until history/message have been updated
  637. await tick();
  638. // Scroll down
  639. scrollToBottom();
  640. const messagesBody = [
  641. params?.system || $settings.system || (responseMessage?.userContext ?? null)
  642. ? {
  643. role: 'system',
  644. content: `${promptTemplate(
  645. params?.system ?? $settings?.system ?? '',
  646. $user.name,
  647. $settings?.userLocation
  648. ? await getAndUpdateUserLocation(localStorage.token)
  649. : undefined
  650. )}${
  651. (responseMessage?.userContext ?? null)
  652. ? `\n\nUser Context:\n${responseMessage?.userContext ?? ''}`
  653. : ''
  654. }`
  655. }
  656. : undefined,
  657. ...messages
  658. ]
  659. .filter((message) => message?.content?.trim())
  660. .map((message, idx, arr) => {
  661. // Prepare the base message object
  662. const baseMessage = {
  663. role: message.role,
  664. content: message.content
  665. };
  666. // Extract and format image URLs if any exist
  667. const imageUrls = message.files
  668. ?.filter((file) => file.type === 'image')
  669. .map((file) => file.url.slice(file.url.indexOf(',') + 1));
  670. // Add images array only if it contains elements
  671. if (imageUrls && imageUrls.length > 0 && message.role === 'user') {
  672. baseMessage.images = imageUrls;
  673. }
  674. return baseMessage;
  675. });
  676. let lastImageIndex = -1;
  677. // Find the index of the last object with images
  678. messagesBody.forEach((item, index) => {
  679. if (item.images) {
  680. lastImageIndex = index;
  681. }
  682. });
  683. // Remove images from all but the last one
  684. messagesBody.forEach((item, index) => {
  685. if (index !== lastImageIndex) {
  686. delete item.images;
  687. }
  688. });
  689. let files = JSON.parse(JSON.stringify(chatFiles));
  690. if (model?.info?.meta?.knowledge ?? false) {
  691. files.push(...model.info.meta.knowledge);
  692. }
  693. files.push(
  694. ...(userMessage?.files ?? []).filter((item) =>
  695. ['doc', 'file', 'collection'].includes(item.type)
  696. ),
  697. ...(responseMessage?.files ?? []).filter((item) => ['web_search_results'].includes(item.type))
  698. );
  699. eventTarget.dispatchEvent(
  700. new CustomEvent('chat:start', {
  701. detail: {
  702. id: responseMessageId
  703. }
  704. })
  705. );
  706. await tick();
  707. const [res, controller] = await generateChatCompletion(localStorage.token, {
  708. stream: true,
  709. model: model.id,
  710. messages: messagesBody,
  711. options: {
  712. ...(params ?? $settings.params ?? {}),
  713. stop:
  714. (params?.stop ?? $settings?.params?.stop ?? undefined)
  715. ? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map(
  716. (str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
  717. )
  718. : undefined,
  719. num_predict: params?.max_tokens ?? $settings?.params?.max_tokens ?? undefined,
  720. repeat_penalty:
  721. params?.frequency_penalty ?? $settings?.params?.frequency_penalty ?? undefined
  722. },
  723. format: $settings.requestFormat ?? undefined,
  724. keep_alive: $settings.keepAlive ?? undefined,
  725. tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
  726. files: files.length > 0 ? files : undefined,
  727. session_id: $socket?.id,
  728. chat_id: $chatId,
  729. id: responseMessageId
  730. });
  731. if (res && res.ok) {
  732. console.log('controller', controller);
  733. const reader = res.body
  734. .pipeThrough(new TextDecoderStream())
  735. .pipeThrough(splitStream('\n'))
  736. .getReader();
  737. while (true) {
  738. const { value, done } = await reader.read();
  739. if (done || stopResponseFlag || _chatId !== $chatId) {
  740. responseMessage.done = true;
  741. messages = messages;
  742. if (stopResponseFlag) {
  743. controller.abort('User: Stop Response');
  744. } else {
  745. const messages = createMessagesList(responseMessageId);
  746. await chatCompletedHandler(_chatId, model.id, responseMessageId, messages);
  747. }
  748. _response = responseMessage.content;
  749. break;
  750. }
  751. try {
  752. let lines = value.split('\n');
  753. for (const line of lines) {
  754. if (line !== '') {
  755. console.log(line);
  756. let data = JSON.parse(line);
  757. if ('citations' in data) {
  758. responseMessage.citations = data.citations;
  759. continue;
  760. }
  761. if ('detail' in data) {
  762. throw data;
  763. }
  764. if (data.done == false) {
  765. if (responseMessage.content == '' && data.message.content == '\n') {
  766. continue;
  767. } else {
  768. responseMessage.content += data.message.content;
  769. if (navigator.vibrate && ($settings?.hapticFeedback ?? false)) {
  770. navigator.vibrate(5);
  771. }
  772. const sentences = extractSentencesForAudio(responseMessage.content);
  773. sentences.pop();
  774. // dispatch only last sentence and make sure it hasn't been dispatched before
  775. if (
  776. sentences.length > 0 &&
  777. sentences[sentences.length - 1] !== responseMessage.lastSentence
  778. ) {
  779. responseMessage.lastSentence = sentences[sentences.length - 1];
  780. eventTarget.dispatchEvent(
  781. new CustomEvent('chat', {
  782. detail: { id: responseMessageId, content: sentences[sentences.length - 1] }
  783. })
  784. );
  785. }
  786. messages = messages;
  787. }
  788. } else {
  789. responseMessage.done = true;
  790. if (responseMessage.content == '') {
  791. responseMessage.error = {
  792. code: 400,
  793. content: `Oops! No text generated from Ollama, Please try again.`
  794. };
  795. }
  796. responseMessage.context = data.context ?? null;
  797. responseMessage.info = {
  798. total_duration: data.total_duration,
  799. load_duration: data.load_duration,
  800. sample_count: data.sample_count,
  801. sample_duration: data.sample_duration,
  802. prompt_eval_count: data.prompt_eval_count,
  803. prompt_eval_duration: data.prompt_eval_duration,
  804. eval_count: data.eval_count,
  805. eval_duration: data.eval_duration
  806. };
  807. messages = messages;
  808. if ($settings.notificationEnabled && !document.hasFocus()) {
  809. const notification = new Notification(`${model.id}`, {
  810. body: responseMessage.content,
  811. icon: `${WEBUI_BASE_URL}/static/favicon.png`
  812. });
  813. }
  814. if ($settings?.responseAutoCopy ?? false) {
  815. copyToClipboard(responseMessage.content);
  816. }
  817. if ($settings.responseAutoPlayback && !$showCallOverlay) {
  818. await tick();
  819. document.getElementById(`speak-button-${responseMessage.id}`)?.click();
  820. }
  821. }
  822. }
  823. }
  824. } catch (error) {
  825. console.log(error);
  826. if ('detail' in error) {
  827. toast.error(error.detail);
  828. }
  829. break;
  830. }
  831. if (autoScroll) {
  832. scrollToBottom();
  833. }
  834. }
  835. await saveChatHandler(_chatId);
  836. } else {
  837. if (res !== null) {
  838. const error = await res.json();
  839. console.log(error);
  840. if ('detail' in error) {
  841. toast.error(error.detail);
  842. responseMessage.error = { content: error.detail };
  843. } else {
  844. toast.error(error.error);
  845. responseMessage.error = { content: error.error };
  846. }
  847. } else {
  848. toast.error(
  849. $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, { provider: 'Ollama' })
  850. );
  851. responseMessage.error = {
  852. content: $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
  853. provider: 'Ollama'
  854. })
  855. };
  856. }
  857. responseMessage.done = true;
  858. messages = messages;
  859. }
  860. stopResponseFlag = false;
  861. await tick();
  862. let lastSentence = extractSentencesForAudio(responseMessage.content)?.at(-1) ?? '';
  863. if (lastSentence) {
  864. eventTarget.dispatchEvent(
  865. new CustomEvent('chat', {
  866. detail: { id: responseMessageId, content: lastSentence }
  867. })
  868. );
  869. }
  870. eventTarget.dispatchEvent(
  871. new CustomEvent('chat:finish', {
  872. detail: {
  873. id: responseMessageId,
  874. content: responseMessage.content
  875. }
  876. })
  877. );
  878. if (autoScroll) {
  879. scrollToBottom();
  880. }
  881. if (messages.length == 2 && messages.at(1).content !== '' && selectedModels[0] === model.id) {
  882. window.history.replaceState(history.state, '', `/c/${_chatId}`);
  883. const _title = await generateChatTitle(userPrompt);
  884. await setChatTitle(_chatId, _title);
  885. }
  886. return _response;
  887. };
  888. const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
  889. let _response = null;
  890. const responseMessage = history.messages[responseMessageId];
  891. const userMessage = history.messages[responseMessage.parentId];
  892. let files = JSON.parse(JSON.stringify(chatFiles));
  893. if (model?.info?.meta?.knowledge ?? false) {
  894. files.push(...model.info.meta.knowledge);
  895. }
  896. files.push(
  897. ...(userMessage?.files ?? []).filter((item) =>
  898. ['doc', 'file', 'collection'].includes(item.type)
  899. ),
  900. ...(responseMessage?.files ?? []).filter((item) => ['web_search_results'].includes(item.type))
  901. );
  902. scrollToBottom();
  903. eventTarget.dispatchEvent(
  904. new CustomEvent('chat:start', {
  905. detail: {
  906. id: responseMessageId
  907. }
  908. })
  909. );
  910. await tick();
  911. try {
  912. const [res, controller] = await generateOpenAIChatCompletion(
  913. localStorage.token,
  914. {
  915. stream: true,
  916. model: model.id,
  917. stream_options:
  918. (model.info?.meta?.capabilities?.usage ?? false)
  919. ? {
  920. include_usage: true
  921. }
  922. : undefined,
  923. messages: [
  924. params?.system || $settings.system || (responseMessage?.userContext ?? null)
  925. ? {
  926. role: 'system',
  927. content: `${promptTemplate(
  928. params?.system ?? $settings?.system ?? '',
  929. $user.name,
  930. $settings?.userLocation
  931. ? await getAndUpdateUserLocation(localStorage.token)
  932. : undefined
  933. )}${
  934. (responseMessage?.userContext ?? null)
  935. ? `\n\nUser Context:\n${responseMessage?.userContext ?? ''}`
  936. : ''
  937. }`
  938. }
  939. : undefined,
  940. ...messages
  941. ]
  942. .filter((message) => message?.content?.trim())
  943. .map((message, idx, arr) => ({
  944. role: message.role,
  945. ...((message.files?.filter((file) => file.type === 'image').length > 0 ?? false) &&
  946. message.role === 'user'
  947. ? {
  948. content: [
  949. {
  950. type: 'text',
  951. text:
  952. arr.length - 1 !== idx
  953. ? message.content
  954. : (message?.raContent ?? message.content)
  955. },
  956. ...message.files
  957. .filter((file) => file.type === 'image')
  958. .map((file) => ({
  959. type: 'image_url',
  960. image_url: {
  961. url: file.url
  962. }
  963. }))
  964. ]
  965. }
  966. : {
  967. content:
  968. arr.length - 1 !== idx
  969. ? message.content
  970. : (message?.raContent ?? message.content)
  971. })
  972. })),
  973. seed: params?.seed ?? $settings?.params?.seed ?? undefined,
  974. stop:
  975. (params?.stop ?? $settings?.params?.stop ?? undefined)
  976. ? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map(
  977. (str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
  978. )
  979. : undefined,
  980. temperature: params?.temperature ?? $settings?.params?.temperature ?? undefined,
  981. top_p: params?.top_p ?? $settings?.params?.top_p ?? undefined,
  982. frequency_penalty:
  983. params?.frequency_penalty ?? $settings?.params?.frequency_penalty ?? undefined,
  984. max_tokens: params?.max_tokens ?? $settings?.params?.max_tokens ?? undefined,
  985. tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
  986. files: files.length > 0 ? files : undefined,
  987. session_id: $socket?.id,
  988. chat_id: $chatId,
  989. id: responseMessageId
  990. },
  991. `${WEBUI_BASE_URL}/api`
  992. );
  993. // Wait until history/message have been updated
  994. await tick();
  995. scrollToBottom();
  996. if (res && res.ok && res.body) {
  997. const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
  998. for await (const update of textStream) {
  999. const { value, done, citations, error, usage } = update;
  1000. if (error) {
  1001. await handleOpenAIError(error, null, model, responseMessage);
  1002. break;
  1003. }
  1004. if (done || stopResponseFlag || _chatId !== $chatId) {
  1005. responseMessage.done = true;
  1006. messages = messages;
  1007. if (stopResponseFlag) {
  1008. controller.abort('User: Stop Response');
  1009. } else {
  1010. const messages = createMessagesList(responseMessageId);
  1011. await chatCompletedHandler(_chatId, model.id, responseMessageId, messages);
  1012. }
  1013. _response = responseMessage.content;
  1014. break;
  1015. }
  1016. if (usage) {
  1017. responseMessage.info = { ...usage, openai: true };
  1018. }
  1019. if (citations) {
  1020. responseMessage.citations = citations;
  1021. continue;
  1022. }
  1023. if (responseMessage.content == '' && value == '\n') {
  1024. continue;
  1025. } else {
  1026. responseMessage.content += value;
  1027. if (navigator.vibrate && ($settings?.hapticFeedback ?? false)) {
  1028. navigator.vibrate(5);
  1029. }
  1030. const sentences = extractSentencesForAudio(responseMessage.content);
  1031. sentences.pop();
  1032. // dispatch only last sentence and make sure it hasn't been dispatched before
  1033. if (
  1034. sentences.length > 0 &&
  1035. sentences[sentences.length - 1] !== responseMessage.lastSentence
  1036. ) {
  1037. responseMessage.lastSentence = sentences[sentences.length - 1];
  1038. eventTarget.dispatchEvent(
  1039. new CustomEvent('chat', {
  1040. detail: { id: responseMessageId, content: sentences[sentences.length - 1] }
  1041. })
  1042. );
  1043. }
  1044. messages = messages;
  1045. }
  1046. if (autoScroll) {
  1047. scrollToBottom();
  1048. }
  1049. }
  1050. if ($settings.notificationEnabled && !document.hasFocus()) {
  1051. const notification = new Notification(`${model.id}`, {
  1052. body: responseMessage.content,
  1053. icon: `${WEBUI_BASE_URL}/static/favicon.png`
  1054. });
  1055. }
  1056. if ($settings.responseAutoCopy) {
  1057. copyToClipboard(responseMessage.content);
  1058. }
  1059. if ($settings.responseAutoPlayback && !$showCallOverlay) {
  1060. await tick();
  1061. document.getElementById(`speak-button-${responseMessage.id}`)?.click();
  1062. }
  1063. if ($chatId == _chatId) {
  1064. if (!$temporaryChatEnabled) {
  1065. chat = await updateChatById(localStorage.token, _chatId, {
  1066. models: selectedModels,
  1067. messages: messages,
  1068. history: history,
  1069. params: params,
  1070. files: chatFiles
  1071. });
  1072. currentChatPage.set(1);
  1073. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  1074. }
  1075. }
  1076. } else {
  1077. await handleOpenAIError(null, res, model, responseMessage);
  1078. }
  1079. } catch (error) {
  1080. await handleOpenAIError(error, null, model, responseMessage);
  1081. }
  1082. messages = messages;
  1083. stopResponseFlag = false;
  1084. await tick();
  1085. let lastSentence = extractSentencesForAudio(responseMessage.content)?.at(-1) ?? '';
  1086. if (lastSentence) {
  1087. eventTarget.dispatchEvent(
  1088. new CustomEvent('chat', {
  1089. detail: { id: responseMessageId, content: lastSentence }
  1090. })
  1091. );
  1092. }
  1093. eventTarget.dispatchEvent(
  1094. new CustomEvent('chat:finish', {
  1095. detail: {
  1096. id: responseMessageId,
  1097. content: responseMessage.content
  1098. }
  1099. })
  1100. );
  1101. if (autoScroll) {
  1102. scrollToBottom();
  1103. }
  1104. if (messages.length == 2 && selectedModels[0] === model.id) {
  1105. window.history.replaceState(history.state, '', `/c/${_chatId}`);
  1106. const _title = await generateChatTitle(userPrompt);
  1107. await setChatTitle(_chatId, _title);
  1108. }
  1109. return _response;
  1110. };
  1111. const handleOpenAIError = async (error, res: Response | null, model, responseMessage) => {
  1112. let errorMessage = '';
  1113. let innerError;
  1114. if (error) {
  1115. innerError = error;
  1116. } else if (res !== null) {
  1117. innerError = await res.json();
  1118. }
  1119. console.error(innerError);
  1120. if ('detail' in innerError) {
  1121. toast.error(innerError.detail);
  1122. errorMessage = innerError.detail;
  1123. } else if ('error' in innerError) {
  1124. if ('message' in innerError.error) {
  1125. toast.error(innerError.error.message);
  1126. errorMessage = innerError.error.message;
  1127. } else {
  1128. toast.error(innerError.error);
  1129. errorMessage = innerError.error;
  1130. }
  1131. } else if ('message' in innerError) {
  1132. toast.error(innerError.message);
  1133. errorMessage = innerError.message;
  1134. }
  1135. responseMessage.error = {
  1136. content:
  1137. $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
  1138. provider: model.name ?? model.id
  1139. }) +
  1140. '\n' +
  1141. errorMessage
  1142. };
  1143. responseMessage.done = true;
  1144. messages = messages;
  1145. };
  1146. const stopResponse = () => {
  1147. stopResponseFlag = true;
  1148. console.log('stopResponse');
  1149. };
  1150. const regenerateResponse = async (message) => {
  1151. console.log('regenerateResponse');
  1152. if (messages.length != 0) {
  1153. let userMessage = history.messages[message.parentId];
  1154. let userPrompt = userMessage.content;
  1155. if ((userMessage?.models ?? [...selectedModels]).length == 1) {
  1156. // If user message has only one model selected, sendPrompt automatically selects it for regeneration
  1157. await sendPrompt(userPrompt, userMessage.id);
  1158. } else {
  1159. // If there are multiple models selected, use the model of the response message for regeneration
  1160. // e.g. many model chat
  1161. await sendPrompt(userPrompt, userMessage.id, {
  1162. modelId: message.model,
  1163. modelIdx: message.modelIdx
  1164. });
  1165. }
  1166. }
  1167. };
  1168. const continueGeneration = async () => {
  1169. console.log('continueGeneration');
  1170. const _chatId = JSON.parse(JSON.stringify($chatId));
  1171. if (messages.length != 0 && messages.at(-1).done == true) {
  1172. const responseMessage = history.messages[history.currentId];
  1173. responseMessage.done = false;
  1174. await tick();
  1175. const model = $models.filter((m) => m.id === responseMessage.model).at(0);
  1176. if (model) {
  1177. if (model?.owned_by === 'openai') {
  1178. await sendPromptOpenAI(
  1179. model,
  1180. history.messages[responseMessage.parentId].content,
  1181. responseMessage.id,
  1182. _chatId
  1183. );
  1184. } else
  1185. await sendPromptOllama(
  1186. model,
  1187. history.messages[responseMessage.parentId].content,
  1188. responseMessage.id,
  1189. _chatId
  1190. );
  1191. }
  1192. } else {
  1193. toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
  1194. }
  1195. };
  1196. const generateChatTitle = async (userPrompt) => {
  1197. if ($settings?.title?.auto ?? true) {
  1198. const title = await generateTitle(
  1199. localStorage.token,
  1200. selectedModels[0],
  1201. userPrompt,
  1202. $chatId
  1203. ).catch((error) => {
  1204. console.error(error);
  1205. return 'New Chat';
  1206. });
  1207. return title;
  1208. } else {
  1209. return `${userPrompt}`;
  1210. }
  1211. };
  1212. const setChatTitle = async (_chatId, _title) => {
  1213. if (_chatId === $chatId) {
  1214. title = _title;
  1215. }
  1216. if (!$temporaryChatEnabled) {
  1217. chat = await updateChatById(localStorage.token, _chatId, { title: _title });
  1218. currentChatPage.set(1);
  1219. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  1220. }
  1221. };
  1222. const getWebSearchResults = async (model: string, parentId: string, responseId: string) => {
  1223. const responseMessage = history.messages[responseId];
  1224. const userMessage = history.messages[parentId];
  1225. responseMessage.statusHistory = [
  1226. {
  1227. done: false,
  1228. action: 'web_search',
  1229. description: $i18n.t('Generating search query')
  1230. }
  1231. ];
  1232. messages = messages;
  1233. const prompt = userMessage.content;
  1234. let searchQuery = await generateSearchQuery(localStorage.token, model, messages, prompt).catch(
  1235. (error) => {
  1236. console.log(error);
  1237. return prompt;
  1238. }
  1239. );
  1240. if (!searchQuery) {
  1241. toast.warning($i18n.t('No search query generated'));
  1242. responseMessage.statusHistory.push({
  1243. done: true,
  1244. error: true,
  1245. action: 'web_search',
  1246. description: 'No search query generated'
  1247. });
  1248. messages = messages;
  1249. }
  1250. responseMessage.statusHistory.push({
  1251. done: false,
  1252. action: 'web_search',
  1253. description: $i18n.t(`Searching "{{searchQuery}}"`, { searchQuery })
  1254. });
  1255. messages = messages;
  1256. const results = await runWebSearch(localStorage.token, searchQuery).catch((error) => {
  1257. console.log(error);
  1258. toast.error(error);
  1259. return null;
  1260. });
  1261. if (results) {
  1262. responseMessage.statusHistory.push({
  1263. done: true,
  1264. action: 'web_search',
  1265. description: $i18n.t('Searched {{count}} sites', { count: results.filenames.length }),
  1266. query: searchQuery,
  1267. urls: results.filenames
  1268. });
  1269. if (responseMessage?.files ?? undefined === undefined) {
  1270. responseMessage.files = [];
  1271. }
  1272. responseMessage.files.push({
  1273. collection_name: results.collection_name,
  1274. name: searchQuery,
  1275. type: 'web_search_results',
  1276. urls: results.filenames
  1277. });
  1278. messages = messages;
  1279. } else {
  1280. responseMessage.statusHistory.push({
  1281. done: true,
  1282. error: true,
  1283. action: 'web_search',
  1284. description: 'No search results found'
  1285. });
  1286. messages = messages;
  1287. }
  1288. };
  1289. const getTags = async () => {
  1290. return await getTagsById(localStorage.token, $chatId).catch(async (error) => {
  1291. return [];
  1292. });
  1293. };
  1294. const saveChatHandler = async (_chatId) => {
  1295. if ($chatId == _chatId) {
  1296. if (!$temporaryChatEnabled) {
  1297. chat = await updateChatById(localStorage.token, _chatId, {
  1298. messages: messages,
  1299. history: history,
  1300. models: selectedModels,
  1301. params: params,
  1302. files: chatFiles
  1303. });
  1304. currentChatPage.set(1);
  1305. await chats.set(await getChatList(localStorage.token, $currentChatPage));
  1306. }
  1307. }
  1308. };
  1309. const mergeResponses = async (messageId, responses, _chatId) => {
  1310. console.log('mergeResponses', messageId, responses);
  1311. const message = history.messages[messageId];
  1312. const mergedResponse = {
  1313. status: true,
  1314. content: ''
  1315. };
  1316. message.merged = mergedResponse;
  1317. messages = messages;
  1318. try {
  1319. const [res, controller] = await generateMoACompletion(
  1320. localStorage.token,
  1321. message.model,
  1322. history.messages[message.parentId].content,
  1323. responses
  1324. );
  1325. if (res && res.ok && res.body) {
  1326. const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
  1327. for await (const update of textStream) {
  1328. const { value, done, citations, error, usage } = update;
  1329. if (error || done) {
  1330. break;
  1331. }
  1332. if (mergedResponse.content == '' && value == '\n') {
  1333. continue;
  1334. } else {
  1335. mergedResponse.content += value;
  1336. messages = messages;
  1337. }
  1338. if (autoScroll) {
  1339. scrollToBottom();
  1340. }
  1341. }
  1342. await saveChatHandler(_chatId);
  1343. } else {
  1344. console.error(res);
  1345. }
  1346. } catch (e) {
  1347. console.error(e);
  1348. }
  1349. };
  1350. </script>
  1351. <svelte:head>
  1352. <title>
  1353. {title
  1354. ? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} | ${$WEBUI_NAME}`
  1355. : `${$WEBUI_NAME}`}
  1356. </title>
  1357. </svelte:head>
  1358. <audio id="audioElement" src="" style="display: none;" />
  1359. <EventConfirmDialog
  1360. bind:show={showEventConfirmation}
  1361. title={eventConfirmationTitle}
  1362. message={eventConfirmationMessage}
  1363. input={eventConfirmationInput}
  1364. inputPlaceholder={eventConfirmationInputPlaceholder}
  1365. inputValue={eventConfirmationInputValue}
  1366. on:confirm={(e) => {
  1367. if (e.detail) {
  1368. eventCallback(e.detail);
  1369. } else {
  1370. eventCallback(true);
  1371. }
  1372. }}
  1373. on:cancel={() => {
  1374. eventCallback(false);
  1375. }}
  1376. />
  1377. {#if $showCallOverlay}
  1378. <CallOverlay
  1379. {submitPrompt}
  1380. {stopResponse}
  1381. bind:files
  1382. modelId={selectedModelIds?.at(0) ?? null}
  1383. chatId={$chatId}
  1384. {eventTarget}
  1385. />
  1386. {/if}
  1387. {#if !chatIdProp || (loaded && chatIdProp)}
  1388. <div
  1389. class="h-screen max-h-[100dvh] {$showSidebar
  1390. ? 'md:max-w-[calc(100%-260px)]'
  1391. : ''} w-full max-w-full flex flex-col"
  1392. >
  1393. {#if $settings?.backgroundImageUrl ?? null}
  1394. <div
  1395. class="absolute {$showSidebar
  1396. ? 'md:max-w-[calc(100%-260px)] md:translate-x-[260px]'
  1397. : ''} top-0 left-0 w-full h-full bg-cover bg-center bg-no-repeat"
  1398. style="background-image: url({$settings.backgroundImageUrl}) "
  1399. />
  1400. <div
  1401. class="absolute top-0 left-0 w-full h-full bg-gradient-to-t from-white to-white/85 dark:from-gray-900 dark:to-[#171717]/90 z-0"
  1402. />
  1403. {/if}
  1404. <Navbar
  1405. {title}
  1406. bind:selectedModels
  1407. bind:showModelSelector
  1408. bind:showControls
  1409. shareEnabled={messages.length > 0}
  1410. {chat}
  1411. {initNewChat}
  1412. />
  1413. {#if $banners.length > 0 && messages.length === 0 && !$chatId && selectedModels.length <= 1}
  1414. <div
  1415. class="absolute top-[4.25rem] w-full {$showSidebar
  1416. ? 'md:max-w-[calc(100%-260px)]'
  1417. : ''} {showControls ? 'lg:pr-[24rem]' : ''} z-20"
  1418. >
  1419. <div class=" flex flex-col gap-1 w-full">
  1420. {#each $banners.filter( (b) => (b.dismissible ? !JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]').includes(b.id) : true) ) as banner}
  1421. <Banner
  1422. {banner}
  1423. on:dismiss={(e) => {
  1424. const bannerId = e.detail;
  1425. localStorage.setItem(
  1426. 'dismissedBannerIds',
  1427. JSON.stringify(
  1428. [
  1429. bannerId,
  1430. ...JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]')
  1431. ].filter((id) => $banners.find((b) => b.id === id))
  1432. )
  1433. );
  1434. }}
  1435. />
  1436. {/each}
  1437. </div>
  1438. </div>
  1439. {/if}
  1440. <div class="flex flex-col flex-auto z-10">
  1441. <div
  1442. class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full z-10 scrollbar-hidden {showControls
  1443. ? 'lg:pr-[24rem]'
  1444. : ''}"
  1445. id="messages-container"
  1446. bind:this={messagesContainerElement}
  1447. on:scroll={(e) => {
  1448. autoScroll =
  1449. messagesContainerElement.scrollHeight - messagesContainerElement.scrollTop <=
  1450. messagesContainerElement.clientHeight + 5;
  1451. }}
  1452. >
  1453. <div class=" h-full w-full flex flex-col {chatIdProp ? 'py-4' : 'pt-2 pb-4'}">
  1454. <Messages
  1455. chatId={$chatId}
  1456. {selectedModels}
  1457. {processing}
  1458. bind:history
  1459. bind:messages
  1460. bind:autoScroll
  1461. bind:prompt
  1462. bottomPadding={files.length > 0}
  1463. {sendPrompt}
  1464. {continueGeneration}
  1465. {regenerateResponse}
  1466. {mergeResponses}
  1467. {chatActionHandler}
  1468. />
  1469. </div>
  1470. </div>
  1471. <div class={showControls ? 'lg:pr-[24rem]' : ''}>
  1472. <MessageInput
  1473. bind:files
  1474. bind:prompt
  1475. bind:autoScroll
  1476. bind:selectedToolIds
  1477. bind:webSearchEnabled
  1478. bind:atSelectedModel
  1479. availableToolIds={selectedModelIds.reduce((a, e, i, arr) => {
  1480. const model = $models.find((m) => m.id === e);
  1481. if (model?.info?.meta?.toolIds ?? false) {
  1482. return [...new Set([...a, ...model.info.meta.toolIds])];
  1483. }
  1484. return a;
  1485. }, [])}
  1486. transparentBackground={$settings?.backgroundImageUrl ?? false}
  1487. {selectedModels}
  1488. {messages}
  1489. {submitPrompt}
  1490. {stopResponse}
  1491. />
  1492. </div>
  1493. </div>
  1494. <ChatControls
  1495. models={selectedModelIds.reduce((a, e, i, arr) => {
  1496. const model = $models.find((m) => m.id === e);
  1497. if (model) {
  1498. return [...a, model];
  1499. }
  1500. return a;
  1501. }, [])}
  1502. bind:show={showControls}
  1503. bind:chatFiles
  1504. bind:params
  1505. />
  1506. </div>
  1507. {/if}