index.ts 6.51 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { v4 as uuidv4 } from 'uuid';
import sha256 from 'js-sha256';

//////////////////////////
// Helper functions
//////////////////////////

export const splitStream = (splitOn) => {
	let buffer = '';
	return new TransformStream({
		transform(chunk, controller) {
			buffer += chunk;
			const parts = buffer.split(splitOn);
			parts.slice(0, -1).forEach((part) => controller.enqueue(part));
			buffer = parts[parts.length - 1];
		},
		flush(controller) {
			if (buffer) controller.enqueue(buffer);
		}
	});
};

export const convertMessagesToHistory = (messages) => {
24
	const history = {
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
		messages: {},
		currentId: null
	};

	let parentMessageId = null;
	let messageId = null;

	for (const message of messages) {
		messageId = uuidv4();

		if (parentMessageId !== null) {
			history.messages[parentMessageId].childrenIds = [
				...history.messages[parentMessageId].childrenIds,
				messageId
			];
		}

		history.messages[messageId] = {
			...message,
			id: messageId,
			parentId: parentMessageId,
			childrenIds: []
		};

		parentMessageId = messageId;
	}

	history.currentId = messageId;
	return history;
};

export const getGravatarURL = (email) => {
	// Trim leading and trailing whitespace from
	// an email address and force all characters
	// to lower case
	const address = String(email).trim().toLowerCase();

	// Create a SHA256 hash of the final string
	const hash = sha256(address);

	// Grab the actual image URL
	return `https://www.gravatar.com/avatar/${hash}`;
};
Timothy J. Baek's avatar
Timothy J. Baek committed
68

Timothy J. Baek's avatar
Timothy J. Baek committed
69
export const copyToClipboard = (text) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
70
	if (!navigator.clipboard) {
Timothy J. Baek's avatar
Timothy J. Baek committed
71
		const textArea = document.createElement('textarea');
Timothy J. Baek's avatar
Timothy J. Baek committed
72
73
74
75
76
77
78
79
80
81
82
83
		textArea.value = text;

		// Avoid scrolling to bottom
		textArea.style.top = '0';
		textArea.style.left = '0';
		textArea.style.position = 'fixed';

		document.body.appendChild(textArea);
		textArea.focus();
		textArea.select();

		try {
Timothy J. Baek's avatar
Timothy J. Baek committed
84
85
			const successful = document.execCommand('copy');
			const msg = successful ? 'successful' : 'unsuccessful';
Timothy J. Baek's avatar
Timothy J. Baek committed
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
			console.log('Fallback: Copying text command was ' + msg);
		} catch (err) {
			console.error('Fallback: Oops, unable to copy', err);
		}

		document.body.removeChild(textArea);
		return;
	}
	navigator.clipboard.writeText(text).then(
		function () {
			console.log('Async: Copying to clipboard was successful!');
		},
		function (err) {
			console.error('Async: Could not copy text: ', err);
		}
	);
};
Timothy J. Baek's avatar
Timothy J. Baek committed
103
104

export const checkVersion = (required, current) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
105
	// Returns true when current version is below required
106
	return current === '0.0.0'
Timothy J. Baek's avatar
Timothy J. Baek committed
107
		? false
108
109
110
111
112
		: current.localeCompare(required, undefined, {
				numeric: true,
				sensitivity: 'case',
				caseFirst: 'upper'
		  }) < 0;
Timothy J. Baek's avatar
Timothy J. Baek committed
113
};
Timothy J. Baek's avatar
Timothy J. Baek committed
114
115
116

export const findWordIndices = (text) => {
	const regex = /\[([^\]]+)\]/g;
117
	const matches = [];
Timothy J. Baek's avatar
Timothy J. Baek committed
118
119
120
121
122
123
124
125
126
127
128
129
	let match;

	while ((match = regex.exec(text)) !== null) {
		matches.push({
			word: match[1],
			startIndex: match.index,
			endIndex: regex.lastIndex - 1
		});
	}

	return matches;
};
130

Timothy J. Baek's avatar
Timothy J. Baek committed
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
export const removeFirstHashWord = (inputString) => {
	// Split the string into an array of words
	const words = inputString.split(' ');

	// Find the index of the first word that starts with #
	const index = words.findIndex((word) => word.startsWith('#'));

	// Remove the first word with #
	if (index !== -1) {
		words.splice(index, 1);
	}

	// Join the remaining words back into a string
	const resultString = words.join(' ');

	return resultString;
};

