diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 50f303f7..72cd1564 100755 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -84ec3a6d42105c92f224232f0d83a33b \ No newline at end of file +1d8f9adbde8018f90d013cc740e0405b \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css index 51cc7c71..ae9b9451 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -715,6 +715,29 @@ body[data-theme='dark'] .gonavi-query-editor-db-token { color: #c4b5fd; } +.gonavi-query-editor-ai-inline-ghost { + color: rgba(100, 116, 139, 0.7); + font-style: italic; + pointer-events: none; +} + +.gonavi-query-editor-ai-inline-ghost-overlay { + color: rgba(100, 116, 139, 0.7); + font-style: italic; + pointer-events: none; + position: absolute; + white-space: pre; + z-index: 6; +} + +body[data-theme='dark'] .gonavi-query-editor-ai-inline-ghost { + color: rgba(203, 213, 225, 0.56); +} + +body[data-theme='dark'] .gonavi-query-editor-ai-inline-ghost-overlay { + color: rgba(203, 213, 225, 0.56); +} + /* Legacy sidebar resize bounds — mirror v2 .gn-v2-app-sider so Ant Design inline width locks do not collapse drag range. */ body[data-ui-version="legacy"] .ant-layout-sider { min-width: 232px !important; diff --git a/frontend/src/components/AISettingsModal.tsx b/frontend/src/components/AISettingsModal.tsx index 888bf208..be0a6b2e 100644 --- a/frontend/src/components/AISettingsModal.tsx +++ b/frontend/src/components/AISettingsModal.tsx @@ -374,6 +374,7 @@ const AISettingsModal: React.FC = ({ open, onClose, darkMo valuesModel: values.model, customModels: values.models, }); + const inlineCompletionModel = String(values.inlineCompletionModel || '').trim(); // 内置供应商自动使用 preset label 作为名称 const finalName = isCustomLike ? (values.name || localizedPreset.label) : localizedPreset.label; @@ -398,6 +399,7 @@ const AISettingsModal: React.FC = ({ open, onClose, darkMo apiKey: secretDraft.apiKey, hasSecret: secretDraft.hasSecret, model: finalModel, + inlineCompletionModel, models: resolvedModels, baseUrl: finalBaseUrl, apiFormat: resolvedTransport.apiFormat, @@ -669,6 +671,7 @@ const AISettingsModal: React.FC = ({ open, onClose, darkMo hasSecret: secretDraft.hasSecret, baseUrl: finalBaseUrl, model: finalModel, + inlineCompletionModel: String(values.inlineCompletionModel || '').trim(), models: resolvedModels, maxTokens: Number(values.maxTokens) || 4096, temperature: Number(values.temperature) ?? 0.7, @@ -687,12 +690,20 @@ const AISettingsModal: React.FC = ({ open, onClose, darkMo presetFixedApiFormat: preset.fixedApiFormat, valuesApiFormat: form.getFieldValue('apiFormat'), }); + const { model: presetModel, models: presetModels } = resolvePresetModelSelection({ + presetKey, + presetDefaultModel: preset.defaultModel, + presetModels: preset.models, + customModels: preset.models, + }); form.setFieldsValue({ presetKey, type: resolvedTransport.type, apiFormat: resolvedTransport.apiFormat || 'openai', baseUrl: preset.defaultBaseUrl, - model: preset.defaultModel, + model: presetModel, + models: presetModels, + inlineCompletionModel: '', }); }; diff --git a/frontend/src/components/QueryEditor.external-sql-save.test.tsx b/frontend/src/components/QueryEditor.external-sql-save.test.tsx index 2f9dbd35..9df1edbd 100644 --- a/frontend/src/components/QueryEditor.external-sql-save.test.tsx +++ b/frontend/src/components/QueryEditor.external-sql-save.test.tsx @@ -186,7 +186,13 @@ const editorState = vi.hoisted(() => { const state = { value: '', editor: null as any, - domNode: { style: { cursor: '' }, addEventListener: vi.fn(), removeEventListener: vi.fn() }, + domNode: { + style: { cursor: '' }, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + appendChild: vi.fn(), + removeChild: vi.fn(), + }, position: { lineNumber: 1, column: 1 }, selection: null as any, providers: [] as any[], @@ -194,6 +200,7 @@ const editorState = vi.hoisted(() => { contentChangeListeners: [] as Array<() => void>, cursorPositionListeners: [] as Array<(event: any) => void>, modelContentListeners: [] as Array<(event: any) => void>, + keyDownListeners: [] as Array<(event: any) => void>, mouseMoveListeners: [] as Array<(event: any) => void>, mouseDownListeners: [] as Array<(event: any) => void>, mouseLeaveListeners: [] as Array<() => void>, @@ -277,6 +284,7 @@ const editorState = vi.hoisted(() => { }); }), addAction: vi.fn(), + addCommand: vi.fn(), onDidChangeModelContent: vi.fn((listener: (event?: any) => void) => { state.contentChangeListeners.push(listener); state.modelContentListeners.push(listener); @@ -286,6 +294,10 @@ const editorState = vi.hoisted(() => { state.cursorPositionListeners.push(listener); return { dispose: vi.fn() }; }), + onKeyDown: vi.fn((listener: (event: any) => void) => { + state.keyDownListeners.push(listener); + return { dispose: vi.fn() }; + }), onMouseMove: vi.fn((listener: (event: any) => void) => { state.mouseMoveListeners.push(listener); return { dispose: vi.fn() }; @@ -298,6 +310,8 @@ const editorState = vi.hoisted(() => { state.mouseLeaveListeners.push(listener); return { dispose: vi.fn() }; }), + onDidScrollChange: vi.fn(() => ({ dispose: vi.fn() })), + onDidLayoutChange: vi.fn(() => ({ dispose: vi.fn() })), deltaDecorations: vi.fn((oldDecorations: string[], newDecorations: any[]) => { state.decorationIds = newDecorations.map((_: any, index: number) => `decoration-${index + 1}`); return state.decorationIds; @@ -311,6 +325,13 @@ const editorState = vi.hoisted(() => { layout: vi.fn(), focus: vi.fn(), trigger: vi.fn(), + createContextKey: vi.fn((_key: string, initialValue: boolean) => ({ + set: vi.fn(), + get: vi.fn(() => initialValue), + reset: vi.fn(), + })), + getScrolledVisiblePosition: vi.fn(() => ({ left: 0, top: 0, height: 20 })), + getOption: vi.fn(() => null), }; return state; }); @@ -348,7 +369,7 @@ vi.mock('@monaco-editor/react', () => ({ const mountEditor = () => onMount?.(editorState.editor, { editor: { setTheme: vi.fn() }, KeyMod: { CtrlCmd: 2048, WinCtrl: 256, Alt: 512, Shift: 1024 }, - KeyCode: { KeyD: 68, KeyE: 69, KeyF: 70, KeyM: 77, KeyQ: 81, KeyS: 83 }, + KeyCode: { KeyD: 68, KeyE: 69, KeyF: 70, KeyM: 77, KeyQ: 81, KeyS: 83, RightArrow: 39 }, languages: { CompletionItemKind: { Keyword: 1, Function: 2, Field: 3 }, CompletionItemInsertTextRule: { InsertAsSnippet: 1 }, @@ -443,6 +464,7 @@ vi.mock('@ant-design/icons', () => { RobotOutlined: Icon, SearchOutlined: Icon, DatabaseOutlined: Icon, + DownOutlined: Icon, EyeOutlined: Icon, EyeInvisibleOutlined: Icon, }; @@ -478,6 +500,10 @@ vi.mock('antd', () => { ); const Empty = ({ description }: { description?: React.ReactNode }) =>
{description}
; (Empty as any).PRESENTED_IMAGE_SIMPLE = 'simple'; + const Input: any = ({ value, onChange, placeholder }: any) => ; + Input.TextArea = ({ value, onChange, placeholder, disabled }: any) => ( +