fix(ai): preserve editor content for SQL actions
This commit is contained in:
parent
343a5ba6c9
commit
088a4a081b
|
|
@ -72,6 +72,7 @@ import {
|
|||
import { isPreviewTab } from "@/lib/tabs/tabPresentation";
|
||||
import { supportsSqlFileExecution } from "@/lib/database/databaseCapabilities";
|
||||
import { classifyAiSqlExecution } from "@/lib/ai/aiSqlExecutionPolicy";
|
||||
import { buildAppendedEditorSql } from "@/lib/ai/aiSqlAppend";
|
||||
import { buildHistoryAiAnalysisPrompt } from "@/lib/history/historyAiAnalysis";
|
||||
import { countAvailableAgentDriverUpdates, type AgentDriverUpdateBadgeState } from "@/lib/connection/agentDriverUpdateBadge";
|
||||
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
|
||||
|
|
@ -1250,16 +1251,25 @@ function onAiReplaceSql(sql: string) {
|
|||
queryStore.updateSql(tabId, sql);
|
||||
}
|
||||
|
||||
function onAiExecuteSql(sql: string) {
|
||||
const tabId = ensureQueryTab();
|
||||
queryStore.updateSql(tabId, sql);
|
||||
function runAiGeneratedSql(sql: string) {
|
||||
selectedSql.value = "";
|
||||
nextTick(() => tryExecute(sql));
|
||||
}
|
||||
|
||||
function onAiExecuteSql(sql: string) {
|
||||
const tabId = ensureQueryTab();
|
||||
queryStore.updateSql(tabId, buildAppendedEditorSql(activeTab.value?.sql || "", sql));
|
||||
runAiGeneratedSql(sql);
|
||||
}
|
||||
|
||||
function onAiTempRunSql(sql: string) {
|
||||
ensureQueryTab();
|
||||
runAiGeneratedSql(sql);
|
||||
}
|
||||
|
||||
function onAiRequestAutoExecuteSql(sql: string) {
|
||||
const tabId = ensureQueryTab();
|
||||
queryStore.updateSql(tabId, sql);
|
||||
queryStore.updateSql(tabId, buildAppendedEditorSql(activeTab.value?.sql || "", sql));
|
||||
selectedSql.value = "";
|
||||
|
||||
const decision = classifyAiSqlExecution(sql, activeConnection.value);
|
||||
|
|
@ -1281,7 +1291,7 @@ function onAiRequestAutoExecuteSql(sql: string) {
|
|||
|
||||
function onAiOpenExplainPlan(sql: string) {
|
||||
const tabId = ensureQueryTab();
|
||||
queryStore.updateSql(tabId, sql);
|
||||
queryStore.updateSql(tabId, buildAppendedEditorSql(activeTab.value?.sql || "", sql));
|
||||
selectedSql.value = "";
|
||||
nextTick(() => {
|
||||
void tryExplain(sql);
|
||||
|
|
@ -1952,7 +1962,18 @@ onUnmounted(() => {
|
|||
<div v-if="showAiPanel" :class="isClassicLayout ? 'h-full shrink-0 relative z-30 isolate bg-background' : 'h-full shrink-0 relative z-30 isolate rounded-md border border-border/80 bg-background'" :style="{ width: aiPanelWidth + 'px' }">
|
||||
<div class="panel-resize-handle panel-resize-handle--left" @mousedown="startAiPanelResize" />
|
||||
<div class="h-full min-h-0 overflow-hidden">
|
||||
<AiAssistant v-if="aiPanelReady" ref="aiAssistantRef" :tab="activeTab" :connection="activeConnection" @replace-sql="onAiReplaceSql" @execute-sql="onAiExecuteSql" @request-auto-execute-sql="onAiRequestAutoExecuteSql" @open-explain-plan="onAiOpenExplainPlan" @close="toggleAiPanel" />
|
||||
<AiAssistant
|
||||
v-if="aiPanelReady"
|
||||
ref="aiAssistantRef"
|
||||
:tab="activeTab"
|
||||
:connection="activeConnection"
|
||||
@replace-sql="onAiReplaceSql"
|
||||
@execute-sql="onAiExecuteSql"
|
||||
@temp-run-sql="onAiTempRunSql"
|
||||
@request-auto-execute-sql="onAiRequestAutoExecuteSql"
|
||||
@open-explain-plan="onAiOpenExplainPlan"
|
||||
@close="toggleAiPanel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
Copy,
|
||||
Database,
|
||||
FileCode,
|
||||
FlaskConical,
|
||||
GitBranch,
|
||||
HelpCircle,
|
||||
History,
|
||||
|
|
@ -123,6 +124,7 @@ const props = defineProps<{
|
|||
const emit = defineEmits<{
|
||||
replaceSql: [sql: string];
|
||||
executeSql: [sql: string];
|
||||
tempRunSql: [sql: string];
|
||||
requestAutoExecuteSql: [sql: string];
|
||||
openExplainPlan: [sql: string];
|
||||
close: [];
|
||||
|
|
@ -1516,10 +1518,13 @@ function applySql(code: string) {
|
|||
}
|
||||
|
||||
function executeSql(code: string) {
|
||||
emit("replaceSql", code);
|
||||
emit("executeSql", code);
|
||||
}
|
||||
|
||||
function tempRunSql(code: string) {
|
||||
emit("tempRunSql", code);
|
||||
}
|
||||
|
||||
const copiedIndex = ref("");
|
||||
|
||||
async function copyCode(code: string, key: string) {
|
||||
|
|
@ -1891,6 +1896,9 @@ async function openExternalUrl(url: string) {
|
|||
<span>{{ seg.lang }}</span>
|
||||
<span class="flex-1" />
|
||||
<div class="flex items-center gap-1.5">
|
||||
<button v-if="seg.isSql" class="rounded p-0.5 text-zinc-500 hover:bg-zinc-200 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:hover:text-zinc-200" :title="t('ai.tempRunSql')" @click="tempRunSql(seg.content)">
|
||||
<FlaskConical class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button v-if="seg.isSql" class="rounded p-0.5 text-zinc-500 hover:bg-zinc-200 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:hover:text-zinc-200" :title="t('ai.executeSql')" @click="executeSql(seg.content)">
|
||||
<Play class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1234,6 +1234,7 @@ export default {
|
|||
copySql: "Copy SQL",
|
||||
copyCode: "Copy Code",
|
||||
executeSql: "Execute SQL",
|
||||
tempRunSql: "Run without modifying editor",
|
||||
copyAll: "Copy All",
|
||||
copied: "Copied",
|
||||
copyTestResult: "Copy test result",
|
||||
|
|
|
|||
|
|
@ -1196,6 +1196,7 @@ export default withEnglishFallback({
|
|||
copySql: "Copiar SQL",
|
||||
copyCode: "Copiar código",
|
||||
executeSql: "Ejecutar SQL",
|
||||
tempRunSql: "Ejecutar sin modificar",
|
||||
copyAll: "Copiar todo",
|
||||
copied: "Copiado",
|
||||
copyTestResult: "Copiar resultado de prueba",
|
||||
|
|
|
|||
|
|
@ -1177,6 +1177,7 @@ export default withEnglishFallback({
|
|||
copySql: "Copia SQL",
|
||||
copyCode: "Copia Codice",
|
||||
executeSql: "Esegui SQL",
|
||||
tempRunSql: "Esegui senza modificare",
|
||||
copyAll: "Copia Tutto",
|
||||
copied: "Copiato",
|
||||
copyTestResult: "Copia risultato test",
|
||||
|
|
|
|||
|
|
@ -1199,6 +1199,7 @@ export default withEnglishFallback({
|
|||
copySql: "SQLをコピー",
|
||||
copyCode: "コードをコピー",
|
||||
executeSql: "SQLを実行",
|
||||
tempRunSql: "編集せずに実行",
|
||||
copyAll: "すべてコピー",
|
||||
copied: "コピーしました",
|
||||
copyTestResult: "テスト結果をコピー",
|
||||
|
|
|
|||
|
|
@ -1197,6 +1197,7 @@ export default withEnglishFallback({
|
|||
copySql: "Copiar SQL",
|
||||
copyCode: "Copiar Código",
|
||||
executeSql: "Executar SQL",
|
||||
tempRunSql: "Executar sem modificar",
|
||||
copyAll: "Copiar Tudo",
|
||||
copied: "Copiado",
|
||||
copyTestResult: "Copiar resultado do teste",
|
||||
|
|
|
|||
|
|
@ -1235,6 +1235,7 @@ export default withEnglishFallback({
|
|||
copySql: "复制 SQL",
|
||||
copyCode: "复制代码",
|
||||
executeSql: "立即执行",
|
||||
tempRunSql: "临时运行",
|
||||
copyAll: "复制全部",
|
||||
copied: "已复制",
|
||||
copyTestResult: "复制测试结果",
|
||||
|
|
|
|||
|
|
@ -1177,6 +1177,7 @@ export default withEnglishFallback({
|
|||
copySql: "複製 SQL",
|
||||
copyCode: "複製程式碼",
|
||||
executeSql: "立即執行",
|
||||
tempRunSql: "臨時執行",
|
||||
copyAll: "複製全部",
|
||||
copied: "已複製",
|
||||
copyTestResult: "複製測試結果",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Build the editor content after appending AI-generated SQL to existing editor SQL.
|
||||
* Preserves the existing editor content exactly and adds only the newline separator
|
||||
* needed to leave a blank line before the appended SQL.
|
||||
*/
|
||||
export function buildAppendedEditorSql(currentEditorSql: string, newSql: string): string {
|
||||
if (!currentEditorSql) return newSql;
|
||||
|
||||
let separator = "\n\n";
|
||||
if (currentEditorSql.endsWith("\r\n\r\n") || currentEditorSql.endsWith("\n\n")) {
|
||||
separator = "";
|
||||
} else if (currentEditorSql.endsWith("\r\n")) {
|
||||
separator = "\r\n";
|
||||
} else if (currentEditorSql.endsWith("\n")) {
|
||||
separator = "\n";
|
||||
}
|
||||
|
||||
return `${currentEditorSql}${separator}${newSql}`;
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { strict as assert } from "node:assert";
|
||||
import { test } from "vitest";
|
||||
import { buildAppendedEditorSql } from "../../apps/desktop/src/lib/ai/aiSqlAppend.ts";
|
||||
|
||||
test("buildAppendedEditorSql returns newSql unchanged when editor is empty", () => {
|
||||
assert.equal(buildAppendedEditorSql("", "SELECT 1"), "SELECT 1");
|
||||
});
|
||||
|
||||
test("buildAppendedEditorSql prepends blank-line separator when editor has content", () => {
|
||||
assert.equal(buildAppendedEditorSql("SELECT 1", "SELECT 2"), "SELECT 1\n\nSELECT 2");
|
||||
});
|
||||
|
||||
test("buildAppendedEditorSql preserves multiline existing content", () => {
|
||||
assert.equal(buildAppendedEditorSql("SELECT *\nFROM users", "SELECT *\nFROM orders"), "SELECT *\nFROM users\n\nSELECT *\nFROM orders");
|
||||
});
|
||||
|
||||
test("buildAppendedEditorSql preserves trailing newlines already present in the editor", () => {
|
||||
assert.equal(buildAppendedEditorSql("SELECT 1\n\n\n", "SELECT 2"), "SELECT 1\n\n\nSELECT 2");
|
||||
});
|
||||
|
||||
test("buildAppendedEditorSql preserves trailing spaces", () => {
|
||||
assert.equal(buildAppendedEditorSql("SELECT 1 ", "SELECT 2"), "SELECT 1 \n\nSELECT 2");
|
||||
});
|
||||
|
||||
test("buildAppendedEditorSql preserves trailing tabs", () => {
|
||||
assert.equal(buildAppendedEditorSql("SELECT 1\t\t", "SELECT 2"), "SELECT 1\t\t\n\nSELECT 2");
|
||||
});
|
||||
|
||||
test("buildAppendedEditorSql preserves whitespace-only editor content", () => {
|
||||
assert.equal(buildAppendedEditorSql(" \t ", "SELECT 2"), " \t \n\nSELECT 2");
|
||||
});
|
||||
|
||||
test("buildAppendedEditorSql preserves unfinished SQL", () => {
|
||||
assert.equal(buildAppendedEditorSql("SELECT * FROM", "SELECT * FROM users"), "SELECT * FROM\n\nSELECT * FROM users");
|
||||
});
|
||||
Loading…
Reference in New Issue