Skip to content

Commit a5019ca

Browse files
Dev Assistant: Add English to SQL in the Agroal extension
Signed-off-by: Phillip Kruger <[email protected]>
1 parent 615bd80 commit a5019ca

File tree

2 files changed

+177
-33
lines changed

2 files changed

+177
-33
lines changed

extensions/agroal/deployment/src/main/resources/dev-ui/qwc-agroal-datasource.js

Lines changed: 126 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import '@vaadin/combo-box';
55
import '@vaadin/item';
66
import '@vaadin/icon';
77
import '@vaadin/list-box';
8+
import '@vaadin/text-field';
89
import '@qomponent/qui-card';
910
import '@vaadin/grid';
1011
import '@vaadin/tabs';
@@ -19,6 +20,7 @@ import '@vaadin/dialog';
1920
import '@qomponent/qui-dot';
2021
import 'qui-assistant-button';
2122
import 'qui-assistant-warning';
23+
import '@qomponent/qui-badge';
2224
import { dialogFooterRenderer, dialogHeaderRenderer, dialogRenderer } from '@vaadin/dialog/lit.js';
2325
import { observeState } from 'lit-element-state';
2426
import { assistantState } from 'assistant-state';
@@ -91,8 +93,6 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
9193
display: flex;
9294
align-items: center;
9395
padding-bottom: 10px;
94-
border-bottom-style: dotted;
95-
border-bottom-color: var(--lumo-contrast-10pct);
9696
}
9797
9898
.sqlInput .cm-content {
@@ -111,9 +111,13 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
111111
margin: 0;
112112
}
113113
114-
#sql {
114+
#sqlInput {
115+
width: 100%;
116+
}
117+
#assistantInput {
115118
width: 100%;
116119
}
120+
117121
.data {
118122
display: flex;
119123
flex-direction: column;
@@ -161,6 +165,12 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
161165
align-self: center;
162166
padding-top: 50px;
163167
}
168+
169+
qui-badge {
170+
cursor: pointer;
171+
padding-left: 2px;
172+
padding-right: 5px;
173+
}
164174
`;
165175

166176
static properties = {
@@ -172,6 +182,7 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
172182
_selectedTableIndex:{state: true},
173183
_selectedTableCols:{state: true},
174184
_currentSQL: {state: true},
185+
_currentEnglish: {state: true},
175186
_currentDataSet: {state: true},
176187
_isWatching: {state: true},
177188
_watchId: {state: false},
@@ -186,7 +197,9 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
186197
_showBusyLoadingDialog: {state: true},
187198
_showAssistantWarning: {state: true},
188199
_showImportSQLDialog: {state: true},
189-
_showErDiagramDialog: {state: true}
200+
_showErDiagramDialog: {state: true},
201+
_englishToSQLEnabled: {state: true},
202+
_englishToSQLLoadingMessage: {state: true}
190203
};
191204

192205
constructor() {
@@ -199,6 +212,7 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
199212
this._selectedTableCols = null;
200213
this._selectedTableIndex = 0;
201214
this._currentSQL = null;
215+
this._currentEnglish = null;
202216
this._currentDataSet = null;
203217
this._isWatching = false;
204218
this._watchId = null;
@@ -214,6 +228,8 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
214228
this._showAssistantWarning = false;
215229
this._showImportSQLDialog = false;
216230
this._showErDiagramDialog = false;
231+
this._englishToSQLEnabled = false;
232+
this._englishToSQLLoadingMessage = null;
217233
}
218234

219235
connectedCallback() {
@@ -236,6 +252,8 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
236252
return map;
237253
}, {});
238254
});
255+
256+
this._englishToSQLEnabled = false; // TODO: Load from storage
239257
}
240258

241259
disconnectedCallback() {
@@ -551,17 +569,25 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
551569
_renderTableData(){
552570
if(this._selectedTable && this._currentDataSet && this._currentDataSet.cols){
553571
return html`<div class="data">
554-
${this._renderSqlInput()}
555572
${this._renderTableDataGrid()}
573+
${this._renderSqlInput()}
556574
</div>
557575
`;
558576
}else if(this._displaymessage){
559577
return html`<span>${this._displaymessage}</span>`;
560578
}else{
561-
return html`<div style="color: var(--lumo-secondary-text-color);width: 95%;" >
562-
<div>Fetching data...</div>
563-
<vaadin-progress-bar indeterminate></vaadin-progress-bar>
564-
</div>`;
579+
if(assistantState.current.isConfigured && this._englishToSQLEnabled){
580+
return html`<div style="color: var(--lumo-secondary-text-color);width: 95%;" >
581+
<div>${this._englishToSQLLoadingMessage}</div>
582+
<vaadin-progress-bar indeterminate></vaadin-progress-bar>
583+
</div>`;
584+
}else{
585+
return html`<div style="color: var(--lumo-secondary-text-color);width: 95%;" >
586+
<div>Fetching data ...</div>
587+
<vaadin-progress-bar indeterminate></vaadin-progress-bar>
588+
</div>`;
589+
}
590+
565591
}
566592
}
567593

@@ -633,15 +659,14 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
633659
if (this._allowSql) {
634660
return html`
635661
<div class="sqlInput">
636-
<qui-themed-code-block @shiftEnter=${this._shiftEnterPressed} content="${this._currentSQL}"
637-
class="font-large cursor-text" id="sql" mode="sql"
638-
value='${this._currentSQL}' editable></qui-themed-code-block>
639-
<vaadin-button class="no-margin" slot="suffix" theme="icon tertiary small" aria-label="Clear">
662+
${this._renderEnglishToSQLButton()}
663+
${this._renderInputTextField()}
664+
<vaadin-button class="no-margin" theme="icon tertiary small" aria-label="Clear">
640665
<vaadin-tooltip .hoverDelay=${500} slot="tooltip" text="Clear"></vaadin-tooltip>
641-
<vaadin-icon class="small-icon" @click=${this._clearSqlInput}
666+
<vaadin-icon class="small-icon" @click=${this._clearInput}
642667
icon="font-awesome-solid:broom"></vaadin-icon>
643668
</vaadin-button>
644-
<vaadin-button class="no-margin" slot="suffix" theme="icon tertiary small" aria-label="Run">
669+
<vaadin-button class="no-margin" theme="icon tertiary small" aria-label="Run">
645670
<vaadin-tooltip .hoverDelay=${500} slot="tooltip" text="Run"></vaadin-tooltip>
646671
<vaadin-icon class="small-icon" @click=${this._executeClicked}
647672
icon="font-awesome-solid:play"></vaadin-icon>
@@ -652,6 +677,42 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
652677
}
653678
}
654679

