diff --git a/packages/web/src/buttons/InlineButton.svelte b/packages/web/src/buttons/InlineButton.svelte
index 147751a3a..97c266d3b 100644
--- a/packages/web/src/buttons/InlineButton.svelte
+++ b/packages/web/src/buttons/InlineButton.svelte
@@ -3,6 +3,7 @@
export let square = false;
export let narrow = false;
export let title = null;
+ export let inlineBlock=false;
let domButton;
@@ -17,6 +18,7 @@
class:disabled
class:square
class:narrow
+ class:inlineBlock
on:click
bind:this={domButton}
data-testid={$$props['data-testid']}
@@ -71,4 +73,8 @@
.square {
width: 18px;
}
+
+ .inlineBlock {
+ display: inline-block;
+ }
diff --git a/packages/web/src/buttons/ToolStripContainer.svelte b/packages/web/src/buttons/ToolStripContainer.svelte
index 9bff78276..29bdc3ad1 100644
--- a/packages/web/src/buttons/ToolStripContainer.svelte
+++ b/packages/web/src/buttons/ToolStripContainer.svelte
@@ -4,7 +4,8 @@
const thisInstance = get_current_component();
- export const activator = createActivator('ToolStripContainer', true);
+ export let showAlways = false;
+ export const activator = showAlways ? null : createActivator('ToolStripContainer', true);
export function activate() {
activator?.activate();
@@ -13,7 +14,7 @@
export let scrollContent = false;
export let hideToolStrip = false;
- $: isComponentActive = $isComponentActiveStore('ToolStripContainer', thisInstance) && !hideToolStrip;
+ $: isComponentActive = showAlways || ($isComponentActiveStore('ToolStripContainer', thisInstance) && !hideToolStrip);
diff --git a/packages/web/src/query/MessageView.svelte b/packages/web/src/query/MessageView.svelte
index 4d67e3c77..465e53713 100644
--- a/packages/web/src/query/MessageView.svelte
+++ b/packages/web/src/query/MessageView.svelte
@@ -11,6 +11,7 @@
export let showCaller = false;
export let startLine = 0;
export let onMessageClick = null;
+ export let onExplainError = null;
export let filter = '';
@@ -90,6 +91,7 @@
{startLine}
previousRow={index > 0 ? items[index - 1] : null}
{onMessageClick}
+ {onExplainError}
/>
{/each}
diff --git a/packages/web/src/query/MessageViewRow.svelte b/packages/web/src/query/MessageViewRow.svelte
index 0d63ddbe3..4822f365a 100644
--- a/packages/web/src/query/MessageViewRow.svelte
+++ b/packages/web/src/query/MessageViewRow.svelte
@@ -16,6 +16,7 @@
import JSONTree from '../jsontree/JSONTree.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import { plusExpandIcon } from '../icons/expandIcons';
+ import InlineButton from '../buttons/InlineButton.svelte';
export let row;
export let index;
@@ -27,6 +28,7 @@
export let previousRow = null;
export let onMessageClick = null;
+ export let onExplainError = null;
let isExpanded = false;
@@ -43,6 +45,15 @@
{row.message}
+ {#if row.severity == 'error' && onExplainError}
+
{
+ onExplainError(row);
+ }}> Explain
+ {/if}
{moment(row.time).format('HH:mm:ss')} |
{formatDuration(new Date(row.time).getTime() - time0)} |
diff --git a/packages/web/src/query/SocketMessageView.svelte b/packages/web/src/query/SocketMessageView.svelte
index 4c0700d0d..90e66f44f 100644
--- a/packages/web/src/query/SocketMessageView.svelte
+++ b/packages/web/src/query/SocketMessageView.svelte
@@ -17,6 +17,7 @@
export let startLine = 0;
export let onChangeErrors = null;
export let onMessageClick = null;
+ export let onExplainError = null;
const cachedMessagesRef = createRef([]);
const lastErrorMessageCountRef = createRef(0);
@@ -70,5 +71,13 @@
{#if showNoMessagesAlert && (!displayedMessages || displayedMessages.length == 0)}
{:else}
-
+
{/if}
diff --git a/packages/web/src/query/useEditorData.ts b/packages/web/src/query/useEditorData.ts
index 3a1bd52f4..34525bd0f 100644
--- a/packages/web/src/query/useEditorData.ts
+++ b/packages/web/src/query/useEditorData.ts
@@ -21,8 +21,8 @@ function getParsedLocalStorage(key) {
const saveHandlersList = [];
-export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = null, onInitialData = null }) {
- const localStorageKey = `tabdata_editor_${tabid}`;
+export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = null, onInitialData = null, editorKeyword = 'editor' }) {
+ const localStorageKey = `tabdata_${editorKeyword}_${tabid}`;
let changeCounter = 0;
let savedCounter = 0;
diff --git a/packages/web/src/tabs/QueryTab.svelte b/packages/web/src/tabs/QueryTab.svelte
index b7d8ddb59..fd55fee86 100644
--- a/packages/web/src/tabs/QueryTab.svelte
+++ b/packages/web/src/tabs/QueryTab.svelte
@@ -13,6 +13,15 @@
testEnabled: () => getCurrentEditor()?.isSqlEditor(),
onClick: () => getCurrentEditor().formatCode(),
});
+ registerCommand({
+ id: 'query.switchAiAssistant',
+ category: 'Query',
+ name: 'AI Assistant',
+ keyText: 'Shift+Alt+A',
+ icon: 'icon ai',
+ testEnabled: () => isProApp(),
+ onClick: () => getCurrentEditor().toggleAiAssistant(),
+ });
registerCommand({
id: 'query.insertSqlJoin',
category: 'Query',
@@ -155,6 +164,7 @@
import _ from 'lodash';
import FontIcon from '../icons/FontIcon.svelte';
import hasPermission from '../utility/hasPermission';
+ import QueryAiAssistant from '../ai/QueryAiAssistant.svelte';
export let tabid;
export let conid;
@@ -227,6 +237,9 @@
let queryRowsLimit = getInitialRowsLimit();
$: localStorage.setItem(queryRowsLimitLocalStorageKey, queryRowsLimit ? queryRowsLimit.toString() : 'nolimit');
+ let isAiAssistantVisible = isProApp() && localStorage.getItem(`tabdata_isAiAssistantVisible_${tabid}`) == 'true';
+ let domAiAssistant;
+
onMount(() => {
intervalId = setInterval(() => {
if (!driver?.singleConnectionOnly && sessionId) {
@@ -301,6 +314,10 @@
visibleResultTabs = !visibleResultTabs;
}
+ export function toggleAiAssistant() {
+ isAiAssistantVisible = !isAiAssistantVisible;
+ }
+
function getParameterSplitterOptions() {
if (!queryParameterStyle) {
return null;
@@ -571,6 +588,16 @@
errorMessages = errors;
}
+ async function handleExplainError(errorObject) {
+ if (!isProApp()) return;
+ isAiAssistantVisible = true;
+ await tick();
+ domAiAssistant?.explainError({
+ userQuery: $editorValue,
+ errorObject,
+ });
+ }
+
function handleSetFrontMatterField(field, value) {
const text = $editorValue;
setEditorData(
@@ -651,91 +678,127 @@
localStorage.getItem(`tabdata_queryParamStyle_${tabid}`) ??
initialArgs?.queryParameterStyle ??
(initialArgs?.scriptTemplate == 'CALL OBJECT' ? ':' : '');
+
+ $: localStorage.setItem(`tabdata_isAiAssistantVisible_${tabid}`, isAiAssistantVisible ? 'true' : 'false');
-
+
- {#if driver?.databaseEngineTypes?.includes('sql')}
- {
- setEditorData(e.detail);
- if (isInitialized) {
- markTabUnsaved(tabid);
- }
- errorMessages = [];
- }}
- on:focus={() => {
- activator.activate();
- domToolStrip?.activate();
- invalidateCommands();
- setTimeout(() => {
- isInitialized = true;
- }, 100);
- }}
- bind:this={domEditor}
- onExecuteFragment={(sql, startLine) => executeCore(sql, startLine)}
- {errorMessages}
- />
- {:else}
- setEditorData(e.detail)}
- on:focus={() => {
- activator.activate();
- domToolStrip?.activate();
- invalidateCommands();
- }}
- bind:this={domEditor}
- />
- {/if}
+
+
+ {#if driver?.databaseEngineTypes?.includes('sql')}
+ {
+ setEditorData(e.detail);
+ if (isInitialized) {
+ markTabUnsaved(tabid);
+ }
+ errorMessages = [];
+ }}
+ on:focus={() => {
+ activator.activate();
+ domToolStrip?.activate();
+ invalidateCommands();
+ setTimeout(() => {
+ isInitialized = true;
+ }, 100);
+ }}
+ bind:this={domEditor}
+ onExecuteFragment={(sql, startLine) => executeCore(sql, startLine)}
+ {errorMessages}
+ />
+ {:else}
+ setEditorData(e.detail)}
+ on:focus={() => {
+ activator.activate();
+ domToolStrip?.activate();
+ invalidateCommands();
+ }}
+ bind:this={domEditor}
+ />
+ {/if}
+
+
+ getSqlFrontMatter($editorValue, yaml)}
+ >
+
+
+
+
+
+
- getSqlFrontMatter($editorValue, yaml)}
- >
-
-
-
-
+ onClose={() => {
+ isAiAssistantVisible = false;
+ }}
+ text={$editorValue}
+ getLine={() => domEditor.getEditor().getSelectionRange().start.row}
+ onInsertAtCursor={text => {
+ const editor = domEditor.getEditor();
+ editor.session.insert(editor.getCursorPosition(), text);
+ domEditor?.getEditor()?.focus();
+ }}
+ getTextOrSelectedText={() => domEditor.getEditor().getSelectedText() || $editorValue}
+ onSetSelectedText={text => {
+ const editor = domEditor.getEditor();
+ if (editor.getSelectedText()) {
+ const range = editor.selection.getRange();
+ editor.session.replace(range, text);
+ } else {
+ editor.setValue(text);
+ }
+ }}
+ {tabid}
+ />
-
+
+