Merge branch 'master' into feature/cassandra

This commit is contained in:
SPRINX0\prochazka
2025-02-10 14:23:16 +01:00
26 changed files with 321 additions and 103 deletions

View File

@@ -17,4 +17,5 @@
{menu}
{hideDisabled}
{buttonLabel}
{...$$restProps}
/>

View File

@@ -21,7 +21,7 @@
<div class="button" class:disabled {title}>
<div class="inner" class:disabled>
<div class="main" class:disabled on:click={handleClick}>
<div class="main" class:disabled on:click={handleClick} data-testid={$$props['data-testid']}>
<span class="icon" class:disabled><FontIcon {icon} /></span>
<slot />
</div>

View File

@@ -19,6 +19,6 @@
}
</script>
<ToolStripSplitButton {title} {icon} {disabled} on:splitclick={handleClick} on:click>
<ToolStripSplitButton {title} {icon} {disabled} on:splitclick={handleClick} on:click {...$$restProps}>
<slot />
</ToolStripSplitButton>

View File

@@ -5,6 +5,7 @@
export let selection;
export let showWholeRow = false;
export let expandAll = false;
let json = null;
let error = null;
@@ -31,7 +32,7 @@
{:else}
<div class="outer">
<div class="inner">
<JSONTree value={json} expanded />
<JSONTree value={json} {expandAll} expanded />
</div>
</div>
{/if}

View File

@@ -0,0 +1,6 @@
<script lang="ts">
import JsonCellView from './JsonCellView.svelte';
export let selection;
</script>
<JsonCellView {selection} expandAll />

View File

@@ -148,6 +148,7 @@
'icon parent-filter-outline': 'mdi mdi-home-alert-outline',
'icon download': 'mdi mdi-download',
'icon text': 'mdi mdi-text',
'icon ai': 'mdi mdi-head-lightbulb',
'icon run': 'mdi mdi-play',
'icon chevron-down': 'mdi mdi-chevron-down',

View File

@@ -21,8 +21,14 @@
closeCurrentModal();
onConfirm();
}}
data-testid="ConfirmModal_okButton"
/>
<FormStyledButton
type="button"
value="Close"
on:click={closeCurrentModal}
data-testid="ConfirmModal_closeButton"
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>

View File

@@ -0,0 +1 @@
AI Assistant

View File

