Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,7 @@ export const DEFAULT_SETTINGS: CopilotSettings = {
selfHostUrl: "",
selfHostApiKey: "",
miyoServerUrl: "",
miyoVaultName: "",
selfHostSearchProvider: "firecrawl",
firecrawlApiKey: "",
perplexityApiKey: "",
Expand Down
30 changes: 28 additions & 2 deletions src/miyo/miyoUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CopilotSettings } from "@/settings/model";
import { CopilotSettings, getSettings } from "@/settings/model";
import { App, FileSystemAdapter } from "obsidian";

/**
Expand All @@ -15,10 +15,36 @@ export function getMiyoCustomUrl(settings: CopilotSettings): string {
/**
* Resolve the Miyo source id for the current vault.
*
* Uses the user-configured vault name when set, otherwise falls back to the
* vault filesystem path or vault name.
*
* @param app - Obsidian application instance.
* @returns Vault folder path when available, otherwise vault name.
* @returns Custom vault name when configured, vault folder path when available, otherwise vault name.
*/
export function getMiyoSourceId(app: App): string {
const customName = (getSettings().miyoVaultName || "").trim();
if (customName) {
return customName;
}
const vaultPath = getVaultBasePath(app);
if (vaultPath) {
return vaultPath;
}
return app.vault.getName();
}

