index.ts 7.7 KB

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