SYNC: Merge pull request #6 from dbgate/feature/ai-assistant

This commit is contained in:
Jan Prochazka
2025-07-23 15:45:36 +02:00
committed by Diflow
parent 082d0aa02f
commit c07e19c898
15 changed files with 883 additions and 174 deletions

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import XmlHighlighter from './XmlHighlighter.svelte';
import XmlHighlighter from '../elements/XmlHighlighter.svelte';
export let selection;
</script>

View File

@@ -714,6 +714,28 @@ if (isProApp()) {
);
},
});
registerCommand({
id: 'database.chat',
category: 'Database',
name: 'Database chat',
toolbar: true,
icon: 'icon ai',
testEnabled: () =>
getCurrentDatabase() != null &&
findEngineDriver(getCurrentDatabase()?.connection, getExtensions())?.databaseEngineTypes?.includes('sql'),
onClick: () => {
openNewTab({
title: 'Chat',
icon: 'img ai',
tabComponent: 'DatabaseChatTab',
props: {
conid: getCurrentDatabase()?.connection?._id,
database: getCurrentDatabase()?.name,
},
});
},
});
}
if (hasPermission('settings/change')) {

View File

@@ -0,0 +1,41 @@
<script>
/* npm i highlight.js sql-formatter */
import hljs from 'highlight.js/lib/core';
import sqlGrammar from './sqlGrammar';
import { onMount, afterUpdate } from 'svelte';
export let code = '';
let domCode;
onMount(() => {
hljs.registerLanguage('sql', sqlGrammar);
// first paint
if (domCode) {
hljs.highlightElement(domCode);
}
});
afterUpdate(() => {
if (domCode) {
hljs.highlightElement(domCode);
}
});
</script>
{#key code}
<!--
The `sql` class hints the language; highlight.js will
read it even though we register the grammar explicitly.
-->
<pre bind:this={domCode} class="sql">{code}</pre>
{/key}
<style>
pre {
margin: 0;
padding: 0;
padding: 0.5em;
}
</style>

View File

@@ -6,7 +6,7 @@
export let code = '';
$: formattedCode = xmlFormat(code, { indentation: ' ' });
$: formattedCode = xmlFormat(code, { indentation: ' ', throwOnFailure: false });
onMount(() => {
hljs.registerLanguage('xml', xmlGrammar);

View File

@@ -0,0 +1,691 @@
/*
Language: SQL
Website: https://en.wikipedia.org/wiki/SQL
Category: common, database
*/
/*
Goals:
SQL is intended to highlight basic/common SQL keywords and expressions
- If pretty much every single SQL server includes supports, then it's a canidate.
- It is NOT intended to include tons of vendor specific keywords (Oracle, MySQL,
PostgreSQL) although the list of data types is purposely a bit more expansive.
- For more specific SQL grammars please see:
- PostgreSQL and PL/pgSQL - core
- T-SQL - https://github.com/highlightjs/highlightjs-tsql
- sql_more (core)
*/
export default function(hljs) {
const regex = hljs.regex;
const COMMENT_MODE = hljs.COMMENT('--', '$');
const STRING = {
scope: 'string',
variants: [
{
begin: /'/,
end: /'/,
contains: [ { match: /''/ } ]
}
]
};
const QUOTED_IDENTIFIER = {
begin: /"/,
end: /"/,
contains: [ { match: /""/ } ]
};
const LITERALS = [
"true",
"false",
// Not sure it's correct to call NULL literal, and clauses like IS [NOT] NULL look strange that way.
// "null",
"unknown"
];
const MULTI_WORD_TYPES = [
"double precision",
"large object",
"with timezone",
"without timezone"
];
const TYPES = [
'bigint',
'binary',
'blob',
'boolean',
'char',
'character',
'clob',
'date',
'dec',
'decfloat',
'decimal',
'float',
'int',
'integer',
'interval',
'nchar',
'nclob',
'national',
'numeric',
'real',
'row',
'smallint',
'time',
'timestamp',
'varchar',
'varying', // modifier (character varying)
'varbinary'
];
const NON_RESERVED_WORDS = [
"add",
"asc",
"collation",
"desc",
"final",
"first",
"last",
"view"
];
// https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#reserved-word
const RESERVED_WORDS = [
"abs",
"acos",
"all",
"allocate",
"alter",
"and",
"any",
"are",
"array",
"array_agg",
"array_max_cardinality",
"as",
"asensitive",
"asin",
"asymmetric",
"at",
"atan",
"atomic",
"authorization",
"avg",
"begin",
"begin_frame",
"begin_partition",
"between",
"bigint",
"binary",
"blob",
"boolean",
"both",
"by",
"call",
"called",
"cardinality",
"cascaded",
"case",
"cast",
"ceil",
"ceiling",
"char",
"char_length",
"character",
"character_length",
"check",
"classifier",
"clob",
"close",
"coalesce",
"collate",
"collect",
"column",
"commit",
"condition",
"connect",
"constraint",
"contains",
"convert",
"copy",
"corr",
"corresponding",
"cos",
"cosh",
"count",
"covar_pop",
"covar_samp",
"create",
"cross",
"cube",
"cume_dist",
"current",
"current_catalog",
"current_date",
"current_default_transform_group",
"current_path",
"current_role",
"current_row",
"current_schema",
"current_time",
"current_timestamp",
"current_path",
"current_role",
"current_transform_group_for_type",
"current_user",
"cursor",
"cycle",
"date",
"day",
"deallocate",
"dec",
"decimal",
"decfloat",
"declare",
"default",
"define",
"delete",
"dense_rank",
"deref",
"describe",
"deterministic",
"disconnect",
"distinct",
"double",
"drop",
"dynamic",
"each",
"element",
"else",
"empty",
"end",
"end_frame",
"end_partition",
"end-exec",
"equals",
"escape",
"every",
"except",
"exec",
"execute",
"exists",
"exp",
"external",
"extract",
"false",
"fetch",
"filter",
"first_value",
"float",
"floor",
"for",
"foreign",
"frame_row",
"free",
"from",
"full",
"function",
"fusion",
"get",
"global",
"grant",
"group",
"grouping",
"groups",
"having",
"hold",
"hour",
"identity",
"in",
"indicator",
"initial",
"inner",
"inout",
"insensitive",
"insert",
"int",
"integer",
"intersect",
"intersection",
"interval",
"into",
"is",
"join",
"json_array",
"json_arrayagg",
"json_exists",
"json_object",
"json_objectagg",
"json_query",
"json_table",
"json_table_primitive",
"json_value",
"lag",
"language",
"large",
"last_value",
"lateral",
"lead",
"leading",
"left",
"like",
"like_regex",
"listagg",
"ln",
"local",
"localtime",
"localtimestamp",
"log",
"log10",
"lower",
"match",
"match_number",
"match_recognize",
"matches",
"max",
"member",
"merge",
"method",
"min",
"minute",
"mod",
"modifies",
"module",
"month",
"multiset",
"national",
"natural",
"nchar",
"nclob",
"new",
"no",
"none",
"normalize",
"not",
"nth_value",
"ntile",
"null",
"nullif",
"numeric",
"octet_length",
"occurrences_regex",
"of",
"offset",
"old",
"omit",
"on",
"one",
"only",
"open",
"or",
"order",
"out",
"outer",
"over",
"overlaps",
"overlay",
"parameter",
"partition",
"pattern",
"per",
"percent",
"percent_rank",
"percentile_cont",
"percentile_disc",
"period",
"portion",
"position",
"position_regex",
"power",
"precedes",
"precision",
"prepare",
"primary",
"procedure",
"ptf",
"range",
"rank",
"reads",
"real",
"recursive",
"ref",
"references",
"referencing",
"regr_avgx",
"regr_avgy",
"regr_count",
"regr_intercept",
"regr_r2",
"regr_slope",
"regr_sxx",
"regr_sxy",
"regr_syy",
"release",
"result",
"return",
"returns",
"revoke",
"right",
"rollback",
"rollup",
"row",
"row_number",
"rows",
"running",
"savepoint",
"scope",
"scroll",
"search",
"second",
"seek",
"select",
"sensitive",
"session_user",
"set",
"show",
"similar",
"sin",
"sinh",
"skip",
"smallint",
"some",
"specific",
"specifictype",
"sql",
"sqlexception",
"sqlstate",
"sqlwarning",
"sqrt",
"start",
"static",
"stddev_pop",
"stddev_samp",
"submultiset",
"subset",
"substring",
"substring_regex",
"succeeds",
"sum",
"symmetric",
"system",
"system_time",
"system_user",
"table",
"tablesample",
"tan",
"tanh",
"then",
"time",
"timestamp",
"timezone_hour",
"timezone_minute",
"to",
"trailing",
"translate",
"translate_regex",
"translation",
"treat",
"trigger",
"trim",
"trim_array",
"true",
"truncate",
"uescape",
"union",
"unique",
"unknown",
"unnest",
"update",
"upper",
"user",
"using",
"value",
"values",
"value_of",
"var_pop",
"var_samp",
"varbinary",
"varchar",
"varying",
"versioning",
"when",
"whenever",
"where",
"width_bucket",
"window",
"with",
"within",
"without",
"year",
];
// these are reserved words we have identified to be functions
// and should only be highlighted in a dispatch-like context
// ie, array_agg(...), etc.
const RESERVED_FUNCTIONS = [
"abs",
"acos",
"array_agg",
"asin",
"atan",
"avg",
"cast",
"ceil",
"ceiling",
"coalesce",
"corr",
"cos",
"cosh",
"count",
"covar_pop",
"covar_samp",
"cume_dist",
"dense_rank",
"deref",
"element",
"exp",
"extract",
"first_value",
"floor",
"json_array",
"json_arrayagg",
"json_exists",
"json_object",
"json_objectagg",
"json_query",
"json_table",
"json_table_primitive",
"json_value",
"lag",
"last_value",
"lead",
"listagg",
"ln",
"log",
"log10",
"lower",
"max",
"min",
"mod",
"nth_value",
"ntile",
"nullif",
"percent_rank",
"percentile_cont",
"percentile_disc",
"position",
"position_regex",
"power",
"rank",
"regr_avgx",
"regr_avgy",
"regr_count",
"regr_intercept",
"regr_r2",
"regr_slope",
"regr_sxx",
"regr_sxy",
"regr_syy",
"row_number",
"sin",
"sinh",
"sqrt",
"stddev_pop",
"stddev_samp",
"substring",
"substring_regex",
"sum",
"tan",
"tanh",
"translate",
"translate_regex",
"treat",
"trim",
"trim_array",
"unnest",
"upper",
"value_of",
"var_pop",
"var_samp",
"width_bucket",
];
// these functions can
const POSSIBLE_WITHOUT_PARENS = [
"current_catalog",
"current_date",
"current_default_transform_group",
"current_path",
"current_role",
"current_schema",
"current_transform_group_for_type",
"current_user",
"session_user",
"system_time",
"system_user",
"current_time",
"localtime",
"current_timestamp",
"localtimestamp"
];
// those exist to boost relevance making these very
// "SQL like" keyword combos worth +1 extra relevance
const COMBOS = [
"create table",
"insert into",
"primary key",
"foreign key",
"not null",
"alter table",
"add constraint",
"grouping sets",
"on overflow",
"character set",
"respect nulls",
"ignore nulls",
"nulls first",
"nulls last",
"depth first",
"breadth first"
];
const FUNCTIONS = RESERVED_FUNCTIONS;
const KEYWORDS = [
...RESERVED_WORDS,
...NON_RESERVED_WORDS
].filter((keyword) => {
return !RESERVED_FUNCTIONS.includes(keyword);
});
const VARIABLE = {
scope: "variable",
match: /@[a-z0-9][a-z0-9_]*/,
};
const OPERATOR = {
scope: "operator",
match: /[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,
relevance: 0,
};
const FUNCTION_CALL = {
match: regex.concat(/\b/, regex.either(...FUNCTIONS), /\s*\(/),
relevance: 0,
keywords: { built_in: FUNCTIONS }
};
// turns a multi-word keyword combo into a regex that doesn't
// care about extra whitespace etc.
// input: "START QUERY"
// output: /\bSTART\s+QUERY\b/
function kws_to_regex(list) {
return regex.concat(
/\b/,
regex.either(...list.map((kw) => {
return kw.replace(/\s+/, "\\s+")
})),
/\b/
)
}
const MULTI_WORD_KEYWORDS = {
scope: "keyword",
match: kws_to_regex(COMBOS),
relevance: 0,
};
// keywords with less than 3 letters are reduced in relevancy
function reduceRelevancy(list, {
exceptions, when
} = {}) {
const qualifyFn = when;
exceptions = exceptions || [];
return list.map((item) => {
if (item.match(/\|\d+$/) || exceptions.includes(item)) {
return item;
} else if (qualifyFn(item)) {
return `${item}|0`;
} else {
return item;
}
});
}
return {
name: 'SQL',
case_insensitive: true,
// does not include {} or HTML tags `</`
illegal: /[{}]|<\//,
keywords: {
$pattern: /\b[\w\.]+/,
keyword:
reduceRelevancy(KEYWORDS, { when: (x) => x.length < 3 }),
literal: LITERALS,
type: TYPES,
built_in: POSSIBLE_WITHOUT_PARENS
},
contains: [
{
scope: "type",
match: kws_to_regex(MULTI_WORD_TYPES)
},
MULTI_WORD_KEYWORDS,
FUNCTION_CALL,
VARIABLE,
STRING,
QUOTED_IDENTIFIER,
hljs.C_NUMBER_MODE,
hljs.C_BLOCK_COMMENT_MODE,
COMMENT_MODE,
OPERATOR
]
};
}

View File

@@ -73,6 +73,7 @@
'icon scheduler-event': 'mdi mdi-calendar-blank',
'icon arrow-link': 'mdi mdi-arrow-top-right-thick',
'icon reset': 'mdi mdi-cancel',
'icon send': 'mdi mdi-send',
'icon window-restore': 'mdi mdi-window-restore',
'icon window-maximize': 'mdi mdi-window-maximize',
@@ -163,8 +164,10 @@
'icon wait': 'mdi mdi-timer-sand',
'icon more': 'mdi mdi-more',
'icon copy': 'mdi mdi-content-copy',
'icon arrow-start-here': 'mdi mdi-arrow-down-bold-circle',
'icon run': 'mdi mdi-play',
'icon run-settings': 'mdi mdi-cog-play',
'icon chevron-down': 'mdi mdi-chevron-down',
'icon chevron-left': 'mdi mdi-chevron-left',
'icon chevron-right': 'mdi mdi-chevron-right',
@@ -280,6 +283,8 @@
'img admin': 'mdi mdi-security color-icon-blue',
'img auth': 'mdi mdi-account-key color-icon-blue',
'img cloud-connection': 'mdi mdi-cloud-lock color-icon-blue',
'img ai': 'mdi mdi-head-lightbulb color-icon-yellow',
'img run': 'mdi mdi-play color-icon-blue',
'img add': 'mdi mdi-plus-circle color-icon-green',
'img minus': 'mdi mdi-minus-circle color-icon-red',
@@ -338,6 +343,7 @@
'img db-restore': 'mdi mdi-database-import color-icon-red',
'img settings': 'mdi mdi-cog color-icon-blue',
'img data-deploy': 'mdi mdi-database-settings color-icon-green',
'img arrow-start-here': 'mdi mdi-arrow-down-bold-circle color-icon-green',
};
</script>

View File

@@ -105,6 +105,15 @@
disabledMessage: 'Database comparison is not available for current database',
isProFeature: true,
},
{
icon: 'icon ai',
colorClass: 'color-icon-blue',
title: 'Database Chat',
description: 'Chat with your database using AI',
command: 'database.chat',
isProFeature: true,
disabledMessage: 'Database chat is not available for current database',
}
];
</script>

View File

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

View File

@@ -13,15 +13,6 @@
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',
@@ -157,7 +148,6 @@
import QueryParametersModal from '../modals/QueryParametersModal.svelte';
import { isProApp } from '../utility/proTools';
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
import QueryAiAssistant from '../query/QueryAiAssistant.svelte';
import uuidv1 from 'uuid/v1';
import ToolStripButton from '../buttons/ToolStripButton.svelte';
import { getIntSettingsValue } from '../settings/settingsTools';
@@ -212,8 +202,6 @@
let domEditor;
let domToolStrip;
let intervalId;
let isAiAssistantVisible = isProApp() && localStorage.getItem(`tabdata_isAiAssistantVisible_${tabid}`) == 'true';
let domAiAssistant;
let isInTransaction = false;
let isAutocommit = false;
let splitterInitialValue = undefined;
@@ -287,12 +275,6 @@
domEditor?.getEditor()?.focus();
}
$: {
if (!isAiAssistantVisible && domEditor) {
domEditor?.getEditor()?.focus();
}
}
export function isSqlEditor() {
return driver?.databaseEngineTypes?.includes('sql');
}
@@ -317,10 +299,6 @@
visibleResultTabs = !visibleResultTabs;
}
export function toggleAiAssistant() {
isAiAssistantVisible = !isAiAssistantVisible;
}
function getParameterSplitterOptions() {
if (!queryParameterStyle) {
return null;
@@ -631,29 +609,6 @@
);
}
async function handleKeyDown(event) {
if (isProApp()) {
if (event.code == 'Space' && event.shiftKey && event.ctrlKey && !isAiAssistantVisible) {
event.preventDefault();
event.stopPropagation();
toggleAiAssistant();
await sleep(100);
if (domAiAssistant) {
domAiAssistant.handleCompleteOnCursor();
domEditor?.getEditor()?.focus();
}
} else if (event.code == 'Space' && event.shiftKey && event.ctrlKey && isAiAssistantVisible && domAiAssistant) {
event.preventDefault();
event.stopPropagation();
domAiAssistant.handleCompleteOnCursor();
} else if (event.code?.startsWith('Digit') && event.altKey && isAiAssistantVisible && domAiAssistant) {
event.preventDefault();
event.stopPropagation();
domAiAssistant.insertCompletion(parseInt(event.code.substring(5)) - 1);
}
}
}
function createMenu() {
return [
{ command: 'query.execute' },
@@ -675,7 +630,6 @@
{ command: 'query.replace' },
{ divider: true },
{ command: 'query.toggleVisibleResultTabs' },
{ command: 'query.switchAiAssistant', hideDisabled: true },
];
}
@@ -695,127 +649,91 @@
localStorage.getItem(`tabdata_queryParamStyle_${tabid}`) ??
initialArgs?.queryParameterStyle ??
(initialArgs?.scriptTemplate == 'CALL OBJECT' ? ':' : '');
$: localStorage.setItem(`tabdata_isAiAssistantVisible_${tabid}`, isAiAssistantVisible ? 'true' : 'false');
</script>
<ToolStripContainer bind:this={domToolStrip}>
<HorizontalSplitter isSplitter={isAiAssistantVisible} initialSizeRight={300}>
<VerticalSplitter isSplitter={visibleResultTabs} initialValue={splitterInitialValue}>
<svelte:fragment slot="1">
<VerticalSplitter isSplitter={visibleResultTabs} initialValue={splitterInitialValue}>
<svelte:fragment slot="1">
{#if driver?.databaseEngineTypes?.includes('sql')}
<SqlEditor
engine={$connection && $connection.engine}
{conid}
{database}
splitterOptions={{
...driver?.getQuerySplitterOptions('editor'),
splitByEmptyLine: !$settingsValue?.['sqlEditor.disableSplitByEmptyLine'],
}}
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}
onKeyDown={handleKeyDown}
/>
{:else}
<AceEditor
mode={driver?.editorMode || 'sql'}
value={$editorState.value || ''}
splitterOptions={{
...driver?.getQuerySplitterOptions('editor'),
splitByEmptyLine: !$settingsValue?.['sqlEditor.disableSplitByEmptyLine'],
}}
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
bind:this={domResultTabs}
tabs={[{ label: 'Messages', slot: 0 }]}
{sessionId}
{executeNumber}
bind:resultCount
{driver}
onSetFrontMatterField={handleSetFrontMatterField}
onGetFrontMatter={() => getSqlFrontMatter($editorValue, yaml)}
>
<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>
{#if driver?.databaseEngineTypes?.includes('sql')}
<SqlEditor
engine={$connection && $connection.engine}
{conid}
{database}
splitterOptions={{
...driver?.getQuerySplitterOptions('editor'),
splitByEmptyLine: !$settingsValue?.['sqlEditor.disableSplitByEmptyLine'],
}}
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'),
splitByEmptyLine: !$settingsValue?.['sqlEditor.disableSplitByEmptyLine'],
}}
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">
<QueryAiAssistant
bind:this={domAiAssistant}
{conid}
{database}
<ResultTabs
bind:this={domResultTabs}
tabs={[{ label: 'Messages', slot: 0 }]}
{sessionId}
{executeNumber}
bind:resultCount
{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}
/>
onSetFrontMatterField={handleSetFrontMatterField}
onGetFrontMatter={() => getSqlFrontMatter($editorValue, yaml)}
>
<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>
</HorizontalSplitter>
</VerticalSplitter>
<svelte:fragment slot="toolstrip">
<ToolStripCommandSplitButton
commands={['query.execute', 'query.executeCurrent']}
@@ -854,13 +772,6 @@
icon="icon at"
title="Query parameter style"
/>
<ToolStripCommandButton
command="query.switchAiAssistant"
hideDisabled
data-testid="QueryTab_switchAiAssistantButton"
>
AI Assistant
</ToolStripCommandButton>
<ToolStripCommandButton
command="query.beginTransaction"
data-testid="QueryTab_beginTransactionButton"

View File

@@ -60,11 +60,11 @@
name: 'archive',
title: 'Archive (saved tabular data)',
},
{
icon: 'icon plugin',
name: 'plugins',
title: 'Extensions & Plugins',
},
// {
// icon: 'icon plugin',
// name: 'plugins',
// title: 'Extensions & Plugins',
// },
{
icon: 'icon cell-data',
name: 'cell-data',
@@ -116,6 +116,13 @@
$visibleWidgetSideBar = true;
},
},
{
text: 'Manage plugins',
onClick: () => {
$selectedWidget = 'plugins';
$visibleWidgetSideBar = true;
},
},
];
currentDropDownMenu.set({ left, top, items });
}
@@ -159,6 +166,7 @@
{#each widgets
.filter(x => x && hasPermission(`widgets/${x.name}`))
.filter(x => !x.isPremiumPromo || !isProApp())
// .filter(x => !x.isPremiumOnly || isProApp())
.filter(x => x.name != 'cloud-private' || $cloudSigninTokenHolder) as item}
<div
class="wrapper"