680+
_renderInputTextField(){
681+
if(assistantState.current.isConfigured && this._englishToSQLEnabled){
682+
return html`<vaadin-text-field value='${this._currentEnglish}' placeholder="Describe the data you are looking for in English" @keydown="${this._englishKeyDown}" id="assistantInput">
683+
<vaadin-icon slot="prefix" icon="font-awesome-solid:robot" title="${this._currentSQL}"></vaadin-icon>
684+
</vaadin-text-field>`;
685+
}else{
686+
return html`<qui-themed-code-block @shiftEnter=${this._shiftEnterPressed} content="${this._currentSQL}"
687+
class="font-large cursor-text" id="sqlInput" mode="sql"
688+
value='${this._currentSQL}' editable></qui-themed-code-block>`;
689+
}
690+
}
691+
692+
_renderEnglishToSQLButton(){
693+
if(assistantState.current.isConfigured){
694+
if(this._englishToSQLEnabled){
695+
return html`Using <qui-badge @click=${this._switchEnglishToSQL} color="var(--quarkus-assistant)"><span>Assistant</span></qui-badge>`;
696+
}else{
697+
return html`Using <qui-badge @click=${this._switchEnglishToSQL}><span>SQL</span></qui-badge>`;
698+
}
699+
}
700+
}
701+
702+
_switchEnglishToSQL(){
703+
if(this._englishToSQLEnabled){
704+
this._englishToSQLEnabled = false;
705+
}else{
706+
this._englishToSQLEnabled = true;
707+
}
708+
}
709+
710+
_englishKeyDown(e){
711+
if (e.key === 'Enter') {
712+
this._executeClickedAI();
713+
}
714+
}
715+
655716
_generateErDiagram(){
656717
if(this._selectedDataSource){
657718
this._showBusyLoadingDialog = "Generating ER Diagram ... please wait";
@@ -670,7 +731,7 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
670731
'value': 'true'
671732
}).then(e => {
672733
this._allowSql = true;
673-
});;
734+
});
674735
}
675736

