Skip to content

Commit 34d9e3e

Browse files
authored
Merge pull request #124 from yjg30737/Dev
Dev
2 parents bcebf50 + c35daac commit 34d9e3e

32 files changed

+1133
-450
lines changed

pyqt_openai/chatGPTImportDialog.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from qtpy.QtCore import Qt
2+
from qtpy.QtWidgets import QMessageBox, QPushButton, QGroupBox, QTableWidgetItem, \
3+
QLabel, QDialogButtonBox, QCheckBox, QDialog, QVBoxLayout, QSpinBox, QAbstractItemView
4+
5+
from pyqt_openai.constants import THREAD_ORDERBY
6+
from pyqt_openai.util.script import get_conversation_from_chatgpt, get_chatgpt_data
7+
from pyqt_openai.widgets.checkBoxTableWidget import CheckBoxTableWidget
8+
from pyqt_openai.widgets.findPathWidget import FindPathWidget
9+
10+
11+
class ChatGPTImportDialog(QDialog):
12+
def __init__(self):
13+
super(ChatGPTImportDialog, self).__init__()
14+
self.__initVal()
15+
self.__initUi()
16+
17+
def __initVal(self):
18+
# Get the most recent n conversation threads
19+
self.__most_recent_n = 10
20+
# Data to be imported
21+
self.__data = []
22+
23+
def __initUi(self):
24+
self.setWindowTitle("Import ChatGPT Data")
25+
self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint)
26+
27+
findPathWidget = FindPathWidget()
28+
findPathWidget.added.connect(self.__setPath)
29+
findPathWidget.getLineEdit().setPlaceholderText('Select a ChatGPT JSON file')
30+
findPathWidget.setExtOfFiles('JSON Files (*.json)')
31+
32+
self.__chkBoxMostRecent = QCheckBox('Get most recent')
33+
34+
self.__mostRecentNSpinBox = QSpinBox()
35+
self.__mostRecentNSpinBox.setRange(1, 10000)
36+
self.__mostRecentNSpinBox.setValue(self.__most_recent_n)
37+
self.__mostRecentNSpinBox.setEnabled(False)
38+
39+
self.__chkBoxMostRecent.stateChanged.connect(lambda state: self.__mostRecentNSpinBox.setEnabled(state == Qt.CheckState.Checked))
40+
41+
chatGPTImportGrpBox = QGroupBox('ChatGPT Import Options')
42+
lay = QVBoxLayout()
43+
lay.addWidget(self.__chkBoxMostRecent)
44+
lay.addWidget(self.__mostRecentNSpinBox)
45+
chatGPTImportGrpBox.setLayout(lay)
46+
47+
self.__checkBoxTableWidget = CheckBoxTableWidget()
48+
self.__checkBoxTableWidget.setColumnCount(0)
49+
# self.__checkBoxTableWidget.checkedSignal.connect(self.getData)
50+
51+
self.__allCheckBox = QCheckBox('Select All')
52+
self.__allCheckBox.stateChanged.connect(self.__checkBoxTableWidget.toggleState) # if allChkBox is checked, tablewidget checkboxes will also be checked
53+
54+
lay = QVBoxLayout()
55+
lay.addWidget(QLabel('Select the threads you want to import.'))
56+
lay.addWidget(self.__allCheckBox)
57+
lay.addWidget(self.__checkBoxTableWidget)
58+
59+
self.__chatGPTDataGroupBox = QGroupBox('ChatGPT Data')
60+
self.__chatGPTDataGroupBox.setLayout(lay)
61+
self.__chatGPTDataGroupBox.setEnabled(False)
62+
63+
self.__buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
64+
self.__buttonBox.accepted.connect(self.__accept)
65+
self.__buttonBox.rejected.connect(self.reject)
66+
self.__buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
67+
68+
lay = QVBoxLayout()
69+
lay.addWidget(findPathWidget)
70+
lay.addWidget(chatGPTImportGrpBox)
71+
lay.addWidget(self.__chatGPTDataGroupBox)
72+
lay.addWidget(self.__buttonBox)
73+
74+
self.setLayout(lay)
75+
76+
self.resize(800, 600)
77+
78+
def __setData(self):
79+
checked_rows = self.__checkBoxTableWidget.getCheckedRows()
80+
self.__data = get_chatgpt_data([self.__data[r] for r in checked_rows])
81+
82+
def __toggleOkButton(self):
83+
self.__buttonBox.button(QDialogButtonBox.Ok).setEnabled(len(self.__checkBoxTableWidget.getCheckedRows()) > 0)
84+
85+
def __setPath(self, path):
86+
try:
87+
most_recent_n = self.__mostRecentNSpinBox.value() if self.__chkBoxMostRecent.isChecked() else None
88+
result_dict = get_conversation_from_chatgpt(path, most_recent_n)
89+
columns = result_dict['columns']
90+
self.__data = result_dict['data']
91+
self.__checkBoxTableWidget.setHorizontalHeaderLabels(columns)
92+
self.__checkBoxTableWidget.setRowCount(len(self.__data))
93+
94+
for r_idx, r in enumerate(self.__data):
95+
for c_idx, c in enumerate(columns):
96+
v = r[c]
97+
self.__checkBoxTableWidget.setItem(r_idx, c_idx+1, QTableWidgetItem(str(v)))
98+
99+
self.__checkBoxTableWidget.resizeColumnsToContents()
100+
self.__checkBoxTableWidget.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
101+
if THREAD_ORDERBY in columns:
102+
self.__checkBoxTableWidget.sortByColumn(columns.index(THREAD_ORDERBY)+1, Qt.SortOrder.DescendingOrder)
103+
self.__chatGPTDataGroupBox.setEnabled(True)
104+
self.__allCheckBox.setChecked(True)
105+
self.__toggleOkButton()
106+
107+
self.__checkBoxTableWidget.hideColumn(1)
108+
except Exception as e:
109+
QMessageBox.critical(self, "Error", 'Check whether the file is a valid JSON file for importing.')
110+
111+
def __accept(self):
112+
if len(self.__checkBoxTableWidget.getCheckedRows()) > 0:
113+
self.__setData()
114+
self.accept()
115+
else:
116+
QMessageBox.critical(self, "Error", 'Select at least one thread to import.')
117+
118+
def getData(self):
119+
return self.__data

