feat: add SQL library history query entry points
This commit is contained in:
1 parent
634e26e8e7
commit
806d7c758b
16 files changed
+345
-8
No files matched your search
@@ -30,6 +30,7 @@ import { useVisibilityChange } from "@/composables/useVisibilityChange";
|
||||
import "@/i18n";
|
||||
import { translateBackendError } from "@/i18n/backend-errors";
|
||||
import * as api from "@/lib/api";
|
||||
import { connectionRedactedNameLabel } from "@/lib/connectionPresentation";
|
||||
import { resolveDefaultDatabase } from "@/lib/defaultDatabase";
|
||||
import { findTreeNodeById, resolveNewQueryTarget } from "@/lib/newQueryContext";
|
||||
import { buildExecutableObjectSourceStatements, objectSourceSaveExecutionMode } from "@/lib/objectSourceEditor";
|
||||
@@ -61,6 +62,7 @@ import { classifyAiSqlExecution } from "@/lib/aiSqlExecutionPolicy";
|
||||
import { buildHistoryAiAnalysisPrompt } from "@/lib/historyAiAnalysis";
|
||||
import { countAvailableAgentDriverUpdates, type AgentDriverUpdateBadgeState } from "@/lib/agentDriverUpdateBadge";
|
||||
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/safeStorage";
|
||||
import { rankSavedSqlHistory } from "@/lib/savedSqlHistory";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -215,6 +217,20 @@ const connectionStats = computed(() => ({
|
||||
types: new Set(connectionStore.connections.map((c) => c.driver_profile || c.db_type)).size,
|
||||
}));
|
||||
const recentConnections = computed(() => connectionStore.connections.slice(0, 5));
|
||||
const savedSqlHistoryItems = computed(() => {
|
||||
const folderById = new Map(savedSqlStore.allFolders.map((folder) => [folder.id, folder.name]));
|
||||
return rankSavedSqlHistory(savedSqlStore.allFiles, { limit: 6 }).map((file) => {
|
||||
const connection = connectionStore.getConfig(file.connectionId);
|
||||
return {
|
||||
id: file.id,
|
||||
name: file.name,
|
||||
connectionName: connection ? connectionRedactedNameLabel(connection) : t("welcome.unknownConnection"),
|
||||
database: file.database,
|
||||
folderName: file.folderId ? folderById.get(file.folderId) : undefined,
|
||||
openCount: file.openCount ?? 0,
|
||||
};
|
||||
});
|
||||
});
|
||||
const saveSqlFolders = computed(() => {
|
||||
return savedSqlStore.allFolders;
|
||||
});
|
||||
@@ -619,6 +635,15 @@ async function openConnectionQuery(connectionId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function openSavedSqlFromWelcome(fileId: string) {
|
||||
const file = savedSqlStore.getFile(fileId);
|
||||
if (!file) return;
|
||||
queryStore.openSavedSql(file);
|
||||
connectionStore.activeConnectionId = file.connectionId;
|
||||
void savedSqlStore.recordFileUsage(file.id);
|
||||
toast(t("welcome.fileOpened", { name: file.name }), 2000);
|
||||
}
|
||||
|
||||
async function onClickTable(tableName: string) {
|
||||
const tab = activeTab.value;
|
||||
if (!tab) return;
|
||||
@@ -1088,9 +1113,11 @@ onUnmounted(() => {
|
||||
v-else
|
||||
:connection-stats="connectionStats"
|
||||
:recent-connections="recentConnections"
|
||||
:saved-sql-history-items="savedSqlHistoryItems"
|
||||
:app-version="appVersion"
|
||||
:has-connections="connectionStore.connections.length > 0"
|
||||
@open-connection-query="openConnectionQuery"
|
||||
@open-saved-sql="openSavedSqlFromWelcome"
|
||||
@new-connection="showConnectionDialog = true"
|
||||
@new-query="newQuery"
|
||||
@show-history="showHistory = true"
|
||||
|
||||
@@ -439,6 +439,7 @@ function openFile(file: SavedSqlFile) {
|
||||
if (suppressNextRowClick.value) return;
|
||||
queryStore.openSavedSql(file);
|
||||
connectionStore.activeConnectionId = file.connectionId;
|
||||
void savedSqlStore.recordFileUsage(file.id);
|
||||
}
|
||||
|
||||
const contextTarget = ref<SavedSqlFolder | SavedSqlFile | "panel" | null>(null);
|
||||
|
||||
@@ -5,15 +5,26 @@ import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
|
||||
import { connectionDriverLabel, connectionIconType, connectionRedactedNameLabel, connectionRedactedOptionSubtitle } from "@/lib/connectionPresentation";
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
|
||||
export interface WelcomeSavedSqlHistoryItem {
|
||||
id: string;
|
||||
name: string;
|
||||
connectionName: string;
|
||||
database?: string;
|
||||
folderName?: string;
|
||||
openCount?: number;
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
connectionStats: { total: number; connected: number; types: number };
|
||||
recentConnections: ConnectionConfig[];
|
||||
savedSqlHistoryItems: WelcomeSavedSqlHistoryItem[];
|
||||
appVersion: string;
|
||||
hasConnections: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"open-connection-query": [connectionId: string];
|
||||
"open-saved-sql": [fileId: string];
|
||||
"new-connection": [];
|
||||
"new-query": [];
|
||||
"show-history": [];
|
||||
@@ -83,6 +94,30 @@ const { t } = useI18n();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border">
|
||||
<div class="flex items-center justify-between border-b px-4 py-3">
|
||||
<div class="flex items-center gap-2 text-sm font-medium"><History class="h-4 w-4" /> {{ t("welcome.sqlHistory") }}</div>
|
||||
</div>
|
||||
<div class="divide-y">
|
||||
<button v-for="item in savedSqlHistoryItems" :key="item.id" class="flex w-full items-center gap-3 px-4 py-3 text-left hover:bg-muted/40" @click="emit('open-saved-sql', item.id)">
|
||||
<History class="h-4 w-4 text-muted-foreground" />
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-sm font-medium">{{ item.name }}</div>
|
||||
<div class="truncate text-xs text-muted-foreground">
|
||||
<span>{{ item.connectionName }}</span>
|
||||
<span v-if="item.database"> · {{ item.database }}</span>
|
||||
<span v-if="item.folderName"> · {{ item.folderName }}</span>
|
||||
<span v-if="item.openCount"> · {{ t("welcome.sqlHistoryOpenCount", { count: item.openCount }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<FilePlus2 class="h-4 w-4 text-muted-foreground" />
|
||||
</button>
|
||||
<div v-if="savedSqlHistoryItems.length === 0" class="px-4 py-8 text-sm text-muted-foreground">
|
||||
{{ t("welcome.sqlHistoryEmpty") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MCP Integration Hint -->
|
||||
<div class="rounded-lg border bg-muted/10 px-5 py-4">
|
||||
<div class="flex items-start gap-3">
|
||||
|
||||
@@ -53,6 +53,7 @@ import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomC
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { useSavedSqlStore } from "@/stores/savedSqlStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { useDatabaseOptions } from "@/composables/useDatabaseOptions";
|
||||
import type { ColumnInfo, DatabaseType, TreeNode, TreeNodeType } from "@/types/database";
|
||||
@@ -103,6 +104,7 @@ import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
import { copyToClipboard } from "@/lib/clipboard";
|
||||
import { hasEnabledTransportLayers } from "@/lib/connectionTransport";
|
||||
import { formatShortcut } from "@/lib/shortcutRegistry";
|
||||
import { rankSavedSqlHistory, type SavedSqlHistoryScope } from "@/lib/savedSqlHistory";
|
||||
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
|
||||
import ConnectionErrorIndicator from "@/components/connection/ConnectionErrorIndicator.vue";
|
||||
import VisibleDatabasesDialog from "@/components/sidebar/VisibleDatabasesDialog.vue";
|
||||
@@ -126,6 +128,7 @@ function isLabelTruncated(): boolean {
|
||||
const connectionStore = useConnectionStore();
|
||||
const queryStore = useQueryStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const savedSqlStore = useSavedSqlStore();
|
||||
const { toast } = useToast();
|
||||
const { highlight } = useSqlHighlighter();
|
||||
|
||||
@@ -2529,6 +2532,60 @@ function copyStructureAsSubmenu(): ContextMenuItem {
|
||||
};
|
||||
}
|
||||
|
||||
function savedSqlHistoryScopeForNode(node: TreeNode): SavedSqlHistoryScope | null {
|
||||
if (!node.connectionId) return null;
|
||||
if (node.type === "connection") {
|
||||
return { connectionId: node.connectionId };
|
||||
}
|
||||
if ((node.type === "database" || node.type === "schema") && hasTreeNodeDatabaseContext(node)) {
|
||||
return {
|
||||
connectionId: node.connectionId,
|
||||
database: node.database,
|
||||
schema: node.type === "schema" ? node.schema : undefined,
|
||||
};
|
||||
}
|
||||
if ((node.type === "table" || node.type === "view") && hasTreeNodeDatabaseContext(node)) {
|
||||
return {
|
||||
connectionId: node.connectionId,
|
||||
database: node.database,
|
||||
schema: node.schema,
|
||||
tableName: node.label,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function openSavedSqlHistoryFile(fileId: string) {
|
||||
const file = savedSqlStore.getFile(fileId);
|
||||
if (!file) return;
|
||||
queryStore.openSavedSql(file);
|
||||
connectionStore.activeConnectionId = file.connectionId;
|
||||
void savedSqlStore.recordFileUsage(file.id);
|
||||
}
|
||||
|
||||
function savedSqlHistorySubmenu(): ContextMenuItem | null {
|
||||
const scope = savedSqlHistoryScopeForNode(props.node);
|
||||
if (!scope) return null;
|
||||
const files = rankSavedSqlHistory(savedSqlStore.allFiles, { ...scope, limit: 10 });
|
||||
return {
|
||||
label: t("contextMenu.sqlHistory"),
|
||||
icon: ScrollText,
|
||||
children:
|
||||
files.length > 0
|
||||
? files.map((file) => ({
|
||||
label: file.name,
|
||||
action: () => openSavedSqlHistoryFile(file.id),
|
||||
icon: FileCode,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
label: t("contextMenu.noSqlHistory"),
|
||||
disabled: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function treeItemMenuItems(): ContextMenuItem[] {
|
||||
const node = props.node;
|
||||
const items: ContextMenuItem[] = [];
|
||||
@@ -2554,6 +2611,8 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
||||
items.push({ label: t("contextMenu.closeConnection"), action: disconnectConnection, icon: Unplug });
|
||||
}
|
||||
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
|
||||
const sqlHistoryMenu = savedSqlHistorySubmenu();
|
||||
if (sqlHistoryMenu) items.push(sqlHistoryMenu);
|
||||
if (supportsDatabaseUserAdmin(currentDatabaseType())) {
|
||||
items.push({ label: t("contextMenu.userAdmin"), action: openUserAdmin, icon: UsersRound });
|
||||
}
|
||||
@@ -2641,6 +2700,8 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
||||
items.push({ label: t("contextMenu.openObjectBrowser"), action: openObjectBrowser, icon: TableProperties });
|
||||
}
|
||||
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
|
||||
const sqlHistoryMenu = savedSqlHistorySubmenu();
|
||||
if (sqlHistoryMenu) items.push(sqlHistoryMenu);
|
||||
if (node.type === "database") {
|
||||
if (!isNodeDefaultDatabase.value) {
|
||||
items.push({ label: t("contextMenu.setDefaultDatabase"), action: setNodeAsDefaultDatabase, icon: Database });
|
||||
@@ -2758,6 +2819,8 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
||||
});
|
||||
}
|
||||
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
|
||||
const sqlHistoryMenu = savedSqlHistorySubmenu();
|
||||
if (sqlHistoryMenu) items.push(sqlHistoryMenu);
|
||||
if (canOpenDiagram.value) {
|
||||
items.push({ label: t("diagram.open"), action: openDiagram, icon: Network });
|
||||
}
|
||||
|
||||
@@ -761,6 +761,10 @@
|
||||
quickConnectionsHint: "Click a connection to open a query tab.",
|
||||
shortcuts: "Shortcuts",
|
||||
shortcutsHint: "Start the next action from here.",
|
||||
sqlHistory: "Query History",
|
||||
sqlHistoryEmpty: "No SQL Library records yet",
|
||||
sqlHistoryOpenCount: "Opened {count} times",
|
||||
unknownConnection: "Unknown connection",
|
||||
tip: "You can also expand connections on the left and right-click databases or tables for more actions.",
|
||||
tipSidebar: "Click a table on the left to view data",
|
||||
tipExecute: "to execute query",
|
||||
@@ -1002,6 +1006,8 @@
|
||||
openUserAdmin: "Open Users & Privileges",
|
||||
duplicateConnection: "Duplicate Connection",
|
||||
newQuery: "New Query",
|
||||
sqlHistory: "Query History",
|
||||
noSqlHistory: "No related SQL",
|
||||
openObjectBrowser: "Browse Objects",
|
||||
viewData: "View Data",
|
||||
editStructure: "Edit Structure",
|
||||
|
||||
@@ -669,6 +669,10 @@
|
||||
quickConnectionsHint: "Haz clic en una conexión para abrir una pestaña de consulta.",
|
||||
shortcuts: "Atajos",
|
||||
shortcutsHint: "Inicia la siguiente acción desde aquí.",
|
||||
sqlHistory: "Historial de consultas",
|
||||
sqlHistoryEmpty: "Aún no hay registros de la biblioteca SQL",
|
||||
sqlHistoryOpenCount: "Abierto {count} veces",
|
||||
unknownConnection: "Conexión desconocida",
|
||||
tip: "También puedes expandir las conexiones a la izquierda y hacer clic derecho en bases de datos o tablas para más opciones.",
|
||||
tipSidebar: "Haz clic en una tabla a la izquierda para ver los datos",
|
||||
tipExecute: "para ejecutar la consulta",
|
||||
@@ -865,6 +869,8 @@
|
||||
selectVisibleDatabases: "Seleccionar bases visibles",
|
||||
duplicateConnection: "Duplicar conexión",
|
||||
newQuery: "Nueva consulta",
|
||||
sqlHistory: "Historial de consultas",
|
||||
noSqlHistory: "Sin SQL relacionado",
|
||||
openObjectBrowser: "Explorar objetos",
|
||||
viewData: "Ver datos",
|
||||
editStructure: "Editar estructura",
|
||||
|
||||
@@ -729,6 +729,10 @@
|
||||
quickConnectionsHint: "Fai clic su una connessione per aprire una scheda query.",
|
||||
shortcuts: "Scorciatoie",
|
||||
shortcutsHint: "Avvia la prossima azione da qui.",
|
||||
sqlHistory: "Cronologia Query",
|
||||
sqlHistoryEmpty: "Nessun record nella Libreria SQL",
|
||||
sqlHistoryOpenCount: "Aperto {count} volte",
|
||||
unknownConnection: "Connessione sconosciuta",
|
||||
tip: "Puoi anche espandere le connessioni a sinistra e fare clic con il tasto destro su database o tabelle per altre azioni.",
|
||||
tipSidebar: "Fai clic su una tabella a sinistra per visualizzarne i dati",
|
||||
tipExecute: "per eseguire la query",
|
||||
@@ -966,6 +970,8 @@
|
||||
selectVisibleDatabases: "Seleziona Database Visibili",
|
||||
duplicateConnection: "Duplica Connessione",
|
||||
newQuery: "Nuova Query",
|
||||
sqlHistory: "Cronologia Query",
|
||||
noSqlHistory: "Nessun SQL correlato",
|
||||
openObjectBrowser: "Esplora Oggetti",
|
||||
viewData: "Visualizza Dati",
|
||||
editStructure: "Modifica Struttura",
|
||||
|
||||
@@ -729,6 +729,10 @@
|
||||
quickConnectionsHint: "Clique em uma conexão para abrir uma aba de consulta.",
|
||||
shortcuts: "Atalhos",
|
||||
shortcutsHint: "Inicie a próxima ação a partir daqui.",
|
||||
sqlHistory: "Histórico de Consultas",
|
||||
sqlHistoryEmpty: "Nenhum registro da Biblioteca SQL ainda",
|
||||
sqlHistoryOpenCount: "Aberto {count} vezes",
|
||||
unknownConnection: "Conexão desconhecida",
|
||||
tip: "Você também pode expandir as conexões à esquerda e clicar com o botão direito em bancos de dados ou tabelas para mais ações.",
|
||||
tipSidebar: "Clique em uma tabela à esquerda para visualizar os dados",
|
||||
tipExecute: "para executar a consulta",
|
||||
@@ -966,6 +970,8 @@
|
||||
selectVisibleDatabases: "Selecionar Bancos de Dados Visíveis",
|
||||
duplicateConnection: "Duplicar Conexão",
|
||||
newQuery: "Nova Consulta",
|
||||
sqlHistory: "Histórico de Consultas",
|
||||
noSqlHistory: "Nenhum SQL relacionado",
|
||||
openObjectBrowser: "Explorar Objetos",
|
||||
viewData: "Ver Dados",
|
||||
editStructure: "Editar Estrutura",
|
||||
|
||||
@@ -762,6 +762,10 @@
|
||||
quickConnectionsHint: "点击连接即可新建查询标签页。",
|
||||
shortcuts: "常用操作",
|
||||
shortcutsHint: "从这里开始你的下一步。",
|
||||
sqlHistory: "历史查询",
|
||||
sqlHistoryEmpty: "暂无 SQL 库记录",
|
||||
sqlHistoryOpenCount: "打开 {count} 次",
|
||||
unknownConnection: "未知连接",
|
||||
tip: "也可以在左侧展开连接,右键数据库或表查看更多操作。",
|
||||
fileOpened: "已打开 {name}",
|
||||
mcpTitle: "AI 编程助手集成",
|
||||
@@ -1001,6 +1005,8 @@
|
||||
userAdmin: "用户与权限",
|
||||
openUserAdmin: "打开用户与权限",
|
||||
newQuery: "新建查询",
|
||||
sqlHistory: "历史查询",
|
||||
noSqlHistory: "暂无关联 SQL",
|
||||
openObjectBrowser: "浏览对象",
|
||||
viewData: "查看数据",
|
||||
editStructure: "编辑表结构",
|
||||
|
||||
@@ -708,6 +708,10 @@
|
||||
quickConnectionsHint: "點選連線以開啟查詢分頁。",
|
||||
shortcuts: "常用操作",
|
||||
shortcutsHint: "從這裡開始你的下一步。",
|
||||
sqlHistory: "歷史查詢",
|
||||
sqlHistoryEmpty: "暫無 SQL 庫記錄",
|
||||
sqlHistoryOpenCount: "開啟 {count} 次",
|
||||
unknownConnection: "未知連線",
|
||||
tip: "也可以在左側展開連線,並在資料庫或資料表上按右鍵查看更多操作。",
|
||||
tipSidebar: "點選左側資料表以檢視資料",
|
||||
tipExecute: "執行查詢",
|
||||
@@ -945,6 +949,8 @@
|
||||
selectVisibleDatabases: "選擇顯示資料庫",
|
||||
duplicateConnection: "複製連線",
|
||||
newQuery: "建立查詢",
|
||||
sqlHistory: "歷史查詢",
|
||||
noSqlHistory: "暫無關聯 SQL",
|
||||
openObjectBrowser: "瀏覽物件",
|
||||
viewData: "檢視資料",
|
||||
editStructure: "編輯資料表結構",
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { SavedSqlFile } from "@/types/database";
|
||||
|
||||
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||
const RECENCY_WINDOW_MS = 30 * DAY_MS;
|
||||
|
||||
export interface SavedSqlHistoryScope {
|
||||
connectionId?: string;
|
||||
database?: string;
|
||||
schema?: string;
|
||||
tableName?: string;
|
||||
limit?: number;
|
||||
now?: number;
|
||||
}
|
||||
|
||||
function timestamp(value?: string) {
|
||||
if (!value) return 0;
|
||||
const time = Date.parse(value);
|
||||
return Number.isFinite(time) ? time : 0;
|
||||
}
|
||||
|
||||
function normalize(value?: string) {
|
||||
return (value || "").trim().toLowerCase();
|
||||
}
|
||||
|
||||
function containsToken(text: string | undefined, token: string) {
|
||||
const normalizedText = normalize(text);
|
||||
const normalizedToken = normalize(token);
|
||||
if (!normalizedText || !normalizedToken) return false;
|
||||
return normalizedText.includes(normalizedToken);
|
||||
}
|
||||
|
||||
function scopeRelevance(file: SavedSqlFile, scope: SavedSqlHistoryScope) {
|
||||
let score = 0;
|
||||
if (scope.database != null && file.database === scope.database) score += 100;
|
||||
if (scope.schema && file.schema === scope.schema) score += 30;
|
||||
if (scope.tableName) {
|
||||
const schemaTable = scope.schema ? `${scope.schema}.${scope.tableName}` : "";
|
||||
if (containsToken(file.name, scope.tableName) || containsToken(file.sql, scope.tableName)) score += 80;
|
||||
if (schemaTable && (containsToken(file.name, schemaTable) || containsToken(file.sql, schemaTable))) score += 40;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
export function savedSqlHistoryScore(file: SavedSqlFile, now = Date.now()) {
|
||||
const openCount = Math.max(0, file.openCount ?? 0);
|
||||
const openedAt = timestamp(file.openedAt);
|
||||
const age = openedAt > 0 ? Math.max(0, now - openedAt) : RECENCY_WINDOW_MS;
|
||||
const recency = openedAt > 0 ? Math.max(0, 1 - age / RECENCY_WINDOW_MS) : 0;
|
||||
return openCount * 1000 + recency * 999;
|
||||
}
|
||||
|
||||
export function savedSqlMatchesHistoryScope(file: SavedSqlFile, scope: SavedSqlHistoryScope) {
|
||||
if (scope.connectionId && file.connectionId !== scope.connectionId) return false;
|
||||
if (scope.database != null && file.database !== scope.database) return false;
|
||||
if (scope.schema && file.schema && file.schema !== scope.schema) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function rankSavedSqlHistory(files: SavedSqlFile[], scope: SavedSqlHistoryScope = {}) {
|
||||
const now = scope.now ?? Date.now();
|
||||
const ranked = files
|
||||
.filter((file) => savedSqlMatchesHistoryScope(file, scope))
|
||||
.map((file) => ({
|
||||
file,
|
||||
score: scopeRelevance(file, scope) + savedSqlHistoryScore(file, now),
|
||||
openedAt: timestamp(file.openedAt),
|
||||
updatedAt: timestamp(file.updatedAt),
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
const scoreDiff = b.score - a.score;
|
||||
if (scoreDiff !== 0) return scoreDiff;
|
||||
const openedDiff = b.openedAt - a.openedAt;
|
||||
if (openedDiff !== 0) return openedDiff;
|
||||
const updatedDiff = b.updatedAt - a.updatedAt;
|
||||
if (updatedDiff !== 0) return updatedDiff;
|
||||
return a.file.name.localeCompare(b.file.name, undefined, { numeric: true, sensitivity: "base" });
|
||||
})
|
||||
.map((item) => item.file);
|
||||
|
||||
return typeof scope.limit === "number" ? ranked.slice(0, Math.max(0, scope.limit)) : ranked;
|
||||
}
|
||||
@@ -187,6 +187,24 @@ export const useSavedSqlStore = defineStore("savedSql", () => {
|
||||
await syncToLocalDirectory();
|
||||
}
|
||||
|
||||
async function recordFileUsage(id: string) {
|
||||
const existing = getFile(id);
|
||||
if (!existing) return;
|
||||
try {
|
||||
const saved = await api.saveSavedSqlFile({
|
||||
...existing,
|
||||
openCount: (existing.openCount ?? 0) + 1,
|
||||
openedAt: nowIso(),
|
||||
});
|
||||
files.value = files.value.map((file) => (file.id === id ? saved : file));
|
||||
bumpVersion();
|
||||
return saved;
|
||||
} catch (error) {
|
||||
console.warn("[DBX][saved-sql:usage:error]", error);
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFile(id: string) {
|
||||
await api.deleteSavedSqlFile(id);
|
||||
files.value = files.value.filter((file) => file.id !== id);
|
||||
@@ -340,6 +358,7 @@ export const useSavedSqlStore = defineStore("savedSql", () => {
|
||||
deleteFolder,
|
||||
saveFile,
|
||||
renameFile,
|
||||
recordFileUsage,
|
||||
deleteFile,
|
||||
reorderFolders,
|
||||
reorderFiles,
|
||||
|
||||
@@ -466,6 +466,8 @@ export interface SavedSqlFile {
|
||||
schema?: string;
|
||||
sql: string;
|
||||
orderIndex?: number;
|
||||
openCount?: number;
|
||||
openedAt?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user