/**
* Resolve the Miyo source id that would apply if a given vault name override were saved.
*
* @param app - Obsidian application instance.
* @param vaultNameOverride - The vault name value to use (empty string = auto-detect).
* @returns Resolved source id string.
*/
export function resolveMiyoSourceId(app: App, vaultNameOverride: string): string {
const trimmed = vaultNameOverride.trim();
if (trimmed) {
return trimmed;
}
const vaultPath = getVaultBasePath(app);
if (vaultPath) {
return vaultPath;
Expand Down
7 changes: 7 additions & 0 deletions src/settings/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ export interface CopilotSettings {
selfHostApiKey: string;
/** Custom Miyo server URL, e.g. "http://192.168.1.10:8742" (empty = use local service discovery) */
miyoServerUrl: string;
/** Custom vault name for Miyo indexing (overrides auto-detected vault path/name) */
miyoVaultName: string;
/** Which provider to use for self-host web search */
selfHostSearchProvider: "firecrawl" | "perplexity";
/** Firecrawl API key for self-host web search */
Expand Down Expand Up @@ -416,6 +418,11 @@ export function sanitizeSettings(settings: CopilotSettings): CopilotSettings {
sanitizedSettings.enableMiyo = DEFAULT_SETTINGS.enableMiyo;
}

// Ensure miyoVaultName has a default value
if (typeof sanitizedSettings.miyoVaultName !== "string") {
sanitizedSettings.miyoVaultName = DEFAULT_SETTINGS.miyoVaultName;
}

// Ensure selfHostSearchProvider is a valid value
const validSearchProviders = ["firecrawl", "perplexity"] as const;
if (
Expand Down
102 changes: 100 additions & 2 deletions src/settings/v2/components/CopilotPlusSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { ConfirmModal } from "@/components/modals/ConfirmModal";
import { Badge } from "@/components/ui/badge";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { Input } from "@/components/ui/input";
import { SettingItem } from "@/components/ui/setting-item";
import { DEFAULT_SETTINGS } from "@/constants";
import { logWarn } from "@/logger";
import { MiyoClient } from "@/miyo/MiyoClient";
import { getMiyoCustomUrl } from "@/miyo/miyoUtils";
import { getMiyoCustomUrl, resolveMiyoSourceId } from "@/miyo/miyoUtils";
import { useIsSelfHostEligible, validateSelfHostMode } from "@/plusUtils";
import { updateSetting, useSettingsValue } from "@/settings/model";
import { Notice } from "obsidian";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { ToolSettingsSection } from "./ToolSettingsSection";

export const CopilotPlusSettings: React.FC = () => {
const settings = useSettingsValue();
const [isValidatingSelfHost, setIsValidatingSelfHost] = useState(false);
const isSelfHostEligible = useIsSelfHostEligible();
const [pendingVaultName, setPendingVaultName] = useState(settings.miyoVaultName || "");

// Keep local input in sync if settings change externally (e.g. settings reset)
useEffect(() => {
setPendingVaultName(settings.miyoVaultName || "");
}, [settings.miyoVaultName]);

/**
* Toggle self-host mode and handle validation requirements.
Expand Down Expand Up @@ -91,6 +99,61 @@ export const CopilotPlusSettings: React.FC = () => {
).open();
};

/**
* Apply a vault name change for Miyo. When Miyo is enabled and the name actually changed,
* shows a confirmation modal warning that the existing Miyo index will be deleted and a
* full re-index will be triggered under the new name.
*/
const handleVaultNameApply = async () => {
const newName = pendingVaultName.trim();
if (!newName) return;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow clearing Vault Name to restore auto-detected source

handleVaultNameApply returns immediately when the trimmed input is empty, so a user who already saved miyoVaultName cannot clear it to go back to the default auto-detected vault path/name. That makes the override effectively irreversible from this UI and conflicts with the setting text that says leaving it blank should use auto-detection, which can keep indexing under the wrong source ID after vault moves or namespace changes.

Useful? React with 👍 / 👎.

const oldName = (settings.miyoVaultName || "").trim();
if (newName === oldName) return;
Comment on lines +109 to +110

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare effective source IDs before forcing reindex

This checks newName vs oldName (the raw override values) instead of comparing the resolved source IDs, so when the saved override is blank and the user enters the auto-detected value (vault path/name), the code treats it as a change and still clears the existing index plus triggers a full re-index. In that case the effective Miyo source ID is unchanged, so this causes unnecessary destructive work and avoidable indexing downtime for large vaults.

Useful? React with 👍 / 👎.


// Compare resolved source IDs — if the user typed the auto-detected value
// (e.g. the vault path), the effective Miyo namespace is unchanged and we
// must not clear the existing index or trigger an unnecessary re-index.
const oldSourceId = resolveMiyoSourceId(app, oldName);
const newSourceId = resolveMiyoSourceId(app, newName);

if (!settings.enableMiyo || newSourceId === oldSourceId) {
// Miyo is off, or the effective source ID hasn't changed — save silently.
updateSetting("miyoVaultName", newName);
return;
}
const confirmChange = async () => {
try {
const miyoClient = new MiyoClient();
const baseUrl = await miyoClient.resolveBaseUrl(getMiyoCustomUrl(settings));
await miyoClient.clearIndex(baseUrl, oldSourceId);
} catch (e) {
logWarn("Failed to clear Miyo index for old vault name", e);
}

updateSetting("miyoVaultName", newName);

const VectorStoreManager = (await import("@/search/vectorStoreManager")).default;
await VectorStoreManager.getInstance().indexVaultToVectorStore(false, {
userInitiated: true,
});
};

const cancelChange = () => {
// Reset input back to the saved value if user cancels
setPendingVaultName(oldName);
};

new ConfirmModal(
app,
confirmChange,
`Changing the vault name will delete the existing Miyo index for "${oldSourceId || "(auto-detected)"}" and trigger a full re-index under the new name. This cannot be undone. Continue?`,
"Change Vault Name",
"Change & Re-index",
"Cancel",
cancelChange
).open();
};

return (
<div className="tw-flex tw-flex-col tw-gap-4">
<section className="tw-flex tw-flex-col tw-gap-4">
Expand Down Expand Up @@ -228,6 +291,41 @@ export const CopilotPlusSettings: React.FC = () => {
placeholder="http://127.0.0.1:8742"
/>

<SettingItem
type="custom"
title="Vault Name"
description={
<span>
This is how Miyo identifies and remembers your vault&apos;s index — think of
it as the unique label for all your indexed notes. Leave blank to use the
auto-detected vault path. <strong>Set this before enabling Miyo.</strong> If
you access Miyo from multiple devices or a remote server, use the same vault
name on every device so they all share the same index.
</span>
}
>
<div className="tw-flex tw-items-center tw-gap-2">
<Input
value={pendingVaultName}
onChange={(e) => setPendingVaultName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") handleVaultNameApply();
}}
placeholder="Default: vault path"
className="tw-w-full sm:tw-w-[200px]"
/>
{pendingVaultName.trim() !== "" &&
pendingVaultName.trim() !== (settings.miyoVaultName || "").trim() && (
<button
onClick={handleVaultNameApply}
className="tw-rounded-md tw-bg-interactive-accent tw-px-3 tw-py-1.5 tw-text-sm tw-font-medium tw-text-on-accent hover:tw-bg-interactive-accent-hover"
>
Apply
</button>
)}
</div>
</SettingItem>

<SettingItem
type="switch"
title="Enable Miyo"
Expand Down
Loading