Chat.svelte 66 KB

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