mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
Replace the per-backend install/settings panels with a shared Configure dialog layout. Introduces ConfigDialogShell + ConfigSection, InstallCommandRow, and an installStatus badge/status-line module (with tests), and reworks the Claude, Codex, and opencode Configure dialogs onto it. The settings page now shows each backend's icon, an install-status badge, and a Configure button, dropping the old BinaryInstallContent and SimpleBackendSettingsPanel helpers. Copy is simplified for non-technical users and dialog spacing/dividers are made consistent. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { logError } from "@/logger";
|
|
import { detectBinary } from "@/utils/detectBinary";
|
|
import { Notice } from "obsidian";
|
|
import React from "react";
|
|
|
|
interface Props {
|
|
binaryName: string;
|
|
placeholder: string;
|
|
initialPath: string;
|
|
/** Optional hint surfaced when auto-detect finds nothing. */
|
|
notFoundHint?: string;
|
|
/** Validate & persist on Apply. Returns null on success, error message on failure. */
|
|
onSave: (path: string) => Promise<string | null>;
|
|
/** When true, a successful auto-detect immediately invokes `onSave`. */
|
|
persistOnAutoDetect?: boolean;
|
|
/**
|
|
* Custom detector. Used when the backend has a richer install lookup than
|
|
* a generic `which`/`where` PATH search — e.g. Claude knows about
|
|
* `~/.local/bin/claude`, Volta, asdf, NVM. Falls back to
|
|
* {@link detectBinary} when omitted.
|
|
*/
|
|
detect?: () => Promise<string | null>;
|
|
}
|
|
|
|
/**
|
|
* Shared "binary path" setting row used by every Agent Mode backend that
|
|
* spawns a local executable. Owns the Input + Auto-detect + Apply UX with
|
|
* busy/error state. Callers parameterize the binary name, placeholder, and
|
|
* persistence callback; the surrounding `<SettingItem>` (title/description)
|
|
* stays in the backend-specific panel.
|
|
*/
|
|
export const BinaryPathSetting: React.FC<Props> = ({
|
|
binaryName,
|
|
placeholder,
|
|
initialPath,
|
|
notFoundHint,
|
|
onSave,
|
|
persistOnAutoDetect = false,
|
|
detect,
|
|
}) => {
|
|
const [pathInput, setPathInput] = React.useState(initialPath);
|
|
const [error, setError] = React.useState<string | null>(null);
|
|
const [busy, setBusy] = React.useState(false);
|
|
|
|
React.useEffect(() => {
|
|
setPathInput(initialPath);
|
|
}, [initialPath]);
|
|
|
|
const apply = React.useCallback(async (): Promise<void> => {
|
|
const trimmed = pathInput.trim();
|
|
if (!trimmed) {
|
|
setError("Path is required.");
|
|
return;
|
|
}
|
|
const err = await onSave(trimmed);
|
|
if (err) {
|
|
setError(err);
|
|
return;
|
|
}
|
|
setError(null);
|
|
}, [pathInput, onSave]);
|
|
|
|
const autoDetect = React.useCallback(async (): Promise<void> => {
|
|
if (busy) return;
|
|
setBusy(true);
|
|
setError(null);
|
|
try {
|
|
const found = detect ? await detect() : await detectBinary(binaryName);
|
|
if (!found) {
|
|
setError(
|
|
notFoundHint ??
|
|
`${binaryName} not found on PATH. Install it or paste a custom path manually.`
|
|
);
|
|
return;
|
|
}
|
|
setPathInput(found);
|
|
if (persistOnAutoDetect) {
|
|
const err = await onSave(found);
|
|
if (err) {
|
|
setError(err);
|
|
return;
|
|
}
|
|
}
|
|
new Notice(`Found ${binaryName} at ${found}`);
|
|
} catch (e) {
|
|
logError(`[AgentMode] auto-detect ${binaryName} failed`, e);
|
|
setError(e instanceof Error ? e.message : String(e));
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}, [binaryName, busy, notFoundHint, onSave, persistOnAutoDetect, detect]);
|
|
|
|
return (
|
|
<div className="tw-flex tw-w-full tw-flex-col tw-gap-2">
|
|
<div className="tw-flex tw-items-center tw-gap-2">
|
|
<Input
|
|
type="text"
|
|
placeholder={placeholder}
|
|
value={pathInput}
|
|
onChange={(e) => setPathInput(e.target.value)}
|
|
className="tw-flex-1"
|
|
/>
|
|
<Button variant="secondary" size="default" onClick={autoDetect} disabled={busy}>
|
|
Auto-detect
|
|
</Button>
|
|
<Button variant="default" size="default" onClick={apply} disabled={busy}>
|
|
Apply
|
|
</Button>
|
|
</div>
|
|
{error && <div className="tw-text-sm tw-text-error">{error}</div>}
|
|
</div>
|
|
);
|
|
};
|