sqlshell 0.4.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlshell/__init__.py +84 -0
- sqlshell/__main__.py +4926 -0
- sqlshell/ai_autocomplete.py +392 -0
- sqlshell/ai_settings_dialog.py +337 -0
- sqlshell/context_suggester.py +768 -0
- sqlshell/create_test_data.py +152 -0
- sqlshell/data/create_test_data.py +137 -0
- sqlshell/db/__init__.py +6 -0
- sqlshell/db/database_manager.py +1318 -0
- sqlshell/db/export_manager.py +188 -0
- sqlshell/editor.py +1166 -0
- sqlshell/editor_integration.py +127 -0
- sqlshell/execution_handler.py +421 -0
- sqlshell/menus.py +262 -0
- sqlshell/notification_manager.py +370 -0
- sqlshell/query_tab.py +904 -0
- sqlshell/resources/__init__.py +1 -0
- sqlshell/resources/icon.png +0 -0
- sqlshell/resources/logo_large.png +0 -0
- sqlshell/resources/logo_medium.png +0 -0
- sqlshell/resources/logo_small.png +0 -0
- sqlshell/resources/splash_screen.gif +0 -0
- sqlshell/space_invaders.py +501 -0
- sqlshell/splash_screen.py +405 -0
- sqlshell/sqlshell/__init__.py +5 -0
- sqlshell/sqlshell/create_test_data.py +118 -0
- sqlshell/sqlshell/create_test_databases.py +96 -0
- sqlshell/sqlshell_demo.png +0 -0
- sqlshell/styles.py +257 -0
- sqlshell/suggester_integration.py +330 -0
- sqlshell/syntax_highlighter.py +124 -0
- sqlshell/table_list.py +996 -0
- sqlshell/ui/__init__.py +6 -0
- sqlshell/ui/bar_chart_delegate.py +49 -0
- sqlshell/ui/filter_header.py +469 -0
- sqlshell/utils/__init__.py +16 -0
- sqlshell/utils/profile_cn2.py +1661 -0
- sqlshell/utils/profile_column.py +2635 -0
- sqlshell/utils/profile_distributions.py +616 -0
- sqlshell/utils/profile_entropy.py +347 -0
- sqlshell/utils/profile_foreign_keys.py +779 -0
- sqlshell/utils/profile_keys.py +2834 -0
- sqlshell/utils/profile_ohe.py +934 -0
- sqlshell/utils/profile_ohe_advanced.py +754 -0
- sqlshell/utils/profile_ohe_comparison.py +237 -0
- sqlshell/utils/profile_prediction.py +926 -0
- sqlshell/utils/profile_similarity.py +876 -0
- sqlshell/utils/search_in_df.py +90 -0
- sqlshell/widgets.py +400 -0
- sqlshell-0.4.4.dist-info/METADATA +441 -0
- sqlshell-0.4.4.dist-info/RECORD +54 -0
- sqlshell-0.4.4.dist-info/WHEEL +5 -0
- sqlshell-0.4.4.dist-info/entry_points.txt +2 -0
- sqlshell-0.4.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI Settings Dialog for SQLShell.
|
|
3
|
+
|
|
4
|
+
This module provides a dialog for configuring OpenAI API settings
|
|
5
|
+
for the AI autocomplete feature.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from PyQt6.QtWidgets import (
|
|
9
|
+
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
|
10
|
+
QPushButton, QCheckBox, QComboBox, QGroupBox, QMessageBox,
|
|
11
|
+
QFormLayout, QFrame
|
|
12
|
+
)
|
|
13
|
+
from PyQt6.QtCore import Qt
|
|
14
|
+
from PyQt6.QtGui import QFont
|
|
15
|
+
|
|
16
|
+
from sqlshell.ai_autocomplete import get_ai_autocomplete_manager
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AISettingsDialog(QDialog):
|
|
20
|
+
"""Dialog for configuring AI autocomplete settings."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, parent=None):
|
|
23
|
+
super().__init__(parent)
|
|
24
|
+
self.ai_manager = get_ai_autocomplete_manager()
|
|
25
|
+
self.setWindowTitle("AI Autocomplete Settings")
|
|
26
|
+
self.setModal(True)
|
|
27
|
+
self.setMinimumWidth(500)
|
|
28
|
+
self.setup_ui()
|
|
29
|
+
self.load_settings()
|
|
30
|
+
|
|
31
|
+
def setup_ui(self):
|
|
32
|
+
"""Set up the dialog UI."""
|
|
33
|
+
layout = QVBoxLayout(self)
|
|
34
|
+
layout.setSpacing(16)
|
|
35
|
+
|
|
36
|
+
# Header
|
|
37
|
+
header = QLabel("🤖 AI-Powered SQL Autocomplete")
|
|
38
|
+
header_font = QFont()
|
|
39
|
+
header_font.setPointSize(14)
|
|
40
|
+
header_font.setBold(True)
|
|
41
|
+
header.setFont(header_font)
|
|
42
|
+
layout.addWidget(header)
|
|
43
|
+
|
|
44
|
+
# Description
|
|
45
|
+
desc = QLabel(
|
|
46
|
+
"Enable intelligent SQL suggestions powered by OpenAI. "
|
|
47
|
+
"This feature uses GPT models to provide context-aware completions "
|
|
48
|
+
"based on your database schema and query context."
|
|
49
|
+
)
|
|
50
|
+
desc.setWordWrap(True)
|
|
51
|
+
desc.setStyleSheet("color: #666; margin-bottom: 8px;")
|
|
52
|
+
layout.addWidget(desc)
|
|
53
|
+
|
|
54
|
+
# Enable/Disable checkbox
|
|
55
|
+
self.enabled_checkbox = QCheckBox("Enable AI Autocomplete")
|
|
56
|
+
self.enabled_checkbox.setStyleSheet("font-weight: bold; font-size: 13px;")
|
|
57
|
+
layout.addWidget(self.enabled_checkbox)
|
|
58
|
+
|
|
59
|
+
# API Key group
|
|
60
|
+
api_group = QGroupBox("OpenAI API Configuration")
|
|
61
|
+
api_layout = QFormLayout(api_group)
|
|
62
|
+
api_layout.setSpacing(12)
|
|
63
|
+
|
|
64
|
+
# API Key input
|
|
65
|
+
api_key_layout = QHBoxLayout()
|
|
66
|
+
self.api_key_input = QLineEdit()
|
|
67
|
+
self.api_key_input.setEchoMode(QLineEdit.EchoMode.Password)
|
|
68
|
+
self.api_key_input.setPlaceholderText("sk-...")
|
|
69
|
+
self.api_key_input.setMinimumWidth(300)
|
|
70
|
+
api_key_layout.addWidget(self.api_key_input)
|
|
71
|
+
|
|
72
|
+
self.show_key_btn = QPushButton("👁")
|
|
73
|
+
self.show_key_btn.setFixedWidth(40)
|
|
74
|
+
self.show_key_btn.setCheckable(True)
|
|
75
|
+
self.show_key_btn.toggled.connect(self.toggle_key_visibility)
|
|
76
|
+
self.show_key_btn.setToolTip("Show/Hide API Key")
|
|
77
|
+
api_key_layout.addWidget(self.show_key_btn)
|
|
78
|
+
|
|
79
|
+
api_layout.addRow("API Key:", api_key_layout)
|
|
80
|
+
|
|
81
|
+
# API key help text
|
|
82
|
+
help_label = QLabel(
|
|
83
|
+
'<a href="https://platform.openai.com/api-keys">Get your API key from OpenAI</a>'
|
|
84
|
+
)
|
|
85
|
+
help_label.setOpenExternalLinks(True)
|
|
86
|
+
help_label.setStyleSheet("color: #1890ff; font-size: 11px;")
|
|
87
|
+
api_layout.addRow("", help_label)
|
|
88
|
+
|
|
89
|
+
# Model selection
|
|
90
|
+
self.model_combo = QComboBox()
|
|
91
|
+
self.model_combo.addItems([
|
|
92
|
+
"gpt-4o-mini",
|
|
93
|
+
"gpt-4o",
|
|
94
|
+
"gpt-4-turbo",
|
|
95
|
+
"gpt-3.5-turbo"
|
|
96
|
+
])
|
|
97
|
+
self.model_combo.setToolTip(
|
|
98
|
+
"gpt-4o-mini: Fast and cost-effective (recommended)\n"
|
|
99
|
+
"gpt-4o: Most capable, higher cost\n"
|
|
100
|
+
"gpt-4-turbo: Good balance of capability and speed\n"
|
|
101
|
+
"gpt-3.5-turbo: Fastest, lowest cost"
|
|
102
|
+
)
|
|
103
|
+
api_layout.addRow("Model:", self.model_combo)
|
|
104
|
+
|
|
105
|
+
layout.addWidget(api_group)
|
|
106
|
+
|
|
107
|
+
# Status indicator
|
|
108
|
+
self.status_frame = QFrame()
|
|
109
|
+
self.status_frame.setStyleSheet("""
|
|
110
|
+
QFrame {
|
|
111
|
+
background-color: #f0f0f0;
|
|
112
|
+
border-radius: 4px;
|
|
113
|
+
padding: 8px;
|
|
114
|
+
}
|
|
115
|
+
""")
|
|
116
|
+
status_layout = QHBoxLayout(self.status_frame)
|
|
117
|
+
status_layout.setContentsMargins(12, 8, 12, 8)
|
|
118
|
+
|
|
119
|
+
self.status_icon = QLabel("⚪")
|
|
120
|
+
self.status_icon.setFixedWidth(24)
|
|
121
|
+
status_layout.addWidget(self.status_icon)
|
|
122
|
+
|
|
123
|
+
self.status_label = QLabel("Not configured")
|
|
124
|
+
status_layout.addWidget(self.status_label)
|
|
125
|
+
status_layout.addStretch()
|
|
126
|
+
|
|
127
|
+
self.test_btn = QPushButton("Test Connection")
|
|
128
|
+
self.test_btn.clicked.connect(self.test_connection)
|
|
129
|
+
status_layout.addWidget(self.test_btn)
|
|
130
|
+
|
|
131
|
+
layout.addWidget(self.status_frame)
|
|
132
|
+
|
|
133
|
+
# Info about usage
|
|
134
|
+
info_frame = QFrame()
|
|
135
|
+
info_frame.setStyleSheet("""
|
|
136
|
+
QFrame {
|
|
137
|
+
background-color: #e6f7ff;
|
|
138
|
+
border: 1px solid #91d5ff;
|
|
139
|
+
border-radius: 4px;
|
|
140
|
+
padding: 8px;
|
|
141
|
+
}
|
|
142
|
+
""")
|
|
143
|
+
info_layout = QVBoxLayout(info_frame)
|
|
144
|
+
info_layout.setContentsMargins(12, 8, 12, 8)
|
|
145
|
+
|
|
146
|
+
info_label = QLabel(
|
|
147
|
+
"💡 <b>How it works:</b><br>"
|
|
148
|
+
"• AI suggestions appear as ghost text while you type<br>"
|
|
149
|
+
"• Press <b>Tab</b> to accept a suggestion<br>"
|
|
150
|
+
"• The AI uses your table schema for context-aware completions<br>"
|
|
151
|
+
"• API calls are rate-limited and cached for efficiency"
|
|
152
|
+
)
|
|
153
|
+
info_label.setWordWrap(True)
|
|
154
|
+
info_label.setStyleSheet("color: #0050b3;")
|
|
155
|
+
info_layout.addWidget(info_label)
|
|
156
|
+
|
|
157
|
+
layout.addWidget(info_frame)
|
|
158
|
+
|
|
159
|
+
# Buttons
|
|
160
|
+
button_layout = QHBoxLayout()
|
|
161
|
+
button_layout.addStretch()
|
|
162
|
+
|
|
163
|
+
cancel_btn = QPushButton("Cancel")
|
|
164
|
+
cancel_btn.clicked.connect(self.reject)
|
|
165
|
+
button_layout.addWidget(cancel_btn)
|
|
166
|
+
|
|
167
|
+
save_btn = QPushButton("Save")
|
|
168
|
+
save_btn.setDefault(True)
|
|
169
|
+
save_btn.clicked.connect(self.save_settings)
|
|
170
|
+
save_btn.setStyleSheet("""
|
|
171
|
+
QPushButton {
|
|
172
|
+
background-color: #1890ff;
|
|
173
|
+
color: white;
|
|
174
|
+
border: none;
|
|
175
|
+
padding: 8px 24px;
|
|
176
|
+
border-radius: 4px;
|
|
177
|
+
font-weight: bold;
|
|
178
|
+
}
|
|
179
|
+
QPushButton:hover {
|
|
180
|
+
background-color: #40a9ff;
|
|
181
|
+
}
|
|
182
|
+
""")
|
|
183
|
+
button_layout.addWidget(save_btn)
|
|
184
|
+
|
|
185
|
+
layout.addLayout(button_layout)
|
|
186
|
+
|
|
187
|
+
def toggle_key_visibility(self, visible: bool):
|
|
188
|
+
"""Toggle API key visibility."""
|
|
189
|
+
if visible:
|
|
190
|
+
self.api_key_input.setEchoMode(QLineEdit.EchoMode.Normal)
|
|
191
|
+
self.show_key_btn.setText("🔒")
|
|
192
|
+
else:
|
|
193
|
+
self.api_key_input.setEchoMode(QLineEdit.EchoMode.Password)
|
|
194
|
+
self.show_key_btn.setText("👁")
|
|
195
|
+
|
|
196
|
+
def load_settings(self):
|
|
197
|
+
"""Load current settings into the dialog."""
|
|
198
|
+
# Load enabled state
|
|
199
|
+
self.enabled_checkbox.setChecked(self.ai_manager.is_enabled())
|
|
200
|
+
|
|
201
|
+
# Load API key (raw for editing)
|
|
202
|
+
raw_key = self.ai_manager.get_raw_api_key()
|
|
203
|
+
if raw_key:
|
|
204
|
+
self.api_key_input.setText(raw_key)
|
|
205
|
+
|
|
206
|
+
# Load model
|
|
207
|
+
current_model = self.ai_manager.get_model()
|
|
208
|
+
index = self.model_combo.findText(current_model)
|
|
209
|
+
if index >= 0:
|
|
210
|
+
self.model_combo.setCurrentIndex(index)
|
|
211
|
+
|
|
212
|
+
# Update status
|
|
213
|
+
self.update_status()
|
|
214
|
+
|
|
215
|
+
def update_status(self):
|
|
216
|
+
"""Update the status indicator."""
|
|
217
|
+
if self.ai_manager.is_available:
|
|
218
|
+
self.status_icon.setText("🟢")
|
|
219
|
+
self.status_label.setText("Ready - AI autocomplete is active")
|
|
220
|
+
self.status_frame.setStyleSheet("""
|
|
221
|
+
QFrame {
|
|
222
|
+
background-color: #f6ffed;
|
|
223
|
+
border: 1px solid #b7eb8f;
|
|
224
|
+
border-radius: 4px;
|
|
225
|
+
}
|
|
226
|
+
""")
|
|
227
|
+
elif self.ai_manager.is_configured:
|
|
228
|
+
self.status_icon.setText("🟡")
|
|
229
|
+
self.status_label.setText("Configured but not validated")
|
|
230
|
+
self.status_frame.setStyleSheet("""
|
|
231
|
+
QFrame {
|
|
232
|
+
background-color: #fffbe6;
|
|
233
|
+
border: 1px solid #ffe58f;
|
|
234
|
+
border-radius: 4px;
|
|
235
|
+
}
|
|
236
|
+
""")
|
|
237
|
+
else:
|
|
238
|
+
self.status_icon.setText("⚪")
|
|
239
|
+
self.status_label.setText("Not configured - enter your API key")
|
|
240
|
+
self.status_frame.setStyleSheet("""
|
|
241
|
+
QFrame {
|
|
242
|
+
background-color: #f0f0f0;
|
|
243
|
+
border-radius: 4px;
|
|
244
|
+
}
|
|
245
|
+
""")
|
|
246
|
+
|
|
247
|
+
def test_connection(self):
|
|
248
|
+
"""Test the OpenAI API connection."""
|
|
249
|
+
api_key = self.api_key_input.text().strip()
|
|
250
|
+
|
|
251
|
+
if not api_key:
|
|
252
|
+
QMessageBox.warning(self, "No API Key", "Please enter an API key first.")
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
# Temporarily set the key for testing
|
|
256
|
+
old_key = self.ai_manager.get_raw_api_key()
|
|
257
|
+
self.ai_manager.set_api_key(api_key)
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
from openai import OpenAI
|
|
261
|
+
client = OpenAI(api_key=api_key)
|
|
262
|
+
|
|
263
|
+
# Make a minimal API call to test
|
|
264
|
+
response = client.chat.completions.create(
|
|
265
|
+
model="gpt-4o-mini",
|
|
266
|
+
messages=[{"role": "user", "content": "Say 'OK'"}],
|
|
267
|
+
max_tokens=5
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if response.choices:
|
|
271
|
+
QMessageBox.information(
|
|
272
|
+
self,
|
|
273
|
+
"Connection Successful",
|
|
274
|
+
"✅ Successfully connected to OpenAI API!\n\n"
|
|
275
|
+
"AI autocomplete is ready to use."
|
|
276
|
+
)
|
|
277
|
+
self.update_status()
|
|
278
|
+
else:
|
|
279
|
+
raise Exception("No response received")
|
|
280
|
+
|
|
281
|
+
except ImportError:
|
|
282
|
+
QMessageBox.critical(
|
|
283
|
+
self,
|
|
284
|
+
"Missing Dependency",
|
|
285
|
+
"The OpenAI library is not installed.\n\n"
|
|
286
|
+
"Please run: pip install openai"
|
|
287
|
+
)
|
|
288
|
+
# Restore old key
|
|
289
|
+
if old_key:
|
|
290
|
+
self.ai_manager.set_api_key(old_key)
|
|
291
|
+
except Exception as e:
|
|
292
|
+
error_msg = str(e)
|
|
293
|
+
if "invalid_api_key" in error_msg.lower() or "incorrect api key" in error_msg.lower():
|
|
294
|
+
QMessageBox.critical(
|
|
295
|
+
self,
|
|
296
|
+
"Invalid API Key",
|
|
297
|
+
"❌ The API key is invalid.\n\n"
|
|
298
|
+
"Please check your API key and try again."
|
|
299
|
+
)
|
|
300
|
+
elif "rate" in error_msg.lower():
|
|
301
|
+
QMessageBox.warning(
|
|
302
|
+
self,
|
|
303
|
+
"Rate Limited",
|
|
304
|
+
"⚠️ Rate limited by OpenAI.\n\n"
|
|
305
|
+
"The key appears valid but you've hit the rate limit. "
|
|
306
|
+
"Please wait a moment and try again."
|
|
307
|
+
)
|
|
308
|
+
else:
|
|
309
|
+
QMessageBox.critical(
|
|
310
|
+
self,
|
|
311
|
+
"Connection Failed",
|
|
312
|
+
f"❌ Failed to connect to OpenAI:\n\n{error_msg}"
|
|
313
|
+
)
|
|
314
|
+
# Restore old key on failure
|
|
315
|
+
if old_key != api_key:
|
|
316
|
+
self.ai_manager.set_api_key(old_key)
|
|
317
|
+
|
|
318
|
+
def save_settings(self):
|
|
319
|
+
"""Save the settings and close the dialog."""
|
|
320
|
+
# Save API key
|
|
321
|
+
api_key = self.api_key_input.text().strip()
|
|
322
|
+
self.ai_manager.set_api_key(api_key)
|
|
323
|
+
|
|
324
|
+
# Save enabled state
|
|
325
|
+
self.ai_manager.set_enabled(self.enabled_checkbox.isChecked())
|
|
326
|
+
|
|
327
|
+
# Save model selection
|
|
328
|
+
self.ai_manager.set_model(self.model_combo.currentText())
|
|
329
|
+
|
|
330
|
+
self.accept()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def show_ai_settings_dialog(parent=None):
|
|
334
|
+
"""Show the AI settings dialog."""
|
|
335
|
+
dialog = AISettingsDialog(parent)
|
|
336
|
+
return dialog.exec()
|
|
337
|
+
|