Add auto selection to context setting (#2018)

* Add setting for auto text selection inclusion and add back manual command

* Group chat save settings

* Update constants
This commit is contained in:
Logan Yang 2025-11-09 19:56:07 -08:00 committed by GitHub
parent 72f34463c2
commit ff278c4d3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 145 additions and 60 deletions

View file

@ -13,6 +13,15 @@ export function registerContextMenu(menu: Menu) {
const submenu = (item as any).submenu;
if (!submenu) return;
// Add the main selection command
submenu.addItem((subItem: any) => {
subItem.setTitle("Add selection to chat context").onClick(() => {
(app as any).commands.executeCommandById(
`copilot:${COMMAND_IDS.ADD_SELECTION_TO_CHAT_CONTEXT}`
);
});
});
submenu.addItem((subItem: any) => {
subItem.setTitle("Trigger quick command").onClick(() => {
(app as any).commands.executeCommandById(`copilot:${COMMAND_IDS.TRIGGER_QUICK_COMMAND}`);

View file

@ -19,8 +19,10 @@ import { checkIsPlusUser } from "@/plusUtils";
import CopilotPlugin from "@/main";
import { getAllQAMarkdownContent } from "@/search/searchUtils";
import { CopilotSettings, getSettings, updateSetting } from "@/settings/model";
import { SelectedTextContext } from "@/types/message";
import { ensureFolderExists, isSourceModeOn } from "@/utils";
import { Editor, MarkdownView, Notice, TFile } from "obsidian";
import { v4 as uuidv4 } from "uuid";
import { COMMAND_IDS, COMMAND_NAMES, CommandId } from "../constants";
/**
@ -363,6 +365,48 @@ export function registerCommands(
new Notice(`Copilot autocomplete ${newValue ? "enabled" : "disabled"}`);
});
// Add selection to chat context command (manual)
addEditorCommand(plugin, COMMAND_IDS.ADD_SELECTION_TO_CHAT_CONTEXT, async (editor: Editor) => {
const selectedText = editor.getSelection();
if (!selectedText) {
new Notice("No text selected");
return;
}
const activeFile = plugin.app.workspace.getActiveFile();
if (!activeFile) {
new Notice("No active file");
return;
}
// Get selection range to determine line numbers
const selectionRange = editor.listSelections()[0];
if (!selectionRange) {
new Notice("Could not determine selection range");
return;
}
const startLine = selectionRange.anchor.line + 1; // Convert to 1-based line numbers
const endLine = selectionRange.head.line + 1;
// Create selected text context
const selectedTextContext: SelectedTextContext = {
id: uuidv4(),
content: selectedText,
noteTitle: activeFile.basename,
notePath: activeFile.path,
startLine: Math.min(startLine, endLine),
endLine: Math.max(startLine, endLine),
};
// Replace selected text contexts (consistent with auto mode behavior)
const { setSelectedTextContexts } = await import("@/aiParams");
setSelectedTextContexts([selectedTextContext]);
// Open chat window to show the context was added
plugin.activateView();
});
// Add command to create a new custom command
addCommand(plugin, COMMAND_IDS.ADD_CUSTOM_COMMAND, async () => {
const commands = getCachedCustomCommands();

View file

@ -31,7 +31,8 @@ export const DEFAULT_SYSTEM_PROMPT = `You are Obsidian Copilot, a helpful assist
11. Always respond in the language of the user's query.
12. Do NOT mention the additional context provided such as getCurrentTime and getTimeRangeMs if it's irrelevant to the user message.
13. If the user mentions "tags", it most likely means tags in Obsidian note properties.
14. YouTube URLs: If the user provides YouTube URLs in their message, transcriptions will be automatically fetched and provided to you. You don't need to do anything special - just use the transcription content if available.`;
14. YouTube URLs: If the user provides YouTube URLs in their message, transcriptions will be automatically fetched and provided to you. You don't need to do anything special - just use the transcription content if available.
15. For markdown lists, always use '- ' (hyphen followed by exactly one space) for bullet points, with no leading spaces before the hyphen. Never use '*' (asterisk) for bullets.`;
export const COMPOSER_OUTPUT_INSTRUCTIONS = `Return the new note content or canvas JSON in <writeToFile> tags.
@ -167,21 +168,21 @@ export enum ChatModels {
// Model Providers
export enum ChatModelProviders {
OPENROUTERAI = "openrouterai",
OPENAI = "openai",
OPENAI_FORMAT = "3rd party (openai-format)",
AZURE_OPENAI = "azure openai",
ANTHROPIC = "anthropic",
COHEREAI = "cohereai",
GOOGLE = "google",
XAI = "xai",
OPENROUTERAI = "openrouterai",
AMAZON_BEDROCK = "amazon-bedrock",
AZURE_OPENAI = "azure openai",
GROQ = "groq",
OLLAMA = "ollama",
LM_STUDIO = "lm-studio",
COPILOT_PLUS = "copilot-plus",
MISTRAL = "mistralai",
DEEPSEEK = "deepseek",
AMAZON_BEDROCK = "amazon-bedrock",
COHEREAI = "cohereai",
SILICONFLOW = "siliconflow",
}
@ -682,6 +683,7 @@ export const COMMAND_IDS = {
SEARCH_ORAMA_DB: "copilot-search-orama-db",
TOGGLE_COPILOT_CHAT_WINDOW: "chat-toggle-window",
TOGGLE_AUTOCOMPLETE: "toggle-autocomplete",
ADD_SELECTION_TO_CHAT_CONTEXT: "add-selection-to-chat-context",
ADD_CUSTOM_COMMAND: "add-custom-command",
APPLY_CUSTOM_COMMAND: "apply-custom-command",
OPEN_LOG_FILE: "open-log-file",
@ -709,6 +711,7 @@ export const COMMAND_NAMES: Record<CommandId, string> = {
[COMMAND_IDS.SEARCH_ORAMA_DB]: "Search semantic index (debug)",
[COMMAND_IDS.TOGGLE_COPILOT_CHAT_WINDOW]: "Toggle Copilot Chat Window",
[COMMAND_IDS.TOGGLE_AUTOCOMPLETE]: "Toggle autocomplete",
[COMMAND_IDS.ADD_SELECTION_TO_CHAT_CONTEXT]: "Add selection to chat context",
[COMMAND_IDS.ADD_CUSTOM_COMMAND]: "Add new custom command",
[COMMAND_IDS.APPLY_CUSTOM_COMMAND]: "Apply custom command",
[COMMAND_IDS.OPEN_LOG_FILE]: "Create log file",
@ -829,6 +832,7 @@ export const DEFAULT_SETTINGS: CopilotSettings = {
enableSavedMemory: true,
quickCommandModelKey: undefined,
quickCommandIncludeNoteContext: true,
autoIncludeTextSelection: true,
};
export const EVENT_NAMES = {

View file

@ -315,6 +315,12 @@ export default class CopilotPlugin extends Plugin {
* Only processes selections from markdown editors
*/
handleSelectionChange() {
// Check if auto-inclusion is enabled
const settings = getSettings();
if (!settings.autoIncludeTextSelection) {
return;
}
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!activeView || !activeView.editor) {
return;

View file

@ -155,6 +155,8 @@ export interface CopilotSettings {
quickCommandModelKey: string | undefined;
/** Last checkbox state for including note context in quick command */
quickCommandIncludeNoteContext: boolean;
/** Automatically add text selections to chat context */
autoIncludeTextSelection: boolean;
}
export const settingsStore = createStore();
@ -418,6 +420,11 @@ export function sanitizeSettings(settings: CopilotSettings): CopilotSettings {
sanitizedSettings.quickCommandModelKey = DEFAULT_SETTINGS.quickCommandModelKey;
}
// Ensure autoIncludeTextSelection has a default value
if (typeof sanitizedSettings.autoIncludeTextSelection !== "boolean") {
sanitizedSettings.autoIncludeTextSelection = DEFAULT_SETTINGS.autoIncludeTextSelection;
}
// Ensure defaultSendShortcut has a valid value
if (!Object.values(SEND_SHORTCUT).includes(sanitizedSettings.defaultSendShortcut)) {
sanitizedSettings.defaultSendShortcut = DEFAULT_SETTINGS.defaultSendShortcut;

View file

@ -1,9 +1,9 @@
import { ChainType } from "@/chainFactory";
import { Button } from "@/components/ui/button";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { Input } from "@/components/ui/input";
import { getModelDisplayWithIcons } from "@/components/ui/model-display";
import { SettingItem } from "@/components/ui/setting-item";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { DEFAULT_OPEN_AREA, PLUS_UTM_MEDIUMS, SEND_SHORTCUT } from "@/constants";
import { cn } from "@/lib/utils";
import { createPlusPageUrl } from "@/plusUtils";
@ -265,6 +265,74 @@ export const BasicSettings: React.FC = () => {
]}
/>
<SettingItem
type="switch"
title="Include Current Note in Context Menu"
description="Automatically include the current note in the chat context menu by default when sending messages to the AI."
checked={settings.includeActiveNoteAsContext}
onCheckedChange={(checked) => {
updateSetting("includeActiveNoteAsContext", checked);
}}
/>
<SettingItem
type="switch"
title="Auto-Add Text Selection to Context"
description="Automatically add selected text to chat context when you make a text selection in markdown notes. Disable to use manual command instead."
checked={settings.autoIncludeTextSelection}
onCheckedChange={(checked) => {
updateSetting("autoIncludeTextSelection", checked);
}}
/>
<SettingItem
type="switch"
title="Images in Markdown"
description="Pass embedded images in markdown to the AI along with the text. Only works with multimodal models."
checked={settings.passMarkdownImages}
onCheckedChange={(checked) => {
updateSetting("passMarkdownImages", checked);
}}
/>
<SettingItem
type="switch"
title="Suggested Prompts"
description="Show suggested prompts in the chat view"
checked={settings.showSuggestedPrompts}
onCheckedChange={(checked) => updateSetting("showSuggestedPrompts", checked)}
/>
<SettingItem
type="switch"
title="Relevant Notes"
description="Show relevant notes in the chat view"
checked={settings.showRelevantNotes}
onCheckedChange={(checked) => updateSetting("showRelevantNotes", checked)}
/>
</div>
</section>
{/* Saving Conversations Section */}
<section>
<div className="tw-mb-3 tw-text-xl tw-font-bold">Saving Conversations</div>
<div className="tw-space-y-4">
<SettingItem
type="switch"
title="Autosave Chat"
description="Automatically saves the chat after every user message and AI response."
checked={settings.autosaveChat}
onCheckedChange={(checked) => updateSetting("autosaveChat", checked)}
/>
<SettingItem
type="switch"
title="Generate AI Chat Title on Save"
description="When enabled, uses an AI model to generate a concise title for saved chat notes. When disabled, uses the first 10 words of the first user message."
checked={settings.generateAIChatTitleOnSave}
onCheckedChange={(checked) => updateSetting("generateAIChatTitleOnSave", checked)}
/>
<SettingItem
type="text"
title="Default Conversation Folder Name"
@ -352,59 +420,6 @@ export const BasicSettings: React.FC = () => {
</Button>
</div>
</SettingItem>
{/* Feature Toggle Group */}
<SettingItem
type="switch"
title="Autosave Chat"
description="Automatically saves the chat after every user message and AI response."
checked={settings.autosaveChat}
onCheckedChange={(checked) => updateSetting("autosaveChat", checked)}
/>
<SettingItem
type="switch"
title="Generate AI Chat Title on Save"
description="When enabled, uses an AI model to generate a concise title for saved chat notes. When disabled, uses the first 10 words of the first user message."
checked={settings.generateAIChatTitleOnSave}
onCheckedChange={(checked) => updateSetting("generateAIChatTitleOnSave", checked)}
/>
<SettingItem
type="switch"
title="Include Current Note in Context Menu"
description="Automatically include the current note in the chat context menu by default when sending messages to the AI."
checked={settings.includeActiveNoteAsContext}
onCheckedChange={(checked) => {
updateSetting("includeActiveNoteAsContext", checked);
}}
/>
<SettingItem
type="switch"
title="Images in Markdown"
description="Pass embedded images in markdown to the AI along with the text. Only works with multimodal models."
checked={settings.passMarkdownImages}
onCheckedChange={(checked) => {
updateSetting("passMarkdownImages", checked);
}}
/>
<SettingItem
type="switch"
title="Suggested Prompts"
description="Show suggested prompts in the chat view"
checked={settings.showSuggestedPrompts}
onCheckedChange={(checked) => updateSetting("showSuggestedPrompts", checked)}
/>
<SettingItem
type="switch"
title="Relevant Notes"
description="Show relevant notes in the chat view"
checked={settings.showRelevantNotes}
onCheckedChange={(checked) => updateSetting("showRelevantNotes", checked)}
/>
</div>
</section>
</div>

View file

@ -69,7 +69,7 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
const settings = getSettings();
const defaultProvider = isEmbeddingModel
? EmbeddingModelProviders.OPENAI
: ChatModelProviders.OPENAI;
: ChatModelProviders.OPENROUTERAI;
const [dialogElement, setDialogElement] = useState<HTMLDivElement | null>(null);
const [isOpen, setIsOpen] = useState(false);