From 82e58b52e7cf8b99e8794851481bc386305a937e Mon Sep 17 00:00:00 2001 From: Andrew Beal Date: Sun, 19 Apr 2026 20:39:04 +0100 Subject: [PATCH] refactor: replace edit/planning mode toggles with unified chat mode selector Replace separate editModeActive and planningModeActive boolean flags with a single ChatMode enum (ReadOnly, Edit, Planning). Add ChatModeSelector component for mode switching. Update all components to use chatMode instead of individual flags. Refactor MainAgent and ChatService to accept ChatMode parameter. Remove edit-mode styling variants throughout UI components. Update system prompt to document user reference system. Consolidate input button styling with shared input-button-highlight class. --- .../ToolDefinitions/AIToolDefinitions.ts | 9 +- AIPrompts/SystemPrompt.ts | 13 ++ Components/ChatArea.svelte | 5 +- Components/ChatInput.svelte | 152 +++++-------- Components/ChatModeSelector.svelte | 210 ++++++++++++++++++ Components/ChatPlanArea.svelte | 5 +- Components/ChatWindow.svelte | 16 +- Components/InputDisplay.svelte | 10 +- Components/Spinner.svelte | 6 - Components/StreamingIndicator.svelte | 7 +- Components/ThoughtIndicator.svelte | 4 +- Enums/ChatMode.ts | 20 ++ Enums/Copy.ts | 11 +- Services/AIServices/MainAgent.ts | 17 +- Services/ChatService.ts | 5 +- Styles/custom_styles.css | 12 +- 16 files changed, 343 insertions(+), 159 deletions(-) create mode 100644 Components/ChatModeSelector.svelte create mode 100644 Enums/ChatMode.ts diff --git a/AIClasses/ToolDefinitions/AIToolDefinitions.ts b/AIClasses/ToolDefinitions/AIToolDefinitions.ts index 691b882..512569d 100644 --- a/AIClasses/ToolDefinitions/AIToolDefinitions.ts +++ b/AIClasses/ToolDefinitions/AIToolDefinitions.ts @@ -22,6 +22,7 @@ import { CreateVaultFolder } from "./Tools/CreateVaultFolder"; import { GetWebViewerContent } from "./Tools/GetWebViewerContent"; import { DeleteVaultFolder } from "./Tools/DeleteVaultFolder"; import { MoveVaultFolder } from "./Tools/MoveVaultFolder"; +import { ChatMode, chatModeAllowsEdits } from "Enums/ChatMode"; export abstract class AIToolDefinitions { @@ -31,10 +32,10 @@ export abstract class AIToolDefinitions { private static readonly definitionsList = [SearchVaultFiles, ReadVaultFiles, ListVaultFiles, GetWebViewerContent, WriteVaultFile, PatchVaultFile, DeleteVaultFiles, MoveVaultFiles, CreateVaultFolder, DeleteVaultFolder, MoveVaultFolder]; - public static agentDefinitions(destructive: boolean, planningMode: boolean, memories: boolean, updateMemories: boolean, webViewer: boolean): IAIToolDefinition[] { + public static agentDefinitions(chatMode: ChatMode, memories: boolean, updateMemories: boolean, webViewer: boolean): IAIToolDefinition[] { this.isGated = false; - if (planningMode) { + if (chatMode === ChatMode.Planning) { return [ExecuteWorkflow]; } @@ -56,7 +57,7 @@ export abstract class AIToolDefinitions { actions = actions.concat([UpdateMemories]); } - if (destructive) { + if (chatModeAllowsEdits(chatMode)) { actions = actions.concat([ WriteVaultFile, PatchVaultFile, @@ -110,7 +111,7 @@ export abstract class AIToolDefinitions { } public static executionAgentDefinitions(): IAIToolDefinition[] { - return [...this.agentDefinitions(true, false, false, false, false), CompleteTask]; + return [...this.agentDefinitions(ChatMode.Edit, false, false, false), CompleteTask]; } public static compactSummaryForPlanningAgent(): string { diff --git a/AIPrompts/SystemPrompt.ts b/AIPrompts/SystemPrompt.ts index 41a1ad3..79068ca 100644 --- a/AIPrompts/SystemPrompt.ts +++ b/AIPrompts/SystemPrompt.ts @@ -327,6 +327,19 @@ When searches return or reference images or PDFs: - Extract relevant information to answer the user's query - Reference the source file with [[wiki-links]] as usual +### User-Provided References + +The chat input supports an autocomplete reference system. When a user types \`@\`, \`/\`, or \`#\`, they can select matching vault items from a dropdown. Selected references are serialized into the message as: + +- \`file:"path/to/note.md"\` — a specific file the user selected with \`@\` +- \`folder:"path/to/folder"\` — a specific folder the user selected with \`/\` +- \`tag:"#tagname"\` — a specific tag the user selected with \`#\` + +**These references are always valid — the user selected them from live vault data.** You likely do not have to verify their existence. +Example: If the user says "create a note in \`folder:"Projects/2025"\`", the folder exists and should be used directly as the destination (does not need to be created first). + +--- + ### File Attachments **Users can attach files directly to their messages.** When a file is attached, its content is provided inline in the conversation. Attached files are likely NOT present in the vault so use the attached content directly. diff --git a/Components/ChatArea.svelte b/Components/ChatArea.svelte index 51a43dd..ad5a209 100644 --- a/Components/ChatArea.svelte +++ b/Components/ChatArea.svelte @@ -17,7 +17,6 @@ export let isSubmitting: boolean = false; export let chatContainer: HTMLDivElement; export let currentStreamingMessageId: string | null = null; - export let editModeActive: boolean = false; export function resetChatArea() { autoScroll = true; @@ -291,9 +290,9 @@ {/if} {/each} - + {#if isSubmitting} - + {/if}
diff --git a/Components/ChatInput.svelte b/Components/ChatInput.svelte index d8a84f2..e75a4b1 100644 --- a/Components/ChatInput.svelte +++ b/Components/ChatInput.svelte @@ -10,6 +10,7 @@ import type { Writable } from "svelte/store"; import type { InputService } from "Services/InputService"; import UserInstruction from "./UserInstruction.svelte"; + import ChatModeSelector from "./ChatModeSelector.svelte"; import DiffControls from "./DiffControls.svelte"; import type { EventService } from "Services/EventService"; import { Event } from "Enums/Event"; @@ -22,13 +23,13 @@ import { HelpModal } from "Modals/HelpModal"; import type { IPrompt } from "AIPrompts/IPrompt"; import type { SettingsService } from "Services/SettingsService"; + import { ChatMode, chatModeAllowsEdits, iconForChatMode } from "Enums/ChatMode"; export let attachments: Attachment[] = []; export let hasNoApiKey: boolean; export let isSubmitting: boolean; - export let editModeActive: boolean; - export let planningModeActive: boolean; + export let chatMode: ChatMode; export let onSubmit: (userRequest: string, formattedRequest: string) => void; export let onStop: () => void; @@ -47,9 +48,10 @@ let userInstructionButton: HTMLButtonElement; let webSearchButton: HTMLButtonElement; let submitButton: HTMLButtonElement; - let editModeButton: HTMLButtonElement; - let planningModeButton: HTMLButtonElement; + let attachmentButton: HTMLButtonElement; + let chatModeButton: HTMLButtonElement; + let chatModeSelectionAreaActive: boolean = false; let userInstructionAreaActive: boolean = false; let userInstructionActive: boolean = true; let stacked: boolean = false; @@ -198,12 +200,12 @@ } } - $: if (editModeButton) { - setIcon(editModeButton, editModeActive ? "pencil" : "pencil-off"); + $: if (attachmentButton) { + setIcon(attachmentButton, "paperclip"); } - $: if (planningModeButton) { - setIcon(planningModeButton, planningModeActive ? "list-ordered" : "list-x"); + $: if (chatModeButton) { + setIcon(chatModeButton, iconForChatMode(chatMode)); } $: inputPlaceholder = (() => { @@ -290,24 +292,8 @@ settingsService.saveSettings(); } - function toggleEditMode() { - if (planningModeActive) { - planningModeActive = false - } - editModeActive = !editModeActive; - focusInput(); - } - - function togglePlanningMode() { - if (planningModeActive) { - planningModeActive = false; - } else { - if (!editModeActive) { - toggleEditMode(); // Mandatory for planning mode - } - planningModeActive = true; - } - focusInput(); + function toggleChatModeSelectionArea() { + chatModeSelectionAreaActive = !chatModeSelectionAreaActive; } async function handleKeydown(e: KeyboardEvent) { @@ -501,9 +487,9 @@ } -
+
0 ? "var(--size-4-2)" : 0}> - +
0 ? "var(--size-4-2)" : 0}> @@ -522,9 +508,13 @@
+
+ +
+