diff --git a/src/components/chat-components/RelevantNotes.tsx b/src/components/chat-components/RelevantNotes.tsx index 70b5466c..3c4eaa88 100644 --- a/src/components/chat-components/RelevantNotes.tsx +++ b/src/components/chat-components/RelevantNotes.tsx @@ -10,7 +10,7 @@ import { useNoteDrag } from "@/hooks/useNoteDrag"; import { cn } from "@/lib/utils"; import { logWarn } from "@/logger"; import { SemanticSearchToggleModal } from "@/components/modals/SemanticSearchToggleModal"; -import type { CopilotSettings } from "@/settings/model"; +import { shouldUseMiyo } from "@/miyo/miyoUtils"; import { findRelevantNotes, getSimilarityCategory, @@ -30,30 +30,6 @@ import { import { Notice, TFile } from "obsidian"; import React, { memo, useCallback, useEffect, useState } from "react"; -const SELF_HOST_GRACE_PERIOD_MS = 15 * 24 * 60 * 60 * 1000; - -/** - * Return true when Miyo-backed semantic index is expected to be active. - * - * @param settings - Current Copilot settings object. - * @returns True when Miyo mode and self-host validation are active. - */ -function shouldUseMiyoIndex(settings: CopilotSettings): boolean { - if (!settings.enableMiyo || !settings.enableSemanticSearchV3) { - return false; - } - - if (settings.selfHostModeValidatedAt == null) { - return false; - } - - if ((settings.selfHostValidationCount ?? 0) >= 3) { - return true; - } - - return Date.now() - settings.selfHostModeValidatedAt < SELF_HOST_GRACE_PERIOD_MS; -} - function useRelevantNotes(refresher: number) { const [relevantNotes, setRelevantNotes] = useState([]); const [signalTick, setSignalTick] = useState(0); @@ -93,9 +69,9 @@ function useHasIndex(notePath: string, refresher: number) { const VectorStoreManager = (await import("@/search/vectorStoreManager")).default; const { getSettings } = await import("@/settings/model"); const settings = getSettings(); - const shouldUseMiyo = shouldUseMiyoIndex(settings); + const useMiyo = shouldUseMiyo(settings); - if (shouldUseMiyo) { + if (useMiyo) { const isEmpty = await VectorStoreManager.getInstance().isIndexEmpty(); setHasIndex(!isEmpty); return; diff --git a/src/miyo/miyoUtils.ts b/src/miyo/miyoUtils.ts index 11c493af..dce29aad 100644 --- a/src/miyo/miyoUtils.ts +++ b/src/miyo/miyoUtils.ts @@ -1,5 +1,6 @@ +import { isSelfHostAccessValid } from "@/plusUtils"; import { CopilotSettings, getSettings } from "@/settings/model"; -import { App, FileSystemAdapter } from "obsidian"; +import { App, FileSystemAdapter, Platform } from "obsidian"; /** * Return the user-configured Miyo server URL, or "" to fall back to local service discovery. @@ -12,6 +13,28 @@ export function getMiyoCustomUrl(settings: CopilotSettings): string { return (settings.miyoServerUrl || "").trim(); } +/** + * Single source of truth for whether Miyo should be used. + * + * Returns false when: + * - `enableMiyo` is off, or + * - self-host access is invalid, or + * - running on mobile without a remote server URL (local service discovery + * is unavailable on mobile, so Miyo can only work via an explicit URL). + * + * Note: `enableSemanticSearchV3` need not be checked — the UI enforces that + * enabling Miyo also enables semantic search, and disabling semantic search + * also disables Miyo. + * + * @param settings - Current Copilot settings. + */ +export function shouldUseMiyo(settings: CopilotSettings): boolean { + if (!settings.enableMiyo || !isSelfHostAccessValid()) { + return false; + } + return !Platform.isMobile || !!getMiyoCustomUrl(settings); +} + /** * Resolve the vault identifier sent to Miyo. * diff --git a/src/search/RetrieverFactory.ts b/src/search/RetrieverFactory.ts index ba57247c..b2c39d33 100644 --- a/src/search/RetrieverFactory.ts +++ b/src/search/RetrieverFactory.ts @@ -1,5 +1,6 @@ import { logInfo, logWarn } from "@/logger"; -import { isSelfHostAccessValid, isSelfHostModeValid } from "@/plusUtils"; +import { isSelfHostModeValid } from "@/plusUtils"; +import { shouldUseMiyo } from "@/miyo/miyoUtils"; import { getSettings, CopilotSettings } from "@/settings/model"; import { App } from "obsidian"; import { SelfHostRetriever, VectorSearchBackend } from "./selfHostRetriever"; @@ -323,7 +324,7 @@ export class RetrieverFactory { * @returns True when Miyo should be used for semantic retrieval. */ private static shouldUseMiyo(settings: CopilotSettings): boolean { - return settings.enableMiyo && settings.enableSemanticSearchV3 && isSelfHostAccessValid(); + return shouldUseMiyo(settings); } /** diff --git a/src/search/findRelevantNotes.test.ts b/src/search/findRelevantNotes.test.ts index 4346b079..5187b9da 100644 --- a/src/search/findRelevantNotes.test.ts +++ b/src/search/findRelevantNotes.test.ts @@ -2,9 +2,8 @@ import { TFile } from "obsidian"; import { getBacklinkedNotes, getLinkedNotes } from "@/noteUtils"; import { findRelevantNotes } from "@/search/findRelevantNotes"; import { MiyoClient } from "@/miyo/MiyoClient"; -import { getMiyoVault } from "@/miyo/miyoUtils"; +import { getMiyoVault, shouldUseMiyo } from "@/miyo/miyoUtils"; import { getSettings } from "@/settings/model"; -import { isSelfHostAccessValid } from "@/plusUtils"; import VectorStoreManager from "@/search/vectorStoreManager"; jest.mock("@/noteUtils", () => ({ @@ -50,10 +49,7 @@ jest.mock("@/miyo/MiyoClient", () => ({ jest.mock("@/miyo/miyoUtils", () => ({ getMiyoVault: jest.fn(), getMiyoCustomUrl: jest.fn().mockReturnValue(""), -})); - -jest.mock("@/plusUtils", () => ({ - isSelfHostAccessValid: jest.fn(), + shouldUseMiyo: jest.fn(), })); jest.mock("@/logger", () => ({ @@ -75,9 +71,7 @@ function createMarkdownFile(path: string): TFile { describe("findRelevantNotes", () => { const mockedGetSettings = getSettings as jest.MockedFunction; - const mockedIsSelfHostAccessValid = isSelfHostAccessValid as jest.MockedFunction< - typeof isSelfHostAccessValid - >; + const mockedShouldUseMiyo = shouldUseMiyo as jest.MockedFunction; const mockedGetLinkedNotes = getLinkedNotes as jest.MockedFunction; const mockedGetBacklinkedNotes = getBacklinkedNotes as jest.MockedFunction< typeof getBacklinkedNotes @@ -93,7 +87,7 @@ describe("findRelevantNotes", () => { beforeEach(() => { jest.clearAllMocks(); - mockedIsSelfHostAccessValid.mockReturnValue(false); + mockedShouldUseMiyo.mockReturnValue(false); mockedGetSettings.mockReturnValue({ debug: false, miyoServerUrl: "", @@ -180,7 +174,7 @@ describe("findRelevantNotes", () => { }); it("uses Miyo when shouldUseMiyoForRelevantNotes is true (enableMiyo=true and valid self-host)", async () => { - mockedIsSelfHostAccessValid.mockReturnValue(true); + mockedShouldUseMiyo.mockReturnValue(true); mockedGetSettings.mockReturnValue({ debug: false, miyoServerUrl: "http://127.0.0.1:8742", @@ -256,7 +250,7 @@ describe("findRelevantNotes", () => { }); it("falls back to link-only relevance when Miyo related-note search fails", async () => { - mockedIsSelfHostAccessValid.mockReturnValue(true); + mockedShouldUseMiyo.mockReturnValue(true); mockedGetSettings.mockReturnValue({ debug: false, miyoServerUrl: "http://127.0.0.1:8742", diff --git a/src/search/findRelevantNotes.ts b/src/search/findRelevantNotes.ts index 77fec2da..02f4d8e4 100644 --- a/src/search/findRelevantNotes.ts +++ b/src/search/findRelevantNotes.ts @@ -1,12 +1,11 @@ import { logInfo, logWarn } from "@/logger"; import { MiyoClient } from "@/miyo/MiyoClient"; -import { getMiyoCustomUrl, getMiyoVault } from "@/miyo/miyoUtils"; +import { getMiyoCustomUrl, getMiyoVault, shouldUseMiyo } from "@/miyo/miyoUtils"; import { getBacklinkedNotes, getLinkedNotes } from "@/noteUtils"; import { DBOperations } from "@/search/dbOperations"; import type { SemanticIndexDocument } from "@/search/indexBackend/SemanticIndexBackend"; import VectorStoreManager from "@/search/vectorStoreManager"; import { getSettings } from "@/settings/model"; -import { isSelfHostAccessValid } from "@/plusUtils"; import { InternalTypedDocument, Orama, Result } from "@orama/orama"; import { TFile } from "obsidian"; @@ -20,8 +19,7 @@ const LINKS_WEIGHT = 0.3; * @returns True when Miyo mode and self-host access validation are active. */ function shouldUseMiyoForRelevantNotes(): boolean { - const settings = getSettings(); - return settings.enableMiyo && settings.enableSemanticSearchV3 && isSelfHostAccessValid(); + return shouldUseMiyo(getSettings()); } /** diff --git a/src/search/vectorStoreManager.ts b/src/search/vectorStoreManager.ts index 97a344aa..5b0d7772 100644 --- a/src/search/vectorStoreManager.ts +++ b/src/search/vectorStoreManager.ts @@ -4,7 +4,7 @@ import { updateIndexingProgressState } from "@/aiParams"; import { CustomError } from "@/error"; import { logInfo, logWarn } from "@/logger"; import EmbeddingsManager from "@/LLMProviders/embeddingManager"; -import { isSelfHostAccessValid } from "@/plusUtils"; +import { shouldUseMiyo } from "@/miyo/miyoUtils"; import { CopilotSettings, getSettings, subscribeToSettingsChange } from "@/settings/model"; import { Orama } from "@orama/orama"; import { Notice, Platform, TFile } from "obsidian"; @@ -211,7 +211,7 @@ export default class VectorStoreManager { * @returns True when Miyo should be the active backend. */ private shouldUseMiyo(settings: CopilotSettings): boolean { - return settings.enableMiyo && settings.enableSemanticSearchV3 && isSelfHostAccessValid(); + return shouldUseMiyo(settings); } /**