149
150
151
152
153
154
155
156
157
158
159
160
161
export const transformFileName = (fileName) => {
	// Convert to lowercase
	const lowerCaseFileName = fileName.toLowerCase();

	// Remove special characters using regular expression
	const sanitizedFileName = lowerCaseFileName.replace(/[^\w\s]/g, '');

	// Replace spaces with dashes
	const finalFileName = sanitizedFileName.replace(/\s+/g, '-');

	return finalFileName;
};

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
export const calculateSHA256 = async (file) => {
	// Create a FileReader to read the file asynchronously
	const reader = new FileReader();

	// Define a promise to handle the file reading
	const readFile = new Promise((resolve, reject) => {
		reader.onload = () => resolve(reader.result);
		reader.onerror = reject;
	});

	// Read the file as an ArrayBuffer
	reader.readAsArrayBuffer(file);

	try {
		// Wait for the FileReader to finish reading the file
		const buffer = await readFile;

		// Convert the ArrayBuffer to a Uint8Array
		const uint8Array = new Uint8Array(buffer);

		// Calculate the SHA-256 hash using Web Crypto API
		const hashBuffer = await crypto.subtle.digest('SHA-256', uint8Array);

		// Convert the hash to a hexadecimal string
		const hashArray = Array.from(new Uint8Array(hashBuffer));
		const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, '0')).join('');

Timothy J. Baek's avatar
Timothy J. Baek committed
189
		return `${hashHex}`;
190
191
192
193
194
	} catch (error) {
		console.error('Error calculating SHA-256 hash:', error);
		throw error;
	}
};
195
196
197

export const getImportOrigin = (_chats) => {
	// Check what external service chat imports are from
Timothy J. Baek's avatar
Timothy J. Baek committed
198
199
200
201
202
	if ('mapping' in _chats[0]) {
		return 'openai';
	}
	return 'webui';
};
203

Timothy J. Baek's avatar
Timothy J. Baek committed
204
const convertOpenAIMessages = (convo) => {
205
	// Parse OpenAI chat messages and create chat dictionary for creating new chats
Timothy J. Baek's avatar
Timothy J. Baek committed
206
	const mapping = convo['mapping'];
207
	const messages = [];
Timothy J. Baek's avatar
Timothy J. Baek committed
208
	let currentId = '';
209

Timothy J. Baek's avatar
Timothy J. Baek committed
210
211
	for (let message_id in mapping) {
		const message = mapping[message_id];
212
		currentId = message_id;
Timothy J. Baek's avatar
Timothy J. Baek committed
213
		if (message['message'] == null || message['message']['content']['parts'][0] == '') {
214
215
216
217
			// Skip chat messages with no content
			continue;
		} else {
			const new_chat = {
Timothy J. Baek's avatar
Timothy J. Baek committed
218
				id: message_id,
Timothy J. Baek's avatar
Timothy J. Baek committed
219
				parentId: messages.length > 0 && message['parent'] in mapping ? message['parent'] : null,
Timothy J. Baek's avatar
Timothy J. Baek committed
220
221
222
				childrenIds: message['children'] || [],
				role: message['message']?.['author']?.['role'] !== 'user' ? 'assistant' : 'user',
				content: message['message']?.['content']?.['parts']?.[0] || '',
Timothy J. Baek's avatar
Timothy J. Baek committed
223
				model: 'gpt-3.5-turbo',
Timothy J. Baek's avatar
Timothy J. Baek committed
224
225
226
227
				done: true,
				context: null
			};
			messages.push(new_chat);
228
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
229
	}
230
231

	let history = {};
Timothy J. Baek's avatar
Timothy J. Baek committed
232
	messages.forEach((obj) => (history[obj.id] = obj));
233
234

	const chat = {
Timothy J. Baek's avatar
Timothy J. Baek committed
235
236
237
		history: {
			currentId: currentId,
			messages: history // Need to convert this to not a list and instead a json object
238
		},
Timothy J. Baek's avatar
Timothy J. Baek committed
239
		models: ['gpt-3.5-turbo'],
Timothy J. Baek's avatar
Timothy J. Baek committed
240
241
242
		messages: messages,
		options: {},
		timestamp: convo['create_time'],
Timothy J. Baek's avatar
Timothy J. Baek committed
243
		title: convo['title'] ?? 'New Chat'
Timothy J. Baek's avatar
Timothy J. Baek committed
244
245
246
	};
	return chat;
};
247

Timothy J. Baek's avatar
Timothy J. Baek committed
248
export const convertOpenAIChats = (_chats) => {
249
	// Create a list of dictionaries with each conversation from import
Timothy J. Baek's avatar
Timothy J. Baek committed
250
251
	const chats = [];
	for (let convo of _chats) {
Timothy J. Baek's avatar
Timothy J. Baek committed
252
253
254
255
256
257
258
259
260
261
262
		const chat = convertOpenAIMessages(convo);

		if (Object.keys(chat.history.messages).length > 0) {
			chats.push({
				id: convo['id'],
				user_id: '',
				title: convo['title'],
				chat: chat,
				timestamp: convo['timestamp']
			});
		}
263
	}
Timothy J. Baek's avatar
Timothy J. Baek committed
264
265
	return chats;
};