mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
Show Self-Host Mode section to all users with disabled toggle (#2169)
* Show Self-Host Mode section to all users with disabled toggle for non-lifetime The Self-Host Mode settings section is now visible to everyone instead of being hidden behind an eligibility check. The toggle is disabled (greyed out) for users who are not on a lifetime plan (believer/supporter), making the feature discoverable while preserving the access restriction. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix self-host toggle not disabled after removing license key Three issues fixed: 1. useIsSelfHostEligible() checked grace period before license key, so removing the key didn't revoke eligibility while in grace period. Moved license key check to the top and added auto-revocation of enableSelfHostMode when key is absent. 2. isSelfHostModeValid() didn't check license key at all, so backend code (RetrieverFactory, isPlusEnabled) continued to grant self-host access after key removal. Added license key guard. 3. useIsPlusUser() self-host bypass didn't check license key. Added the same guard for consistency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix self-host toggle not disabled when switching to non-lifetime key useIsSelfHostEligible() trusted cached grace period / permanent validation before consulting the API, so swapping from a believer key to a plus key kept the toggle enabled. Now always verifies via API when a license key is present. Cached validation (grace period, permanent count) is only used as an offline fallback when the API call fails. Also auto-revokes enableSelfHostMode when the API confirms the key is not eligible. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
69f668f7f3
commit
3de16740ec
2 changed files with 80 additions and 69 deletions
|
|
@ -57,10 +57,13 @@ const SELF_HOST_ELIGIBLE_PLANS = ["believer", "supporter"];
|
|||
|
||||
/**
|
||||
* Check if self-host mode is valid.
|
||||
* Valid if: permanently validated (3+ successful checks) OR within 15-day grace period.
|
||||
* Requires license key + toggle enabled + (permanent validation OR within grace period).
|
||||
*/
|
||||
export function isSelfHostModeValid(): boolean {
|
||||
const settings = getSettings();
|
||||
if (!settings.plusLicenseKey) {
|
||||
return false;
|
||||
}
|
||||
if (!settings.enableSelfHostMode || settings.selfHostModeValidatedAt == null) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -97,8 +100,12 @@ export function isPlusEnabled(): boolean {
|
|||
*/
|
||||
export function useIsPlusUser(): boolean | undefined {
|
||||
const settings = useSettingsValue();
|
||||
// Self-host mode with valid plan validation bypasses Plus requirements
|
||||
if (settings.enableSelfHostMode && settings.selfHostModeValidatedAt != null) {
|
||||
// Self-host mode with valid plan validation bypasses Plus requirements (requires license key)
|
||||
if (
|
||||
settings.plusLicenseKey &&
|
||||
settings.enableSelfHostMode &&
|
||||
settings.selfHostModeValidatedAt != null
|
||||
) {
|
||||
// Permanently valid after 3 successful validations
|
||||
if (settings.selfHostValidationCount >= SELF_HOST_PERMANENT_VALIDATION_COUNT) {
|
||||
return true;
|
||||
|
|
@ -146,44 +153,52 @@ export async function isSelfHostEligiblePlan(): Promise<boolean> {
|
|||
* Hook to check if user should see the self-host mode settings section.
|
||||
* Returns undefined while loading, boolean once checked.
|
||||
*
|
||||
* Eligibility rules (checked in order):
|
||||
* 1. Permanent validation (count >= 3): Always show (offline-safe, toggle-independent)
|
||||
* 2. Within 15-day grace period: Always show (offline-safe, toggle-independent)
|
||||
* 3. Has license key: Check via API (requires online)
|
||||
* 4. No license key: Hide section
|
||||
*
|
||||
* This allows offline re-enable for users who previously validated.
|
||||
* If grace period expires while offline, user must go online to revalidate.
|
||||
* Eligibility rules:
|
||||
* 1. No license key: Not eligible (immediately revokes access)
|
||||
* 2. Has license key: Verify via API (handles key changes, e.g. believer → plus)
|
||||
* - API success: Use result (revoke self-host mode if not eligible)
|
||||
* - API failure (offline): Fall back to cached validation
|
||||
* (permanent count >= 3 OR within 15-day grace period)
|
||||
*/
|
||||
export function useIsSelfHostEligible(): boolean | undefined {
|
||||
const settings = useSettingsValue();
|
||||
const [isEligible, setIsEligible] = React.useState<boolean | undefined>(undefined);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Permanently validated users can always see the section (even if toggle is off, offline)
|
||||
if (settings.selfHostValidationCount >= SELF_HOST_PERMANENT_VALIDATION_COUNT) {
|
||||
setIsEligible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Users within grace period can see the section (supports offline re-enable)
|
||||
if (
|
||||
settings.selfHostModeValidatedAt != null &&
|
||||
Date.now() - settings.selfHostModeValidatedAt < SELF_HOST_GRACE_PERIOD_MS
|
||||
) {
|
||||
setIsEligible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// No license key = not eligible, regardless of cached validation state.
|
||||
// Also force self-host mode OFF so the toggle reflects the revoked state.
|
||||
if (!settings.plusLicenseKey) {
|
||||
if (settings.enableSelfHostMode) {
|
||||
updateSetting("enableSelfHostMode", false);
|
||||
}
|
||||
setIsEligible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check via API for users who haven't enabled self-host mode yet
|
||||
// Has license key - always verify via API to handle key changes (e.g. believer → plus).
|
||||
// Fall back to cached validation only when offline.
|
||||
isSelfHostEligiblePlan()
|
||||
.then(setIsEligible)
|
||||
.catch(() => setIsEligible(false));
|
||||
.then((eligible) => {
|
||||
if (!eligible && settings.enableSelfHostMode) {
|
||||
updateSetting("enableSelfHostMode", false);
|
||||
}
|
||||
setIsEligible(eligible);
|
||||
})
|
||||
.catch(() => {
|
||||
// Offline fallback: trust cached validation state
|
||||
if (settings.selfHostValidationCount >= SELF_HOST_PERMANENT_VALIDATION_COUNT) {
|
||||
setIsEligible(true);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
settings.selfHostModeValidatedAt != null &&
|
||||
Date.now() - settings.selfHostModeValidatedAt < SELF_HOST_GRACE_PERIOD_MS
|
||||
) {
|
||||
setIsEligible(true);
|
||||
return;
|
||||
}
|
||||
setIsEligible(false);
|
||||
});
|
||||
}, [
|
||||
settings.plusLicenseKey,
|
||||
settings.enableSelfHostMode,
|
||||
|
|
|
|||
|
|
@ -99,47 +99,43 @@ export const CopilotPlusSettings: React.FC = () => {
|
|||
}}
|
||||
/>
|
||||
|
||||
{isSelfHostEligible && (
|
||||
<>
|
||||
<div className="tw-flex tw-items-center tw-gap-1.5 tw-pt-4 tw-text-xl tw-font-semibold">
|
||||
Self-Host Mode
|
||||
<HelpTooltip content="Lifetime license required" />
|
||||
</div>
|
||||
<div className="tw-flex tw-items-center tw-gap-1.5 tw-pt-4 tw-text-xl tw-font-semibold">
|
||||
Self-Host Mode
|
||||
<HelpTooltip content="Lifetime license required" />
|
||||
</div>
|
||||
|
||||
<SettingItem
|
||||
type="switch"
|
||||
title="Enable Self-Host Mode"
|
||||
description={
|
||||
<div className="tw-flex tw-items-center tw-gap-1.5">
|
||||
<span className="tw-leading-none">
|
||||
Use your own infrastructure for LLMs, embeddings (and local document
|
||||
understanding soon with our upcoming desktop app).
|
||||
</span>
|
||||
<HelpTooltip
|
||||
content={
|
||||
<div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
|
||||
<div className="tw-text-sm tw-font-medium tw-text-accent">
|
||||
Self-Host Mode (Believer/Supporter only)
|
||||
</div>
|
||||
<div className="tw-text-xs tw-text-muted">
|
||||
Connect to your own self-hosted backend (e.g., Miyo) for complete
|
||||
control over your AI infrastructure. This allows offline usage and
|
||||
custom model deployments.
|
||||
</div>
|
||||
<div className="tw-text-xs tw-text-muted">
|
||||
Requires re-validation every 15 days when online.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
checked={settings.enableSelfHostMode}
|
||||
onCheckedChange={handleSelfHostModeToggle}
|
||||
disabled={isValidatingSelfHost}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<SettingItem
|
||||
type="switch"
|
||||
title="Enable Self-Host Mode"
|
||||
description={
|
||||
<div className="tw-flex tw-items-center tw-gap-1.5">
|
||||
<span className="tw-leading-none">
|
||||
Use your own infrastructure for LLMs, embeddings (and local document understanding
|
||||
soon with our upcoming desktop app).
|
||||
</span>
|
||||
<HelpTooltip
|
||||
content={
|
||||
<div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
|
||||
<div className="tw-text-sm tw-font-medium tw-text-accent">
|
||||
Self-Host Mode (Believer/Supporter only)
|
||||
</div>
|
||||
<div className="tw-text-xs tw-text-muted">
|
||||
Connect to your own self-hosted backend (e.g., Miyo) for complete control
|
||||
over your AI infrastructure. This allows offline usage and custom model
|
||||
deployments.
|
||||
</div>
|
||||
<div className="tw-text-xs tw-text-muted">
|
||||
Requires re-validation every 15 days when online.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
checked={settings.enableSelfHostMode}
|
||||
onCheckedChange={handleSelfHostModeToggle}
|
||||
disabled={!isSelfHostEligible || isValidatingSelfHost}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue