index.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. import { v4 as uuidv4 } from 'uuid';
  2. import sha256 from 'js-sha256';
  3. import { getOllamaModels } from '$lib/apis/ollama';
  4. import { getOpenAIModels } from '$lib/apis/openai';
  5. import { getLiteLLMModels } from '$lib/apis/litellm';
  6. export const getModels = async (token: string) => {
  7. let models = await Promise.all([
  8. await getOllamaModels(token).catch((error) => {
  9. console.log(error);
  10. return null;
  11. }),
  12. await getOpenAIModels(token).catch((error) => {
  13. console.log(error);
  14. return null;
  15. }),
  16. await getLiteLLMModels(token).catch((error) => {
  17. console.log(error);
  18. return null;
  19. })
  20. ]);
  21. models = models
  22. .filter((models) => models)
  23. .reduce((a, e, i, arr) => a.concat(e, ...(i < arr.length - 1 ? [{ name: 'hr' }] : [])), []);
  24. return models;
  25. };
  26. //////////////////////////
  27. // Helper functions
  28. //////////////////////////
  29. export const capitalizeFirstLetter = (string) => {
  30. return string.charAt(0).toUpperCase() + string.slice(1);
  31. };
  32. export const splitStream = (splitOn) => {
  33. let buffer = '';
  34. return new TransformStream({
  35. transform(chunk, controller) {
  36. buffer += chunk;
  37. const parts = buffer.split(splitOn);
  38. parts.slice(0, -1).forEach((part) => controller.enqueue(part));
  39. buffer = parts[parts.length - 1];
  40. },
  41. flush(controller) {
  42. if (buffer) controller.enqueue(buffer);
  43. }
  44. });
  45. };
  46. export const convertMessagesToHistory = (messages) => {
  47. const history = {
  48. messages: {},
  49. currentId: null
  50. };
  51. let parentMessageId = null;
  52. let messageId = null;
  53. for (const message of messages) {
  54. messageId = uuidv4();
  55. if (parentMessageId !== null) {
  56. history.messages[parentMessageId].childrenIds = [
  57. ...history.messages[parentMessageId].childrenIds,
  58. messageId
  59. ];
  60. }
  61. history.messages[messageId] = {
  62. ...message,
  63. id: messageId,
  64. parentId: parentMessageId,
  65. childrenIds: []
  66. };
  67. parentMessageId = messageId;
  68. }
  69. history.currentId = messageId;
  70. return history;
  71. };
  72. export const getGravatarURL = (email) => {
  73. // Trim leading and trailing whitespace from
  74. // an email address and force all characters
  75. // to lower case
  76. const address = String(email).trim().toLowerCase();
  77. // Create a SHA256 hash of the final string
  78. const hash = sha256(address);
  79. // Grab the actual image URL
  80. return `https://www.gravatar.com/avatar/${hash}`;
  81. };
  82. export const generateInitialsImage = (name) => {
  83. const canvas = document.createElement('canvas');
  84. const ctx = canvas.getContext('2d');
  85. canvas.width = 100;
  86. canvas.height = 100;
  87. ctx.fillStyle = '#F39C12';
  88. ctx.fillRect(0, 0, canvas.width, canvas.height);
  89. ctx.fillStyle = '#FFFFFF';
  90. ctx.font = '40px Helvetica';
  91. ctx.textAlign = 'center';
  92. ctx.textBaseline = 'middle';
  93. const sanitizedName = name.trim();
  94. const initials =
  95. sanitizedName.length > 0
  96. ? sanitizedName[0] +
  97. (sanitizedName.split(' ').length > 1
  98. ? sanitizedName[sanitizedName.lastIndexOf(' ') + 1]
  99. : '')
  100. : '';
  101. ctx.fillText(initials.toUpperCase(), canvas.width / 2, canvas.height / 2);
  102. return canvas.toDataURL();
  103. };
  104. export const copyToClipboard = (text) => {
  105. if (!navigator.clipboard) {
  106. const textArea = document.createElement('textarea');
  107. textArea.value = text;
  108. // Avoid scrolling to bottom
  109. textArea.style.top = '0';
  110. textArea.style.left = '0';
  111. textArea.style.position = 'fixed';
  112. document.body.appendChild(textArea);
  113. textArea.focus();
  114. textArea.select();
  115. try {
  116. const successful = document.execCommand('copy');
  117. const msg = successful ? 'successful' : 'unsuccessful';
  118. console.log('Fallback: Copying text command was ' + msg);
  119. } catch (err) {
  120. console.error('Fallback: Oops, unable to copy', err);
  121. }
  122. document.body.removeChild(textArea);
  123. return;
  124. }
  125. navigator.clipboard.writeText(text).then(
  126. function () {
  127. console.log('Async: Copying to clipboard was successful!');
  128. },
  129. function (err) {
  130. console.error('Async: Could not copy text: ', err);
  131. }
  132. );
  133. };
  134. export const compareVersion = (latest, current) => {
  135. return current === '0.0.0'
  136. ? false
  137. : current.localeCompare(latest, undefined, {
  138. numeric: true,
  139. sensitivity: 'case',
  140. caseFirst: 'upper'
  141. }) < 0;
  142. };
  143. export const findWordIndices = (text) => {
  144. const regex = /\[([^\]]+)\]/g;
  145. const matches = [];
  146. let match;
  147. while ((match = regex.exec(text)) !== null) {
  148. matches.push({
  149. word: match[1],
  150. startIndex: match.index,
  151. endIndex: regex.lastIndex - 1
  152. });
  153. }
  154. return matches;
  155. };
  156. export const removeFirstHashWord = (inputString) => {
  157. // Split the string into an array of words
  158. const words = inputString.split(' ');
  159. // Find the index of the first word that starts with #
  160. const index = words.findIndex((word) => word.startsWith('#'));
  161. // Remove the first word with #
  162. if (index !== -1) {
  163. words.splice(index, 1);
  164. }
  165. // Join the remaining words back into a string
  166. const resultString = words.join(' ');
  167. return resultString;
  168. };
  169. export const transformFileName = (fileName) => {
  170. // Convert to lowercase
  171. const lowerCaseFileName = fileName.toLowerCase();
  172. // Remove special characters using regular expression
  173. const sanitizedFileName = lowerCaseFileName.replace(/[^\w\s]/g, '');
  174. // Replace spaces with dashes
  175. const finalFileName = sanitizedFileName.replace(/\s+/g, '-');
  176. return finalFileName;
  177. };
  178. export const calculateSHA256 = async (file) => {
  179. // Create a FileReader to read the file asynchronously
  180. const reader = new FileReader();
  181. // Define a promise to handle the file reading
  182. const readFile = new Promise((resolve, reject) => {
  183. reader.onload = () => resolve(reader.result);
  184. reader.onerror = reject;
  185. });
  186. // Read the file as an ArrayBuffer
  187. reader.readAsArrayBuffer(file);
  188. try {
  189. // Wait for the FileReader to finish reading the file
  190. const buffer = await readFile;
  191. // Convert the ArrayBuffer to a Uint8Array
  192. const uint8Array = new Uint8Array(buffer);
  193. // Calculate the SHA-256 hash using Web Crypto API
  194. const hashBuffer = await crypto.subtle.digest('SHA-256', uint8Array);
  195. // Convert the hash to a hexadecimal string
  196. const hashArray = Array.from(new Uint8Array(hashBuffer));
  197. const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, '0')).join('');
  198. return `${hashHex}`;
  199. } catch (error) {
  200. console.error('Error calculating SHA-256 hash:', error);
  201. throw error;
  202. }
  203. };
  204. export const getImportOrigin = (_chats) => {
  205. // Check what external service chat imports are from
  206. if ('mapping' in _chats[0]) {
  207. return 'openai';
  208. }
  209. return 'webui';
  210. };
  211. const convertOpenAIMessages = (convo) => {
  212. // Parse OpenAI chat messages and create chat dictionary for creating new chats
  213. const mapping = convo['mapping'];
  214. const messages = [];
  215. let currentId = '';
  216. let lastId = null;
  217. for (let message_id in mapping) {
  218. const message = mapping[message_id];
  219. currentId = message_id;
  220. try {
  221. if (
  222. messages.length == 0 &&
  223. (message['message'] == null ||
  224. (message['message']['content']['parts']?.[0] == '' &&
  225. message['message']['content']['text'] == null))
  226. ) {
  227. // Skip chat messages with no content
  228. continue;
  229. } else {
  230. const new_chat = {
  231. id: message_id,
  232. parentId: lastId,
  233. childrenIds: message['children'] || [],
  234. role: message['message']?.['author']?.['role'] !== 'user' ? 'assistant' : 'user',
  235. content:
  236. message['message']?.['content']?.['parts']?.[0] ||
  237. message['message']?.['content']?.['text'] ||
  238. '',
  239. model: 'gpt-3.5-turbo',
  240. done: true,
  241. context: null
  242. };
  243. messages.push(new_chat);
  244. lastId = currentId;
  245. }
  246. } catch (error) {
  247. console.log('Error with', message, '\nError:', error);
  248. }
  249. }
  250. let history = {};
  251. messages.forEach((obj) => (history[obj.id] = obj));
  252. const chat = {
  253. history: {
  254. currentId: currentId,
  255. messages: history // Need to convert this to not a list and instead a json object
  256. },
  257. models: ['gpt-3.5-turbo'],
  258. messages: messages,
  259. options: {},
  260. timestamp: convo['create_time'],
  261. title: convo['title'] ?? 'New Chat'
  262. };
  263. return chat;
  264. };
  265. const validateChat = (chat) => {
  266. // Because ChatGPT sometimes has features we can't use like DALL-E or migh have corrupted messages, need to validate
  267. const messages = chat.messages;
  268. // Check if messages array is empty
  269. if (messages.length === 0) {
  270. return false;
  271. }
  272. // Last message's children should be an empty array
  273. const lastMessage = messages[messages.length - 1];
  274. if (lastMessage.childrenIds.length !== 0) {
  275. return false;
  276. }
  277. // First message's parent should be null
  278. const firstMessage = messages[0];
  279. if (firstMessage.parentId !== null) {
  280. return false;
  281. }
  282. // Every message's content should be a string
  283. for (let message of messages) {
  284. if (typeof message.content !== 'string') {
  285. return false;
  286. }
  287. }
  288. return true;
  289. };
  290. export const convertOpenAIChats = (_chats) => {
  291. // Create a list of dictionaries with each conversation from import
  292. const chats = [];
  293. let failed = 0;
  294. for (let convo of _chats) {
  295. const chat = convertOpenAIMessages(convo);
  296. if (validateChat(chat)) {
  297. chats.push({
  298. id: convo['id'],
  299. user_id: '',
  300. title: convo['title'],
  301. chat: chat,
  302. timestamp: convo['timestamp']
  303. });
  304. } else {
  305. failed++;
  306. }
  307. }
  308. console.log(failed, 'Conversations could not be imported');
  309. return chats;
  310. };
  311. export const isValidHttpUrl = (string) => {
  312. let url;
  313. try {
  314. url = new URL(string);
  315. } catch (_) {
  316. return false;
  317. }
  318. return url.protocol === 'http:' || url.protocol === 'https:';
  319. };
  320. export const removeEmojis = (str) => {
  321. // Regular expression to match emojis
  322. const emojiRegex = /[\uD800-\uDBFF][\uDC00-\uDFFF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g;
  323. // Replace emojis with an empty string
  324. return str.replace(emojiRegex, '');
  325. };
  326. export const extractSentences = (text) => {
  327. // Split the paragraph into sentences based on common punctuation marks
  328. const sentences = text.split(/(?<=[.!?])/);
  329. return sentences
  330. .map((sentence) => removeEmojis(sentence.trim()))
  331. .filter((sentence) => sentence !== '');
  332. };
  333. export const blobToFile = (blob, fileName) => {
  334. // Create a new File object from the Blob
  335. const file = new File([blob], fileName, { type: blob.type });
  336. return file;
  337. };