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} + /> - + +