diff --git a/apps/desktop/src/components/editor/AiAssistant.vue b/apps/desktop/src/components/editor/AiAssistant.vue index 85990a6279..b3a906023c 100644 --- a/apps/desktop/src/components/editor/AiAssistant.vue +++ b/apps/desktop/src/components/editor/AiAssistant.vue @@ -323,6 +323,10 @@ const conversationSearchInput = ref(null); const conversationSearchIndex = computed(() => buildAiConversationSearchIndex(conversations.value)); const filteredConversations = computed(() => filterAiConversationSearchIndex(conversationSearchIndex.value, conversationSearchQuery.value)); const showConversationList = ref(false); +const renamingConversationId = ref(null); +const renamingConversationTitle = ref(""); +const renamingConversationInput = ref(null); +const renamedConversationTitles = reactive(new Map()); const showTemplateSelector = ref(false); const modeActionOpen = ref(false); // A normal-send FIFO run recovered at startup as an editable pending draft. @@ -1407,6 +1411,12 @@ const dbSelectOptions = computed(() => { // AI can inspect more than the tab's active database. Keep this selection local // to the composer so changing the visible query tab does not rewrite SQL state. const selectedDatabases = ref([]); +const databaseSearchQuery = ref(""); +const filteredDbSelectOptions = computed(() => { + const query = databaseSearchQuery.value.trim().toLowerCase(); + if (!query) return dbSelectOptions.value; + return dbSelectOptions.value.filter((option) => option.label.toLowerCase().includes(query) || option.database.toLowerCase().includes(query)); +}); const selectedDatabaseValues = computed(() => new Set(selectedDatabases.value)); const selectedDatabaseLabel = computed(() => { @@ -3835,9 +3845,10 @@ function clearAttachmentDraftState() { function buildConversationSnapshot(targetConversationId: string, targetMessages: ChatMessage[], connectionName: string, database: string, createdAt = new Date().toISOString()): AiConversation | null { if (!targetConversationId || !targetMessages.length) return null; const first = targetMessages.find((m) => m.role === "user" && m.kind !== "contextSummary"); + const existingConversation = conversations.value.find((conversation) => conversation.id === targetConversationId); return { id: targetConversationId, - title: first ? messageTitle(first).slice(0, 50) : "Untitled", + title: renamedConversationTitles.get(targetConversationId) || existingConversation?.title || (first ? messageTitle(first).slice(0, 50) : "Untitled"), connectionName, database, messages: targetMessages.map((m) => ({ @@ -3992,6 +4003,38 @@ async function setConversationListOpen(open: boolean) { } } +async function startRenameConversation(conv: AiConversation) { + renamingConversationId.value = conv.id; + renamingConversationTitle.value = conv.title; + await nextTick(); + renamingConversationInput.value?.focus(); + renamingConversationInput.value?.select(); +} +function cancelRenameConversation() { + renamingConversationId.value = null; + renamingConversationTitle.value = ""; +} +async function commitRenameConversation(conv: AiConversation) { + const title = renamingConversationTitle.value.trim().slice(0, 50); + if (!title || title === conv.title) return cancelRenameConversation(); + try { + const activeRun = desktopAiRun(conv.id); + if (activeRun) await runSnapshotScheduler.save(activeRun); + const latestConversation = conversations.value.find((item) => item.id === conv.id) ?? conv; + const updated = { ...latestConversation, title, updatedAt: new Date().toISOString() }; + // Claim the title before the async save so a throttled snapshot firing + // during the await window cannot persist the previous title over it. + renamedConversationTitles.set(conv.id, title); + await saveAiConversation(updated); + const i = conversations.value.findIndex((item) => item.id === conv.id); + if (i >= 0) conversations.value[i] = updated; + } catch { + toast(t("ai.conversationRenameFailed"), 5000); + } finally { + cancelRenameConversation(); + } +} + function chatMessagesFromConversation(conv: AiConversation): ChatMessage[] { return conv.messages.map((m) => ({ role: m.role as "user" | "assistant", @@ -4580,7 +4623,22 @@ async function openExternalUrl(url: string) {
- {{ conv.title }} + + {{ conv.title }} + {{ t("ai.rowQueuedInput") }} @@ -5011,11 +5069,18 @@ async function openExternalUrl(url: string) { - -
{{ t("editor.selectDatabase") }}
+
+ + +
+ +
+ +
{{ t("ai.noDatabasesFound") }}
+