Chat.svelte 64 KB

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