diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index eba3080b..13e96ac9 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -569,7 +569,7 @@ const ChatInternal: React.FC void reloadCurrentProject()} + onSelect={() => void reloadCurrentProject(app)} > Reload Current Project void forceRebuildCurrentProjectContext()} + onSelect={() => void forceRebuildCurrentProjectContext(app)} > Force Rebuild Context diff --git a/src/components/chat-components/ChatSettingsPopover.tsx b/src/components/chat-components/ChatSettingsPopover.tsx index 39b85722..5a2bf48e 100644 --- a/src/components/chat-components/ChatSettingsPopover.tsx +++ b/src/components/chat-components/ChatSettingsPopover.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useApp } from "@/context"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Label } from "@/components/ui/label"; @@ -33,6 +34,7 @@ const RESETTABLE_MODEL_PARAMS: (keyof CustomModel)[] = [ ]; export function ChatSettingsPopover() { + const app = useApp(); const settings = getSettings(); const modelKey = getModelKey(); diff --git a/src/components/chat-components/ProjectList.tsx b/src/components/chat-components/ProjectList.tsx index 7ebc78b1..b24c5497 100644 --- a/src/components/chat-components/ProjectList.tsx +++ b/src/components/chat-components/ProjectList.tsx @@ -3,6 +3,7 @@ import type CopilotPlugin from "@/main"; import { AddProjectModal } from "@/components/modals/project/AddProjectModal"; import { ConfirmModal } from "@/components/modals/ConfirmModal"; import { Button } from "@/components/ui/button"; +import { useApp } from "@/context"; import { useChatInput } from "@/context/ChatInputContext"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { @@ -74,6 +75,7 @@ function ProjectItem({ onEdit: (project: ProjectConfig) => void; onDelete: (project: ProjectConfig) => void; }) { + const app = useApp(); return (
void; onNavigateToNote: (openInNewLeaf: boolean) => void; }) { + const app = useApp(); const [isOpen, setIsOpen] = useState(false); const [fileContent, setFileContent] = useState(null); const handleDragStart = useNoteDrag(); @@ -128,7 +130,7 @@ function RelevantNote({ // Take first 1000 characters as preview setFileContent(cleanContent.slice(0, 1000) + (cleanContent.length > 1000 ? "..." : "")); } - }, [fileContent, note.note.path]); + }, [app, fileContent, note.note.path]); useEffect(() => { if (isOpen) { @@ -272,6 +274,7 @@ function RelevantNotePopover({ export const RelevantNotes = memo( ({ className, defaultOpen = false }: { className?: string; defaultOpen?: boolean }) => { + const app = useApp(); const [refresher, setRefresher] = useState(0); const [isOpen, setIsOpen] = useState(defaultOpen); const relevantNotes = useRelevantNotes(refresher); diff --git a/src/components/chat-components/hooks/useActiveWebTabState.ts b/src/components/chat-components/hooks/useActiveWebTabState.ts index c6cb11f1..27270a1c 100644 --- a/src/components/chat-components/hooks/useActiveWebTabState.ts +++ b/src/components/chat-components/hooks/useActiveWebTabState.ts @@ -1,6 +1,7 @@ import { useEffect, useState } from "react"; import { Platform } from "obsidian"; +import { useApp } from "@/context"; import { getWebViewerService } from "@/services/webViewerService/webViewerServiceSingleton"; import type { ActiveWebTabStateSnapshot } from "@/services/webViewerService/webViewerServiceTypes"; @@ -18,6 +19,7 @@ const EMPTY_ACTIVE_WEB_TAB_STATE: ActiveWebTabStateSnapshot = { * - activeOrLastWebTab: For pill display (active or last active tab) */ export function useActiveWebTabState(): ActiveWebTabStateSnapshot { + const app = useApp(); const [state, setState] = useState(() => { if (!Platform.isDesktopApp) { return EMPTY_ACTIVE_WEB_TAB_STATE; @@ -49,7 +51,7 @@ export function useActiveWebTabState(): ActiveWebTabStateSnapshot { return () => { unsubscribe?.(); }; - }, []); + }, [app]); return state; } diff --git a/src/components/chat-components/hooks/useOpenWebTabs.ts b/src/components/chat-components/hooks/useOpenWebTabs.ts index 42947c67..e8b90697 100644 --- a/src/components/chat-components/hooks/useOpenWebTabs.ts +++ b/src/components/chat-components/hooks/useOpenWebTabs.ts @@ -1,5 +1,6 @@ import { useEffect, useRef, useState } from "react"; -import { Platform } from "obsidian"; +import { App, Platform } from "obsidian"; +import { useApp } from "@/context"; import { getWebViewerService } from "@/services/webViewerService/webViewerServiceSingleton"; import type { WebTabContext } from "@/types/message"; @@ -26,7 +27,7 @@ export interface UseOpenWebTabsOptions { * Sorting ensures stable output ordering for equality checks. * Includes tabs with title but no URL (not yet loaded) with isLoaded=false. */ -function getOpenWebTabSnapshot(): WebTabContext[] { +function getOpenWebTabSnapshot(app: App): WebTabContext[] { try { const service = getWebViewerService(app); const leaves = service.getLeaves(); @@ -115,6 +116,7 @@ function areWebTabSnapshotsEqual(a: WebTabContext[], b: WebTabContext[]): boolea * @param options.enabled - Whether to enable polling (default: true) */ export function useOpenWebTabs(options: UseOpenWebTabsOptions = {}): WebTabContext[] { + const app = useApp(); const { enabled = true } = options; const [tabs, setTabs] = useState([]); const rafIdRef = useRef(null); @@ -137,7 +139,7 @@ export function useOpenWebTabs(options: UseOpenWebTabsOptions = {}): WebTabConte /** Refresh state from the current Web Viewer tab snapshot. */ const refresh = () => { if (disposed) return; - const next = getOpenWebTabSnapshot(); + const next = getOpenWebTabSnapshot(app); setTabs((prev) => (areWebTabSnapshotsEqual(prev, next) ? prev : next)); }; @@ -176,7 +178,7 @@ export function useOpenWebTabs(options: UseOpenWebTabsOptions = {}): WebTabConte app.workspace.offref(activeLeafRef); unsubscribeWebviewLoad(); }; - }, [enabled]); + }, [app, enabled]); return tabs; } diff --git a/src/components/modals/PatternMatchingModal.tsx b/src/components/modals/PatternMatchingModal.tsx index 5c9eef2e..19d472d4 100644 --- a/src/components/modals/PatternMatchingModal.tsx +++ b/src/components/modals/PatternMatchingModal.tsx @@ -1,5 +1,6 @@ import { App, Modal } from "obsidian"; import { Button } from "@/components/ui/button"; +import { AppContext, useApp } from "@/context"; import React, { useState } from "react"; import { createRoot, Root } from "react-dom/client"; import { @@ -62,6 +63,7 @@ function PatternMatchingModalContent({ onUpdate: (value: string) => void; container: HTMLElement; }) { + const app = useApp(); const [value, setValue] = useState(initialValue); const patterns = getDecodedPatterns(value); const { tagPatterns, extensionPatterns, folderPatterns, notePatterns } = @@ -280,11 +282,13 @@ export class PatternMatchingModal extends Modal { }; this.root.render( - + + + ); } diff --git a/src/components/modals/ProjectPatternMatchingModal.tsx b/src/components/modals/ProjectPatternMatchingModal.tsx index dd224b9e..4473a538 100644 --- a/src/components/modals/ProjectPatternMatchingModal.tsx +++ b/src/components/modals/ProjectPatternMatchingModal.tsx @@ -18,6 +18,7 @@ import { File, FileText, Folder, Tag, Wrench, X } from "lucide-react"; import { App, Modal, TFile } from "obsidian"; import React, { useState } from "react"; import { createRoot, Root } from "react-dom/client"; +import { AppContext, useApp } from "@/context"; import { CustomPatternInputModal } from "./CustomPatternInputModal"; import { ExtensionInputModal } from "./ExtensionInputModal"; import { FolderSearchModal } from "./FolderSearchModal"; @@ -62,6 +63,7 @@ function ProjectPatternMatchingModalContent({ onUpdate: (value: string) => void; container: HTMLElement; }) { + const app = useApp(); const [value, setValue] = useState(initialValue); const patterns = getDecodedPatterns(value); const { tagPatterns, extensionPatterns, folderPatterns, notePatterns } = @@ -279,11 +281,13 @@ export class ProjectPatternMatchingModal extends Modal { }; this.root.render( - + + + ); } diff --git a/src/components/modals/TagSearchModal.tsx b/src/components/modals/TagSearchModal.tsx index 4f233f4a..8203f4e4 100644 --- a/src/components/modals/TagSearchModal.tsx +++ b/src/components/modals/TagSearchModal.tsx @@ -11,7 +11,7 @@ export class TagSearchModal extends FuzzySuggestModal { getItems(): string[] { // Get all Markdown files in the vault. - const files = app.vault.getMarkdownFiles(); + const files = this.app.vault.getMarkdownFiles(); const tagSet = new Set(); // Loop through each file and extract tags. diff --git a/src/components/modals/project/AddProjectModal.tsx b/src/components/modals/project/AddProjectModal.tsx index 1eba113b..0d09a276 100644 --- a/src/components/modals/project/AddProjectModal.tsx +++ b/src/components/modals/project/AddProjectModal.tsx @@ -1,4 +1,5 @@ import { ProjectConfig } from "@/aiParams"; +import { AppContext, useApp } from "@/context"; import { ContextManageModal } from "@/components/modals/project/context-manage-modal"; import { openCachedItemPreview } from "@/utils/cacheFileOpener"; import type { ProcessingItem } from "@/components/project/processingAdapter"; @@ -39,6 +40,7 @@ function AddProjectModalContent({ onCancel, plugin, }: AddProjectModalContentProps) { + const app = useApp(); const settings = useSettingsValue(); const [isSubmitting, setIsSubmitting] = useState(false); const [touched, setTouched] = useState({ @@ -480,12 +482,14 @@ export class AddProjectModal extends Modal { }; this.root.render( - + + + ); } diff --git a/src/components/project/progress-card.tsx b/src/components/project/progress-card.tsx index 0b328bd7..b24c7990 100644 --- a/src/components/project/progress-card.tsx +++ b/src/components/project/progress-card.tsx @@ -23,6 +23,7 @@ import type { ProcessingItem } from "@/components/project/processingAdapter"; import { ProjectFileManager } from "@/projects/ProjectFileManager"; import { splitUrlsStringToArray } from "@/projects/projectUtils"; import CopilotPlugin from "@/main"; +import { useApp } from "@/context"; import { logError } from "@/logger"; interface ProgressCardProps { @@ -32,6 +33,7 @@ interface ProgressCardProps { } export default function ProgressCard({ plugin, setHiddenCard, onEditContext }: ProgressCardProps) { + const app = useApp(); const [isExpanded, setIsExpanded] = useState(false); const [contextLoadState] = useProjectContextLoad(); const totalFiles = contextLoadState.total; diff --git a/src/components/project/useProjectProcessingData.ts b/src/components/project/useProjectProcessingData.ts index 80b6b338..1375f14b 100644 --- a/src/components/project/useProjectProcessingData.ts +++ b/src/components/project/useProjectProcessingData.ts @@ -12,6 +12,7 @@ import { buildProcessingItems, type ProcessingAdapterResult, } from "@/components/project/processingAdapter"; +import { useApp } from "@/context"; import { getMatchingPatterns, shouldIndexFile } from "@/search/searchUtils"; import { FileParserManager } from "@/tools/FileParserManager"; import { TFile } from "obsidian"; @@ -50,6 +51,7 @@ export interface UseProjectProcessingDataResult { export function useProjectProcessingData( params: UseProjectProcessingDataParams ): UseProjectProcessingDataResult { + const app = useApp(); const { cacheProject, contextSource } = params; // Reason: Load the project's persistent context cache. @@ -105,7 +107,7 @@ export function useProjectProcessingData( setProjectFiles( app.vault.getFiles().filter((file) => shouldIndexFile(file, inclusions, exclusions, true)) ); - }, [cacheProject, effectiveInclusions, effectiveExclusions]); + }, [app, cacheProject, effectiveInclusions, effectiveExclusions]); // Reason: Static method — the set of supported extensions never changes at runtime. const supportedExtensions = useMemo(() => FileParserManager.getProjectSupportedExtensions(), []); diff --git a/src/context.ts b/src/context.ts index e47bc913..26dccbf5 100644 --- a/src/context.ts +++ b/src/context.ts @@ -6,3 +6,18 @@ export const AppContext = React.createContext(undefined); // Event target context export const EventTargetContext = React.createContext(undefined); + +/** + * Returns the Obsidian {@link App} provided by the nearest {@link AppContext}. + * + * Use this inside React components and hooks instead of touching the global + * `app` object. Throws if no provider is in scope so callers fail loud rather + * than silently picking up the wrong window's app in popouts. + */ +export function useApp(): App { + const app = React.useContext(AppContext); + if (!app) { + throw new Error("useApp() called outside of an "); + } + return app; +} diff --git a/src/hooks/useActiveFile.ts b/src/hooks/useActiveFile.ts index db9bd1f1..8e2e8d3f 100644 --- a/src/hooks/useActiveFile.ts +++ b/src/hooks/useActiveFile.ts @@ -1,9 +1,10 @@ import { EVENT_NAMES } from "@/constants"; -import { EventTargetContext } from "@/context"; +import { EventTargetContext, useApp } from "@/context"; import { TFile } from "obsidian"; import { useContext, useEffect, useState } from "react"; export function useActiveFile() { + const app = useApp(); const [activeFile, setActiveFile] = useState(null); const eventTarget = useContext(EventTargetContext); @@ -16,7 +17,7 @@ export function useActiveFile() { return () => { eventTarget?.removeEventListener(EVENT_NAMES.ACTIVE_LEAF_CHANGE, handleActiveLeafChange); }; - }, [eventTarget]); + }, [app, eventTarget]); return activeFile; } diff --git a/src/hooks/useNoteDrag.ts b/src/hooks/useNoteDrag.ts index d110ec26..0568a79d 100644 --- a/src/hooks/useNoteDrag.ts +++ b/src/hooks/useNoteDrag.ts @@ -1,5 +1,6 @@ import { useCallback } from "react"; import { TFile } from "obsidian"; +import { useApp } from "@/context"; /** * Returns a drag-start handler that integrates with Obsidian's native dragManager API. @@ -16,24 +17,28 @@ import { TFile } from "obsidian"; * ``` */ export function useNoteDrag() { - const handleDragStart = useCallback((e: React.DragEvent, file: TFile): void => { - const dragManager = ( - app as unknown as { - dragManager?: { - dragLink: (event: DragEvent, linkText: string) => unknown; - onDragStart: (event: DragEvent, data: unknown) => void; - }; - } - ).dragManager; - if (!dragManager) return; + const app = useApp(); + const handleDragStart = useCallback( + (e: React.DragEvent, file: TFile): void => { + const dragManager = ( + app as unknown as { + dragManager?: { + dragLink: (event: DragEvent, linkText: string) => unknown; + onDragStart: (event: DragEvent, data: unknown) => void; + }; + } + ).dragManager; + if (!dragManager) return; - // Mark this drag as internal so the chat drop zone overlay doesn't appear - e.dataTransfer.setData("copilot/internal-drag", "true"); + // Mark this drag as internal so the chat drop zone overlay doesn't appear + e.dataTransfer.setData("copilot/internal-drag", "true"); - const linkText = app.metadataCache.fileToLinktext(file, ""); - const dragData = dragManager.dragLink(e.nativeEvent, linkText); - dragManager.onDragStart(e.nativeEvent, dragData); - }, []); + const linkText = app.metadataCache.fileToLinktext(file, ""); + const dragData = dragManager.dragLink(e.nativeEvent, linkText); + dragManager.onDragStart(e.nativeEvent, dragData); + }, + [app] + ); return handleDragStart; } diff --git a/src/settings/SettingsPage.tsx b/src/settings/SettingsPage.tsx index da900540..a5173515 100644 --- a/src/settings/SettingsPage.tsx +++ b/src/settings/SettingsPage.tsx @@ -8,6 +8,7 @@ import React from "react"; import { createRoot } from "react-dom/client"; import SettingsMainV2 from "@/settings/v2/SettingsMainV2"; import { ContainerContext } from "@/settings/v2/components/ContainerContext"; +import { AppContext } from "@/context"; export class CopilotSettingTab extends PluginSettingTab { plugin: CopilotPlugin; @@ -69,9 +70,11 @@ export class CopilotSettingTab extends PluginSettingTab { const sections = createRoot(div); sections.render( - - - + + + + + ); } } diff --git a/src/settings/v2/SettingsMainV2.tsx b/src/settings/v2/SettingsMainV2.tsx index e18303e0..d491341e 100644 --- a/src/settings/v2/SettingsMainV2.tsx +++ b/src/settings/v2/SettingsMainV2.tsx @@ -87,7 +87,7 @@ const SettingsMainV2: React.FC = ({ plugin }) => { const { latestVersion, hasUpdate } = useLatestVersion(plugin.manifest.version); const handleReset = () => { - const modal = new ResetSettingsConfirmModal(app, () => { + const modal = new ResetSettingsConfirmModal(plugin.app, () => { resetSettings(); // Increment the key to force re-render of all components setResetKey((prev) => prev + 1); diff --git a/src/settings/v2/components/AdvancedSettings.tsx b/src/settings/v2/components/AdvancedSettings.tsx index 070e150f..bf07f19d 100644 --- a/src/settings/v2/components/AdvancedSettings.tsx +++ b/src/settings/v2/components/AdvancedSettings.tsx @@ -1,6 +1,7 @@ import { Button } from "@/components/ui/button"; import { SettingItem } from "@/components/ui/setting-item"; import { ObsidianNativeSelect } from "@/components/ui/obsidian-native-select"; +import { useApp } from "@/context"; import { logFileManager } from "@/logFileManager"; import { flushRecordedPromptPayloadToLog } from "@/LLMProviders/chainRunner/utils/promptPayloadRecorder"; import { updateSetting, useSettingsValue } from "@/settings/model"; @@ -10,6 +11,7 @@ import { getPromptFilePath, SystemPromptAddModal } from "@/system-prompts"; import { useSystemPrompts } from "@/system-prompts/state"; export const AdvancedSettings: React.FC = () => { + const app = useApp(); const settings = useSettingsValue(); const prompts = useSystemPrompts(); diff --git a/src/settings/v2/components/BasicSettings.tsx b/src/settings/v2/components/BasicSettings.tsx index 7823363c..52499b52 100644 --- a/src/settings/v2/components/BasicSettings.tsx +++ b/src/settings/v2/components/BasicSettings.tsx @@ -5,6 +5,7 @@ import { Input } from "@/components/ui/input"; import { getModelDisplayWithIcons } from "@/components/ui/model-display"; import { SettingItem } from "@/components/ui/setting-item"; import { DEFAULT_OPEN_AREA, PLUS_UTM_MEDIUMS, SEND_SHORTCUT } from "@/constants"; +import { useApp } from "@/context"; import { useTab } from "@/contexts/TabContext"; import { cn } from "@/lib/utils"; import { createPlusPageUrl } from "@/plusUtils"; @@ -25,6 +26,7 @@ const ChainType2Label: Record = { }; export const BasicSettings: React.FC = () => { + const app = useApp(); const settings = useSettingsValue(); const { setSelectedTab } = useTab(); const [isChecking, setIsChecking] = useState(false); diff --git a/src/settings/v2/components/CommandSettings.tsx b/src/settings/v2/components/CommandSettings.tsx index 9ae2ac94..fefa95db 100644 --- a/src/settings/v2/components/CommandSettings.tsx +++ b/src/settings/v2/components/CommandSettings.tsx @@ -46,6 +46,7 @@ import { import { generateDefaultCommands } from "@/commands/migrator"; import { CustomCommand } from "@/commands/type"; import { ConfirmModal } from "@/components/modals/ConfirmModal"; +import { useApp } from "@/context"; import { SettingItem } from "@/components/ui/setting-item"; import { Notice } from "obsidian"; @@ -57,6 +58,7 @@ const MobileCommandCard: React.FC<{ onCopy: (command: CustomCommand) => void | Promise; containerRef: React.RefObject; }> = ({ command, commands, onUpdate, onRemove, onCopy, containerRef }) => { + const app = useApp(); const handleEdit = (cmd: CustomCommand) => { const modal = new CustomCommandSettingsModal(app, commands, cmd, async (updatedCommand) => { await onUpdate(updatedCommand, cmd.title); @@ -172,6 +174,7 @@ const SortableTableRow: React.FC<{ onRemove: (command: CustomCommand) => void | Promise; onCopy: (command: CustomCommand) => void | Promise; }> = ({ command, commands, onUpdate, onRemove, onCopy }) => { + const app = useApp(); const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: command.title, }); @@ -285,6 +288,7 @@ const SortableTableRow: React.FC<{ }; export const CommandSettings: React.FC = () => { + const app = useApp(); const rawCommands = useCustomCommands(); const commands = useMemo(() => { return sortCommandsByOrder([...rawCommands]); diff --git a/src/settings/v2/components/CopilotPlusSettings.tsx b/src/settings/v2/components/CopilotPlusSettings.tsx index e7c77e5b..d9654cd1 100644 --- a/src/settings/v2/components/CopilotPlusSettings.tsx +++ b/src/settings/v2/components/CopilotPlusSettings.tsx @@ -1,4 +1,5 @@ import { ConfirmModal } from "@/components/modals/ConfirmModal"; +import { useApp } from "@/context"; import { Badge } from "@/components/ui/badge"; import { HelpTooltip } from "@/components/ui/help-tooltip"; import { SettingItem } from "@/components/ui/setting-item"; @@ -12,6 +13,7 @@ import React, { useState } from "react"; import { ToolSettingsSection } from "./ToolSettingsSection"; export const CopilotPlusSettings: React.FC = () => { + const app = useApp(); const settings = useSettingsValue(); const [isValidatingSelfHost, setIsValidatingSelfHost] = useState(false); const isSelfHostEligible = useIsSelfHostEligible(); diff --git a/src/settings/v2/components/ModelSettings.tsx b/src/settings/v2/components/ModelSettings.tsx index 3405f8df..51dd6c1b 100644 --- a/src/settings/v2/components/ModelSettings.tsx +++ b/src/settings/v2/components/ModelSettings.tsx @@ -1,5 +1,6 @@ import { CustomModel } from "@/aiParams"; import { SettingItem } from "@/components/ui/setting-item"; +import { useApp } from "@/context"; import { BUILTIN_CHAT_MODELS, BUILTIN_EMBEDDING_MODELS } from "@/constants"; import EmbeddingManager from "@/LLMProviders/embeddingManager"; import ProjectManager from "@/LLMProviders/projectManager"; @@ -13,6 +14,7 @@ import { Notice } from "obsidian"; import React, { useState } from "react"; export const ModelSettings: React.FC = () => { + const app = useApp(); const settings = useSettingsValue(); const [showAddDialog, setShowAddDialog] = useState(false); const [showAddEmbeddingDialog, setShowAddEmbeddingDialog] = useState(false); diff --git a/src/settings/v2/components/PatternListEditor.tsx b/src/settings/v2/components/PatternListEditor.tsx index 383684ea..0d00f63a 100644 --- a/src/settings/v2/components/PatternListEditor.tsx +++ b/src/settings/v2/components/PatternListEditor.tsx @@ -16,6 +16,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { useApp } from "@/context"; import { cn } from "@/lib/utils"; import { categorizePatterns, @@ -50,6 +51,7 @@ export const PatternListEditor: React.FC = ({ maxCollapsedHeight = 84, maxExpandedHeight = 200, }) => { + const app = useApp(); const [isExpanded, setIsExpanded] = useState(false); const [isOverflowing, setIsOverflowing] = useState(false); const [contentHeight, setContentHeight] = useState(0); diff --git a/src/settings/v2/components/PlusSettings.tsx b/src/settings/v2/components/PlusSettings.tsx index dcbe75b8..32a9a7de 100644 --- a/src/settings/v2/components/PlusSettings.tsx +++ b/src/settings/v2/components/PlusSettings.tsx @@ -1,4 +1,5 @@ import { CopilotPlusWelcomeModal } from "@/components/modals/CopilotPlusWelcomeModal"; +import { useApp } from "@/context"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { PasswordInput } from "@/components/ui/password-input"; @@ -9,6 +10,7 @@ import { ExternalLink, Loader2 } from "lucide-react"; import React, { useEffect, useState } from "react"; export function PlusSettings() { + const app = useApp(); const settings = useSettingsValue(); const [error, setError] = useState(null); const [isChecking, setIsChecking] = useState(false); diff --git a/src/settings/v2/components/QASettings.tsx b/src/settings/v2/components/QASettings.tsx index cc5d898a..15cf8b9c 100644 --- a/src/settings/v2/components/QASettings.tsx +++ b/src/settings/v2/components/QASettings.tsx @@ -4,6 +4,7 @@ import { Notice } from "obsidian"; import { RebuildIndexConfirmModal } from "@/components/modals/RebuildIndexConfirmModal"; import { SemanticSearchToggleModal } from "@/components/modals/SemanticSearchToggleModal"; +import { useApp } from "@/context"; import { HelpTooltip } from "@/components/ui/help-tooltip"; import { getModelDisplayWithIcons } from "@/components/ui/model-display"; import { SettingItem } from "@/components/ui/setting-item"; @@ -12,6 +13,7 @@ import { getModelKeyFromModel, updateSetting, useSettingsValue } from "@/setting import { PatternListEditor } from "@/settings/v2/components/PatternListEditor"; export const QASettings: React.FC = () => { + const app = useApp(); const settings = useSettingsValue(); const isMiyoSearchActive = settings.enableMiyo; const visibleEmbeddingModels = settings.activeEmbeddingModels;