-
Notifications
You must be signed in to change notification settings - Fork 1
/
ui_functions.py
377 lines (314 loc) · 16.5 KB
/
ui_functions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
from PySide6.QtWidgets import (QMessageBox, QFileDialog, QTableWidgetItem,
QHeaderView,QAbstractItemView)
from PySide6.QtCore import QSize, QEasingCurve, QPropertyAnimation, Qt, QTimer
from PySide6.QtGui import QIcon, QMovie
from srt import parse, SRTParseError, compose
from openai import AsyncOpenAI
from qasync import asyncSlot, QEventLoop
from asyncio import sleep, set_event_loop
import numpy as np
import tiktoken
import json
template_file = None # Stores the loaded .srt file
template_loaded = False # Checked if template is loaded. Needed so that "Next" button doesn't keep loading the same file.
async_signal_used = False # Needed for the Start/Pause button
disable_delete = False # Used to disable deletetion while model is running
waiting_loop = True
class SETTINGS:
EXPAND_WIDTH = 360
TIME_ANIMATION = 500
MENU_SELECTED_STYLESHEET = """
border-left: 22px solid qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0.2 rgb(0, 128, 255), stop:0.201 rgba(85, 170, 255, 0));
background-color: rgb(40, 44, 52);
background-position: left center;
"""
MODEL_MAP = {
"GPT 4o": "gpt-4o",
"GPT 4 Turbo": "gpt-4-turbo",
"GPT 3.5 Turbo": "gpt-3.5-turbo",
}
def button_click(main_window, btn):
btn_name = btn.objectName()
if btn_name == "btn_back":
main_window.StackedWidget.setCurrentWidget(main_window.Home)
def animate_widget(main_window, widget, next_widget=None):
width = widget.width()
width_extended = SETTINGS.EXPAND_WIDTH if width == 0 else 0
main_window.animate_expand = QPropertyAnimation(widget, b"minimumWidth")
main_window.animate_expand.setDuration(SETTINGS.TIME_ANIMATION)
main_window.animate_expand.setStartValue(width)
main_window.animate_expand.setEndValue(width_extended)
main_window.animate_expand.setEasingCurve(QEasingCurve.InOutQuart)
main_window.animate_expand.finished.connect(lambda: animate_widget(main_window, next_widget) if next_widget else None)
main_window.animate_expand.start()
def expand_settings(main_window):
main_window.btn_settings.setEnabled(False)
main_window.btn_info.setEnabled(False)
if main_window.AboutFrame.width() == SETTINGS.EXPAND_WIDTH:
animate_widget(main_window, main_window.AboutFrame, main_window.SettingsExpand)
QTimer.singleShot(2 * SETTINGS.TIME_ANIMATION, lambda: enable_buttons(main_window))
else:
animate_widget(main_window, main_window.SettingsExpand)
QTimer.singleShot(SETTINGS.TIME_ANIMATION, lambda: enable_buttons(main_window))
def expand_about(main_window):
main_window.btn_settings.setEnabled(False)
main_window.btn_info.setEnabled(False)
if main_window.SettingsExpand.width() == SETTINGS.EXPAND_WIDTH:
animate_widget(main_window, main_window.SettingsExpand, main_window.AboutFrame)
QTimer.singleShot(2 * SETTINGS.TIME_ANIMATION, lambda: enable_buttons(main_window))
else:
animate_widget(main_window, main_window.AboutFrame)
QTimer.singleShot(SETTINGS.TIME_ANIMATION, lambda: enable_buttons(main_window))
def enable_buttons(main_window):
main_window.btn_settings.setEnabled(True)
main_window.btn_info.setEnabled(True)
def issue_warning_error(main_window, title, text):
QMessageBox.warning(main_window, title, text)
def load_template_file(main_window):
global template_loaded
main_window.btn_save.setEnabled(False)
dialog = QFileDialog()
dialog.setNameFilters(["Custom Files (*.srt)", "All Files (*)"])
selected_file = ""
if dialog.exec_(): # Open a dialog window and waits. Finishes when file is selected.
selected_file = dialog.selectedFiles()[0]
try:
if selected_file != "": # Needed in case dialog was simply opened and closed without selecting a file
with open(selected_file, 'r', encoding='utf-8') as file: # Could throw FileNotFoundError
template_content = file.read()
global template_file
template_file = list(parse(template_content)) # Could throw SRTParseError
template_file = [sentence for sentence in template_file if sentence.content.strip()] # Removes empty sentences
main_window.subtitle_template.setText(selected_file)
template_loaded = True
except FileNotFoundError:
issue_warning_error(main_window, "Warning", "File not found")
except SRTParseError:
issue_warning_error(main_window, "Error", "Can't parse the template file\nFile might be corrupted")
except IsADirectoryError:
issue_warning_error(main_window, "Error", "Can't parse directories.\nPlease select a template file")
def check_inputs(main_window):
global template_loaded
if not main_window.subtitle_template.text() :
issue_warning_error(main_window, "Warning", "Specify a template file")
elif not main_window.target_language.text():
issue_warning_error(main_window, "Warning", "Specify a target language")
else:
main_window.StackedWidget.setCurrentWidget(main_window.PostProcessing)
if template_loaded == True:
display_template_content(main_window)
template_loaded = False
def display_template_content(main_window):
main_window.data_table.setRowCount(0)
main_window.data_table.setColumnCount(2)
main_window.data_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
main_window.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
main_window.data_table.horizontalHeader().setStretchLastSection(True)
main_window.data_table.setHorizontalHeaderLabels(["Source Text", "Target Text"])
main_window.data_table.setRowCount(len(template_file))
for i, sentence in enumerate(template_file):
item = QTableWidgetItem(sentence.content)
main_window.data_table.setItem(i, 0, item)
main_window.data_table.setItem(i, 1, QTableWidgetItem(""))
# Enable or disable the delete button based on row selection
def items_selected(main_window):
indexes = main_window.data_table.selectedItems()
if not disable_delete:
if len(indexes) == 0:
main_window.btn_delete_row.setEnabled(False)
else:
main_window.btn_delete_row.setEnabled(True)
def delete_row(main_window):
# Save table
table_data = []
for i in range(main_window.data_table.rowCount()):
row_data = []
for j in range(main_window.data_table.columnCount()):
item = main_window.data_table.item(i, j)
row_data.append(item.text() if item and item.text() else "")
table_data.append(row_data)
# Mark for deletion
selectedItems = main_window.data_table.selectedItems()
for item in selectedItems:
table_data[item.row()][item.column()] = None # Mark the item for deletion
# Delete
main_window.data_table.setRowCount(0) # Clear the table
for col in range(len(table_data[0])):
column_items = [row_data[col] for row_data in table_data if row_data[col] is not None] # Skip deleted items
for row, item in enumerate(column_items):
if row == main_window.data_table.rowCount():
main_window.data_table.insertRow(row)
main_window.data_table.setItem(row, col, QTableWidgetItem(item))
def save_template_file(main_window):
global template_file
options = QFileDialog.Options()
fileName, _ = QFileDialog.getSaveFileName(None,"Save File", "","Custom Files (*.srt);;All Files (*)", options=options)
if fileName:
for i, subtitle in enumerate(template_file):
table_row_translation = main_window.data_table.item(i, 1)
if table_row_translation is not None:
translated_text = table_row_translation.text()
subtitle.content = translated_text.strip()
with open(fileName, 'w', encoding="utf-8") as file:
file.write(compose(template_file))
QMessageBox.information(main_window, "File Saved", "File was written successfully!")
def on_box_cosine_changed(main_window, check_box):
if check_box.isChecked():
main_window.threshold_edit.setEnabled(True)
else:
main_window.threshold_edit.setEnabled(False)
def cosine_similarity(sentences_embeddings, translations_embeddings):
dot_product = np.dot(sentences_embeddings, translations_embeddings)
# Compute the L2 norms (Euclidean lengths) of a and b
norm_a = np.linalg.norm(sentences_embeddings)
norm_b = np.linalg.norm(translations_embeddings)
# Compute cosine similarity
similarity = dot_product / (norm_a * norm_b)
return similarity
def create_sentences_dictionaries(main_window, prompt, max_tokens, model):
enc = tiktoken.encoding_for_model(model)
prompt_tokens = enc.encode(prompt)
current_tokens = len(prompt_tokens)
sentences_list = {}
num_dictionaries = 0
sentences_list[f"{num_dictionaries}"] = {}
firstCheck = True
for i in range(main_window.data_table.rowCount()):
item = main_window.data_table.item(i, 0) # Get the item in the first column
sen = item.text().strip() # Get the text of the item
# Replace multiple spaces with a single space
tokens = enc.encode(sen)
if current_tokens + len(tokens) > max_tokens:
current_tokens = len(prompt_tokens)
if not firstCheck:
num_dictionaries = num_dictionaries + 1
sentences_list[f"{num_dictionaries}"] = {}
sentences_list[f"{num_dictionaries}"][f"s_{i}"] = sen
current_tokens += len(tokens)
if firstCheck:
firstCheck = False
return sentences_list
def disable_some_buttons(main_window):
main_window.btn_start.setIcon(QIcon(":/icons/images/icons/cil-media-pause.png"))
main_window.btn_start.setText("Pause")
main_window.btn_back.setEnabled(False)
main_window.btn_settings.setEnabled(False)
main_window.btn_save.setEnabled(False)
main_window.data_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
main_window.data_table.setFocusPolicy(Qt.NoFocus)
main_window.data_table.setSelectionMode(QAbstractItemView.NoSelection)
def enable_some_buttons(main_window):
main_window.btn_start.setIcon(QIcon(":/icons/images/icons/cil-media-play.png"))
main_window.btn_start.setText("Start")
main_window.btn_back.setEnabled(True)
main_window.btn_settings.setEnabled(True)
main_window.btn_save.setEnabled(True)
main_window.data_table.setEditTriggers(QAbstractItemView.AllEditTriggers)
main_window.data_table.setFocusPolicy(Qt.StrongFocus)
main_window.data_table.setSelectionMode(QAbstractItemView.ExtendedSelection)
@asyncSlot()
async def get_embeddings(client, text):
embeddings_response = await client.embeddings.create(
model='text-embedding-ada-002',
input=text
)
return np.array(embeddings_response.data[0].embedding)
@asyncSlot()
async def communicate_with_api(main_window):
if int(main_window.max_tokens.text()) <= 0:
issue_warning_error(main_window, "Error", "Input tokens must be greater than 0")
main_window.btn_start.setChecked(False)
main_window.btn_start.setCheckable(True)
else:
global async_signal_used
global waiting_loop
global disable_delete
if main_window.btn_start.isChecked() and not async_signal_used:
async_signal_used = True
main_window.btn_delete_row.setEnabled(False)
disable_delete = True
disable_some_buttons(main_window)
width = main_window.SettingsExpand.width()
if width == SETTINGS.EXPAND_WIDTH:
expand_settings(main_window)
movie = QMovie(":/gifs/images/loading.gif")
movie.setScaledSize(QSize(45, 45))
main_window.loading_gif.setMovie(movie)
main_window.loading_gif.show()
movie.start()
client = AsyncOpenAI(api_key = main_window.token_edit.text())
target_lang = main_window.target_language.text()
prompt = f"""
{main_window.prompt_edit.toPlainText()}
The target language of translation is {target_lang}.
This input is a JSON string. Please keep it as is and only replace the values with the translations.
"""
max_tokens = int(main_window.max_tokens.text())
model = SETTINGS.MODEL_MAP[main_window.combo_model.currentText()]
sentences_list = create_sentences_dictionaries(main_window, prompt, max_tokens, model)
row = 0
for _, outer_value in sentences_list.items():
json_string = json.dumps(outer_value)
try:
if main_window.box_cosine.isChecked():
sentences_string = ' '.join(outer_value.values())
sentences_embeddings = await get_embeddings(client, str(sentences_string))
while True:
response = await client.chat.completions.create (
model=model,
messages=[
{"role": "system", "content": prompt},
{"role": "user", "content": json_string}
],
response_format={ "type": "json_object" },
)
dict_sentences = response.choices[0].message.content
translations_sentences = list(json.loads(dict_sentences).values())
if main_window.alignment_box.isChecked():
if len(translations_sentences) != len(outer_value.values()):
continue
if main_window.box_cosine.isChecked():
translations_string = ' '.join(translations_sentences)
translations_embeddings = await get_embeddings(client, translations_string)
score = cosine_similarity(sentences_embeddings, translations_embeddings)
if score < float(main_window.threshold_edit.text()):
continue
for _, sentence in enumerate(translations_sentences):
item = QTableWidgetItem(sentence)
main_window.data_table.setItem(row, 1, item)
row = row + 1
main_window.repaint()
break
if not main_window.btn_start.isChecked():
enable_some_buttons(main_window)
main_window.btn_settings.setEnabled(False)
main_window.btn_back.setEnabled(False)
movie.stop()
main_window.loading_gif.hide()
waiting_loop = True
main_window.btn_start.setCheckable(True)
while waiting_loop:
await sleep(1)
main_window.btn_start.setCheckable(True)
main_window.btn_start.setChecked(True)
disable_some_buttons(main_window)
main_window.loading_gif.setMovie(movie)
main_window.loading_gif.show()
movie.start()
except Exception as e:
issue_warning_error(main_window, "Error",f"{e}")
break
enable_some_buttons(main_window)
async_signal_used = False
disable_delete = False
main_window.btn_delete_row.setEnabled(True)
main_window.btn_start.setChecked(False)
main_window.btn_start.setCheckable(True)
movie.stop()
main_window.loading_gif.hide()
else:
# Needed to prevent multiple clicks for Stop
if not main_window.btn_start.isChecked():
main_window.btn_start.setCheckable(False)
else:
waiting_loop = False