diff --git a/package-lock.json b/package-lock.json index 26b88bbbc..a8d4b2b10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.4.0", + "@ydb-platform/monaco-ghost": "^0.6.1", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", @@ -6417,9 +6417,9 @@ "dev": true }, "node_modules/@ydb-platform/monaco-ghost": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.4.0.tgz", - "integrity": "sha512-yOgfQ7PUIPKmTqpOaKtGLc2gDQAzp3z2jASGLB7IAhC8smdDLoLwtNMz30gKkzU29TnICfp6Eem5fxj6nKqmrQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.6.1.tgz", + "integrity": "sha512-BrqRDYQRFIaRHtvHBEesq0ExLM6Az51bk11YG2jIJZ3d9quRx3t+AT3dE6bdlbZqR+jtNGRaC1xfc8IQs0cc4A==", "license": "Apache-2.0", "dependencies": { "uuid": "^9.0.0" diff --git a/package.json b/package.json index ae14682ac..554b5ed38 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.4.0", + "@ydb-platform/monaco-ghost": "^0.6.1", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx index ae6fb2223..344107a56 100644 --- a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -1,5 +1,7 @@ +import React from 'react'; + import NiceModal from '@ebay/nice-modal-react'; -import {useMonacoGhost} from '@ydb-platform/monaco-ghost'; +import {createMonacoGhostInstance} from '@ydb-platform/monaco-ghost'; import throttle from 'lodash/throttle'; import type Monaco from 'monaco-editor'; @@ -11,7 +13,7 @@ import { selectUserInput, } from '../../../../store/reducers/query/query'; import type {QueryAction} from '../../../../types/store/query'; -import {LAST_USED_QUERY_ACTION_KEY} from '../../../../utils/constants'; +import {ENABLE_CODE_ASSISTANT, LAST_USED_QUERY_ACTION_KEY} from '../../../../utils/constants'; import { useEventHandler, useSetting, @@ -23,9 +25,8 @@ import {useUpdateErrorsHighlighting} from '../../../../utils/monaco/highlightErr import {QUERY_ACTIONS} from '../../../../utils/query'; import {SAVE_QUERY_DIALOG} from '../SaveQuery/SaveQuery'; import i18n from '../i18n'; -import {useSavedQueries} from '../utils/useSavedQueries'; -import {useCodeAssist, useEditorOptions} from './helpers'; +import {useCodeAssistHelpers, useEditorOptions} from './helpers'; import {getKeyBindings} from './keybindings'; const CONTEXT_MENU_GROUP_ID = 'navigation'; @@ -45,8 +46,11 @@ export function YqlEditor({ }: YqlEditorProps) { const input = useTypedSelector(selectUserInput); const dispatch = useTypedDispatch(); + const [monacoGhostInstance, setMonacoGhostInstance] = + React.useState>(); const historyQueries = useTypedSelector(selectQueriesHistory); - const savedQueries = useSavedQueries(); + const [isCodeAssistEnabled] = useSetting(ENABLE_CODE_ASSISTANT); + const editorOptions = useEditorOptions(); const updateErrorsHighlighting = useUpdateErrorsHighlighting(); @@ -71,20 +75,18 @@ export function YqlEditor({ window.ydbEditor = undefined; }; - const codeAssist = useCodeAssist(); - const {registerMonacoGhost} = useMonacoGhost({ - api: { - getCodeAssistSuggestions: codeAssist.getCodeAssistSuggestions, - }, - eventHandlers: { - onCompletionAccept: codeAssist.onCompletionAccept, - onCompletionDecline: codeAssist.onCompletionDecline, - onCompletionIgnore: codeAssist.onCompletionIgnore, - }, - config: { - language: YQL_LANGUAGE_ID, - }, - }); + const {monacoGhostConfig, prepareUserQueriesCache} = useCodeAssistHelpers(); + + React.useEffect(() => { + if (monacoGhostInstance && isCodeAssistEnabled) { + monacoGhostInstance.register(monacoGhostConfig); + prepareUserQueriesCache(); + } + + return () => { + monacoGhostInstance?.unregister(); + }; + }, [isCodeAssistEnabled, monacoGhostConfig, monacoGhostInstance, prepareUserQueriesCache]); const editorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => { window.ydbEditor = editor; @@ -100,18 +102,9 @@ export function YqlEditor({ }); if (window.api.codeAssist) { - registerMonacoGhost(editor); - codeAssist.prepareUserQueriesCache([ - ...historyQueries.map((query, index) => ({ - name: `query${index}.yql`, - text: query.queryText, - })), - ...savedQueries.map((query) => ({ - name: query.name, - text: query.body, - })), - ]); + setMonacoGhostInstance(createMonacoGhostInstance(editor)); } + initResizeHandler(editor); initUserPrompt(editor, getLastQueryText); editor.focus(); diff --git a/src/containers/Tenant/Query/QueryEditor/helpers.ts b/src/containers/Tenant/Query/QueryEditor/helpers.ts index c4ebb5d23..34c000958 100644 --- a/src/containers/Tenant/Query/QueryEditor/helpers.ts +++ b/src/containers/Tenant/Query/QueryEditor/helpers.ts @@ -4,9 +4,12 @@ import type {AcceptEvent, DeclineEvent, IgnoreEvent, PromptFile} from '@ydb-plat import type Monaco from 'monaco-editor'; import {codeAssistApi} from '../../../../store/reducers/codeAssist/codeAssist'; +import {selectQueriesHistory} from '../../../../store/reducers/query/query'; import type {TelemetryOpenTabs} from '../../../../types/api/codeAssist'; import {AUTOCOMPLETE_ON_ENTER, ENABLE_AUTOCOMPLETE} from '../../../../utils/constants'; -import {useSetting} from '../../../../utils/hooks'; +import {useSetting, useTypedSelector} from '../../../../utils/hooks'; +import {YQL_LANGUAGE_ID} from '../../../../utils/monaco/constats'; +import {useSavedQueries} from '../utils/useSavedQueries'; export type EditorOptions = Monaco.editor.IEditorOptions & Monaco.editor.IGlobalEditorOptions; @@ -36,12 +39,14 @@ export function useEditorOptions() { return options; } -export function useCodeAssist() { +export function useCodeAssistHelpers() { const [sendCodeAssistPrompt] = codeAssistApi.useLazyGetCodeAssistSuggestionsQuery(); const [acceptSuggestion] = codeAssistApi.useAcceptSuggestionMutation(); const [discardSuggestion] = codeAssistApi.useDiscardSuggestionMutation(); const [ignoreSuggestion] = codeAssistApi.useIgnoreSuggestionMutation(); const [sendUserQueriesData] = codeAssistApi.useSendUserQueriesDataMutation(); + const historyQueries = useTypedSelector(selectQueriesHistory); + const savedQueries = useSavedQueries(); const getCodeAssistSuggestions = React.useCallback( async (promptFiles: PromptFile[]) => sendCodeAssistPrompt(promptFiles).unwrap(), @@ -63,26 +68,49 @@ export function useCodeAssist() { [ignoreSuggestion], ); - const prepareUserQueriesCache = React.useCallback( - async (queries: {text: string; name?: string}[]) => { - const preparedData: TelemetryOpenTabs = queries.map((query, index) => ({ - FileName: query.name || `query${index}.yql`, - Text: query.text, - })); - try { - return sendUserQueriesData(preparedData).unwrap(); - } catch { - return {items: []}; - } - }, - [sendUserQueriesData], - ); + const userQueries = React.useMemo(() => { + return [ + ...historyQueries.map((query, index) => ({ + name: `query${index}.yql`, + text: query.queryText, + })), + ...savedQueries.map((query) => ({ + name: query.name, + text: query.body, + })), + ]; + }, [historyQueries, savedQueries]); + + const prepareUserQueriesCache = React.useCallback(async () => { + const preparedData: TelemetryOpenTabs = userQueries.map((query, index) => ({ + FileName: query.name || `query${index}.yql`, + Text: query.text, + })); + try { + return await sendUserQueriesData(preparedData).unwrap(); + } catch { + return {items: []}; + } + }, [sendUserQueriesData, userQueries]); + + const monacoGhostConfig = React.useMemo(() => { + return { + api: { + getCodeAssistSuggestions, + }, + eventHandlers: { + onCompletionAccept, + onCompletionDecline, + onCompletionIgnore, + }, + config: { + language: YQL_LANGUAGE_ID, + }, + }; + }, [getCodeAssistSuggestions, onCompletionAccept, onCompletionDecline, onCompletionIgnore]); return { - getCodeAssistSuggestions, - onCompletionAccept, - onCompletionDecline, - onCompletionIgnore, prepareUserQueriesCache, + monacoGhostConfig, }; } diff --git a/src/containers/UserSettings/i18n/en.json b/src/containers/UserSettings/i18n/en.json index 541c692b5..c4cd42f89 100644 --- a/src/containers/UserSettings/i18n/en.json +++ b/src/containers/UserSettings/i18n/en.json @@ -14,6 +14,9 @@ "settings.editor.autocomplete.title": "Enable autocomplete", "settings.editor.autocomplete.description": "You're always able to get suggestions by pressing Ctrl+Space.", + "settings.editor.codeAssistant.title": "Code Assistant", + "settings.editor.codeAssistant.description": "Use Code Assistant for autocomplete.", + "settings.editor.autocomplete-on-enter.title": "Accept suggestion on Enter", "settings.editor.autocomplete-on-enter.description": "Controls whether suggestions should be accepted on Enter, in addition to Tab. Helps to avoid ambiguity between inserting new lines or accepting suggestions.", diff --git a/src/containers/UserSettings/settings.tsx b/src/containers/UserSettings/settings.tsx index eccf42e40..0dd0344e4 100644 --- a/src/containers/UserSettings/settings.tsx +++ b/src/containers/UserSettings/settings.tsx @@ -6,6 +6,7 @@ import { AUTOCOMPLETE_ON_ENTER, BINARY_DATA_IN_PLAIN_TEXT_DISPLAY, ENABLE_AUTOCOMPLETE, + ENABLE_CODE_ASSISTANT, ENABLE_NETWORK_TABLE_KEY, INVERTED_DISKS_KEY, LANGUAGE_KEY, @@ -120,6 +121,12 @@ export const enableAutocompleteSetting: SettingProps = { description: i18n('settings.editor.autocomplete.description'), }; +export const enableCodeAssistantSetting: SettingProps = { + settingKey: ENABLE_CODE_ASSISTANT, + title: i18n('settings.editor.codeAssistant.title'), + description: i18n('settings.editor.codeAssistant.description'), +}; + export const autocompleteOnEnterSetting: SettingProps = { settingKey: AUTOCOMPLETE_ON_ENTER, title: i18n('settings.editor.autocomplete-on-enter.title'), @@ -192,14 +199,26 @@ export const aboutPage: SettingsPage = { showTitle: false, }; -export function getUserSettings({singleClusterMode}: {singleClusterMode: boolean}) { +export function getUserSettings({ + singleClusterMode, + codeAssistantConfigured, +}: { + singleClusterMode: boolean; + codeAssistantConfigured?: boolean; +}) { const experiments = singleClusterMode ? experimentsPage : createNextState(experimentsPage, (draft) => { draft.sections[0].settings.push(useClusterBalancerAsBackendSetting); }); - const settings: YDBEmbeddedUISettings = [generalPage, editorPage, experiments, aboutPage]; + const editor = codeAssistantConfigured + ? createNextState(editorPage, (draft) => { + draft.sections[0].settings.push(enableCodeAssistantSetting); + }) + : editorPage; + + const settings: YDBEmbeddedUISettings = [generalPage, editor, experiments, aboutPage]; return settings; } diff --git a/src/services/settings.ts b/src/services/settings.ts index 2f8855888..6e34f0269 100644 --- a/src/services/settings.ts +++ b/src/services/settings.ts @@ -6,6 +6,7 @@ import { BINARY_DATA_IN_PLAIN_TEXT_DISPLAY, CASE_SENSITIVE_JSON_SEARCH, ENABLE_AUTOCOMPLETE, + ENABLE_CODE_ASSISTANT, ENABLE_NETWORK_TABLE_KEY, INVERTED_DISKS_KEY, IS_HOTKEYS_HELP_HIDDEN_KEY, @@ -42,6 +43,7 @@ export const DEFAULT_USER_SETTINGS = { [USE_SHOW_PLAN_SVG_KEY]: false, [USE_CLUSTER_BALANCER_AS_BACKEND_KEY]: true, [ENABLE_AUTOCOMPLETE]: true, + [ENABLE_CODE_ASSISTANT]: true, [AUTOCOMPLETE_ON_ENTER]: true, [IS_HOTKEYS_HELP_HIDDEN_KEY]: false, [AUTO_REFRESH_INTERVAL]: 0, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 4772624cf..70e3bdb76 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -117,6 +117,8 @@ export const USE_CLUSTER_BALANCER_AS_BACKEND_KEY = 'useClusterBalancerAsBacked'; export const ENABLE_AUTOCOMPLETE = 'enableAutocomplete'; +export const ENABLE_CODE_ASSISTANT = 'enableCodeAssistant'; + export const AUTOCOMPLETE_ON_ENTER = 'autocompleteOnEnter'; export const IS_HOTKEYS_HELP_HIDDEN_KEY = 'isHotKeysHelpHidden';