@@ -410,6 +410,14 @@ ORDER BY
{ value: 'download', label: 'Check and download new versions' },
]}
/>
{#if isProApp()}
<FormCheckboxField
name="ai.allowSendModels"
label="Allow to send DB models and query snippets to AI service"
defaultValue={false}
/>
{/if}
</svelte:fragment>
<svelte:fragment slot="7">

View File

@@ -115,6 +115,7 @@ export const currentEditorFontSize = getElectron()
? writableSettingsValue(null, 'currentEditorFontSize')
: writableWithStorage(null, 'currentEditorFontSize');
export const currentEditorFont = writableSettingsValue(null, 'editor.fontFamily');
export const allowedSendToAiService = writableSettingsValue(false, 'ai.allowSendModels');
export const activeTabId = derived([openedTabs], ([$openedTabs]) => $openedTabs.find(x => x.selected)?.tabid);
export const activeTab = derived([openedTabs], ([$openedTabs]) => $openedTabs.find(x => x.selected));
export const recentDatabases = writableWithStorage([], 'recentDatabases');

View File

@@ -12,6 +12,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',
@@ -89,6 +98,9 @@
import ToolStripDropDownButton from '../buttons/ToolStripDropDownButton.svelte';
import { extractQueryParameters, replaceQueryParameters } from 'dbgate-query-splitter';
import QueryParametersModal from '../modals/QueryParametersModal.svelte';
import { isProApp } from '../utility/proTools';
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
import QueryAiAssistant from '../query/QueryAiAssistant.svelte';
export let tabid;
export let conid;
@@ -137,6 +149,7 @@
let domEditor;
let domToolStrip;
let intervalId;
let isAiAssistantVisible = localStorage.getItem(`tabdata_isAiAssistantVisible_${tabid}`) == 'true';
onMount(() => {
intervalId = setInterval(() => {
@@ -210,6 +223,10 @@
visibleResultTabs = !visibleResultTabs;
}
export function toggleAiAssistant() {
isAiAssistantVisible = !isAiAssistantVisible;
}
function getParameterSplitterOptions() {
if (!queryParameterStyle) {
return null;
@@ -401,6 +418,7 @@
{ command: 'query.replace' },
{ divider: true },
{ command: 'query.toggleVisibleResultTabs' },
{ command: 'query.switchAiAssistant', hideDisabled: true },
];
}
@@ -420,79 +438,116 @@
localStorage.getItem(`tabdata_queryParamStyle_${tabid}`) ??
initialArgs?.queryParameterStyle ??
(initialArgs?.scriptTemplate == 'CALL OBJECT' ? ':' : '');
$: localStorage.setItem(`tabdata_isAiAssistantVisible_${tabid}`, isAiAssistantVisible ? 'true' : 'false');
</script>
<ToolStripContainer bind:this={domToolStrip}>
<VerticalSplitter isSplitter={visibleResultTabs}>
<HorizontalSplitter isSplitter={isAiAssistantVisible} initialSizeRight={300}>
<svelte:fragment slot="1">
{#if driver?.databaseEngineTypes?.includes('sql')}
<SqlEditor
engine={$connection && $connection.engine}
{conid}
{database}
splitterOptions={driver?.getQuerySplitterOptions('editor')}
options={{
wrap: enableWrap,
}}
value={$editorState.value || ''}
menu={createMenu()}
on:input={e => {
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}
<AceEditor
mode={driver?.editorMode || 'sql'}
value={$editorState.value || ''}
splitterOptions={driver?.getQuerySplitterOptions('editor')}
options={{
wrap: enableWrap,
}}
menu={createMenu()}
on:input={e => setEditorData(e.detail)}
on:focus={() => {
activator.activate();
domToolStrip?.activate();
invalidateCommands();
}}
bind:this={domEditor}
/>
{/if}
<VerticalSplitter isSplitter={visibleResultTabs}>
<svelte:fragment slot="1">
{#if driver?.databaseEngineTypes?.includes('sql')}
<SqlEditor
engine={$connection && $connection.engine}
{conid}
{database}
splitterOptions={driver?.getQuerySplitterOptions('editor')}
options={{
wrap: enableWrap,
}}
value={$editorState.value || ''}
menu={createMenu()}
on:input={e => {
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}
<AceEditor
mode={driver?.editorMode || 'sql'}
value={$editorState.value || ''}
splitterOptions={driver?.getQuerySplitterOptions('editor')}
options={{
wrap: enableWrap,
}}
menu={createMenu()}
on:input={e => setEditorData(e.detail)}
on:focus={() => {
activator.activate();
domToolStrip?.activate();
invalidateCommands();
}}
bind:this={domEditor}
/>
{/if}
</svelte:fragment>
<svelte:fragment slot="2">
<ResultTabs tabs={[{ label: 'Messages', slot: 0 }]} {sessionId} {executeNumber} bind:resultCount {driver}>
<svelte:fragment slot="0">
<SocketMessageView
eventName={sessionId ? `session-info-${sessionId}` : null}
onMessageClick={handleMesageClick}
{executeNumber}
startLine={executeStartLine}
showProcedure
showLine
onChangeErrors={handleChangeErrors}
/>
</svelte:fragment>
</ResultTabs>
</svelte:fragment>
</VerticalSplitter>
</svelte:fragment>
<svelte:fragment slot="2">
<ResultTabs tabs={[{ label: 'Messages', slot: 0 }]} {sessionId} {executeNumber} bind:resultCount {driver}>
<svelte:fragment slot="0">
<SocketMessageView
eventName={sessionId ? `session-info-${sessionId}` : null}
onMessageClick={handleMesageClick}
{executeNumber}
startLine={executeStartLine}
showProcedure
showLine
onChangeErrors={handleChangeErrors}
/>
</svelte:fragment>
</ResultTabs>
<QueryAiAssistant
{conid}
{database}
{driver}
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}
/>
</svelte:fragment>
</VerticalSplitter>
</HorizontalSplitter>
<svelte:fragment slot="toolstrip">
<ToolStripCommandSplitButton commands={['query.execute', 'query.executeCurrent']} />
<ToolStripCommandButton command="query.kill" />
<ToolStripCommandSplitButton
commands={['query.execute', 'query.executeCurrent']}
data-testid="QueryTab_executeButton"
/>
<ToolStripCommandButton command="query.kill" data-testid="QueryTab_killButton" />
<ToolStripSaveButton idPrefix="query" />
<ToolStripCommandButton command="query.formatCode" />
{#if resultCount == 1}
@@ -511,6 +566,13 @@
icon="icon at"
title="Query parameter style"
/>
<ToolStripCommandButton
command="query.switchAiAssistant"
hideDisabled
data-testid="QueryTab_switchAiAssistantButton"
>
AI Assistant
</ToolStripCommandButton>
</svelte:fragment>
</ToolStripContainer>

View File

@@ -20,6 +20,12 @@
component: JsonCellView,
single: true,
},
{
type: 'jsonExpanded',
title: 'Json - expanded',
component: JsonExpandedCellView,
single: true,
},
{
type: 'jsonRow',
title: 'Json - Row',
@@ -84,6 +90,7 @@
import SelectField from '../forms/SelectField.svelte';
import { selectedCellsCallback } from '../stores';
import WidgetTitle from './WidgetTitle.svelte';
import JsonExpandedCellView from '../celldata/JsonExpandedCellView.svelte';
let selectedFormatType = 'autodetect';
@@ -107,6 +114,7 @@
isNative
value={selectedFormatType}
on:change={e => (selectedFormatType = e.detail)}
data-testid="CellDataWidget_selectFormat"
options={[
{ value: 'autodetect', label: `Autodetect - ${autodetectFormat.title}` },
...formats.map(fmt => ({ label: fmt.title, value: fmt.type })),

View File

@@ -16,6 +16,7 @@
export let collapsed = null;
export let storageName = null;
export let onClose = null;
let size = 0;
@@ -70,7 +71,8 @@
<WidgetTitle
clickable={collapsible}
on:click={collapsible ? () => (visible = !visible) : null}
data-testid={$$props['data-testid']}>{title}</WidgetTitle
data-testid={$$props['data-testid']}
{onClose}>{title}</WidgetTitle
>
{#if visible}

View File

@@ -1,18 +1,35 @@
<script lang="ts">
import FontIcon from '../icons/FontIcon.svelte';
export let clickable = false;
export let onClose = null;
</script>
<div on:click class:clickable {...$$restProps}>
<div on:click class:clickable {...$$restProps} class="wrapper">
<slot />
{#if onClose}
<div class="close" on:click={onClose}>
<FontIcon icon="icon close" />
</div>
{/if}
</div>
<style>
div {
.wrapper {
padding: 5px;
font-weight: bold;
text-transform: uppercase;
background-color: var(--theme-bg-1);
border: 2px solid var(--theme-border);
display: flex;
align-items: center;
justify-content: space-between;
}
.close {
cursor: pointer;
}
.close:hover {
color: var(--theme-font-hover);
}
div.clickable:hover {
background-color: var(--theme-bg-2);