676737
_columnNameRenderer(col){
@@ -762,7 +823,7 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
762823
this._fetchTableDefinitions();
763824
this._selectedTableIndex = event.detail.value;
764825
this._selectedTable = this._tables[this._selectedTableIndex];
765-
this._clearSqlInput();
826+
this._clearInput();
766827
}
767828

768829
_previousPage(){
@@ -801,7 +862,7 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
801862
notifier.showErrorMessage(jsonRpcResponse.result.error);
802863
} else if (jsonRpcResponse.result.message){
803864
notifier.showInfoMessage(jsonRpcResponse.result.message);
804-
this._clearSqlInput();
865+
this._clearInput();
805866
} else {
806867
this._currentDataSet = jsonRpcResponse.result;
807868
this._currentNumberOfPages = this._getNumberOfPages();
@@ -840,16 +901,55 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
840901
}
841902

842903
_executeClicked(){
843-
let newValue = this.shadowRoot.getElementById('sql').getAttribute('value');
904+
if(assistantState.current.isConfigured && this._englishToSQLEnabled){
905+
this._executeClickedAI();
906+
}else {
907+
this._executeClickedSQL();
908+
}
909+
}
910+
911+
_executeClickedSQL(){
912+
let newValue = this.shadowRoot.getElementById('sqlInput').getAttribute('value');
844913
this._executeSQL(newValue);
845914
}
846915

