From 0d27d2deaa94dddb7e0659b11ac5a451b166d0af Mon Sep 17 00:00:00 2001 From: AutumnNazi <104422820+AutumnNazi@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:35:56 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(sidebar):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=B8=8E=E6=B8=85=E7=A9=BA=E8=BF=91=E6=9C=9F?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为每条近期查询增加删除入口,并在标题区提供清空操作 - 通过持久化可见性标记隐藏近期记录,保留完整 SQL 执行日志 - 补齐六语言文案及交互、订阅和状态持久化测试 --- .../Sidebar.sql-log-subscription.test.tsx | 13 ++ frontend/src/components/Sidebar.tsx | 6 + .../SidebarCommandSearch.i18n.test.ts | 2 + .../SidebarSearchPanel.interaction.test.tsx | 111 ++++++++++++++++++ .../components/sidebar/SidebarSearchPanel.tsx | 75 +++++++++--- .../sidebar/sidebarSqlLogSelector.ts | 4 +- .../sidebar/useSidebarSearchModel.tsx | 1 + frontend/src/components/sidebarV2Utils.ts | 1 + frontend/src/store.test.ts | 41 +++++++ frontend/src/store.ts | 18 +++ frontend/src/v2-theme.css | 59 +++++++++- shared/i18n/de-DE.json | 2 + shared/i18n/en-US.json | 2 + shared/i18n/ja-JP.json | 2 + shared/i18n/ru-RU.json | 2 + shared/i18n/zh-CN.json | 2 + shared/i18n/zh-TW.json | 2 + 17 files changed, 321 insertions(+), 22 deletions(-) create mode 100644 frontend/src/components/sidebar/SidebarSearchPanel.interaction.test.tsx diff --git a/frontend/src/components/Sidebar.sql-log-subscription.test.tsx b/frontend/src/components/Sidebar.sql-log-subscription.test.tsx index 0b239d5e..fe0525fa 100644 --- a/frontend/src/components/Sidebar.sql-log-subscription.test.tsx +++ b/frontend/src/components/Sidebar.sql-log-subscription.test.tsx @@ -71,4 +71,17 @@ describe('Sidebar SQL log subscription', () => { renderer.unmount(); }); }); + + it('filters hidden recent queries before applying the five-item limit', () => { + const logs = Array.from({ length: 7 }, (_, index) => makeLog(`log-${7 - index}`, 7 - index)); + logs[1] = { ...logs[1], hiddenFromRecent: true }; + + expect(selectRecentSidebarSqlLogs(logs).map((log) => log.id)).toEqual([ + 'log-7', + 'log-5', + 'log-4', + 'log-3', + 'log-2', + ]); + }); }); diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 464848a2..28ce369d 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -572,6 +572,8 @@ const Sidebar: React.FC<{ const queryOptions = useStore(state => state.queryOptions); const setQueryOptions = useStore(state => state.setQueryOptions); const addSqlLog = useStore(state => state.addSqlLog); + const hideSqlLogFromRecent = useStore(state => state.hideSqlLogFromRecent); + const clearRecentSqlLogs = useStore(state => state.clearRecentSqlLogs); const shortcutOptions = useStore(state => state.shortcutOptions); const languagePreference = useStore(state => state.languagePreference); const setAppearance = useStore(state => state.setAppearance); @@ -2970,6 +2972,10 @@ const Sidebar: React.FC<{ onClose: closeV2CommandSearch, onItemSelect: (item: V2CommandSearchItem) => runCommandSearchItem(item), onItemHover: (key: string) => setV2CommandActiveIndex(commandSearchFlatItems.findIndex((entry) => entry.key === key)), + onRemoveRecentItem: (item: V2CommandSearchItem) => { + if (item.kind === 'recent') hideSqlLogFromRecent(item.logId); + }, + onClearRecentItems: clearRecentSqlLogs, onTogglePersistentFilter: toggleV2CommandSearchPersistentFilter, onResetFilter: resetV2SidebarFilter, }, diff --git a/frontend/src/components/SidebarCommandSearch.i18n.test.ts b/frontend/src/components/SidebarCommandSearch.i18n.test.ts index 014e170e..0e2179f1 100644 --- a/frontend/src/components/SidebarCommandSearch.i18n.test.ts +++ b/frontend/src/components/SidebarCommandSearch.i18n.test.ts @@ -18,6 +18,8 @@ const requiredKeys = [ 'sidebar.command_search.action.open_ai.meta', 'sidebar.command_search.action.open_sql_log.title', 'sidebar.command_search.action.open_sql_log.meta', + 'sidebar.command_search.action.clear_recent', + 'sidebar.command_search.action.remove_recent', 'sidebar.command_search.empty.ai', 'sidebar.command_search.empty.object', 'sidebar.command_search.empty.default', diff --git a/frontend/src/components/sidebar/SidebarSearchPanel.interaction.test.tsx b/frontend/src/components/sidebar/SidebarSearchPanel.interaction.test.tsx new file mode 100644 index 00000000..f4cb224d --- /dev/null +++ b/frontend/src/components/sidebar/SidebarSearchPanel.interaction.test.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { act, create } from 'react-test-renderer'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import SidebarSearchPanel from './SidebarSearchPanel'; + +vi.mock('react-dom', () => ({ + createPortal: (children: React.ReactNode) => children, +})); + +vi.mock('antd', async () => { + const React = await import('react'); + const passthrough = ({ children }: { children?: React.ReactNode }) => <>{children}; + const Input = React.forwardRef>( + (props, ref) => , + ); + return { + Button: ({ children, ...props }: React.ButtonHTMLAttributes) => , + ConfigProvider: passthrough, + Input, + Switch: (props: React.InputHTMLAttributes) => , + Tooltip: passthrough, + }; +}); + +vi.mock('@ant-design/icons', () => { + const Icon = () => ; + return { + CloseOutlined: Icon, + ReloadOutlined: Icon, + RobotOutlined: Icon, + SearchOutlined: Icon, + TableOutlined: Icon, + }; +}); + +vi.mock('../../i18n', () => ({ + t: (key: string) => key, +})); + +const recentItem = { + key: 'recent-log-1', + kind: 'recent' as const, + title: 'SELECT 1', + meta: '10:30 · 12ms', + icon: , +}; + +describe('SidebarSearchPanel recent query actions', () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('removes one recent query without selecting its row and clears the section independently', async () => { + vi.stubGlobal('document', { body: {} }); + const onItemSelect = vi.fn(); + const onRemoveRecentItem = vi.fn(); + const onClearRecentItems = vi.fn(); + + const renderer = create( + , + ); + + const removeButton = renderer.root.findByProps({ className: 'gn-v2-command-row-remove' }); + const removeMouseDown = { preventDefault: vi.fn(), stopPropagation: vi.fn() }; + await act(async () => { + removeButton.props.onMouseDown(removeMouseDown); + removeButton.props.onClick({ stopPropagation: vi.fn() }); + }); + + expect(removeMouseDown.preventDefault).toHaveBeenCalledTimes(1); + expect(removeMouseDown.stopPropagation).toHaveBeenCalledTimes(1); + expect(onRemoveRecentItem).toHaveBeenCalledWith(recentItem); + expect(onItemSelect).not.toHaveBeenCalled(); + + const clearButton = renderer.root.findByProps({ className: 'gn-v2-command-section-clear' }); + const clearMouseDown = { preventDefault: vi.fn(), stopPropagation: vi.fn() }; + await act(async () => { + clearButton.props.onMouseDown(clearMouseDown); + clearButton.props.onClick({ stopPropagation: vi.fn() }); + }); + + expect(clearMouseDown.preventDefault).toHaveBeenCalledTimes(1); + expect(clearMouseDown.stopPropagation).toHaveBeenCalledTimes(1); + expect(onClearRecentItems).toHaveBeenCalledTimes(1); + }); +}); diff --git a/frontend/src/components/sidebar/SidebarSearchPanel.tsx b/frontend/src/components/sidebar/SidebarSearchPanel.tsx index 47253645..e8225e8f 100644 --- a/frontend/src/components/sidebar/SidebarSearchPanel.tsx +++ b/frontend/src/components/sidebar/SidebarSearchPanel.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { createPortal } from 'react-dom'; import { ConfigProvider, Input, Button, Switch, Tooltip } from 'antd'; -import { SearchOutlined, ReloadOutlined, TableOutlined, RobotOutlined } from '@ant-design/icons'; +import { CloseOutlined, SearchOutlined, ReloadOutlined, TableOutlined, RobotOutlined } from '@ant-design/icons'; import { noAutoCapInputProps } from '../../utils/inputAutoCap'; import { t } from '../../i18n'; import { APP_COMMAND_PALETTE_Z_INDEX } from '../../utils/overlayZIndex'; @@ -47,6 +47,8 @@ export interface SidebarSearchPanelProps void; onItemSelect: (item: TItem) => void; onItemHover: (key: string) => void; + onRemoveRecentItem: (item: TItem) => void; + onClearRecentItems: () => void; onTogglePersistentFilter: (enabled: boolean) => void; onResetFilter: () => void; }; @@ -76,28 +78,69 @@ const SidebarSearchPanel = ({ : t('sidebar.command_search.empty.default'); const renderRow = (item: TItem, active: boolean) => ( - + + {item.kind === 'recent' ? ( + + + + ) : null} + ); - const renderSection = (title: string, items: TItem[]) => { + const renderSection = (title: string, items: TItem[], showClear = false) => { if (items.length === 0) return null; return (
-
{title}
+
+
{title}
+ {showClear ? ( + + ) : null} +
{items.map((item) => renderRow(item, flatItems[activeIndex]?.key === item.key), )} @@ -155,7 +198,7 @@ const SidebarSearchPanel = ({ {renderSection(t('sidebar.command_search.section.goto'), sections.goTo)} {renderSection(t('sidebar.command_search.section.ai'), sections.ai)} {renderSection(t('sidebar.command_search.section.actions'), sections.actions)} - {renderSection(t('sidebar.command_search.section.recent'), sections.recent)} + {renderSection(t('sidebar.command_search.section.recent'), sections.recent, true)} {flatItems.length === 0 ? (
{emptyCopy}
) : null} diff --git a/frontend/src/components/sidebar/sidebarSqlLogSelector.ts b/frontend/src/components/sidebar/sidebarSqlLogSelector.ts index 5f06367f..df429477 100644 --- a/frontend/src/components/sidebar/sidebarSqlLogSelector.ts +++ b/frontend/src/components/sidebar/sidebarSqlLogSelector.ts @@ -9,5 +9,7 @@ export const selectSidebarCommandSearchSqlLogs = ( ): SqlLog[] => (enabled ? state.sqlLogs : EMPTY_SIDEBAR_SQL_LOGS); export const selectRecentSidebarSqlLogs = (sqlLogs: SqlLog[]): SqlLog[] => ( - sqlLogs.slice(0, SIDEBAR_RECENT_SQL_LOG_LIMIT) + sqlLogs + .filter((log) => !log.hiddenFromRecent) + .slice(0, SIDEBAR_RECENT_SQL_LOG_LIMIT) ); diff --git a/frontend/src/components/sidebar/useSidebarSearchModel.tsx b/frontend/src/components/sidebar/useSidebarSearchModel.tsx index 5a5ae67f..3374e1f8 100644 --- a/frontend/src/components/sidebar/useSidebarSearchModel.tsx +++ b/frontend/src/components/sidebar/useSidebarSearchModel.tsx @@ -442,6 +442,7 @@ export const useSidebarSearchModel = ({ title: log.sql.replace(/\s+/g, ' ').trim() || t('sidebar.command_search.recent_sql_fallback'), meta: `${new Date(log.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} · ${log.duration}ms${log.dbName ? ` · ${log.dbName}` : ''}`, icon: , + logId: log.id, sql: log.sql, dbName: log.dbName, })); diff --git a/frontend/src/components/sidebarV2Utils.ts b/frontend/src/components/sidebarV2Utils.ts index ca3a4853..7367f42d 100644 --- a/frontend/src/components/sidebarV2Utils.ts +++ b/frontend/src/components/sidebarV2Utils.ts @@ -637,6 +637,7 @@ export type V2CommandSearchItem = title: string; meta: string; icon: ReactNode; + logId: string; sql: string; connectionId?: string; dbName?: string; diff --git a/frontend/src/store.test.ts b/frontend/src/store.test.ts index 0253d4fb..2a2e1292 100644 --- a/frontend/src/store.test.ts +++ b/frontend/src/store.test.ts @@ -2942,6 +2942,47 @@ describe('store appearance persistence', () => { expect(reloaded.useStore.getState().sqlLogs[0]?.sql.length).toBe(12 * 1024); }); + it('hides recent queries without deleting their SQL execution logs', async () => { + const { useStore } = await importStore(); + const makeLog = (id: string) => ({ + id, + timestamp: 100, + sql: `select '${id}'`, + status: 'success' as const, + duration: 12, + }); + + useStore.getState().addSqlLog(makeLog('log-1')); + useStore.getState().addSqlLog(makeLog('log-2')); + useStore.getState().addSqlLog(makeLog('log-3')); + useStore.getState().hideSqlLogFromRecent('log-2'); + + expect(useStore.getState().sqlLogs.map((log) => log.id)).toEqual(['log-3', 'log-2', 'log-1']); + expect(useStore.getState().sqlLogs.find((log) => log.id === 'log-2')).toMatchObject({ + hiddenFromRecent: true, + }); + const persisted = JSON.parse(storage.getItem('lite-db-storage') || '{}'); + expect(persisted.state.sqlLogs.map((log: { id: string }) => log.id)).toEqual(['log-3', 'log-2', 'log-1']); + expect(persisted.state.sqlLogs.find((log: { id: string }) => log.id === 'log-2')).toMatchObject({ + hiddenFromRecent: true, + }); + + useStore.getState().clearRecentSqlLogs(); + expect(useStore.getState().sqlLogs).toHaveLength(3); + expect(useStore.getState().sqlLogs.every((log) => log.hiddenFromRecent === true)).toBe(true); + + useStore.getState().addSqlLog(makeLog('log-4')); + expect(useStore.getState().sqlLogs[0]).toMatchObject({ id: 'log-4' }); + expect(useStore.getState().sqlLogs[0]?.hiddenFromRecent).toBeUndefined(); + + vi.resetModules(); + const reloaded = await importStore(); + expect(reloaded.useStore.getState().sqlLogs).toHaveLength(4); + expect(reloaded.useStore.getState().sqlLogs.find((log) => log.id === 'log-2')).toMatchObject({ + hiddenFromRecent: true, + }); + }); + it('preserves SQL transaction log metadata across persistence', async () => { const { useStore } = await importStore(); diff --git a/frontend/src/store.ts b/frontend/src/store.ts index f1a5d369..15241cfe 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -1674,6 +1674,7 @@ export interface SqlLog { sql: string; status: "success" | "error"; duration: number; + hiddenFromRecent?: boolean; message?: string; dbName?: string; affectedRows?: number; @@ -1964,6 +1965,8 @@ interface AppState { resetBuiltinSqlSnippet: (id: string) => void; addSqlLog: (log: SqlLog) => void; + hideSqlLogFromRecent: (id: string) => void; + clearRecentSqlLogs: () => void; clearSqlLogs: () => void; upsertTableExportHistory: ( historyKey: string, @@ -2627,6 +2630,9 @@ const sanitizeSqlLogEntry = ( if (message) { log.message = message; } + if (raw.hiddenFromRecent === true) { + log.hiddenFromRecent = true; + } if (Number.isFinite(affectedRows)) { log.affectedRows = affectedRows; } @@ -5130,6 +5136,18 @@ export const useStore = create()( addSqlLog: (log) => set((state) => ({ sqlLogs: appendRuntimeSqlLog(state.sqlLogs, log) })), + hideSqlLogFromRecent: (id) => + set((state) => ({ + sqlLogs: state.sqlLogs.map((log) => ( + log.id === id ? { ...log, hiddenFromRecent: true } : log + )), + })), + clearRecentSqlLogs: () => + set((state) => ({ + sqlLogs: state.sqlLogs.map((log) => ( + log.hiddenFromRecent ? log : { ...log, hiddenFromRecent: true } + )), + })), clearSqlLogs: () => set({ sqlLogs: [] }), upsertTableExportHistory: (historyKey, entry) => set((state) => { diff --git a/frontend/src/v2-theme.css b/frontend/src/v2-theme.css index 7cf6cb04..c87c8c5d 100644 --- a/frontend/src/v2-theme.css +++ b/frontend/src/v2-theme.css @@ -2431,8 +2431,15 @@ body[data-ui-version="v2"] .gn-v2-command-section { margin-bottom: 16px; } -body[data-ui-version="v2"] .gn-v2-command-section-title { +body[data-ui-version="v2"] .gn-v2-command-section-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; padding: 0 22px 8px; +} + +body[data-ui-version="v2"] .gn-v2-command-section-title { color: var(--gn-fg-4); font-family: var(--gn-font-mono); font-size: 12px; @@ -2441,10 +2448,39 @@ body[data-ui-version="v2"] .gn-v2-command-section-title { text-transform: uppercase; } -body[data-ui-version="v2"] .gn-v2-command-row { +body[data-ui-version="v2"] .gn-v2-command-section-clear { + flex: 0 0 auto; + border: 0; + padding: 0; + background: transparent; + color: var(--gn-fg-4); + font-size: 12px; + line-height: 1.4; + cursor: pointer; +} + +body[data-ui-version="v2"] .gn-v2-command-section-clear:hover, +body[data-ui-version="v2"] .gn-v2-command-section-clear:focus-visible { + color: var(--gn-danger); +} + +body[data-ui-version="v2"] .gn-v2-command-row-shell { width: 100%; min-height: 48px; display: flex; + align-items: stretch; +} + +body[data-ui-version="v2"] .gn-v2-command-row-shell:hover, +body[data-ui-version="v2"] .gn-v2-command-row-shell.is-active { + background: var(--gn-bg-hover); +} + +body[data-ui-version="v2"] .gn-v2-command-row { + min-width: 0; + min-height: 48px; + flex: 1 1 auto; + display: flex; align-items: center; gap: 14px; border: 0; @@ -2455,9 +2491,22 @@ body[data-ui-version="v2"] .gn-v2-command-row { text-align: left; } -body[data-ui-version="v2"] .gn-v2-command-row:hover, -body[data-ui-version="v2"] .gn-v2-command-row.is-active { - background: var(--gn-bg-hover); +body[data-ui-version="v2"] .gn-v2-command-row-remove { + width: 44px; + flex: 0 0 44px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 0; + padding: 0; + background: transparent; + color: var(--gn-fg-5); + cursor: pointer; +} + +body[data-ui-version="v2"] .gn-v2-command-row-remove:hover, +body[data-ui-version="v2"] .gn-v2-command-row-remove:focus-visible { + color: var(--gn-danger); } body[data-ui-version="v2"] .gn-v2-command-row-icon { diff --git a/shared/i18n/de-DE.json b/shared/i18n/de-DE.json index 156eaec5..1dcd0d24 100644 --- a/shared/i18n/de-DE.json +++ b/shared/i18n/de-DE.json @@ -7126,6 +7126,7 @@ "sidebar.batch_databases": "Datenbanken stapelweise bearbeiten", "sidebar.batch_tables": "Tabellen stapelweise bearbeiten", "sidebar.command_search.action.ask_ai.title": "AI fragen", + "sidebar.command_search.action.clear_recent": "Leeren", "sidebar.command_search.action.new_connection.meta": "Eine Datenbank-, Runtime- oder andere Datenquellenverbindung erstellen", "sidebar.command_search.action.new_connection.title": "Neue Datenquelle", "sidebar.command_search.action.new_query.meta": "Einen neuen SQL-Editor-Tab öffnen", @@ -7133,6 +7134,7 @@ "sidebar.command_search.action.open_ai.title": "AI-Datenanalyse öffnen", "sidebar.command_search.action.open_sql_log.meta": "Das Panel mit den letzten Ausführungen öffnen", "sidebar.command_search.action.open_sql_log.title": "SQL-Ausführungslog anzeigen", + "sidebar.command_search.action.remove_recent": "Letzte Abfrage entfernen", "sidebar.command_search.empty.ai": "Gib nach \"?\" eine Frage ein und drücke Enter, um sie an das AI-Panel zu senden.", "sidebar.command_search.empty.default": "Keine Treffer. Gib @Tabelle ein, um nur Tabellenobjekte zu suchen, oder ?Frage, um AI zu fragen.", "sidebar.command_search.empty.object": "Keine passenden Tabellen, Sichten oder materialisierten Sichten.", diff --git a/shared/i18n/en-US.json b/shared/i18n/en-US.json index c012cbff..0ac9e102 100644 --- a/shared/i18n/en-US.json +++ b/shared/i18n/en-US.json @@ -7126,6 +7126,7 @@ "sidebar.batch_databases": "Batch database operations", "sidebar.batch_tables": "Batch table operations", "sidebar.command_search.action.ask_ai.title": "Ask AI", + "sidebar.command_search.action.clear_recent": "Clear", "sidebar.command_search.action.new_connection.meta": "Create a database, runtime, or other data source connection", "sidebar.command_search.action.new_connection.title": "New data source", "sidebar.command_search.action.new_query.meta": "Open a new SQL editor tab", @@ -7133,6 +7134,7 @@ "sidebar.command_search.action.open_ai.title": "Open AI data insights", "sidebar.command_search.action.open_sql_log.meta": "Open the recent execution history panel", "sidebar.command_search.action.open_sql_log.title": "View SQL execution log", + "sidebar.command_search.action.remove_recent": "Remove recent query", "sidebar.command_search.empty.ai": "Type a question after \"?\" and press Enter to send it to the AI panel.", "sidebar.command_search.empty.default": "No matches. Type @table to search table objects only, or type ?question to ask AI.", "sidebar.command_search.empty.object": "No matching tables, views, or materialized views.", diff --git a/shared/i18n/ja-JP.json b/shared/i18n/ja-JP.json index 82d9d8ec..799c769b 100644 --- a/shared/i18n/ja-JP.json +++ b/shared/i18n/ja-JP.json @@ -7126,6 +7126,7 @@ "sidebar.batch_databases": "一括データベース", "sidebar.batch_tables": "一括テーブル", "sidebar.command_search.action.ask_ai.title": "AI に質問", + "sidebar.command_search.action.clear_recent": "クリア", "sidebar.command_search.action.new_connection.meta": "データベース、ランタイム、またはその他のデータソース接続を作成", "sidebar.command_search.action.new_connection.title": "新規データソース", "sidebar.command_search.action.new_query.meta": "新しい SQL エディタータブを開く", @@ -7133,6 +7134,7 @@ "sidebar.command_search.action.open_ai.title": "AI データインサイトを開く", "sidebar.command_search.action.open_sql_log.meta": "最近の実行履歴パネルを開く", "sidebar.command_search.action.open_sql_log.title": "SQL 実行ログを表示", + "sidebar.command_search.action.remove_recent": "この最近のクエリを削除", "sidebar.command_search.empty.ai": "「?」の後に質問を入力し、Enter で AI パネルへ送信します。", "sidebar.command_search.empty.default": "一致する項目はありません。@テーブル名でテーブルオブジェクトのみ検索、?質問で AI に質問できます。", "sidebar.command_search.empty.object": "一致するテーブル、ビュー、マテリアライズドビューはありません。", diff --git a/shared/i18n/ru-RU.json b/shared/i18n/ru-RU.json index 08d48d94..ce9cdf9d 100644 --- a/shared/i18n/ru-RU.json +++ b/shared/i18n/ru-RU.json @@ -7126,6 +7126,7 @@ "sidebar.batch_databases": "Пакетные базы данных", "sidebar.batch_tables": "Пакетные таблицы", "sidebar.command_search.action.ask_ai.title": "Спросить AI", + "sidebar.command_search.action.clear_recent": "Очистить", "sidebar.command_search.action.new_connection.meta": "Создать подключение к базе данных, среде выполнения или другому источнику данных", "sidebar.command_search.action.new_connection.title": "Новый источник данных", "sidebar.command_search.action.new_query.meta": "Открыть новую вкладку SQL-редактора", @@ -7133,6 +7134,7 @@ "sidebar.command_search.action.open_ai.title": "Открыть AI-анализ данных", "sidebar.command_search.action.open_sql_log.meta": "Открыть панель недавней истории выполнения", "sidebar.command_search.action.open_sql_log.title": "Показать журнал выполнения SQL", + "sidebar.command_search.action.remove_recent": "Удалить недавний запрос", "sidebar.command_search.empty.ai": "Введите вопрос после \"?\" и нажмите Enter, чтобы отправить его на панель AI.", "sidebar.command_search.empty.default": "Совпадений нет. Введите @таблица, чтобы искать только объекты таблиц, или ?вопрос, чтобы спросить AI.", "sidebar.command_search.empty.object": "Подходящие таблицы, представления или материализованные представления не найдены.", diff --git a/shared/i18n/zh-CN.json b/shared/i18n/zh-CN.json index 3e34397b..31225249 100644 --- a/shared/i18n/zh-CN.json +++ b/shared/i18n/zh-CN.json @@ -7126,6 +7126,7 @@ "sidebar.batch_databases": "数据库", "sidebar.batch_tables": "表", "sidebar.command_search.action.ask_ai.title": "让 AI 回答", + "sidebar.command_search.action.clear_recent": "清空", "sidebar.command_search.action.new_connection.meta": "创建数据库、运行时或其他数据源连接", "sidebar.command_search.action.new_connection.title": "新建数据源", "sidebar.command_search.action.new_query.meta": "打开一个新的 SQL 编辑页", @@ -7133,6 +7134,7 @@ "sidebar.command_search.action.open_ai.title": "打开 AI 数据洞察", "sidebar.command_search.action.open_sql_log.meta": "打开最近执行记录面板", "sidebar.command_search.action.open_sql_log.title": "查看 SQL 执行日志", + "sidebar.command_search.action.remove_recent": "删除该条近期查询", "sidebar.command_search.empty.ai": "输入「?」后加问题,按 Enter 发送到 AI 面板。", "sidebar.command_search.empty.default": "未找到匹配项。可输入 @表名 只搜表对象,或输入 ?问题 让 AI 回答。", "sidebar.command_search.empty.object": "未找到匹配的表、视图或物化视图。", diff --git a/shared/i18n/zh-TW.json b/shared/i18n/zh-TW.json index 40f0ef54..48ab1d33 100644 --- a/shared/i18n/zh-TW.json +++ b/shared/i18n/zh-TW.json @@ -7126,6 +7126,7 @@ "sidebar.batch_databases": "資料庫", "sidebar.batch_tables": "資料表", "sidebar.command_search.action.ask_ai.title": "讓 AI 回答", + "sidebar.command_search.action.clear_recent": "清空", "sidebar.command_search.action.new_connection.meta": "建立資料庫、執行階段或其他資料來源連線", "sidebar.command_search.action.new_connection.title": "新增資料來源", "sidebar.command_search.action.new_query.meta": "開啟新的 SQL 編輯頁", @@ -7133,6 +7134,7 @@ "sidebar.command_search.action.open_ai.title": "開啟 AI 資料洞察", "sidebar.command_search.action.open_sql_log.meta": "開啟最近執行記錄面板", "sidebar.command_search.action.open_sql_log.title": "查看 SQL 執行日誌", + "sidebar.command_search.action.remove_recent": "刪除這筆近期查詢", "sidebar.command_search.empty.ai": "輸入「?」後加問題,按 Enter 傳送到 AI 面板。", "sidebar.command_search.empty.default": "未找到符合項目。可輸入 @表名 只搜尋表物件,或輸入 ?問題 讓 AI 回答。", "sidebar.command_search.empty.object": "未找到符合的表格、檢視或物化檢視。",