pyqt_openai/chatNavWidget.py

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
from PyQt5.QtWidgets import QDialog
21
from qtpy.QtCore import Signal, QSortFilterProxyModel, Qt
32
from qtpy.QtSql import QSqlTableModel, QSqlQuery
4-
from qtpy.QtWidgets import QApplication, QWidget, QVBoxLayout, QMessageBox, QStyledItemDelegate, QTableView, \
3+
from qtpy.QtWidgets import QWidget, QVBoxLayout, QMessageBox, QPushButton, QStyledItemDelegate, QTableView, \
54
QAbstractItemView, \
65
QHBoxLayout, \
7-
QLabel, QSpacerItem, QSizePolicy, QFileDialog, QComboBox
6+
QLabel, QSpacerItem, QSizePolicy, QFileDialog, QComboBox, QDialog
87

98
# for search feature
9+
from pyqt_openai.chatGPTImportDialog import ChatGPTImportDialog
10+
from pyqt_openai.constants import THREAD_ORDERBY
1011
from pyqt_openai.exportDialog import ExportDialog
12+
from pyqt_openai.importDialog import ImportDialog
1113
from pyqt_openai.models import ChatThreadContainer
1214
from pyqt_openai.pyqt_openai_data import DB
1315
from pyqt_openai.widgets.button import Button
@@ -58,6 +60,8 @@ class ChatNavWidget(QWidget):
5860
cleared = Signal()
5961
onImport = Signal(str)
6062
onExport = Signal(list)
63+
onChatGPTImport = Signal(list)
64+
onFavoriteClicked = Signal(bool)
6165

6266
def __init__(self, columns, table_nm):
6367
super().__init__()
@@ -143,7 +147,7 @@ def __initUi(self):
143147
self.__model.setHeaderData(i, Qt.Orientation.Horizontal, self.__columns[i])
144148
self.__model.select()
145149
# descending order by insert date
146-
idx = self.__columns.index('insert_dt')
150+
idx = self.__columns.index(THREAD_ORDERBY)
147151
self.__model.sort(idx, Qt.SortOrder.DescendingOrder)
148152

149153
# init the proxy model
@@ -171,9 +175,14 @@ def __initUi(self):
171175
self.__tableView.clicked.connect(self.__clicked)
172176
self.__tableView.activated.connect(self.__clicked)
173177

178+
self.__favoriteBtn = QPushButton('Favorite List')
179+
self.__favoriteBtn.setCheckable(True)
180+
self.__favoriteBtn.toggled.connect(self.__onFavoriteClicked)
181+
174182
lay = QVBoxLayout()
175183
lay.addWidget(menuWidget)
176184
lay.addWidget(self.__tableView)
185+
lay.addWidget(self.__favoriteBtn)
177186
self.setLayout(lay)
178187

179188
self.refreshData()
@@ -186,19 +195,34 @@ def add(self, called_from_parent=False):
186195
self.__model.select()
187196

