Chat.svelte 63 KB

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