feat(ai): add panel shortcut and conversation restore
This commit is contained in:
23 files changed
+165
-2
No files matched your search
@@ -292,6 +292,9 @@ const assistantMode = ref<AiAssistantMode>("ask");
|
||||
// The selection is loaded asynchronously. Apply it once when this panel mounts,
|
||||
// but do not let later setting changes alter an active conversation.
|
||||
let defaultModeInitialized = false;
|
||||
let initialConversationStateLoaded = false;
|
||||
let initialConversationRestored = false;
|
||||
let assistantViewMounted = false;
|
||||
watch(
|
||||
() => settings.isAiConfigLoaded,
|
||||
(loaded) => {
|
||||
@@ -305,6 +308,17 @@ watch(
|
||||
const currentSessionId = ref("");
|
||||
const conversationId = ref("");
|
||||
const conversations = ref<AiConversation[]>([]);
|
||||
function restoreInitialConversation() {
|
||||
if (!assistantViewMounted || initialConversationRestored || !initialConversationStateLoaded || !settings.isAiConfigLoaded) return;
|
||||
initialConversationRestored = true;
|
||||
if (settings.restoreLastConversation && conversations.value[0]) {
|
||||
selectConversation(conversations.value[0]);
|
||||
}
|
||||
}
|
||||
watch(
|
||||
() => settings.isAiConfigLoaded,
|
||||
() => restoreInitialConversation(),
|
||||
);
|
||||
const conversationSearchQuery = ref("");
|
||||
const conversationSearchInput = ref<HTMLInputElement | null>(null);
|
||||
const conversationSearchIndex = computed(() => buildAiConversationSearchIndex(conversations.value));
|
||||
@@ -312,7 +326,6 @@ const filteredConversations = computed(() => filterAiConversationSearchIndex(con
|
||||
const showConversationList = ref(false);
|
||||
const showTemplateSelector = ref(false);
|
||||
const modeActionOpen = ref(false);
|
||||
let assistantViewMounted = false;
|
||||
// A normal-send FIFO run recovered at startup as an editable pending draft.
|
||||
// When the user opens that conversation, the draft is loaded into the input
|
||||
// box and this banner explains it is an unsent, resendable request.
|
||||
@@ -3934,7 +3947,11 @@ async function persistPendingInputRecovery(conversation: AiConversation, message
|
||||
createdAt: conversation.createdAt,
|
||||
updatedAt,
|
||||
};
|
||||
await saveAiRunState(snapshot, run).catch(() => {});
|
||||
await saveAiRunState(snapshot, run)
|
||||
// Recovery updates the conversation timestamp. Keep the in-memory list in
|
||||
// the same order before the initial "restore latest" decision runs.
|
||||
.then(() => syncPersistedConversation(snapshot))
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
async function persistConversation() {
|
||||
@@ -4314,6 +4331,10 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
initialConversationStateLoaded = true;
|
||||
// Restore only after persisted background runs have been registered, so the
|
||||
// selected conversation reflects the same runtime state as manual selection.
|
||||
restoreInitialConversation();
|
||||
shikiCodeHighlighter.value = await createAiShikiCodeHighlighter({
|
||||
appearance: () => aiCodeAppearance.value,
|
||||
}).catch(() => undefined);
|
||||
|
||||
@@ -7941,6 +7941,22 @@ LIMIT 100;</pre
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Restore last AI conversation (list mode, global) -->
|
||||
<div v-if="aiConfigListMode === 'list'" class="space-y-3">
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="ai-restore-last-conversation">
|
||||
{{ t("ai.restoreLastConversation") }}
|
||||
</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("ai.restoreLastConversationDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="ai-restore-last-conversation" :model-value="settingsStore.restoreLastConversation" @update:model-value="(value) => settingsStore.setRestoreLastConversation(Boolean(value))" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Max Retries (list mode, global) -->
|
||||
<div v-if="aiConfigListMode === 'list'" class="space-y-3">
|
||||
<Separator />
|
||||
|
||||
+7
@@ -396,6 +396,13 @@ describe("AI assistant uses platform-specific conversation lifecycle", () => {
|
||||
expect(syncBody).toContain("conversations.value.sort(");
|
||||
});
|
||||
|
||||
it("reorders recovered conversations before restoring the most recent one", () => {
|
||||
const recoveryBody = bodyOf("async function persistPendingInputRecovery(");
|
||||
expect(recoveryBody).toContain(".then(() => syncPersistedConversation(snapshot))");
|
||||
const mountedBody = bodyOf("onMounted(async () => {");
|
||||
expect(mountedBody.indexOf("initialConversationStateLoaded = true;")).toBeLessThan(mountedBody.indexOf("restoreInitialConversation();"));
|
||||
});
|
||||
|
||||
it("does not replace an in-memory desktop run when the AI panel remounts", () => {
|
||||
const mountedBody = bodyOf("onMounted(async () => {");
|
||||
expect(mountedBody).toContain("const liveRun = desktopAiRun<ChatMessage>(persistedRun.conversationId);");
|
||||
|
||||
Reference in new issue
Block a user