188197
def __import(self):
189-
filename = QFileDialog.getOpenFileName(self, 'Import', '', 'SQLite DB files (*.db)')
190-
if filename:
191-
filename = filename[0]
192-
self.onImport.emit(filename)
198+
dialog = ImportDialog()
199+
reply = dialog.exec()
200+
if reply == QDialog.Accepted:
201+
import_type = dialog.getImportType()
202+
if import_type == 'General':
203+
filename = QFileDialog.getOpenFileName(self, 'Import', '', 'JSON files (*.json)')
204+
if filename:
205+
filename = filename[0]
206+
if filename:
207+
self.onImport.emit(filename)
208+
else:
209+
chatgptDialog = ChatGPTImportDialog()
210+
reply = chatgptDialog.exec()
211+
if reply == QDialog.Accepted:
212+
data = chatgptDialog.getData()
213+
self.onChatGPTImport.emit(data)
193214

194215
def __export(self):
195216
columns = ChatThreadContainer.get_keys()
196-
data = DB.selectAllConv()
197-
sort_by = 'update_dt'
198-
dialog = ExportDialog(columns, data, sort_by=sort_by)
199-
reply = dialog.exec()
200-
if reply == QDialog.Accepted:
201-
self.onExport.emit(dialog.getSelectedIds())
217+
data = DB.selectAllThread()
218+
sort_by = THREAD_ORDERBY
219+
if len(data) > 0:
220+
dialog = ExportDialog(columns, data, sort_by=sort_by)
221+
reply = dialog.exec()
222+
if reply == QDialog.Accepted:
223+
self.onExport.emit(dialog.getSelectedIds())
224+
else:
225+
QMessageBox.information(self, 'Information', 'No data to export.')
202226

203227
def __updated(self, i, r):
204228
# send updated signal
@@ -213,22 +237,28 @@ def refreshData(self, title=None):
213237
self.__proxyModel.setFilterRegularExpression(title)
214238

215239
def __clicked(self, idx):
216-
# get id of record
217-
id = self.__model.data(self.__proxyModel.mapToSource(idx.siblingAtColumn(0)), role=Qt.ItemDataRole.DisplayRole)
218-
title = self.__model.data(self.__proxyModel.mapToSource(idx.siblingAtColumn(1)), role=Qt.ItemDataRole.DisplayRole)
240+
# get the source index
241+
source_idx = self.__proxyModel.mapToSource(idx)
242+
# get the primary key value of the row
243+
cur_id = self.__model.record(source_idx.row()).value("id")
244+
clicked_thread = DB.selectThread(cur_id)
245+
# get the title
246+
title = clicked_thread['name']
219247

220-
self.clicked.emit(id, title)
248+
self.clicked.emit(cur_id, title)
221249

222250
def __getSelectedIds(self):
223-
idx_s = [idx.siblingAtColumn(0) for idx in self.__tableView.selectedIndexes()]
224-
idx_s = list(set(idx_s))
225-
ids = [self.__model.data(idx, role=Qt.ItemDataRole.DisplayRole) for idx in idx_s]
251+
selected_idx_s = self.__tableView.selectedIndexes()
252+
ids = []
253+
for idx in selected_idx_s:
254+
ids.append(self.__model.data(self.__proxyModel.mapToSource(idx.siblingAtColumn(0)), role=Qt.ItemDataRole.DisplayRole))
255+
ids = list(set(ids))
226256
return ids
227257

228258
def __delete(self):
229259
ids = self.__getSelectedIds()
230260
for _id in ids:
231-
DB.deleteConv(_id)
261+
DB.deleteThread(_id)
232262
self.__model.select()
233263
self.cleared.emit()
234264