847-
_clearSqlInput(){
848-
if(this._selectedTable){
849-
if(this._appendSql){
850-
this._executeSQL("select * from " + this._selectedTable.tableSchema + "." + this._selectedTable.tableName + " " + this._appendSql);
851-
}else{
852-
this._executeSQL("select * from " + this._selectedTable.tableSchema + "." + this._selectedTable.tableName);
916+
_executeClickedAI(){
917+
this._currentEnglish = this.shadowRoot.getElementById('assistantInput').value;
918+
this._englishToSQLLoadingMessage = "Creating SQL from \"" + this._currentEnglish + "\"";
919+
this._currentDataSet = null;
920+
this._currentSQL = null;
921+
922+
this.jsonRpc.englishToSQL({
923+
datasource:this._selectedDataSource.name,
924+
schema: this._selectedTable.tableSchema,
925+
name: this._selectedTable.tableName,
926+
english: this._currentEnglish
927+
}).then(jsonRpcResponse => {
928+
if(jsonRpcResponse.result.error){
929+
notifier.showErrorMessage(jsonRpcResponse.result.error);
930+
} else {
931+
let sql = jsonRpcResponse.result.sql;
932+
this._englishToSQLLoadingMessage = "Using SQL \"" + sql + "\"";
933+
notifier.showInfoMessage(sql);
934+
this._executeSQL(sql);
935+
}
936+
});
937+
938+
939+
940+
}
941+
942+
_clearInput(){
943+
if(assistantState.current.isConfigured && this._englishToSQLEnabled){
944+
this._currentEnglish = null;
945+
this.shadowRoot.getElementById('assistantInput').value = '';
946+
}else{
947+
if(this._selectedTable){
948+
if(this._appendSql){
949+
this._executeSQL("select * from " + this._selectedTable.tableSchema + "." + this._selectedTable.tableName + " " + this._appendSql);
950+
}else{
951+
this._executeSQL("select * from " + this._selectedTable.tableSchema + "." + this._selectedTable.tableName);
952+
}
853953
}
854954
}
855955
}
@@ -861,6 +961,7 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
861961
_executeSQL(sql){
862962
this._currentSQL = sql.trim();
863963
this._executeCurrentSQL();
964+
this._englishToSQLLoadingMessage = null;
864965
}
865966

866967
_startsWithIgnoreCaseAndSpaces(str, searchString) {

extensions/agroal/runtime-dev/src/main/java/io/quarkus/agroal/runtime/dev/ui/DatabaseInspector.java

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,18 @@ public CompletionStage<Map<String, String>> generateTableData(String datasource,
366366
return CompletableFuture.failedStage(new RuntimeException("Assistant is not available"));
367367
}
368368

369+
public CompletionStage<Map<String, String>> englishToSQL(String datasource, String schema, String name, String english) {
370+
if (isDev && assistant.isPresent()) {
371+
List<Table> tables = getTables(datasource);
372+
373+
return assistant.get().assistBuilder()
374+
.userMessage(englishToSqlPrompt(tables, schema, name, english))
375+
.assist();
376+
377+
}
378+
return CompletableFuture.failedStage(new RuntimeException("Assistant is not available"));
379+
}
380+
369381
private void exportTable(Connection conn, StringWriter writer, String tableName) throws SQLException, IOException {
370382
try (Statement stmt = conn.createStatement();
371383
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName)) {
@@ -487,12 +499,51 @@ private boolean isBinary(int dataType) {
487499
dataType == Types.OTHER;
488500
}
489501

502+
private String englishToSqlPrompt(List<Table> tables, String schema, String name, String english) {
503+
StringBuilder sb = new StringBuilder();
504+
sb.append("Generate valid SQL given the following english statement: ")
505+
.append(english)
506+
.append("\n\nAnd this is the known tables in the database:\n");
507+
508+
for (Table table : tables) {
509+
sb.append(getTableDefinitionAsString(table));
510+
sb.append("\n\n");
511+
}
512+
513+
sb.append("\nIf you can not defer the schema name from the english statement, use ").append(schema)
514+
.append(" as the schema name");
515+
sb.append("\nIf you can not defer the table name from the english statement, use ").append(name)
516+
.append(" as the table name");
517+
518+
sb.append(
519+
"\nReturn the output in a field called `sql` with the contents being valid SQL");
520+
521+
sb.append(
522+
"\nIf you can not reliably create this sql, rather create an output with a field called error containing the reason");
523+
524+
return sb.toString();
525+
}
526+
490527
private String generateInsertPrompt(Table table, int rowCount) {
491528
StringBuilder sb = new StringBuilder();
492529
sb.append("Generate a valid SQL script with ")
493530
.append(rowCount)
494531
.append(" INSERT statements for the following table:\n\n");
495532

533+
sb.append(getTableDefinitionAsString(table));
534+
535+
sb.append(
536+
"\nReturn the output in a field called `script` with the contents being a SQL script with valid INSERT INTO statements for ")
537+
.append(table.tableSchema())
538+
.append(".")
539+
.append(table.tableName())
540+
.append(".\n");
541+
542+
return sb.toString();
543+
}
544+
545+
private String getTableDefinitionAsString(Table table) {
546+
StringBuilder sb = new StringBuilder();
496547
sb.append("Table name: ")
497548
.append(table.tableSchema())
498549
.append(".")
@@ -528,14 +579,6 @@ private String generateInsertPrompt(Table table, int rowCount) {
528579
.append(")\n");
529580
}
530581
}
531-
532-
sb.append(
533-
"\nReturn the output in a field called `script` with the contents being a SQL script with valid INSERT INTO statements for ")
534-
.append(table.tableSchema())
535-
.append(".")
536-
.append(table.tableName())
537-
.append(".\n");
538-
539582
return sb.toString();
540583
}
541584

0 commit comments

Comments
 (0)