@@ -239,7 +269,7 @@ def __clear(self):
239269
# Before clearing, confirm the action
240270
reply = QMessageBox.question(self, 'Confirm', 'Are you sure to clear all data?', QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
241271
if reply == QMessageBox.StandardButton.Yes:
242-
DB.deleteConv()
272+
DB.deleteThread()
243273
self.__model.select()
244274
self.cleared.emit()
245275

@@ -250,8 +280,8 @@ def __search(self, search_text):
250280
# content
251281
elif self.__searchOptionCmbBox.currentText() == 'Content':
252282
if search_text:
253-
convs = DB.selectAllContentOfConv(content_to_select=search_text)
254-
ids = [_[0] for _ in convs]
283+
threads = DB.selectAllContentOfThread(content_to_select=search_text)
284+
ids = [_[0] for _ in threads]
255285
self.__model.setQuery(QSqlQuery(f"SELECT {','.join(self.__columns)} FROM {self.__table_nm} "
256286
f"WHERE id IN ({','.join(map(str, ids))})"))
257287
else:
@@ -265,4 +295,10 @@ def setColumns(self, columns):
265295
self.__model.clear()
266296
self.__model.setTable(self.__table_nm)
267297
self.__model.setQuery(QSqlQuery(f"SELECT {','.join(self.__columns)} FROM {self.__table_nm}"))
268-
self.__model.select()
298+
self.__model.select()
299+
300+
def __onFavoriteClicked(self, f):
301+
self.onFavoriteClicked.emit(f)
302+
303+
def activateFavoriteFromParent(self, f):
304+
self.__favoriteBtn.setChecked(f)

pyqt_openai/chat_widget/aiChatUnit.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import pyperclip
22
from qtpy.QtCore import Qt
3-
from qtpy.QtGui import QPalette, QColor
4-
from qtpy.QtWidgets import QLabel, QWidget, QVBoxLayout, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy, \
3+
from qtpy.QtGui import QPalette
4+
from qtpy.QtWidgets import QLabel, QMessageBox, QWidget, QVBoxLayout, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy, \
55
QTextBrowser, QAbstractScrollArea
66

7-
from pyqt_openai.chat_widget.convUnitResultDialog import ConvUnitResultDialog
8-
from pyqt_openai.widgets.circleProfileImage import RoundedImage
7+
from pyqt_openai.chat_widget.messageResultDialog import MessageResultDialog
8+
from pyqt_openai.constants import DEFAULT_ICON_SIZE
9+
from pyqt_openai.models import ChatMessageContainer
10+
from pyqt_openai.pyqt_openai_data import DB
911
from pyqt_openai.widgets.button import Button
12+
from pyqt_openai.widgets.circleProfileImage import RoundedImage
1013

1114

1215
class SourceBrowser(QWidget):
@@ -79,7 +82,12 @@ def __initUi(self):
7982
lay = QHBoxLayout()
8083

8184
self.__icon = RoundedImage()
82-
self.__icon.setMaximumSize(24, 24)
85+
self.__icon.setMaximumSize(*DEFAULT_ICON_SIZE)
86+
87+
self.__favoriteBtn = Button()
88+
self.__favoriteBtn.setStyleAndIcon('ico/favorite_no.svg')
89+
self.__favoriteBtn.setCheckable(True)
90+
self.__favoriteBtn.toggled.connect(self.__favorite)
8391

8492
self.__infoBtn = Button()
8593
self.__infoBtn.setStyleAndIcon('ico/info.svg')
@@ -92,6 +100,7 @@ def __initUi(self):
92100

93101
lay.addWidget(self.__icon)
94102
lay.addSpacerItem(QSpacerItem(10, 10, QSizePolicy.Policy.MinimumExpanding))
103+
lay.addWidget(self.__favoriteBtn)
95104
lay.addWidget(self.__infoBtn)
96105
lay.addWidget(self.__copyBtn)
97106
lay.setContentsMargins(2, 2, 2, 2)
@@ -112,14 +121,25 @@ def __initUi(self):
112121

113122
self.setLayout(lay)
114123

115-
self.setBackgroundRole(QPalette.ColorRole.Midlight)
124+
self.setBackgroundRole(QPalette.ColorRole.AlternateBase)
116125
self.setAutoFillBackground(True)
117126

118127
def __copy(self):
119128
pyperclip.copy(self.text())
120129

130+
def __favorite(self, f, insert_f=True):
131+
favorite = 1 if f else 0
132+
if favorite:
133+
self.__favoriteBtn.setStyleAndIcon('ico/favorite_yes.svg')
134+
else:
135+
self.__favoriteBtn.setStyleAndIcon('ico/favorite_no.svg')
136+
if insert_f:
137+
current_date = DB.updateMessage(self.__result_info.id, favorite)
138+
self.__result_info.favorite = favorite
139+
self.__result_info.favorite_set_date = current_date
140+
121141
def __showInfo(self):
122-
dialog = ConvUnitResultDialog(self.__result_info)
142+
dialog = MessageResultDialog(self.__result_info)
123143
dialog.exec()
124144

125145
def text(self):
@@ -147,13 +167,16 @@ def setAlignment(self, a0):
147167
widget.setAlignment(a0)
148168

149169
def disableGUIDuringGenerateResponse(self):
170+
self.__favoriteBtn.setEnabled(False)
150171
self.__copyBtn.setEnabled(False)
151172
self.__infoBtn.setEnabled(False)
152173

153-
def showConvResultInfo(self, info_dict):
174+
def showConvResultInfo(self, arg: ChatMessageContainer):
175+
self.__favoriteBtn.setEnabled(True)
154176
self.__copyBtn.setEnabled(True)
155177
self.__infoBtn.setEnabled(True)
156-
self.__result_info = info_dict
178+
self.__result_info = arg
179+
self.__favorite(True if arg.favorite else False, insert_f=False)
157180

158181
def setText(self, text: str):
159182
self.__lbl = QLabel(text)

0 commit comments

Comments
 (0)